mirror of
https://github.com/kubernetes/client-go.git
synced 2026-06-15 22:35:53 +00:00
Compare commits
39 Commits
v0.35.0-al
...
kubernetes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d43aed2afb | ||
|
|
8ebd9bb45e | ||
|
|
00b2f2b182 | ||
|
|
f80003c240 | ||
|
|
8b415569d9 | ||
|
|
2d83546256 | ||
|
|
56b4af2aeb | ||
|
|
891f94c690 | ||
|
|
65ffe044e5 | ||
|
|
2fe4ac239c | ||
|
|
97256a6495 | ||
|
|
46360b527e | ||
|
|
171ef8cd00 | ||
|
|
3878a6464b | ||
|
|
9cee84c27c | ||
|
|
45e0decafa | ||
|
|
990057bc99 | ||
|
|
3980f079a7 | ||
|
|
5020b674f8 | ||
|
|
6ce2c0f8c3 | ||
|
|
5c322d3acd | ||
|
|
aa76619c41 | ||
|
|
99c4257e63 | ||
|
|
cb5e00dc36 | ||
|
|
09ccc185ed | ||
|
|
0e6fc04326 | ||
|
|
c529a209c2 | ||
|
|
9b2717cec1 | ||
|
|
6a14de8219 | ||
|
|
b36e6498c1 | ||
|
|
823f3a75f4 | ||
|
|
092b2fb360 | ||
|
|
d0f0666dd2 | ||
|
|
7d09f53b04 | ||
|
|
773743c372 | ||
|
|
1463bcbba2 | ||
|
|
f8b7fe98c4 | ||
|
|
7cf6a05732 | ||
|
|
f466f58eea |
@@ -68,6 +68,8 @@ type NodeStatusApplyConfiguration struct {
|
||||
RuntimeHandlers []NodeRuntimeHandlerApplyConfiguration `json:"runtimeHandlers,omitempty"`
|
||||
// Features describes the set of features implemented by the CRI implementation.
|
||||
Features *NodeFeaturesApplyConfiguration `json:"features,omitempty"`
|
||||
// DeclaredFeatures represents the features related to feature gates that are declared by the node.
|
||||
DeclaredFeatures []string `json:"declaredFeatures,omitempty"`
|
||||
}
|
||||
|
||||
// NodeStatusApplyConfiguration constructs a declarative configuration of the NodeStatus type for use with
|
||||
@@ -206,3 +208,13 @@ func (b *NodeStatusApplyConfiguration) WithFeatures(value *NodeFeaturesApplyConf
|
||||
b.Features = value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithDeclaredFeatures adds the given value to the DeclaredFeatures field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the DeclaredFeatures field.
|
||||
func (b *NodeStatusApplyConfiguration) WithDeclaredFeatures(values ...string) *NodeStatusApplyConfiguration {
|
||||
for i := range values {
|
||||
b.DeclaredFeatures = append(b.DeclaredFeatures, values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ type PersistentVolumeSpecApplyConfiguration struct {
|
||||
VolumeMode *corev1.PersistentVolumeMode `json:"volumeMode,omitempty"`
|
||||
// nodeAffinity defines constraints that limit what nodes this volume can be accessed from.
|
||||
// This field influences the scheduling of pods that use this volume.
|
||||
// This field is mutable if MutablePVNodeAffinity feature gate is enabled.
|
||||
NodeAffinity *VolumeNodeAffinityApplyConfiguration `json:"nodeAffinity,omitempty"`
|
||||
// Name of VolumeAttributesClass to which this persistent volume belongs. Empty value
|
||||
// is not allowed. When this field is not set, it indicates that this volume does not belong to any
|
||||
|
||||
@@ -273,6 +273,14 @@ type PodSpecApplyConfiguration struct {
|
||||
// This field must be a valid DNS subdomain as defined in RFC 1123 and contain at most 64 characters.
|
||||
// Requires the HostnameOverride feature gate to be enabled.
|
||||
HostnameOverride *string `json:"hostnameOverride,omitempty"`
|
||||
// WorkloadRef provides a reference to the Workload object that this Pod belongs to.
|
||||
// This field is used by the scheduler to identify the PodGroup and apply the
|
||||
// correct group scheduling policies. The Workload object referenced
|
||||
// by this field may not exist at the time the Pod is created.
|
||||
// This field is immutable, but a Workload object with the same name
|
||||
// may be recreated with different policies. Doing this during pod scheduling
|
||||
// may result in the placement not conforming to the expected policies.
|
||||
WorkloadRef *WorkloadReferenceApplyConfiguration `json:"workloadRef,omitempty"`
|
||||
}
|
||||
|
||||
// PodSpecApplyConfiguration constructs a declarative configuration of the PodSpec type for use with
|
||||
@@ -669,3 +677,11 @@ func (b *PodSpecApplyConfiguration) WithHostnameOverride(value string) *PodSpecA
|
||||
b.HostnameOverride = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithWorkloadRef sets the WorkloadRef field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the WorkloadRef field is set to the value of the last call.
|
||||
func (b *PodSpecApplyConfiguration) WithWorkloadRef(value *WorkloadReferenceApplyConfiguration) *PodSpecApplyConfiguration {
|
||||
b.WorkloadRef = value
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -131,6 +131,14 @@ type PodStatusApplyConfiguration struct {
|
||||
ResourceClaimStatuses []PodResourceClaimStatusApplyConfiguration `json:"resourceClaimStatuses,omitempty"`
|
||||
// Status of extended resource claim backed by DRA.
|
||||
ExtendedResourceClaimStatus *PodExtendedResourceClaimStatusApplyConfiguration `json:"extendedResourceClaimStatus,omitempty"`
|
||||
// AllocatedResources is the total requests allocated for this pod by the node.
|
||||
// If pod-level requests are not set, this will be the total requests aggregated
|
||||
// across containers in the pod.
|
||||
AllocatedResources *corev1.ResourceList `json:"allocatedResources,omitempty"`
|
||||
// Resources represents the compute resource requests and limits that have been
|
||||
// applied at the pod level if pod-level requests or limits are set in
|
||||
// PodSpec.Resources
|
||||
Resources *ResourceRequirementsApplyConfiguration `json:"resources,omitempty"`
|
||||
}
|
||||
|
||||
// PodStatusApplyConfiguration constructs a declarative configuration of the PodStatus type for use with
|
||||
@@ -317,3 +325,19 @@ func (b *PodStatusApplyConfiguration) WithExtendedResourceClaimStatus(value *Pod
|
||||
b.ExtendedResourceClaimStatus = value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithAllocatedResources sets the AllocatedResources field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the AllocatedResources field is set to the value of the last call.
|
||||
func (b *PodStatusApplyConfiguration) WithAllocatedResources(value corev1.ResourceList) *PodStatusApplyConfiguration {
|
||||
b.AllocatedResources = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithResources sets the Resources field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Resources field is set to the value of the last call.
|
||||
func (b *PodStatusApplyConfiguration) WithResources(value *ResourceRequirementsApplyConfiguration) *PodStatusApplyConfiguration {
|
||||
b.Resources = value
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -32,9 +32,10 @@ type TolerationApplyConfiguration struct {
|
||||
// If the key is empty, operator must be Exists; this combination means to match all values and all keys.
|
||||
Key *string `json:"key,omitempty"`
|
||||
// Operator represents a key's relationship to the value.
|
||||
// Valid operators are Exists and Equal. Defaults to Equal.
|
||||
// Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal.
|
||||
// Exists is equivalent to wildcard for value, so that a pod can
|
||||
// tolerate all taints of a particular category.
|
||||
// Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators).
|
||||
Operator *corev1.TolerationOperator `json:"operator,omitempty"`
|
||||
// Value is the taint value the toleration matches to.
|
||||
// If the operator is Exists, the value should be empty, otherwise just a regular string.
|
||||
|
||||
74
applyconfigurations/core/v1/workloadreference.go
Normal file
74
applyconfigurations/core/v1/workloadreference.go
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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 applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
// WorkloadReferenceApplyConfiguration represents a declarative configuration of the WorkloadReference type for use
|
||||
// with apply.
|
||||
//
|
||||
// WorkloadReference identifies the Workload object and PodGroup membership
|
||||
// that a Pod belongs to. The scheduler uses this information to apply
|
||||
// workload-aware scheduling semantics.
|
||||
type WorkloadReferenceApplyConfiguration struct {
|
||||
// Name defines the name of the Workload object this Pod belongs to.
|
||||
// Workload must be in the same namespace as the Pod.
|
||||
// If it doesn't match any existing Workload, the Pod will remain unschedulable
|
||||
// until a Workload object is created and observed by the kube-scheduler.
|
||||
// It must be a DNS subdomain.
|
||||
Name *string `json:"name,omitempty"`
|
||||
// PodGroup is the name of the PodGroup within the Workload that this Pod
|
||||
// belongs to. If it doesn't match any existing PodGroup within the Workload,
|
||||
// the Pod will remain unschedulable until the Workload object is recreated
|
||||
// and observed by the kube-scheduler. It must be a DNS label.
|
||||
PodGroup *string `json:"podGroup,omitempty"`
|
||||
// PodGroupReplicaKey specifies the replica key of the PodGroup to which this
|
||||
// Pod belongs. It is used to distinguish pods belonging to different replicas
|
||||
// of the same pod group. The pod group policy is applied separately to each replica.
|
||||
// When set, it must be a DNS label.
|
||||
PodGroupReplicaKey *string `json:"podGroupReplicaKey,omitempty"`
|
||||
}
|
||||
|
||||
// WorkloadReferenceApplyConfiguration constructs a declarative configuration of the WorkloadReference type for use with
|
||||
// apply.
|
||||
func WorkloadReference() *WorkloadReferenceApplyConfiguration {
|
||||
return &WorkloadReferenceApplyConfiguration{}
|
||||
}
|
||||
|
||||
// WithName sets the Name field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Name field is set to the value of the last call.
|
||||
func (b *WorkloadReferenceApplyConfiguration) WithName(value string) *WorkloadReferenceApplyConfiguration {
|
||||
b.Name = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithPodGroup sets the PodGroup field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the PodGroup field is set to the value of the last call.
|
||||
func (b *WorkloadReferenceApplyConfiguration) WithPodGroup(value string) *WorkloadReferenceApplyConfiguration {
|
||||
b.PodGroup = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithPodGroupReplicaKey sets the PodGroupReplicaKey field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the PodGroupReplicaKey field is set to the value of the last call.
|
||||
func (b *WorkloadReferenceApplyConfiguration) WithPodGroupReplicaKey(value string) *WorkloadReferenceApplyConfiguration {
|
||||
b.PodGroupReplicaKey = &value
|
||||
return b
|
||||
}
|
||||
@@ -6770,6 +6770,12 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
type:
|
||||
namedType: io.k8s.api.core.v1.NodeDaemonEndpoints
|
||||
default: {}
|
||||
- name: declaredFeatures
|
||||
type:
|
||||
list:
|
||||
elementType:
|
||||
scalar: string
|
||||
elementRelationship: atomic
|
||||
- name: features
|
||||
type:
|
||||
namedType: io.k8s.api.core.v1.NodeFeatures
|
||||
@@ -7643,9 +7649,17 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
elementRelationship: associative
|
||||
keys:
|
||||
- name
|
||||
- name: workloadRef
|
||||
type:
|
||||
namedType: io.k8s.api.core.v1.WorkloadReference
|
||||
- name: io.k8s.api.core.v1.PodStatus
|
||||
map:
|
||||
fields:
|
||||
- name: allocatedResources
|
||||
type:
|
||||
map:
|
||||
elementType:
|
||||
namedType: io.k8s.apimachinery.pkg.api.resource.Quantity
|
||||
- name: conditions
|
||||
type:
|
||||
list:
|
||||
@@ -7724,6 +7738,9 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
elementRelationship: associative
|
||||
keys:
|
||||
- name
|
||||
- name: resources
|
||||
type:
|
||||
namedType: io.k8s.api.core.v1.ResourceRequirements
|
||||
- name: startTime
|
||||
type:
|
||||
namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time
|
||||
@@ -9017,6 +9034,20 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
- name: runAsUserName
|
||||
type:
|
||||
scalar: string
|
||||
- name: io.k8s.api.core.v1.WorkloadReference
|
||||
map:
|
||||
fields:
|
||||
- name: name
|
||||
type:
|
||||
scalar: string
|
||||
default: ""
|
||||
- name: podGroup
|
||||
type:
|
||||
scalar: string
|
||||
default: ""
|
||||
- name: podGroupReplicaKey
|
||||
type:
|
||||
scalar: string
|
||||
- name: io.k8s.api.discovery.v1.Endpoint
|
||||
map:
|
||||
fields:
|
||||
@@ -14937,6 +14968,45 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
type:
|
||||
scalar: numeric
|
||||
default: 0
|
||||
- name: io.k8s.api.scheduling.v1alpha1.BasicSchedulingPolicy
|
||||
map:
|
||||
elementType:
|
||||
scalar: untyped
|
||||
list:
|
||||
elementType:
|
||||
namedType: __untyped_atomic_
|
||||
elementRelationship: atomic
|
||||
map:
|
||||
elementType:
|
||||
namedType: __untyped_deduced_
|
||||
elementRelationship: separable
|
||||
- name: io.k8s.api.scheduling.v1alpha1.GangSchedulingPolicy
|
||||
map:
|
||||
fields:
|
||||
- name: minCount
|
||||
type:
|
||||
scalar: numeric
|
||||
default: 0
|
||||
- name: io.k8s.api.scheduling.v1alpha1.PodGroup
|
||||
map:
|
||||
fields:
|
||||
- name: name
|
||||
type:
|
||||
scalar: string
|
||||
default: ""
|
||||
- name: policy
|
||||
type:
|
||||
namedType: io.k8s.api.scheduling.v1alpha1.PodGroupPolicy
|
||||
default: {}
|
||||
- name: io.k8s.api.scheduling.v1alpha1.PodGroupPolicy
|
||||
map:
|
||||
fields:
|
||||
- name: basic
|
||||
type:
|
||||
namedType: io.k8s.api.scheduling.v1alpha1.BasicSchedulingPolicy
|
||||
- name: gang
|
||||
type:
|
||||
namedType: io.k8s.api.scheduling.v1alpha1.GangSchedulingPolicy
|
||||
- name: io.k8s.api.scheduling.v1alpha1.PriorityClass
|
||||
map:
|
||||
fields:
|
||||
@@ -14963,6 +15033,51 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
type:
|
||||
scalar: numeric
|
||||
default: 0
|
||||
- name: io.k8s.api.scheduling.v1alpha1.TypedLocalObjectReference
|
||||
map:
|
||||
fields:
|
||||
- name: apiGroup
|
||||
type:
|
||||
scalar: string
|
||||
- name: kind
|
||||
type:
|
||||
scalar: string
|
||||
default: ""
|
||||
- name: name
|
||||
type:
|
||||
scalar: string
|
||||
default: ""
|
||||
- name: io.k8s.api.scheduling.v1alpha1.Workload
|
||||
map:
|
||||
fields:
|
||||
- name: apiVersion
|
||||
type:
|
||||
scalar: string
|
||||
- name: kind
|
||||
type:
|
||||
scalar: string
|
||||
- name: metadata
|
||||
type:
|
||||
namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta
|
||||
default: {}
|
||||
- name: spec
|
||||
type:
|
||||
namedType: io.k8s.api.scheduling.v1alpha1.WorkloadSpec
|
||||
default: {}
|
||||
- name: io.k8s.api.scheduling.v1alpha1.WorkloadSpec
|
||||
map:
|
||||
fields:
|
||||
- name: controllerRef
|
||||
type:
|
||||
namedType: io.k8s.api.scheduling.v1alpha1.TypedLocalObjectReference
|
||||
- name: podGroups
|
||||
type:
|
||||
list:
|
||||
elementType:
|
||||
namedType: io.k8s.api.scheduling.v1alpha1.PodGroup
|
||||
elementRelationship: associative
|
||||
keys:
|
||||
- name
|
||||
- name: io.k8s.api.scheduling.v1beta1.PriorityClass
|
||||
map:
|
||||
fields:
|
||||
|
||||
@@ -23,7 +23,7 @@ package v1
|
||||
//
|
||||
// CounterSet defines a named set of counters
|
||||
// that are available to be used by devices defined in the
|
||||
// ResourceSlice.
|
||||
// ResourcePool.
|
||||
//
|
||||
// The counters are not allocatable by themselves, but
|
||||
// can be referenced by devices. When a device is allocated,
|
||||
@@ -36,7 +36,7 @@ type CounterSetApplyConfiguration struct {
|
||||
// Counters defines the set of counters for this CounterSet
|
||||
// The name of each counter must be unique in that set and must be a DNS label.
|
||||
//
|
||||
// The maximum number of counters in all sets is 32.
|
||||
// The maximum number of counters is 32.
|
||||
Counters map[string]CounterApplyConfiguration `json:"counters,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -48,10 +48,8 @@ type DeviceApplyConfiguration struct {
|
||||
//
|
||||
// There can only be a single entry per counterSet.
|
||||
//
|
||||
// The total number of device counter consumption entries
|
||||
// must be <= 32. In addition, the total number in the
|
||||
// entire ResourceSlice must be <= 1024 (for example,
|
||||
// 64 devices with 16 counters each).
|
||||
// The maximum number of device counter consumptions per
|
||||
// device is 2.
|
||||
ConsumesCounters []DeviceCounterConsumptionApplyConfiguration `json:"consumesCounters,omitempty"`
|
||||
// NodeName identifies the node where the device is available.
|
||||
//
|
||||
|
||||
@@ -29,10 +29,7 @@ type DeviceCounterConsumptionApplyConfiguration struct {
|
||||
CounterSet *string `json:"counterSet,omitempty"`
|
||||
// Counters defines the counters that will be consumed by the device.
|
||||
//
|
||||
// The maximum number counters in a device is 32.
|
||||
// In addition, the maximum number of all counters
|
||||
// in all devices is 1024 (for example, 64 devices with
|
||||
// 16 counters each).
|
||||
// The maximum number of counters is 32.
|
||||
Counters map[string]CounterApplyConfiguration `json:"counters,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,9 @@ type ResourceSliceSpecApplyConfiguration struct {
|
||||
AllNodes *bool `json:"allNodes,omitempty"`
|
||||
// Devices lists some or all of the devices in this pool.
|
||||
//
|
||||
// Must not have more than 128 entries. If any device uses taints the limit is 64.
|
||||
// Must not have more than 128 entries. If any device uses taints or consumes counters the limit is 64.
|
||||
//
|
||||
// Only one of Devices and SharedCounters can be set in a ResourceSlice.
|
||||
Devices []DeviceApplyConfiguration `json:"devices,omitempty"`
|
||||
// PerDeviceNodeSelection defines whether the access from nodes to
|
||||
// resources in the pool is set on the ResourceSlice level or on each
|
||||
@@ -74,9 +76,11 @@ type ResourceSliceSpecApplyConfiguration struct {
|
||||
// SharedCounters defines a list of counter sets, each of which
|
||||
// has a name and a list of counters available.
|
||||
//
|
||||
// The names of the SharedCounters must be unique in the ResourceSlice.
|
||||
// The names of the counter sets must be unique in the ResourcePool.
|
||||
//
|
||||
// The maximum number of counters in all sets is 32.
|
||||
// Only one of Devices and SharedCounters can be set in a ResourceSlice.
|
||||
//
|
||||
// The maximum number of counter sets is 8.
|
||||
SharedCounters []CounterSetApplyConfiguration `json:"sharedCounters,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -44,10 +44,8 @@ type BasicDeviceApplyConfiguration struct {
|
||||
//
|
||||
// There can only be a single entry per counterSet.
|
||||
//
|
||||
// The total number of device counter consumption entries
|
||||
// must be <= 32. In addition, the total number in the
|
||||
// entire ResourceSlice must be <= 1024 (for example,
|
||||
// 64 devices with 16 counters each).
|
||||
// The maximum number of device counter consumptions per
|
||||
// device is 2.
|
||||
ConsumesCounters []DeviceCounterConsumptionApplyConfiguration `json:"consumesCounters,omitempty"`
|
||||
// NodeName identifies the node where the device is available.
|
||||
//
|
||||
|
||||
@@ -23,7 +23,7 @@ package v1beta1
|
||||
//
|
||||
// CounterSet defines a named set of counters
|
||||
// that are available to be used by devices defined in the
|
||||
// ResourceSlice.
|
||||
// ResourcePool.
|
||||
//
|
||||
// The counters are not allocatable by themselves, but
|
||||
// can be referenced by devices. When a device is allocated,
|
||||
|
||||
@@ -29,10 +29,7 @@ type DeviceCounterConsumptionApplyConfiguration struct {
|
||||
CounterSet *string `json:"counterSet,omitempty"`
|
||||
// Counters defines the counters that will be consumed by the device.
|
||||
//
|
||||
// The maximum number counters in a device is 32.
|
||||
// In addition, the maximum number of all counters
|
||||
// in all devices is 1024 (for example, 64 devices with
|
||||
// 16 counters each).
|
||||
// The maximum number of counters is 32.
|
||||
Counters map[string]CounterApplyConfiguration `json:"counters,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,9 @@ type ResourceSliceSpecApplyConfiguration struct {
|
||||
AllNodes *bool `json:"allNodes,omitempty"`
|
||||
// Devices lists some or all of the devices in this pool.
|
||||
//
|
||||
// Must not have more than 128 entries. If any device uses taints the limit is 64.
|
||||
// Must not have more than 128 entries. If any device uses taints or consumes counters the limit is 64.
|
||||
//
|
||||
// Only one of Devices and SharedCounters can be set in a ResourceSlice.
|
||||
Devices []DeviceApplyConfiguration `json:"devices,omitempty"`
|
||||
// PerDeviceNodeSelection defines whether the access from nodes to
|
||||
// resources in the pool is set on the ResourceSlice level or on each
|
||||
@@ -74,9 +76,11 @@ type ResourceSliceSpecApplyConfiguration struct {
|
||||
// SharedCounters defines a list of counter sets, each of which
|
||||
// has a name and a list of counters available.
|
||||
//
|
||||
// The names of the SharedCounters must be unique in the ResourceSlice.
|
||||
// The names of the counter sets must be unique in the ResourcePool.
|
||||
//
|
||||
// The maximum number of SharedCounters is 32.
|
||||
// Only one of Devices and SharedCounters can be set in a ResourceSlice.
|
||||
//
|
||||
// The maximum number of counter sets is 8.
|
||||
SharedCounters []CounterSetApplyConfiguration `json:"sharedCounters,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ package v1beta2
|
||||
//
|
||||
// CounterSet defines a named set of counters
|
||||
// that are available to be used by devices defined in the
|
||||
// ResourceSlice.
|
||||
// ResourcePool.
|
||||
//
|
||||
// The counters are not allocatable by themselves, but
|
||||
// can be referenced by devices. When a device is allocated,
|
||||
@@ -36,7 +36,7 @@ type CounterSetApplyConfiguration struct {
|
||||
// Counters defines the set of counters for this CounterSet
|
||||
// The name of each counter must be unique in that set and must be a DNS label.
|
||||
//
|
||||
// The maximum number of counters in all sets is 32.
|
||||
// The maximum number of counters is 32.
|
||||
Counters map[string]CounterApplyConfiguration `json:"counters,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -48,10 +48,8 @@ type DeviceApplyConfiguration struct {
|
||||
//
|
||||
// There can only be a single entry per counterSet.
|
||||
//
|
||||
// The total number of device counter consumption entries
|
||||
// must be <= 32. In addition, the total number in the
|
||||
// entire ResourceSlice must be <= 1024 (for example,
|
||||
// 64 devices with 16 counters each).
|
||||
// The maximum number of device counter consumptions per
|
||||
// device is 2.
|
||||
ConsumesCounters []DeviceCounterConsumptionApplyConfiguration `json:"consumesCounters,omitempty"`
|
||||
// NodeName identifies the node where the device is available.
|
||||
//
|
||||
|
||||
@@ -29,10 +29,7 @@ type DeviceCounterConsumptionApplyConfiguration struct {
|
||||
CounterSet *string `json:"counterSet,omitempty"`
|
||||
// Counters defines the counters that will be consumed by the device.
|
||||
//
|
||||
// The maximum number counters in a device is 32.
|
||||
// In addition, the maximum number of all counters
|
||||
// in all devices is 1024 (for example, 64 devices with
|
||||
// 16 counters each).
|
||||
// The maximum number of counters is 32.
|
||||
Counters map[string]CounterApplyConfiguration `json:"counters,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,9 @@ type ResourceSliceSpecApplyConfiguration struct {
|
||||
AllNodes *bool `json:"allNodes,omitempty"`
|
||||
// Devices lists some or all of the devices in this pool.
|
||||
//
|
||||
// Must not have more than 128 entries. If any device uses taints the limit is 64.
|
||||
// Must not have more than 128 entries. If any device uses taints or consumes counters the limit is 64.
|
||||
//
|
||||
// Only one of Devices and SharedCounters can be set in a ResourceSlice.
|
||||
Devices []DeviceApplyConfiguration `json:"devices,omitempty"`
|
||||
// PerDeviceNodeSelection defines whether the access from nodes to
|
||||
// resources in the pool is set on the ResourceSlice level or on each
|
||||
@@ -74,9 +76,11 @@ type ResourceSliceSpecApplyConfiguration struct {
|
||||
// SharedCounters defines a list of counter sets, each of which
|
||||
// has a name and a list of counters available.
|
||||
//
|
||||
// The names of the SharedCounters must be unique in the ResourceSlice.
|
||||
// The names of the counter sets must be unique in the ResourcePool.
|
||||
//
|
||||
// The maximum number of counters in all sets is 32.
|
||||
// Only one of Devices and SharedCounters can be set in a ResourceSlice.
|
||||
//
|
||||
// The maximum number of counter sets is 8.
|
||||
SharedCounters []CounterSetApplyConfiguration `json:"sharedCounters,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
// Code generated by applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// GangSchedulingPolicyApplyConfiguration represents a declarative configuration of the GangSchedulingPolicy type for use
|
||||
// with apply.
|
||||
//
|
||||
// GangSchedulingPolicy defines the parameters for gang scheduling.
|
||||
type GangSchedulingPolicyApplyConfiguration struct {
|
||||
// MinCount is the minimum number of pods that must be schedulable or scheduled
|
||||
// at the same time for the scheduler to admit the entire group.
|
||||
// It must be a positive integer.
|
||||
MinCount *int32 `json:"minCount,omitempty"`
|
||||
}
|
||||
|
||||
// GangSchedulingPolicyApplyConfiguration constructs a declarative configuration of the GangSchedulingPolicy type for use with
|
||||
// apply.
|
||||
func GangSchedulingPolicy() *GangSchedulingPolicyApplyConfiguration {
|
||||
return &GangSchedulingPolicyApplyConfiguration{}
|
||||
}
|
||||
|
||||
// WithMinCount sets the MinCount field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the MinCount field is set to the value of the last call.
|
||||
func (b *GangSchedulingPolicyApplyConfiguration) WithMinCount(value int32) *GangSchedulingPolicyApplyConfiguration {
|
||||
b.MinCount = &value
|
||||
return b
|
||||
}
|
||||
53
applyconfigurations/scheduling/v1alpha1/podgroup.go
Normal file
53
applyconfigurations/scheduling/v1alpha1/podgroup.go
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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 applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// PodGroupApplyConfiguration represents a declarative configuration of the PodGroup type for use
|
||||
// with apply.
|
||||
//
|
||||
// PodGroup represents a set of pods with a common scheduling policy.
|
||||
type PodGroupApplyConfiguration struct {
|
||||
// Name is a unique identifier for the PodGroup within the Workload.
|
||||
// It must be a DNS label. This field is immutable.
|
||||
Name *string `json:"name,omitempty"`
|
||||
// Policy defines the scheduling policy for this PodGroup.
|
||||
Policy *PodGroupPolicyApplyConfiguration `json:"policy,omitempty"`
|
||||
}
|
||||
|
||||
// PodGroupApplyConfiguration constructs a declarative configuration of the PodGroup type for use with
|
||||
// apply.
|
||||
func PodGroup() *PodGroupApplyConfiguration {
|
||||
return &PodGroupApplyConfiguration{}
|
||||
}
|
||||
|
||||
// WithName sets the Name field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Name field is set to the value of the last call.
|
||||
func (b *PodGroupApplyConfiguration) WithName(value string) *PodGroupApplyConfiguration {
|
||||
b.Name = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithPolicy sets the Policy field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Policy field is set to the value of the last call.
|
||||
func (b *PodGroupApplyConfiguration) WithPolicy(value *PodGroupPolicyApplyConfiguration) *PodGroupApplyConfiguration {
|
||||
b.Policy = value
|
||||
return b
|
||||
}
|
||||
58
applyconfigurations/scheduling/v1alpha1/podgrouppolicy.go
Normal file
58
applyconfigurations/scheduling/v1alpha1/podgrouppolicy.go
Normal file
@@ -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.
|
||||
*/
|
||||
|
||||
// Code generated by applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1"
|
||||
)
|
||||
|
||||
// PodGroupPolicyApplyConfiguration represents a declarative configuration of the PodGroupPolicy type for use
|
||||
// with apply.
|
||||
//
|
||||
// PodGroupPolicy defines the scheduling configuration for a PodGroup.
|
||||
type PodGroupPolicyApplyConfiguration struct {
|
||||
// Basic specifies that the pods in this group should be scheduled using
|
||||
// standard Kubernetes scheduling behavior.
|
||||
Basic *schedulingv1alpha1.BasicSchedulingPolicy `json:"basic,omitempty"`
|
||||
// Gang specifies that the pods in this group should be scheduled using
|
||||
// all-or-nothing semantics.
|
||||
Gang *GangSchedulingPolicyApplyConfiguration `json:"gang,omitempty"`
|
||||
}
|
||||
|
||||
// PodGroupPolicyApplyConfiguration constructs a declarative configuration of the PodGroupPolicy type for use with
|
||||
// apply.
|
||||
func PodGroupPolicy() *PodGroupPolicyApplyConfiguration {
|
||||
return &PodGroupPolicyApplyConfiguration{}
|
||||
}
|
||||
|
||||
// WithBasic sets the Basic field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Basic field is set to the value of the last call.
|
||||
func (b *PodGroupPolicyApplyConfiguration) WithBasic(value schedulingv1alpha1.BasicSchedulingPolicy) *PodGroupPolicyApplyConfiguration {
|
||||
b.Basic = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithGang sets the Gang field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Gang field is set to the value of the last call.
|
||||
func (b *PodGroupPolicyApplyConfiguration) WithGang(value *GangSchedulingPolicyApplyConfiguration) *PodGroupPolicyApplyConfiguration {
|
||||
b.Gang = value
|
||||
return b
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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 applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// TypedLocalObjectReferenceApplyConfiguration represents a declarative configuration of the TypedLocalObjectReference type for use
|
||||
// with apply.
|
||||
//
|
||||
// TypedLocalObjectReference allows to reference typed object inside the same namespace.
|
||||
type TypedLocalObjectReferenceApplyConfiguration struct {
|
||||
// APIGroup is the group for the resource being referenced.
|
||||
// If APIGroup is empty, the specified Kind must be in the core API group.
|
||||
// For any other third-party types, setting APIGroup is required.
|
||||
// It must be a DNS subdomain.
|
||||
APIGroup *string `json:"apiGroup,omitempty"`
|
||||
// Kind is the type of resource being referenced.
|
||||
// It must be a path segment name.
|
||||
Kind *string `json:"kind,omitempty"`
|
||||
// Name is the name of resource being referenced.
|
||||
// It must be a path segment name.
|
||||
Name *string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// TypedLocalObjectReferenceApplyConfiguration constructs a declarative configuration of the TypedLocalObjectReference type for use with
|
||||
// apply.
|
||||
func TypedLocalObjectReference() *TypedLocalObjectReferenceApplyConfiguration {
|
||||
return &TypedLocalObjectReferenceApplyConfiguration{}
|
||||
}
|
||||
|
||||
// WithAPIGroup sets the APIGroup field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the APIGroup field is set to the value of the last call.
|
||||
func (b *TypedLocalObjectReferenceApplyConfiguration) WithAPIGroup(value string) *TypedLocalObjectReferenceApplyConfiguration {
|
||||
b.APIGroup = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithKind sets the Kind field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Kind field is set to the value of the last call.
|
||||
func (b *TypedLocalObjectReferenceApplyConfiguration) WithKind(value string) *TypedLocalObjectReferenceApplyConfiguration {
|
||||
b.Kind = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithName sets the Name field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Name field is set to the value of the last call.
|
||||
func (b *TypedLocalObjectReferenceApplyConfiguration) WithName(value string) *TypedLocalObjectReferenceApplyConfiguration {
|
||||
b.Name = &value
|
||||
return b
|
||||
}
|
||||
279
applyconfigurations/scheduling/v1alpha1/workload.go
Normal file
279
applyconfigurations/scheduling/v1alpha1/workload.go
Normal file
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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 applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
managedfields "k8s.io/apimachinery/pkg/util/managedfields"
|
||||
internal "k8s.io/client-go/applyconfigurations/internal"
|
||||
v1 "k8s.io/client-go/applyconfigurations/meta/v1"
|
||||
)
|
||||
|
||||
// WorkloadApplyConfiguration represents a declarative configuration of the Workload type for use
|
||||
// with apply.
|
||||
//
|
||||
// Workload allows for expressing scheduling constraints that should be used
|
||||
// when managing lifecycle of workloads from scheduling perspective,
|
||||
// including scheduling, preemption, eviction and other phases.
|
||||
type WorkloadApplyConfiguration struct {
|
||||
v1.TypeMetaApplyConfiguration `json:",inline"`
|
||||
// Standard object's metadata.
|
||||
// Name must be a DNS subdomain.
|
||||
*v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"`
|
||||
// Spec defines the desired behavior of a Workload.
|
||||
Spec *WorkloadSpecApplyConfiguration `json:"spec,omitempty"`
|
||||
}
|
||||
|
||||
// Workload constructs a declarative configuration of the Workload type for use with
|
||||
// apply.
|
||||
func Workload(name, namespace string) *WorkloadApplyConfiguration {
|
||||
b := &WorkloadApplyConfiguration{}
|
||||
b.WithName(name)
|
||||
b.WithNamespace(namespace)
|
||||
b.WithKind("Workload")
|
||||
b.WithAPIVersion("scheduling.k8s.io/v1alpha1")
|
||||
return b
|
||||
}
|
||||
|
||||
// ExtractWorkloadFrom extracts the applied configuration owned by fieldManager from
|
||||
// workload for the specified subresource. Pass an empty string for subresource to extract
|
||||
// the main resource. Common subresources include "status", "scale", etc.
|
||||
// workload must be a unmodified Workload API object that was retrieved from the Kubernetes API.
|
||||
// ExtractWorkloadFrom 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 ExtractWorkloadFrom(workload *schedulingv1alpha1.Workload, fieldManager string, subresource string) (*WorkloadApplyConfiguration, error) {
|
||||
b := &WorkloadApplyConfiguration{}
|
||||
err := managedfields.ExtractInto(workload, internal.Parser().Type("io.k8s.api.scheduling.v1alpha1.Workload"), fieldManager, b, subresource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.WithName(workload.Name)
|
||||
b.WithNamespace(workload.Namespace)
|
||||
|
||||
b.WithKind("Workload")
|
||||
b.WithAPIVersion("scheduling.k8s.io/v1alpha1")
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// ExtractWorkload extracts the applied configuration owned by fieldManager from
|
||||
// workload. If no managedFields are found in workload for fieldManager, a
|
||||
// WorkloadApplyConfiguration 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.
|
||||
// workload must be a unmodified Workload API object that was retrieved from the Kubernetes API.
|
||||
// ExtractWorkload 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 ExtractWorkload(workload *schedulingv1alpha1.Workload, fieldManager string) (*WorkloadApplyConfiguration, error) {
|
||||
return ExtractWorkloadFrom(workload, fieldManager, "")
|
||||
}
|
||||
|
||||
func (b WorkloadApplyConfiguration) IsApplyConfiguration() {}
|
||||
|
||||
// WithKind sets the Kind field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Kind field is set to the value of the last call.
|
||||
func (b *WorkloadApplyConfiguration) WithKind(value string) *WorkloadApplyConfiguration {
|
||||
b.TypeMetaApplyConfiguration.Kind = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the APIVersion field is set to the value of the last call.
|
||||
func (b *WorkloadApplyConfiguration) WithAPIVersion(value string) *WorkloadApplyConfiguration {
|
||||
b.TypeMetaApplyConfiguration.APIVersion = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithName sets the Name field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Name field is set to the value of the last call.
|
||||
func (b *WorkloadApplyConfiguration) WithName(value string) *WorkloadApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
b.ObjectMetaApplyConfiguration.Name = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithGenerateName sets the GenerateName field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the GenerateName field is set to the value of the last call.
|
||||
func (b *WorkloadApplyConfiguration) WithGenerateName(value string) *WorkloadApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
b.ObjectMetaApplyConfiguration.GenerateName = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithNamespace sets the Namespace field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Namespace field is set to the value of the last call.
|
||||
func (b *WorkloadApplyConfiguration) WithNamespace(value string) *WorkloadApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
b.ObjectMetaApplyConfiguration.Namespace = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithUID sets the UID field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the UID field is set to the value of the last call.
|
||||
func (b *WorkloadApplyConfiguration) WithUID(value types.UID) *WorkloadApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
b.ObjectMetaApplyConfiguration.UID = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the ResourceVersion field is set to the value of the last call.
|
||||
func (b *WorkloadApplyConfiguration) WithResourceVersion(value string) *WorkloadApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
b.ObjectMetaApplyConfiguration.ResourceVersion = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithGeneration sets the Generation field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Generation field is set to the value of the last call.
|
||||
func (b *WorkloadApplyConfiguration) WithGeneration(value int64) *WorkloadApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
b.ObjectMetaApplyConfiguration.Generation = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the CreationTimestamp field is set to the value of the last call.
|
||||
func (b *WorkloadApplyConfiguration) WithCreationTimestamp(value metav1.Time) *WorkloadApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
b.ObjectMetaApplyConfiguration.CreationTimestamp = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the DeletionTimestamp field is set to the value of the last call.
|
||||
func (b *WorkloadApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *WorkloadApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call.
|
||||
func (b *WorkloadApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *WorkloadApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithLabels puts the entries into the Labels field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, the entries provided by each call will be put on the Labels field,
|
||||
// overwriting an existing map entries in Labels field with the same key.
|
||||
func (b *WorkloadApplyConfiguration) WithLabels(entries map[string]string) *WorkloadApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 {
|
||||
b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries))
|
||||
}
|
||||
for k, v := range entries {
|
||||
b.ObjectMetaApplyConfiguration.Labels[k] = v
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithAnnotations puts the entries into the Annotations field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, the entries provided by each call will be put on the Annotations field,
|
||||
// overwriting an existing map entries in Annotations field with the same key.
|
||||
func (b *WorkloadApplyConfiguration) WithAnnotations(entries map[string]string) *WorkloadApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 {
|
||||
b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries))
|
||||
}
|
||||
for k, v := range entries {
|
||||
b.ObjectMetaApplyConfiguration.Annotations[k] = v
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the OwnerReferences field.
|
||||
func (b *WorkloadApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *WorkloadApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
for i := range values {
|
||||
if values[i] == nil {
|
||||
panic("nil value passed to WithOwnerReferences")
|
||||
}
|
||||
b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithFinalizers adds the given value to the Finalizers field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the Finalizers field.
|
||||
func (b *WorkloadApplyConfiguration) WithFinalizers(values ...string) *WorkloadApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
for i := range values {
|
||||
b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *WorkloadApplyConfiguration) ensureObjectMetaApplyConfigurationExists() {
|
||||
if b.ObjectMetaApplyConfiguration == nil {
|
||||
b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{}
|
||||
}
|
||||
}
|
||||
|
||||
// WithSpec sets the Spec field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Spec field is set to the value of the last call.
|
||||
func (b *WorkloadApplyConfiguration) WithSpec(value *WorkloadSpecApplyConfiguration) *WorkloadApplyConfiguration {
|
||||
b.Spec = value
|
||||
return b
|
||||
}
|
||||
|
||||
// GetKind retrieves the value of the Kind field in the declarative configuration.
|
||||
func (b *WorkloadApplyConfiguration) GetKind() *string {
|
||||
return b.TypeMetaApplyConfiguration.Kind
|
||||
}
|
||||
|
||||
// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration.
|
||||
func (b *WorkloadApplyConfiguration) GetAPIVersion() *string {
|
||||
return b.TypeMetaApplyConfiguration.APIVersion
|
||||
}
|
||||
|
||||
// GetName retrieves the value of the Name field in the declarative configuration.
|
||||
func (b *WorkloadApplyConfiguration) GetName() *string {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
return b.ObjectMetaApplyConfiguration.Name
|
||||
}
|
||||
|
||||
// GetNamespace retrieves the value of the Namespace field in the declarative configuration.
|
||||
func (b *WorkloadApplyConfiguration) GetNamespace() *string {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
return b.ObjectMetaApplyConfiguration.Namespace
|
||||
}
|
||||
61
applyconfigurations/scheduling/v1alpha1/workloadspec.go
Normal file
61
applyconfigurations/scheduling/v1alpha1/workloadspec.go
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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 applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// WorkloadSpecApplyConfiguration represents a declarative configuration of the WorkloadSpec type for use
|
||||
// with apply.
|
||||
//
|
||||
// WorkloadSpec defines the desired state of a Workload.
|
||||
type WorkloadSpecApplyConfiguration struct {
|
||||
// ControllerRef is an optional reference to the controlling object, such as a
|
||||
// Deployment or Job. This field is intended for use by tools like CLIs
|
||||
// to provide a link back to the original workload definition.
|
||||
// When set, it cannot be changed.
|
||||
ControllerRef *TypedLocalObjectReferenceApplyConfiguration `json:"controllerRef,omitempty"`
|
||||
// PodGroups is the list of pod groups that make up the Workload.
|
||||
// The maximum number of pod groups is 8. This field is immutable.
|
||||
PodGroups []PodGroupApplyConfiguration `json:"podGroups,omitempty"`
|
||||
}
|
||||
|
||||
// WorkloadSpecApplyConfiguration constructs a declarative configuration of the WorkloadSpec type for use with
|
||||
// apply.
|
||||
func WorkloadSpec() *WorkloadSpecApplyConfiguration {
|
||||
return &WorkloadSpecApplyConfiguration{}
|
||||
}
|
||||
|
||||
// WithControllerRef sets the ControllerRef field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the ControllerRef field is set to the value of the last call.
|
||||
func (b *WorkloadSpecApplyConfiguration) WithControllerRef(value *TypedLocalObjectReferenceApplyConfiguration) *WorkloadSpecApplyConfiguration {
|
||||
b.ControllerRef = value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithPodGroups adds the given value to the PodGroups field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the PodGroups field.
|
||||
func (b *WorkloadSpecApplyConfiguration) WithPodGroups(values ...*PodGroupApplyConfiguration) *WorkloadSpecApplyConfiguration {
|
||||
for i := range values {
|
||||
if values[i] == nil {
|
||||
panic("nil value passed to WithPodGroups")
|
||||
}
|
||||
b.PodGroups = append(b.PodGroups, *values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
@@ -1084,6 +1084,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
|
||||
return &applyconfigurationscorev1.WeightedPodAffinityTermApplyConfiguration{}
|
||||
case corev1.SchemeGroupVersion.WithKind("WindowsSecurityContextOptions"):
|
||||
return &applyconfigurationscorev1.WindowsSecurityContextOptionsApplyConfiguration{}
|
||||
case corev1.SchemeGroupVersion.WithKind("WorkloadReference"):
|
||||
return &applyconfigurationscorev1.WorkloadReferenceApplyConfiguration{}
|
||||
|
||||
// Group=discovery.k8s.io, Version=v1
|
||||
case discoveryv1.SchemeGroupVersion.WithKind("Endpoint"):
|
||||
@@ -1886,8 +1888,20 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
|
||||
return &applyconfigurationsschedulingv1.PriorityClassApplyConfiguration{}
|
||||
|
||||
// Group=scheduling.k8s.io, Version=v1alpha1
|
||||
case schedulingv1alpha1.SchemeGroupVersion.WithKind("GangSchedulingPolicy"):
|
||||
return &applyconfigurationsschedulingv1alpha1.GangSchedulingPolicyApplyConfiguration{}
|
||||
case schedulingv1alpha1.SchemeGroupVersion.WithKind("PodGroup"):
|
||||
return &applyconfigurationsschedulingv1alpha1.PodGroupApplyConfiguration{}
|
||||
case schedulingv1alpha1.SchemeGroupVersion.WithKind("PodGroupPolicy"):
|
||||
return &applyconfigurationsschedulingv1alpha1.PodGroupPolicyApplyConfiguration{}
|
||||
case schedulingv1alpha1.SchemeGroupVersion.WithKind("PriorityClass"):
|
||||
return &applyconfigurationsschedulingv1alpha1.PriorityClassApplyConfiguration{}
|
||||
case schedulingv1alpha1.SchemeGroupVersion.WithKind("TypedLocalObjectReference"):
|
||||
return &applyconfigurationsschedulingv1alpha1.TypedLocalObjectReferenceApplyConfiguration{}
|
||||
case schedulingv1alpha1.SchemeGroupVersion.WithKind("Workload"):
|
||||
return &applyconfigurationsschedulingv1alpha1.WorkloadApplyConfiguration{}
|
||||
case schedulingv1alpha1.SchemeGroupVersion.WithKind("WorkloadSpec"):
|
||||
return &applyconfigurationsschedulingv1alpha1.WorkloadSpecApplyConfiguration{}
|
||||
|
||||
// Group=scheduling.k8s.io, Version=v1beta1
|
||||
case schedulingv1beta1.SchemeGroupVersion.WithKind("PriorityClass"):
|
||||
|
||||
@@ -63,8 +63,9 @@ const (
|
||||
// Aggregated discovery content-type (v2beta1). NOTE: content-type parameters
|
||||
// MUST be ordered (g, v, as) for server in "Accept" header (BUT we are resilient
|
||||
// to ordering when comparing returned values in "Content-Type" header).
|
||||
AcceptV2Beta1 = runtime.ContentTypeJSON + ";" + "g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList"
|
||||
AcceptV2 = runtime.ContentTypeJSON + ";" + "g=apidiscovery.k8s.io;v=v2;as=APIGroupDiscoveryList"
|
||||
AcceptV2Beta1 = runtime.ContentTypeJSON + ";" + "g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList"
|
||||
AcceptV2 = runtime.ContentTypeJSON + ";" + "g=apidiscovery.k8s.io;v=v2;as=APIGroupDiscoveryList"
|
||||
AcceptV2NoPeer = runtime.ContentTypeJSON + ";" + "g=apidiscovery.k8s.io;v=v2;as=APIGroupDiscoveryList;profile=nopeer"
|
||||
// Prioritize aggregated discovery by placing first in the order of discovery accept types.
|
||||
acceptDiscoveryFormats = AcceptV2 + "," + AcceptV2Beta1 + "," + AcceptV1
|
||||
)
|
||||
@@ -168,6 +169,11 @@ type DiscoveryClient struct {
|
||||
LegacyPrefix string
|
||||
// Forces the client to request only "unaggregated" (legacy) discovery.
|
||||
UseLegacyDiscovery bool
|
||||
// NoPeerDiscovery will request the "nopeer" profile of aggregated discovery.
|
||||
// This allows a client to get just the discovery documents served by the single apiserver
|
||||
// that it is talking to. This is useful for clients that need to understand the state
|
||||
// of a single apiserver, for example, to validate that the apiserver is ready to serve traffic.
|
||||
NoPeerDiscovery bool
|
||||
}
|
||||
|
||||
var _ AggregatedDiscoveryInterface = &DiscoveryClient{}
|
||||
@@ -241,10 +247,7 @@ func (d *DiscoveryClient) downloadLegacy() (
|
||||
map[schema.GroupVersion]*metav1.APIResourceList,
|
||||
map[schema.GroupVersion]error,
|
||||
error) {
|
||||
accept := acceptDiscoveryFormats
|
||||
if d.UseLegacyDiscovery {
|
||||
accept = AcceptV1
|
||||
}
|
||||
accept := selectDiscoveryAcceptHeader(d.UseLegacyDiscovery, d.NoPeerDiscovery)
|
||||
var responseContentType string
|
||||
body, err := d.restClient.Get().
|
||||
AbsPath("/api").
|
||||
@@ -307,10 +310,7 @@ func (d *DiscoveryClient) downloadAPIs() (
|
||||
map[schema.GroupVersion]*metav1.APIResourceList,
|
||||
map[schema.GroupVersion]error,
|
||||
error) {
|
||||
accept := acceptDiscoveryFormats
|
||||
if d.UseLegacyDiscovery {
|
||||
accept = AcceptV1
|
||||
}
|
||||
accept := selectDiscoveryAcceptHeader(d.UseLegacyDiscovery, d.NoPeerDiscovery)
|
||||
var responseContentType string
|
||||
body, err := d.restClient.Get().
|
||||
AbsPath("/apis").
|
||||
@@ -351,6 +351,16 @@ func (d *DiscoveryClient) downloadAPIs() (
|
||||
return apiGroupList, resourcesByGV, failedGVs, nil
|
||||
}
|
||||
|
||||
func selectDiscoveryAcceptHeader(useLegacy, nopeer bool) string {
|
||||
if useLegacy {
|
||||
return AcceptV1
|
||||
}
|
||||
if nopeer {
|
||||
return AcceptV2NoPeer + "," + acceptDiscoveryFormats
|
||||
}
|
||||
return acceptDiscoveryFormats
|
||||
}
|
||||
|
||||
// ContentTypeIsGVK checks of the content-type string is both
|
||||
// "application/json" and matches the provided GVK. An error
|
||||
// is returned if the content type string is malformed.
|
||||
|
||||
@@ -73,10 +73,6 @@ const (
|
||||
// beta: v1.30
|
||||
//
|
||||
// Allow the client to get a stream of individual items instead of chunking from the server.
|
||||
//
|
||||
// NOTE:
|
||||
// The feature is disabled in Beta by default because
|
||||
// it will only be turned on for selected control plane component(s).
|
||||
WatchListClient Feature = "WatchListClient"
|
||||
)
|
||||
|
||||
@@ -104,5 +100,6 @@ var defaultVersionedKubernetesFeatureGates = map[Feature]VersionedSpecs{
|
||||
},
|
||||
WatchListClient: {
|
||||
{Version: version.MustParse("1.30"), Default: false, PreRelease: Beta},
|
||||
{Version: version.MustParse("1.35"), Default: true, PreRelease: Beta},
|
||||
},
|
||||
}
|
||||
|
||||
14
go.mod
14
go.mod
@@ -18,14 +18,14 @@ require (
|
||||
github.com/spf13/pflag v1.0.9
|
||||
github.com/stretchr/testify v1.11.1
|
||||
go.uber.org/goleak v1.3.0
|
||||
golang.org/x/net v0.43.0
|
||||
golang.org/x/net v0.47.0
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
golang.org/x/term v0.36.0
|
||||
golang.org/x/term v0.37.0
|
||||
golang.org/x/time v0.9.0
|
||||
google.golang.org/protobuf v1.36.8
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0
|
||||
k8s.io/api v0.35.0-alpha.3
|
||||
k8s.io/apimachinery v0.35.0-alpha.3
|
||||
k8s.io/api v0.35.4
|
||||
k8s.io/apimachinery v0.35.4
|
||||
k8s.io/klog/v2 v2.130.1
|
||||
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912
|
||||
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4
|
||||
@@ -48,7 +48,7 @@ require (
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/moby/spdystream v0.5.0 // 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/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||
@@ -57,8 +57,8 @@ require (
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
40
go.sum
40
go.sum
@@ -50,8 +50,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
|
||||
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
|
||||
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=
|
||||
@@ -93,24 +93,24 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
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/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
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/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
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/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -123,10 +123,10 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
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/api v0.35.0-alpha.3 h1:BdcXkJ4n/NKhfg06PaSDG8r8Mpe9g3KO9Fkj7B/F8/4=
|
||||
k8s.io/api v0.35.0-alpha.3/go.mod h1:SArWbUwVv7VhTGGbKX0RoMPXiT6ztjjzkKpRRdl6+E0=
|
||||
k8s.io/apimachinery v0.35.0-alpha.3 h1:aHqVUsi78MIDmMfmMTRMAnpxUlA7poaU1iNXN/sM6gs=
|
||||
k8s.io/apimachinery v0.35.0-alpha.3/go.mod h1:dR9KPaf5L0t2p9jZg/wCGB4b3ma2sXZ2zdNqILs+Sak=
|
||||
k8s.io/api v0.35.4 h1:P7nFYKl5vo9AGUp1Z+Pmd3p2tA7bX2wbFWCvDeRv988=
|
||||
k8s.io/api v0.35.4/go.mod h1:yl4lqySWOgYJJf9RERXKUwE9g2y+CkuwG+xmcOK8wXU=
|
||||
k8s.io/apimachinery v0.35.4 h1:xtdom9RG7e+yDp71uoXoJDWEE2eOiHgeO4GdBzwWpds=
|
||||
k8s.io/apimachinery v0.35.4/go.mod h1:NNi1taPOpep0jOj+oRha3mBJPqvi0hGdaV8TCqGQ+cc=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=
|
||||
|
||||
@@ -426,6 +426,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
|
||||
// Group=scheduling.k8s.io, Version=v1alpha1
|
||||
case schedulingv1alpha1.SchemeGroupVersion.WithResource("priorityclasses"):
|
||||
return &genericInformer{resource: resource.GroupResource(), informer: f.Scheduling().V1alpha1().PriorityClasses().Informer()}, nil
|
||||
case schedulingv1alpha1.SchemeGroupVersion.WithResource("workloads"):
|
||||
return &genericInformer{resource: resource.GroupResource(), informer: f.Scheduling().V1alpha1().Workloads().Informer()}, nil
|
||||
|
||||
// Group=scheduling.k8s.io, Version=v1beta1
|
||||
case schedulingv1beta1.SchemeGroupVersion.WithResource("priorityclasses"):
|
||||
|
||||
@@ -26,6 +26,8 @@ import (
|
||||
type Interface interface {
|
||||
// PriorityClasses returns a PriorityClassInformer.
|
||||
PriorityClasses() PriorityClassInformer
|
||||
// Workloads returns a WorkloadInformer.
|
||||
Workloads() WorkloadInformer
|
||||
}
|
||||
|
||||
type version struct {
|
||||
@@ -43,3 +45,8 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList
|
||||
func (v *version) PriorityClasses() PriorityClassInformer {
|
||||
return &priorityClassInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
|
||||
}
|
||||
|
||||
// Workloads returns a WorkloadInformer.
|
||||
func (v *version) Workloads() WorkloadInformer {
|
||||
return &workloadInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
|
||||
}
|
||||
|
||||
102
informers/scheduling/v1alpha1/workload.go
Normal file
102
informers/scheduling/v1alpha1/workload.go
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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 informer-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
context "context"
|
||||
time "time"
|
||||
|
||||
apischedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
internalinterfaces "k8s.io/client-go/informers/internalinterfaces"
|
||||
kubernetes "k8s.io/client-go/kubernetes"
|
||||
schedulingv1alpha1 "k8s.io/client-go/listers/scheduling/v1alpha1"
|
||||
cache "k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// WorkloadInformer provides access to a shared informer and lister for
|
||||
// Workloads.
|
||||
type WorkloadInformer interface {
|
||||
Informer() cache.SharedIndexInformer
|
||||
Lister() schedulingv1alpha1.WorkloadLister
|
||||
}
|
||||
|
||||
type workloadInformer struct {
|
||||
factory internalinterfaces.SharedInformerFactory
|
||||
tweakListOptions internalinterfaces.TweakListOptionsFunc
|
||||
namespace string
|
||||
}
|
||||
|
||||
// NewWorkloadInformer constructs a new informer for Workload 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 NewWorkloadInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
|
||||
return NewFilteredWorkloadInformer(client, namespace, resyncPeriod, indexers, nil)
|
||||
}
|
||||
|
||||
// NewFilteredWorkloadInformer constructs a new informer for Workload 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 NewFilteredWorkloadInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
|
||||
return cache.NewSharedIndexInformer(
|
||||
cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{
|
||||
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
|
||||
if tweakListOptions != nil {
|
||||
tweakListOptions(&options)
|
||||
}
|
||||
return client.SchedulingV1alpha1().Workloads(namespace).List(context.Background(), options)
|
||||
},
|
||||
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
|
||||
if tweakListOptions != nil {
|
||||
tweakListOptions(&options)
|
||||
}
|
||||
return client.SchedulingV1alpha1().Workloads(namespace).Watch(context.Background(), options)
|
||||
},
|
||||
ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {
|
||||
if tweakListOptions != nil {
|
||||
tweakListOptions(&options)
|
||||
}
|
||||
return client.SchedulingV1alpha1().Workloads(namespace).List(ctx, options)
|
||||
},
|
||||
WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {
|
||||
if tweakListOptions != nil {
|
||||
tweakListOptions(&options)
|
||||
}
|
||||
return client.SchedulingV1alpha1().Workloads(namespace).Watch(ctx, options)
|
||||
},
|
||||
}, client),
|
||||
&apischedulingv1alpha1.Workload{},
|
||||
resyncPeriod,
|
||||
indexers,
|
||||
)
|
||||
}
|
||||
|
||||
func (f *workloadInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
|
||||
return NewFilteredWorkloadInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
|
||||
}
|
||||
|
||||
func (f *workloadInformer) Informer() cache.SharedIndexInformer {
|
||||
return f.factory.InformerFor(&apischedulingv1alpha1.Workload{}, f.defaultInformer)
|
||||
}
|
||||
|
||||
func (f *workloadInformer) Lister() schedulingv1alpha1.WorkloadLister {
|
||||
return schedulingv1alpha1.NewWorkloadLister(f.Informer().GetIndexer())
|
||||
}
|
||||
@@ -143,10 +143,6 @@ import (
|
||||
// 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.
|
||||
//
|
||||
// Deprecated: NewClientset replaces this with support for field management, which significantly improves
|
||||
// server side apply testing. NewClientset is only available when apply configurations are generated (e.g.
|
||||
// via --with-applyconfig).
|
||||
func NewSimpleClientset(objects ...runtime.Object) *Clientset {
|
||||
o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder())
|
||||
for _, obj := range objects {
|
||||
@@ -207,6 +203,10 @@ func (c *Clientset) IsWatchListSemanticsUnSupported() bool {
|
||||
// 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,
|
||||
|
||||
@@ -32,6 +32,10 @@ func (c *FakeSchedulingV1alpha1) PriorityClasses() v1alpha1.PriorityClassInterfa
|
||||
return newFakePriorityClasses(c)
|
||||
}
|
||||
|
||||
func (c *FakeSchedulingV1alpha1) Workloads(namespace string) v1alpha1.WorkloadInterface {
|
||||
return newFakeWorkloads(c, namespace)
|
||||
}
|
||||
|
||||
// RESTClient returns a RESTClient that is used to communicate
|
||||
// with API server by this client implementation.
|
||||
func (c *FakeSchedulingV1alpha1) RESTClient() rest.Interface {
|
||||
|
||||
51
kubernetes/typed/scheduling/v1alpha1/fake/fake_workload.go
Normal file
51
kubernetes/typed/scheduling/v1alpha1/fake/fake_workload.go
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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 client-gen. DO NOT EDIT.
|
||||
|
||||
package fake
|
||||
|
||||
import (
|
||||
v1alpha1 "k8s.io/api/scheduling/v1alpha1"
|
||||
schedulingv1alpha1 "k8s.io/client-go/applyconfigurations/scheduling/v1alpha1"
|
||||
gentype "k8s.io/client-go/gentype"
|
||||
typedschedulingv1alpha1 "k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1"
|
||||
)
|
||||
|
||||
// fakeWorkloads implements WorkloadInterface
|
||||
type fakeWorkloads struct {
|
||||
*gentype.FakeClientWithListAndApply[*v1alpha1.Workload, *v1alpha1.WorkloadList, *schedulingv1alpha1.WorkloadApplyConfiguration]
|
||||
Fake *FakeSchedulingV1alpha1
|
||||
}
|
||||
|
||||
func newFakeWorkloads(fake *FakeSchedulingV1alpha1, namespace string) typedschedulingv1alpha1.WorkloadInterface {
|
||||
return &fakeWorkloads{
|
||||
gentype.NewFakeClientWithListAndApply[*v1alpha1.Workload, *v1alpha1.WorkloadList, *schedulingv1alpha1.WorkloadApplyConfiguration](
|
||||
fake.Fake,
|
||||
namespace,
|
||||
v1alpha1.SchemeGroupVersion.WithResource("workloads"),
|
||||
v1alpha1.SchemeGroupVersion.WithKind("Workload"),
|
||||
func() *v1alpha1.Workload { return &v1alpha1.Workload{} },
|
||||
func() *v1alpha1.WorkloadList { return &v1alpha1.WorkloadList{} },
|
||||
func(dst, src *v1alpha1.WorkloadList) { dst.ListMeta = src.ListMeta },
|
||||
func(list *v1alpha1.WorkloadList) []*v1alpha1.Workload { return gentype.ToPointerSlice(list.Items) },
|
||||
func(list *v1alpha1.WorkloadList, items []*v1alpha1.Workload) {
|
||||
list.Items = gentype.FromPointerSlice(items)
|
||||
},
|
||||
),
|
||||
fake,
|
||||
}
|
||||
}
|
||||
@@ -19,3 +19,5 @@ limitations under the License.
|
||||
package v1alpha1
|
||||
|
||||
type PriorityClassExpansion interface{}
|
||||
|
||||
type WorkloadExpansion interface{}
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
type SchedulingV1alpha1Interface interface {
|
||||
RESTClient() rest.Interface
|
||||
PriorityClassesGetter
|
||||
WorkloadsGetter
|
||||
}
|
||||
|
||||
// SchedulingV1alpha1Client is used to interact with features provided by the scheduling.k8s.io group.
|
||||
@@ -40,6 +41,10 @@ func (c *SchedulingV1alpha1Client) PriorityClasses() PriorityClassInterface {
|
||||
return newPriorityClasses(c)
|
||||
}
|
||||
|
||||
func (c *SchedulingV1alpha1Client) Workloads(namespace string) WorkloadInterface {
|
||||
return newWorkloads(c, namespace)
|
||||
}
|
||||
|
||||
// NewForConfig creates a new SchedulingV1alpha1Client for the given config.
|
||||
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
|
||||
// where httpClient was generated with rest.HTTPClientFor(c).
|
||||
|
||||
71
kubernetes/typed/scheduling/v1alpha1/workload.go
Normal file
71
kubernetes/typed/scheduling/v1alpha1/workload.go
Normal file
@@ -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.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
applyconfigurationsschedulingv1alpha1 "k8s.io/client-go/applyconfigurations/scheduling/v1alpha1"
|
||||
gentype "k8s.io/client-go/gentype"
|
||||
scheme "k8s.io/client-go/kubernetes/scheme"
|
||||
)
|
||||
|
||||
// WorkloadsGetter has a method to return a WorkloadInterface.
|
||||
// A group's client should implement this interface.
|
||||
type WorkloadsGetter interface {
|
||||
Workloads(namespace string) WorkloadInterface
|
||||
}
|
||||
|
||||
// WorkloadInterface has methods to work with Workload resources.
|
||||
type WorkloadInterface interface {
|
||||
Create(ctx context.Context, workload *schedulingv1alpha1.Workload, opts v1.CreateOptions) (*schedulingv1alpha1.Workload, error)
|
||||
Update(ctx context.Context, workload *schedulingv1alpha1.Workload, opts v1.UpdateOptions) (*schedulingv1alpha1.Workload, error)
|
||||
Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
|
||||
DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
|
||||
Get(ctx context.Context, name string, opts v1.GetOptions) (*schedulingv1alpha1.Workload, error)
|
||||
List(ctx context.Context, opts v1.ListOptions) (*schedulingv1alpha1.WorkloadList, error)
|
||||
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
|
||||
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *schedulingv1alpha1.Workload, err error)
|
||||
Apply(ctx context.Context, workload *applyconfigurationsschedulingv1alpha1.WorkloadApplyConfiguration, opts v1.ApplyOptions) (result *schedulingv1alpha1.Workload, err error)
|
||||
WorkloadExpansion
|
||||
}
|
||||
|
||||
// workloads implements WorkloadInterface
|
||||
type workloads struct {
|
||||
*gentype.ClientWithListAndApply[*schedulingv1alpha1.Workload, *schedulingv1alpha1.WorkloadList, *applyconfigurationsschedulingv1alpha1.WorkloadApplyConfiguration]
|
||||
}
|
||||
|
||||
// newWorkloads returns a Workloads
|
||||
func newWorkloads(c *SchedulingV1alpha1Client, namespace string) *workloads {
|
||||
return &workloads{
|
||||
gentype.NewClientWithListAndApply[*schedulingv1alpha1.Workload, *schedulingv1alpha1.WorkloadList, *applyconfigurationsschedulingv1alpha1.WorkloadApplyConfiguration](
|
||||
"workloads",
|
||||
c.RESTClient(),
|
||||
scheme.ParameterCodec,
|
||||
namespace,
|
||||
func() *schedulingv1alpha1.Workload { return &schedulingv1alpha1.Workload{} },
|
||||
func() *schedulingv1alpha1.WorkloadList { return &schedulingv1alpha1.WorkloadList{} },
|
||||
gentype.PrefersProtobuf[*schedulingv1alpha1.Workload](),
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -21,3 +21,11 @@ package v1alpha1
|
||||
// PriorityClassListerExpansion allows custom methods to be added to
|
||||
// PriorityClassLister.
|
||||
type PriorityClassListerExpansion interface{}
|
||||
|
||||
// WorkloadListerExpansion allows custom methods to be added to
|
||||
// WorkloadLister.
|
||||
type WorkloadListerExpansion interface{}
|
||||
|
||||
// WorkloadNamespaceListerExpansion allows custom methods to be added to
|
||||
// WorkloadNamespaceLister.
|
||||
type WorkloadNamespaceListerExpansion interface{}
|
||||
|
||||
70
listers/scheduling/v1alpha1/workload.go
Normal file
70
listers/scheduling/v1alpha1/workload.go
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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 lister-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1"
|
||||
labels "k8s.io/apimachinery/pkg/labels"
|
||||
listers "k8s.io/client-go/listers"
|
||||
cache "k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// WorkloadLister helps list Workloads.
|
||||
// All objects returned here must be treated as read-only.
|
||||
type WorkloadLister interface {
|
||||
// List lists all Workloads in the indexer.
|
||||
// Objects returned here must be treated as read-only.
|
||||
List(selector labels.Selector) (ret []*schedulingv1alpha1.Workload, err error)
|
||||
// Workloads returns an object that can list and get Workloads.
|
||||
Workloads(namespace string) WorkloadNamespaceLister
|
||||
WorkloadListerExpansion
|
||||
}
|
||||
|
||||
// workloadLister implements the WorkloadLister interface.
|
||||
type workloadLister struct {
|
||||
listers.ResourceIndexer[*schedulingv1alpha1.Workload]
|
||||
}
|
||||
|
||||
// NewWorkloadLister returns a new WorkloadLister.
|
||||
func NewWorkloadLister(indexer cache.Indexer) WorkloadLister {
|
||||
return &workloadLister{listers.New[*schedulingv1alpha1.Workload](indexer, schedulingv1alpha1.Resource("workload"))}
|
||||
}
|
||||
|
||||
// Workloads returns an object that can list and get Workloads.
|
||||
func (s *workloadLister) Workloads(namespace string) WorkloadNamespaceLister {
|
||||
return workloadNamespaceLister{listers.NewNamespaced[*schedulingv1alpha1.Workload](s.ResourceIndexer, namespace)}
|
||||
}
|
||||
|
||||
// WorkloadNamespaceLister helps list and get Workloads.
|
||||
// All objects returned here must be treated as read-only.
|
||||
type WorkloadNamespaceLister interface {
|
||||
// List lists all Workloads in the indexer for a given namespace.
|
||||
// Objects returned here must be treated as read-only.
|
||||
List(selector labels.Selector) (ret []*schedulingv1alpha1.Workload, err error)
|
||||
// Get retrieves the Workload from the indexer for a given namespace and name.
|
||||
// Objects returned here must be treated as read-only.
|
||||
Get(name string) (*schedulingv1alpha1.Workload, error)
|
||||
WorkloadNamespaceListerExpansion
|
||||
}
|
||||
|
||||
// workloadNamespaceLister implements the WorkloadNamespaceLister
|
||||
// interface.
|
||||
type workloadNamespaceLister struct {
|
||||
listers.ResourceIndexer[*schedulingv1alpha1.Workload]
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -39,6 +40,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/util/dump"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/client-go/pkg/apis/clientauthentication"
|
||||
"k8s.io/client-go/pkg/apis/clientauthentication/install"
|
||||
clientauthenticationv1 "k8s.io/client-go/pkg/apis/clientauthentication/v1"
|
||||
@@ -177,13 +179,29 @@ func newAuthenticator(c *cache, isTerminalFunc func(int) bool, config *api.ExecC
|
||||
connTracker,
|
||||
)
|
||||
|
||||
if err := ValidatePluginPolicy(config.PluginPolicy); err != nil {
|
||||
return nil, fmt.Errorf("invalid plugin policy: %w", err)
|
||||
}
|
||||
|
||||
allowlistLookup := sets.New[string]()
|
||||
for _, entry := range config.PluginPolicy.Allowlist {
|
||||
if entry.Name != "" {
|
||||
allowlistLookup.Insert(entry.Name)
|
||||
}
|
||||
}
|
||||
|
||||
a := &Authenticator{
|
||||
cmd: config.Command,
|
||||
// Clean is called to normalize the path to facilitate comparison with
|
||||
// the allowlist, when present
|
||||
cmd: filepath.Clean(config.Command),
|
||||
args: config.Args,
|
||||
group: gv,
|
||||
cluster: cluster,
|
||||
provideClusterInfo: config.ProvideClusterInfo,
|
||||
|
||||
allowlistLookup: allowlistLookup,
|
||||
execPluginPolicy: config.PluginPolicy,
|
||||
|
||||
installHint: config.InstallHint,
|
||||
sometimes: &sometimes{
|
||||
threshold: 10,
|
||||
@@ -250,6 +268,9 @@ type Authenticator struct {
|
||||
cluster *clientauthentication.Cluster
|
||||
provideClusterInfo bool
|
||||
|
||||
allowlistLookup sets.Set[string]
|
||||
execPluginPolicy api.PluginPolicy
|
||||
|
||||
// Used to avoid log spew by rate limiting install hint printing. We didn't do
|
||||
// this by interval based rate limiting alone since that way may have prevented
|
||||
// the install hint from showing up for kubectl users.
|
||||
@@ -441,6 +462,12 @@ func (a *Authenticator) refreshCredsLocked() error {
|
||||
cmd.Stdin = a.stdin
|
||||
}
|
||||
|
||||
err = a.updateCommandAndCheckAllowlistLocked(cmd)
|
||||
incrementPolicyMetric(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cmd.Run()
|
||||
incrementCallsMetric(err)
|
||||
if err != nil {
|
||||
@@ -545,3 +572,131 @@ func (a *Authenticator) wrapCmdRunErrorLocked(err error) error {
|
||||
return fmt.Errorf("exec: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// `updateCommandAndCheckAllowlistLocked` determines whether or not the specified executable may run
|
||||
// according to the credential plugin policy. If the plugin is allowed, `nil`
|
||||
// is returned. If the plugin is not allowed, an error must be returned
|
||||
// explaining why.
|
||||
func (a *Authenticator) updateCommandAndCheckAllowlistLocked(cmd *exec.Cmd) error {
|
||||
switch a.execPluginPolicy.PolicyType {
|
||||
case "", api.PluginPolicyAllowAll:
|
||||
return nil
|
||||
case api.PluginPolicyDenyAll:
|
||||
return fmt.Errorf("plugin %q not allowed: policy set to %q", a.cmd, api.PluginPolicyDenyAll)
|
||||
case api.PluginPolicyAllowlist:
|
||||
return a.checkAllowlistLocked(cmd)
|
||||
default:
|
||||
return fmt.Errorf("unknown plugin policy %q", a.execPluginPolicy.PolicyType)
|
||||
}
|
||||
}
|
||||
|
||||
// `checkAllowlistLocked` checks the specified plugin against the allowlist,
|
||||
// and may update the Authenticator's allowlistLookup set.
|
||||
func (a *Authenticator) checkAllowlistLocked(cmd *exec.Cmd) error {
|
||||
// a.cmd is the original command as specified in the configuration, then filepath.Clean().
|
||||
// cmd.Path is the possibly-resolved command.
|
||||
// If either are an exact match in the allowlist, return success.
|
||||
if a.allowlistLookup.Has(a.cmd) || a.allowlistLookup.Has(cmd.Path) {
|
||||
return nil
|
||||
}
|
||||
|
||||
var cmdResolvedPath string
|
||||
var cmdResolvedErr error
|
||||
if cmd.Path != a.cmd {
|
||||
// cmd.Path changed, use the already-resolved LookPath results
|
||||
cmdResolvedPath = cmd.Path
|
||||
cmdResolvedErr = cmd.Err
|
||||
} else {
|
||||
// cmd.Path is unchanged, do LookPath ourselves
|
||||
cmdResolvedPath, cmdResolvedErr = exec.LookPath(cmd.Path)
|
||||
// update cmd.Path to cmdResolvedPath so we only run the resolved path
|
||||
if cmdResolvedPath != "" {
|
||||
cmd.Path = cmdResolvedPath
|
||||
}
|
||||
}
|
||||
|
||||
if cmdResolvedErr != nil {
|
||||
return fmt.Errorf("plugin path %q cannot be resolved for credential plugin allowlist check: %w", cmd.Path, cmdResolvedErr)
|
||||
}
|
||||
|
||||
// cmdResolvedPath may have changed, and the changed value may be in the allowlist
|
||||
if a.allowlistLookup.Has(cmdResolvedPath) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// There is no verbatim match
|
||||
a.resolveAllowListEntriesLocked(cmd.Path)
|
||||
|
||||
// allowlistLookup may have changed, recheck
|
||||
if a.allowlistLookup.Has(cmdResolvedPath) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("plugin path %q is not permitted by the credential plugin allowlist", cmd.Path)
|
||||
}
|
||||
|
||||
// resolveAllowListEntriesLocked tries to resolve allowlist entries with LookPath,
|
||||
// and adds successfully resolved entries to allowlistLookup.
|
||||
// The optional commandHint can be used to limit which entries are resolved to ones which match the hint basename.
|
||||
func (a *Authenticator) resolveAllowListEntriesLocked(commandHint string) {
|
||||
hintName := filepath.Base(commandHint)
|
||||
for _, entry := range a.execPluginPolicy.Allowlist {
|
||||
entryBasename := filepath.Base(entry.Name)
|
||||
if hintName != "" && hintName != entryBasename {
|
||||
// we got a hint, and this allowlist entry does not match it
|
||||
continue
|
||||
}
|
||||
entryResolvedPath, err := exec.LookPath(entry.Name)
|
||||
if err != nil {
|
||||
klog.V(5).ErrorS(err, "resolving credential plugin allowlist", "name", entry.Name)
|
||||
continue
|
||||
}
|
||||
if entryResolvedPath != "" {
|
||||
a.allowlistLookup.Insert(entryResolvedPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ValidatePluginPolicy(policy api.PluginPolicy) error {
|
||||
switch policy.PolicyType {
|
||||
// "" is equivalent to "AllowAll"
|
||||
case "", api.PluginPolicyAllowAll, api.PluginPolicyDenyAll:
|
||||
if policy.Allowlist != nil {
|
||||
return fmt.Errorf("misconfigured credential plugin allowlist: plugin policy is %q but allowlist is non-nil", policy.PolicyType)
|
||||
}
|
||||
return nil
|
||||
case api.PluginPolicyAllowlist:
|
||||
return validateAllowlist(policy.Allowlist)
|
||||
default:
|
||||
return fmt.Errorf("unknown plugin policy: %q", policy.PolicyType)
|
||||
}
|
||||
}
|
||||
|
||||
var emptyAllowlistEntry api.AllowlistEntry
|
||||
|
||||
func validateAllowlist(list []api.AllowlistEntry) error {
|
||||
// This will be the case if the user has misspelled the field name for the
|
||||
// allowlist. Because this is a security knob, fail immediately rather than
|
||||
// proceed when the user has made a mistake.
|
||||
if list == nil {
|
||||
return fmt.Errorf("credential plugin policy set to %q, but allowlist is unspecified", api.PluginPolicyAllowlist)
|
||||
}
|
||||
|
||||
if len(list) == 0 {
|
||||
return fmt.Errorf("credential plugin policy set to %q, but allowlist is empty; use %q policy instead", api.PluginPolicyAllowlist, api.PluginPolicyDenyAll)
|
||||
}
|
||||
|
||||
for i, item := range list {
|
||||
if item == emptyAllowlistEntry {
|
||||
return fmt.Errorf("misconfigured credential plugin allowlist: empty allowlist entry #%d", i+1)
|
||||
}
|
||||
|
||||
if cleaned := filepath.Clean(item.Name); cleaned != item.Name {
|
||||
return fmt.Errorf("non-normalized file path: %q vs %q", item.Name, cleaned)
|
||||
} else if item.Name == "" {
|
||||
return fmt.Errorf("empty file path: %q", item.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -29,8 +29,12 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
mathrand "math/rand/v2"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -845,6 +849,354 @@ func TestRefreshCreds(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type pluginPolicyTest struct {
|
||||
name string
|
||||
wantErr bool
|
||||
wantErrSubstr string
|
||||
config *api.ExecConfig
|
||||
pluginExists bool
|
||||
entryExists bool
|
||||
usePluginAbsPath bool
|
||||
useEntryAbsPath bool
|
||||
allowlistLength int
|
||||
policyType api.PolicyType
|
||||
allowlist []api.AllowlistEntry
|
||||
}
|
||||
|
||||
type dualBool struct{ plugin, entry bool }
|
||||
|
||||
type pluginPolicyTestMatrix struct {
|
||||
exists []dualBool
|
||||
absolute []dualBool
|
||||
allowlistLengths []int
|
||||
policies []api.PolicyType
|
||||
|
||||
// the logic of whether or not a given test configuration should produce an error
|
||||
shouldErrFunc func(tt *pluginPolicyTest) (bool, string)
|
||||
}
|
||||
|
||||
var allPermutations = []dualBool{
|
||||
{plugin: false, entry: false},
|
||||
{plugin: false, entry: true},
|
||||
{plugin: true, entry: false},
|
||||
{plugin: true, entry: true},
|
||||
}
|
||||
|
||||
// TestPluginPolicy tests the functioning of various plugin policies, as well
|
||||
// as the the validity of a wide variety of policies.
|
||||
func TestPluginPolicy(t *testing.T) {
|
||||
// this is inlined to make highly visible and explicit the logic of which
|
||||
// test configurations should pass and which should fail
|
||||
shouldErrFunc := func(test *pluginPolicyTest) (bool, string) {
|
||||
switch test.policyType {
|
||||
case "", api.PluginPolicyAllowAll:
|
||||
if test.allowlist != nil { // invalid
|
||||
return true, "allowlist is non-nil"
|
||||
}
|
||||
|
||||
if test.pluginExists {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
return true, "not found"
|
||||
case api.PluginPolicyDenyAll:
|
||||
if test.allowlist != nil { // invalid
|
||||
return true, "allowlist is non-nil"
|
||||
}
|
||||
|
||||
return true, `policy set to "DenyAll"`
|
||||
case api.PluginPolicyAllowlist:
|
||||
if test.allowlist == nil { // invalid
|
||||
return true, "allowlist is unspecified"
|
||||
}
|
||||
|
||||
if len(test.allowlist) == 0 { // invalid
|
||||
return true, "allowlist is empty; use \"DenyAll\" policy instead"
|
||||
}
|
||||
|
||||
switch {
|
||||
case test.pluginExists && test.entryExists:
|
||||
return false, ""
|
||||
case test.pluginExists && !test.entryExists:
|
||||
return true, "is not permitted by the credential plugin allowlist"
|
||||
case !test.pluginExists && test.entryExists:
|
||||
// error message varies depending on whether the paths are relative or absolute
|
||||
// what is important is that an error arises here
|
||||
return true, ""
|
||||
case !test.pluginExists && !test.entryExists:
|
||||
// error message varies depending on whether the paths are relative or absolute
|
||||
// what is important is that an error arises here
|
||||
return true, ""
|
||||
}
|
||||
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
return true, "unknown plugin policy"
|
||||
}
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("could not get working directory: %s", err)
|
||||
}
|
||||
|
||||
testdataDir := filepath.Join(wd, "testdata")
|
||||
|
||||
path := os.Getenv("PATH")
|
||||
defer func() {
|
||||
if err = os.Setenv("PATH", path); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
matrix := pluginPolicyTestMatrix{
|
||||
shouldErrFunc: shouldErrFunc,
|
||||
exists: allPermutations,
|
||||
absolute: allPermutations,
|
||||
allowlistLengths: []int{-1 /*nil*/, 0, 1, 3},
|
||||
policies: []api.PolicyType{
|
||||
"",
|
||||
api.PluginPolicyAllowAll,
|
||||
api.PluginPolicyDenyAll,
|
||||
api.PluginPolicyAllowlist,
|
||||
api.PolicyType("HIGHLYILLEGAL"),
|
||||
},
|
||||
}
|
||||
|
||||
tests := matrix.makeTests(t, testdataDir, path)
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
c := test.config
|
||||
a, err := newAuthenticator(newCache(), func(_ int) bool { return false }, c, &clientauthentication.Cluster{})
|
||||
if err != nil {
|
||||
if !test.wantErr {
|
||||
t.Fatalf("unexpected validation error: %v", err)
|
||||
} else if !strings.Contains(err.Error(), test.wantErrSubstr) {
|
||||
t.Fatalf("expected error with substring '%v' got '%v'", test.wantErrSubstr, err.Error())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
stderr := &bytes.Buffer{}
|
||||
a.stderr = stderr
|
||||
a.environ = func() []string { return nil }
|
||||
|
||||
if err := a.refreshCredsLocked(); err != nil {
|
||||
if !test.wantErr {
|
||||
t.Fatalf("unexpected allows plugin error: %v", err)
|
||||
} else if !strings.Contains(err.Error(), test.wantErrSubstr) {
|
||||
t.Fatalf("expected error with substring '%v' got '%v'", test.wantErrSubstr, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if test.wantErr {
|
||||
t.Fatal("expected allowlist error, but error was nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (m *pluginPolicyTestMatrix) makeTests(t *testing.T, testdataDir, path string) []pluginPolicyTest {
|
||||
const existingPluginInPATHBasename = "test-plugin.sh"
|
||||
existingPluginInPATHAbsolutePath := filepath.Join(testdataDir, existingPluginInPATHBasename)
|
||||
|
||||
err := os.Setenv("PATH", fmt.Sprintf("%s:%s", testdataDir, path))
|
||||
if err != nil {
|
||||
t.Fatalf("error setting PATH: %s", err)
|
||||
}
|
||||
|
||||
resolved, err := exec.LookPath(existingPluginInPATHBasename)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if existingPluginInPATHAbsolutePath != resolved {
|
||||
t.Fatalf("test plugin basename resolved incorrectly: did not resolve to %s", existingPluginInPATHAbsolutePath)
|
||||
}
|
||||
|
||||
resolvedAbs, err := exec.LookPath(existingPluginInPATHAbsolutePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if existingPluginInPATHAbsolutePath != resolvedAbs {
|
||||
t.Fatalf("test plugin absolute path resolved incorrectly: did not resolve to %s", existingPluginInPATHAbsolutePath)
|
||||
}
|
||||
|
||||
tests := make([]pluginPolicyTest, 0, len(m.exists)*2+len(m.absolute)*2+len(m.allowlistLengths)+len(m.policies))
|
||||
|
||||
var tt *pluginPolicyTest
|
||||
for _, exists := range m.exists {
|
||||
tt = new(pluginPolicyTest)
|
||||
|
||||
tt.setExists(exists)
|
||||
for _, absolute := range m.absolute {
|
||||
tt.setAbsolute(absolute)
|
||||
|
||||
for _, length := range m.allowlistLengths {
|
||||
tt.setAllowlist(length, existingPluginInPATHAbsolutePath, existingPluginInPATHBasename)
|
||||
|
||||
for _, p := range m.policies {
|
||||
tt.policyType = p
|
||||
tt.setName()
|
||||
tt.setExecConfig(existingPluginInPATHAbsolutePath, existingPluginInPATHBasename)
|
||||
tt.setErrDetails(m)
|
||||
|
||||
tests = append(tests, *tt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tests
|
||||
}
|
||||
|
||||
func (tt *pluginPolicyTest) setErrDetails(m *pluginPolicyTestMatrix) {
|
||||
tt.wantErr, tt.wantErrSubstr = m.shouldErrFunc(tt)
|
||||
}
|
||||
|
||||
func (tt *pluginPolicyTest) setAbsolute(absolute dualBool) {
|
||||
tt.usePluginAbsPath = absolute.plugin
|
||||
tt.useEntryAbsPath = absolute.entry
|
||||
}
|
||||
|
||||
func (tt *pluginPolicyTest) setExists(exists dualBool) {
|
||||
tt.pluginExists = exists.plugin
|
||||
tt.entryExists = exists.entry
|
||||
}
|
||||
|
||||
func (tt *pluginPolicyTest) setAllowlist(l int, existingPluginInPATHAbsolutePath string, existingPluginInPATHBasename string) {
|
||||
tt.allowlistLength = l
|
||||
if tt.allowlistLength >= 0 {
|
||||
tt.allowlist = make([]api.AllowlistEntry, 0, tt.allowlistLength)
|
||||
}
|
||||
|
||||
if tt.allowlistLength >= 1 {
|
||||
entry := tt.makeAllowlistEntry(existingPluginInPATHAbsolutePath, existingPluginInPATHBasename)
|
||||
tt.allowlist = append(tt.allowlist, entry)
|
||||
}
|
||||
|
||||
for i := 1; i < tt.allowlistLength; i++ {
|
||||
tt.allowlist = append(tt.allowlist, api.AllowlistEntry{Name: fmt.Sprintf("foo-%d", i)})
|
||||
}
|
||||
|
||||
// shuffle the allowlist to guarantee ordering doesn't matter
|
||||
for i := range tt.allowlist {
|
||||
j := mathrand.IntN(i + 1)
|
||||
tt.allowlist[i], tt.allowlist[j] = tt.allowlist[j], tt.allowlist[i]
|
||||
}
|
||||
}
|
||||
|
||||
func (tt *pluginPolicyTest) makeAllowlistEntry(existingPluginInPATHAbsolutePath string, existingPluginInPATHBasename string) api.AllowlistEntry {
|
||||
var entry api.AllowlistEntry
|
||||
|
||||
switch {
|
||||
case tt.entryExists && tt.useEntryAbsPath:
|
||||
entry.Name = existingPluginInPATHAbsolutePath
|
||||
case tt.entryExists && !tt.useEntryAbsPath:
|
||||
entry.Name = existingPluginInPATHBasename
|
||||
case !tt.entryExists && tt.useEntryAbsPath:
|
||||
entry.Name = "/this/path/does/not/exist"
|
||||
case !tt.entryExists && !tt.useEntryAbsPath:
|
||||
entry.Name = "does not exist"
|
||||
}
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
func (tt *pluginPolicyTest) setExecConfig(existingPluginInPATHAbsolutePath string, existingPluginInPATHBasename string) {
|
||||
var cmd string
|
||||
|
||||
switch {
|
||||
case tt.pluginExists && tt.usePluginAbsPath:
|
||||
cmd = existingPluginInPATHAbsolutePath
|
||||
case tt.pluginExists && !tt.usePluginAbsPath:
|
||||
cmd = existingPluginInPATHBasename
|
||||
case !tt.pluginExists && tt.usePluginAbsPath:
|
||||
cmd = "/this/path/does/not/exist"
|
||||
case !tt.pluginExists && !tt.usePluginAbsPath:
|
||||
cmd = "does not exist"
|
||||
default: // verifiably unreachable
|
||||
cmd = "does not exist"
|
||||
}
|
||||
|
||||
config := api.ExecConfig{}
|
||||
if strings.HasSuffix(cmd, "test-plugin.sh") {
|
||||
config.Env = append(config.Env, api.ExecEnvVar{
|
||||
Name: "TEST_OUTPUT",
|
||||
Value: `{
|
||||
"kind": "ExecCredential",
|
||||
"apiVersion": "client.authentication.k8s.io/v1",
|
||||
"status": {
|
||||
"token": "foo-bar"
|
||||
}
|
||||
}`,
|
||||
})
|
||||
config.Env = append(config.Env, api.ExecEnvVar{
|
||||
Name: "TEST_EXIT_CODE",
|
||||
Value: strconv.Itoa(0),
|
||||
})
|
||||
}
|
||||
|
||||
config.APIVersion = "client.authentication.k8s.io/v1"
|
||||
config.InteractiveMode = api.IfAvailableExecInteractiveMode
|
||||
config.Command = cmd
|
||||
config.PluginPolicy.PolicyType = tt.policyType
|
||||
config.PluginPolicy.Allowlist = tt.allowlist
|
||||
|
||||
tt.config = &config
|
||||
}
|
||||
|
||||
func makeExistsString(s string, t bool) string {
|
||||
ne := "nonexistent"
|
||||
if t {
|
||||
ne = "existing"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s-%s-path", ne, s)
|
||||
}
|
||||
|
||||
func makePathString(s string, t bool) string {
|
||||
ba := "basename"
|
||||
if t {
|
||||
ba = "absolute-path"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s-%s", ba, s)
|
||||
}
|
||||
|
||||
func (tt *pluginPolicyTest) setName() {
|
||||
p := string(tt.policyType)
|
||||
if string(tt.policyType) == "" {
|
||||
p = "unspecified"
|
||||
}
|
||||
var lengthStr string
|
||||
switch tt.allowlistLength {
|
||||
case -1:
|
||||
lengthStr = "nil-allowlist"
|
||||
case 0:
|
||||
lengthStr = "empty-allowlist"
|
||||
case 1:
|
||||
lengthStr = "single-entry-allowlist"
|
||||
default:
|
||||
lengthStr = "multiple-entry-allowlist"
|
||||
}
|
||||
pluginExistsString := makeExistsString("plugin", tt.pluginExists)
|
||||
entryExistsString := makeExistsString("entry", tt.entryExists)
|
||||
pluginPathString := makePathString("plugin", tt.usePluginAbsPath)
|
||||
entryPathString := makePathString("entry", tt.useEntryAbsPath)
|
||||
policyString := fmt.Sprintf("with-%s-policy", strings.ToLower(p))
|
||||
|
||||
tt.name = filepath.Join(
|
||||
policyString,
|
||||
lengthStr,
|
||||
pluginExistsString,
|
||||
entryExistsString,
|
||||
pluginPathString,
|
||||
entryPathString,
|
||||
)
|
||||
}
|
||||
|
||||
func TestRoundTripper(t *testing.T) {
|
||||
wantToken := ""
|
||||
|
||||
|
||||
@@ -49,6 +49,13 @@ const (
|
||||
// used in some failure modes (e.g., plugin not found, client internal error) so that someone
|
||||
// can more easily monitor all unsuccessful invocations.
|
||||
failureExitCode = 1
|
||||
|
||||
// pluginAllowed represents an exec plugin invocation that was allowed by
|
||||
// the plugin policy and/or the allowlist
|
||||
pluginAllowed = "allowed"
|
||||
// pluginAllowed represents an exec plugin invocation that was denied by
|
||||
// the plugin policy and/or the allowlist
|
||||
pluginDenied = "denied"
|
||||
)
|
||||
|
||||
type certificateExpirationTracker struct {
|
||||
@@ -109,3 +116,12 @@ func incrementCallsMetric(err error) {
|
||||
metrics.ExecPluginCalls.Increment(failureExitCode, clientInternalError)
|
||||
}
|
||||
}
|
||||
|
||||
func incrementPolicyMetric(err error) {
|
||||
if err != nil {
|
||||
metrics.ExecPluginPolicyCalls.Increment(pluginDenied)
|
||||
return
|
||||
}
|
||||
|
||||
metrics.ExecPluginPolicyCalls.Increment(pluginAllowed)
|
||||
}
|
||||
|
||||
@@ -196,3 +196,114 @@ func TestCallsMetric(t *testing.T) {
|
||||
t.Fatalf("got unexpected metrics calls; -want, +got:\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
type mockPolicyCallsMetric struct {
|
||||
status string
|
||||
}
|
||||
|
||||
type mockPolicyCallsMetricCounter struct {
|
||||
policyCalls []mockPolicyCallsMetric
|
||||
}
|
||||
|
||||
func (f *mockPolicyCallsMetricCounter) Increment(status string) {
|
||||
f.policyCalls = append(f.policyCalls, mockPolicyCallsMetric{status: status})
|
||||
}
|
||||
|
||||
func TestPolicyCallsMetric(t *testing.T) {
|
||||
const (
|
||||
goodOutput = `{
|
||||
"kind": "ExecCredential",
|
||||
"apiVersion": "client.authentication.k8s.io/v1beta1",
|
||||
"status": {
|
||||
"token": "foo-bar"
|
||||
}
|
||||
}`
|
||||
)
|
||||
|
||||
policyCallsMetricCounter := &mockPolicyCallsMetricCounter{}
|
||||
originalExecPluginPolicyCalls := metrics.ExecPluginPolicyCalls
|
||||
t.Cleanup(func() { metrics.ExecPluginPolicyCalls = originalExecPluginPolicyCalls })
|
||||
metrics.ExecPluginPolicyCalls = policyCallsMetricCounter
|
||||
|
||||
tests := []struct {
|
||||
wantDenied bool
|
||||
policy api.PluginPolicy
|
||||
}{
|
||||
{
|
||||
wantDenied: false,
|
||||
policy: api.PluginPolicy{PolicyType: api.PluginPolicyAllowAll},
|
||||
},
|
||||
{
|
||||
wantDenied: true,
|
||||
policy: api.PluginPolicy{PolicyType: api.PluginPolicyDenyAll},
|
||||
},
|
||||
{
|
||||
wantDenied: false,
|
||||
policy: api.PluginPolicy{
|
||||
PolicyType: api.PluginPolicyAllowlist,
|
||||
Allowlist: []api.AllowlistEntry{
|
||||
{
|
||||
Name: "foobar",
|
||||
},
|
||||
{
|
||||
Name: "testdata/test-plugin.sh",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
wantDenied: true,
|
||||
policy: api.PluginPolicy{
|
||||
PolicyType: api.PluginPolicyAllowlist,
|
||||
Allowlist: []api.AllowlistEntry{
|
||||
{Name: "foobar"},
|
||||
{Name: "baz"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var wantPolicyCallsMetrics []mockPolicyCallsMetric
|
||||
for _, test := range tests {
|
||||
c := api.ExecConfig{
|
||||
Command: "./testdata/test-plugin.sh",
|
||||
APIVersion: "client.authentication.k8s.io/v1beta1",
|
||||
Env: []api.ExecEnvVar{
|
||||
{Name: "TEST_EXIT_CODE", Value: fmt.Sprintf("%d", 0)},
|
||||
{Name: "TEST_OUTPUT", Value: goodOutput},
|
||||
},
|
||||
InteractiveMode: api.IfAvailableExecInteractiveMode,
|
||||
PluginPolicy: test.policy,
|
||||
}
|
||||
|
||||
a, err := newAuthenticator(newCache(), func(_ int) bool { return false }, &c, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a.stderr = io.Discard
|
||||
|
||||
err = a.refreshCredsLocked()
|
||||
if err != nil && !test.wantDenied {
|
||||
t.Fatalf("wanted no error, but got %q", err.Error())
|
||||
}
|
||||
if err == nil && test.wantDenied {
|
||||
t.Fatal("wanted error, but got nil")
|
||||
}
|
||||
|
||||
mockPolicyCallsMetric := mockPolicyCallsMetric{status: "allowed"}
|
||||
if test.wantDenied {
|
||||
mockPolicyCallsMetric.status = "denied"
|
||||
}
|
||||
|
||||
wantPolicyCallsMetrics = append(wantPolicyCallsMetrics, mockPolicyCallsMetric)
|
||||
}
|
||||
|
||||
policyCallsMetricComparer := cmp.Comparer(func(a, b mockPolicyCallsMetric) bool {
|
||||
return a.status == b.status
|
||||
})
|
||||
|
||||
actualPolicyCallsMetrics := policyCallsMetricCounter.policyCalls
|
||||
if diff := cmp.Diff(wantPolicyCallsMetrics, actualPolicyCallsMetrics, policyCallsMetricComparer); diff != "" {
|
||||
t.Fatalf("got unexpected metrics calls; -want, +got:\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
31
tools/cache/controller.go
vendored
31
tools/cache/controller.go
vendored
@@ -708,20 +708,7 @@ func newInformer(clientState Store, options InformerOptions) Controller {
|
||||
// KeyLister, that way resync operations will result in the correct set
|
||||
// of update/delete deltas.
|
||||
|
||||
var fifo Queue
|
||||
if clientgofeaturegate.FeatureGates().Enabled(clientgofeaturegate.InOrderInformers) {
|
||||
fifo = NewRealFIFOWithOptions(RealFIFOOptions{
|
||||
KeyFunction: MetaNamespaceKeyFunc,
|
||||
KnownObjects: clientState,
|
||||
Transformer: options.Transform,
|
||||
})
|
||||
} else {
|
||||
fifo = NewDeltaFIFOWithOptions(DeltaFIFOOptions{
|
||||
KnownObjects: clientState,
|
||||
EmitDeltaTypeReplaced: true,
|
||||
Transformer: options.Transform,
|
||||
})
|
||||
}
|
||||
fifo := newQueueFIFO(clientState, options.Transform)
|
||||
|
||||
cfg := &Config{
|
||||
Queue: fifo,
|
||||
@@ -742,3 +729,19 @@ func newInformer(clientState Store, options InformerOptions) Controller {
|
||||
}
|
||||
return New(cfg)
|
||||
}
|
||||
|
||||
func newQueueFIFO(clientState Store, transform TransformFunc) Queue {
|
||||
if clientgofeaturegate.FeatureGates().Enabled(clientgofeaturegate.InOrderInformers) {
|
||||
return NewRealFIFOWithOptions(RealFIFOOptions{
|
||||
KeyFunction: MetaNamespaceKeyFunc,
|
||||
KnownObjects: clientState,
|
||||
Transformer: transform,
|
||||
})
|
||||
} else {
|
||||
return NewDeltaFIFOWithOptions(DeltaFIFOOptions{
|
||||
KnownObjects: clientState,
|
||||
EmitDeltaTypeReplaced: true,
|
||||
Transformer: transform,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
3
tools/cache/delta_fifo.go
vendored
3
tools/cache/delta_fifo.go
vendored
@@ -270,7 +270,8 @@ func NewDeltaFIFOWithOptions(opts DeltaFIFOOptions) *DeltaFIFO {
|
||||
}
|
||||
|
||||
var (
|
||||
_ = Queue(&DeltaFIFO{}) // DeltaFIFO is a Queue
|
||||
_ = Queue(&DeltaFIFO{}) // DeltaFIFO is a Queue
|
||||
_ = TransformingStore(&DeltaFIFO{}) // DeltaFIFO implements TransformingStore to allow memory optimizations
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
22
tools/cache/delta_fifo_test.go
vendored
22
tools/cache/delta_fifo_test.go
vendored
@@ -28,7 +28,7 @@ import (
|
||||
// from the most recent Delta.
|
||||
// You should treat the items returned inside the deltas as immutable.
|
||||
// This function was moved here because it is not consistent with normal list semantics, but is used in unit testing.
|
||||
func (f *DeltaFIFO) List() []interface{} {
|
||||
func (f *DeltaFIFO) list() []interface{} {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
return f.listLocked()
|
||||
@@ -46,7 +46,7 @@ func (f *DeltaFIFO) listLocked() []interface{} {
|
||||
// ListKeys returns a list of all the keys of the objects currently
|
||||
// in the FIFO.
|
||||
// This function was moved here because it is not consistent with normal list semantics, but is used in unit testing.
|
||||
func (f *DeltaFIFO) ListKeys() []string {
|
||||
func (f *DeltaFIFO) listKeys() []string {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
list := make([]string, 0, len(f.queue))
|
||||
@@ -60,19 +60,19 @@ func (f *DeltaFIFO) ListKeys() []string {
|
||||
// or sets exists=false.
|
||||
// You should treat the items returned inside the deltas as immutable.
|
||||
// This function was moved here because it is not consistent with normal list semantics, but is used in unit testing.
|
||||
func (f *DeltaFIFO) Get(obj interface{}) (item interface{}, exists bool, err error) {
|
||||
func (f *DeltaFIFO) get(obj interface{}) (item interface{}, exists bool, err error) {
|
||||
key, err := f.KeyOf(obj)
|
||||
if err != nil {
|
||||
return nil, false, KeyError{obj, err}
|
||||
}
|
||||
return f.GetByKey(key)
|
||||
return f.getByKey(key)
|
||||
}
|
||||
|
||||
// GetByKey returns the complete list of deltas for the requested item,
|
||||
// setting exists=false if that list is empty.
|
||||
// You should treat the items returned inside the deltas as immutable.
|
||||
// This function was moved here because it is not consistent with normal list semantics, but is used in unit testing.
|
||||
func (f *DeltaFIFO) GetByKey(key string) (item interface{}, exists bool, err error) {
|
||||
func (f *DeltaFIFO) getByKey(key string) (item interface{}, exists bool, err error) {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
d, exists := f.items[key]
|
||||
@@ -320,10 +320,10 @@ func TestDeltaFIFO_addUpdate(t *testing.T) {
|
||||
f.Update(mkFifoObj("foo", 12))
|
||||
f.Delete(mkFifoObj("foo", 15))
|
||||
|
||||
if e, a := []interface{}{mkFifoObj("foo", 15)}, f.List(); !reflect.DeepEqual(e, a) {
|
||||
if e, a := []interface{}{mkFifoObj("foo", 15)}, f.list(); !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %+v, got %+v", e, a)
|
||||
}
|
||||
if e, a := []string{"foo"}, f.ListKeys(); !reflect.DeepEqual(e, a) {
|
||||
if e, a := []string{"foo"}, f.listKeys(); !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %+v, got %+v", e, a)
|
||||
}
|
||||
|
||||
@@ -349,7 +349,7 @@ func TestDeltaFIFO_addUpdate(t *testing.T) {
|
||||
t.Errorf("Got second value %v", unexpected.val)
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
}
|
||||
_, exists, _ := f.Get(mkFifoObj("foo", ""))
|
||||
_, exists, _ := f.get(mkFifoObj("foo", ""))
|
||||
if exists {
|
||||
t.Errorf("item did not get removed")
|
||||
}
|
||||
@@ -397,7 +397,7 @@ func TestDeltaFIFO_transformer(t *testing.T) {
|
||||
must(f.Replace([]interface{}{}, ""))
|
||||
|
||||
// Should be empty
|
||||
if e, a := []string{"foo", "bar"}, f.ListKeys(); !reflect.DeepEqual(e, a) {
|
||||
if e, a := []string{"foo", "bar"}, f.listKeys(); !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %+v, got %+v", e, a)
|
||||
}
|
||||
|
||||
@@ -507,7 +507,7 @@ func TestDeltaFIFO_addReplace(t *testing.T) {
|
||||
t.Errorf("Got second value %v", unexpected.val)
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
}
|
||||
_, exists, _ := f.Get(mkFifoObj("foo", ""))
|
||||
_, exists, _ := f.get(mkFifoObj("foo", ""))
|
||||
if exists {
|
||||
t.Errorf("item did not get removed")
|
||||
}
|
||||
@@ -991,7 +991,7 @@ func BenchmarkDeltaFIFOListKeys(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_ = f.ListKeys()
|
||||
_ = f.listKeys()
|
||||
}
|
||||
})
|
||||
b.StopTimer()
|
||||
|
||||
13
tools/cache/reflector.go
vendored
13
tools/cache/reflector.go
vendored
@@ -79,7 +79,7 @@ type ReflectorStore interface {
|
||||
// TransformingStore is an optional interface that can be implemented by the provided store.
|
||||
// If implemented on the provided store reflector will use the same transformer in its internal stores.
|
||||
type TransformingStore interface {
|
||||
Store
|
||||
ReflectorStore
|
||||
Transformer() TransformFunc
|
||||
}
|
||||
|
||||
@@ -426,7 +426,10 @@ func (r *Reflector) ListAndWatchWithContext(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
logger.Error(err, "The watchlist request ended with an error, falling back to the standard LIST/WATCH semantics because making progress is better than deadlocking")
|
||||
logger.V(4).Info(
|
||||
"Data couldn't be fetched in watchlist mode. Falling back to regular list. This is expected if watchlist is not supported or disabled in kube-apiserver.",
|
||||
"err", err,
|
||||
)
|
||||
fallbackToList = true
|
||||
// ensure that we won't accidentally pass some garbage down the watch.
|
||||
w = nil
|
||||
@@ -733,9 +736,11 @@ func (r *Reflector) watchList(ctx context.Context) (watch.Interface, error) {
|
||||
return false
|
||||
}
|
||||
|
||||
var transformer TransformFunc
|
||||
storeOpts := []StoreOption{}
|
||||
if tr, ok := r.store.(TransformingStore); ok && tr.Transformer() != nil {
|
||||
storeOpts = append(storeOpts, WithTransformer(tr.Transformer()))
|
||||
transformer = tr.Transformer()
|
||||
storeOpts = append(storeOpts, WithTransformer(transformer))
|
||||
}
|
||||
|
||||
initTrace := trace.New("Reflector WatchList", trace.Field{Key: "name", Value: r.name})
|
||||
@@ -795,7 +800,7 @@ func (r *Reflector) watchList(ctx context.Context) (watch.Interface, error) {
|
||||
// we utilize the temporaryStore to ensure independence from the current store implementation.
|
||||
// as of today, the store is implemented as a queue and will be drained by the higher-level
|
||||
// component as soon as it finishes replacing the content.
|
||||
checkWatchListDataConsistencyIfRequested(ctx, r.name, resourceVersion, r.listerWatcher.ListWithContext, temporaryStore.List)
|
||||
checkWatchListDataConsistencyIfRequested(ctx, r.name, resourceVersion, r.listerWatcher.ListWithContext, transformer, temporaryStore.List)
|
||||
|
||||
if err := r.store.Replace(temporaryStore.List(), resourceVersion); err != nil {
|
||||
return nil, fmt.Errorf("unable to sync watch-list result: %w", err)
|
||||
|
||||
@@ -33,11 +33,11 @@ import (
|
||||
//
|
||||
// Note that this function will panic when data inconsistency is detected.
|
||||
// This is intentional because we want to catch it in the CI.
|
||||
func checkWatchListDataConsistencyIfRequested[T runtime.Object, U any](ctx context.Context, identity string, lastSyncedResourceVersion string, listFn consistencydetector.ListFunc[T], retrieveItemsFn consistencydetector.RetrieveItemsFunc[U]) {
|
||||
func checkWatchListDataConsistencyIfRequested[T runtime.Object, U any](ctx context.Context, identity string, lastSyncedResourceVersion string, listFn consistencydetector.ListFunc[T], listItemTransformFunc func(interface{}) (interface{}, error), retrieveItemsFn consistencydetector.RetrieveItemsFunc[U]) {
|
||||
if !consistencydetector.IsDataConsistencyDetectionForWatchListEnabled() {
|
||||
return
|
||||
}
|
||||
// for informers we pass an empty ListOptions because
|
||||
// listFn might be wrapped for filtering during informer construction.
|
||||
consistencydetector.CheckDataConsistency(ctx, identity, lastSyncedResourceVersion, listFn, metav1.ListOptions{}, retrieveItemsFn)
|
||||
consistencydetector.CheckDataConsistency(ctx, identity, lastSyncedResourceVersion, listFn, listItemTransformFunc, metav1.ListOptions{}, retrieveItemsFn)
|
||||
}
|
||||
|
||||
114
tools/cache/reflector_data_consistency_detector_test.go
vendored
Normal file
114
tools/cache/reflector_data_consistency_detector_test.go
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
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 cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
clientfeatures "k8s.io/client-go/features"
|
||||
clientfeaturestesting "k8s.io/client-go/features/testing"
|
||||
"k8s.io/client-go/util/consistencydetector"
|
||||
"k8s.io/klog/v2/ktesting"
|
||||
)
|
||||
|
||||
func TestReflectorDataConsistencyDetector(t *testing.T) {
|
||||
clientfeaturestesting.SetFeatureDuringTest(t, clientfeatures.WatchListClient, true)
|
||||
restore := consistencydetector.SetDataConsistencyDetectionForWatchListEnabledForTest(true)
|
||||
defer restore()
|
||||
|
||||
markTransformed := func(obj interface{}) (interface{}, error) {
|
||||
pod, ok := obj.(*v1.Pod)
|
||||
if !ok {
|
||||
return obj, nil
|
||||
}
|
||||
newPod := pod.DeepCopy()
|
||||
if newPod.Labels == nil {
|
||||
newPod.Labels = make(map[string]string)
|
||||
}
|
||||
newPod.Labels["transformed"] = "true"
|
||||
return newPod, nil
|
||||
}
|
||||
|
||||
for _, inOrder := range []bool{false, true} {
|
||||
t.Run(fmt.Sprintf("InOrder=%v", inOrder), func(t *testing.T) {
|
||||
clientfeaturestesting.SetFeatureDuringTest(t, clientfeatures.InOrderInformers, inOrder)
|
||||
for _, transformerEnabled := range []bool{false, true} {
|
||||
var transformer TransformFunc
|
||||
if transformerEnabled {
|
||||
transformer = markTransformed
|
||||
}
|
||||
t.Run(fmt.Sprintf("Transformer=%v", transformerEnabled), func(t *testing.T) {
|
||||
runTestReflectorDataConsistencyDetector(t, transformer)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func runTestReflectorDataConsistencyDetector(t *testing.T, transformer TransformFunc) {
|
||||
_, ctx := ktesting.NewTestContext(t)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
store := NewStore(MetaNamespaceKeyFunc)
|
||||
fifo := newQueueFIFO(store, transformer)
|
||||
|
||||
lw := &ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
return &v1.PodList{
|
||||
ListMeta: metav1.ListMeta{ResourceVersion: "1"},
|
||||
Items: []v1.Pod{
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: "pod-1", ResourceVersion: "1"}},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
w := watch.NewFake()
|
||||
go func() {
|
||||
w.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod-1", ResourceVersion: "1"}})
|
||||
w.Action(watch.Bookmark, &v1.Pod{ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pod-1",
|
||||
ResourceVersion: "1",
|
||||
Annotations: map[string]string{metav1.InitialEventsAnnotationKey: "true"},
|
||||
}})
|
||||
}()
|
||||
return w, nil
|
||||
},
|
||||
}
|
||||
|
||||
r := NewReflector(lw, &v1.Pod{}, fifo, 0)
|
||||
|
||||
go func() {
|
||||
_ = wait.PollUntilContextTimeout(ctx, 10*time.Millisecond, 5*time.Second, true, func(ctx context.Context) (bool, error) {
|
||||
return r.LastSyncResourceVersion() != "", nil
|
||||
})
|
||||
cancel()
|
||||
}()
|
||||
|
||||
err := r.ListAndWatchWithContext(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("ListAndWatchWithContext returned error: %v", err)
|
||||
}
|
||||
}
|
||||
242
tools/cache/reflector_test.go
vendored
242
tools/cache/reflector_test.go
vendored
@@ -20,10 +20,12 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"reflect"
|
||||
goruntime "runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -1962,7 +1964,7 @@ func TestReflectorReplacesStoreOnUnsafeDelete(t *testing.T) {
|
||||
s := NewFIFO(MetaNamespaceKeyFunc)
|
||||
var replaceInvoked atomic.Int32
|
||||
store := &fakeStore{
|
||||
Store: s,
|
||||
ReflectorStore: s,
|
||||
beforeReplace: func(list []interface{}, rv string) {
|
||||
// interested in the Replace call that happens after the Error event
|
||||
if rv == lastExpectedRV {
|
||||
@@ -2057,131 +2059,165 @@ func TestReflectorReplacesStoreOnUnsafeDelete(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReflectorRespectStoreTransformer(t *testing.T) {
|
||||
mkPod := func(id string, rv string) *v1.Pod {
|
||||
return &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: id, ResourceVersion: rv},
|
||||
Spec: v1.PodSpec{
|
||||
Hostname: "test",
|
||||
for name, test := range map[string]struct {
|
||||
storeBuilder func(counter *atomic.Int32) ReflectorStore
|
||||
items func(rs ReflectorStore) []interface{}
|
||||
}{
|
||||
"real-fifo": {
|
||||
storeBuilder: func(counter *atomic.Int32) ReflectorStore {
|
||||
return NewRealFIFO(MetaNamespaceKeyFunc, NewStore(MetaNamespaceKeyFunc), func(i interface{}) (interface{}, error) {
|
||||
counter.Add(1)
|
||||
cast := i.(*v1.Pod)
|
||||
cast.Spec.Hostname = "transformed"
|
||||
return cast, nil
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
preExisting1 := mkPod("foo-1", "1")
|
||||
preExisting2 := mkPod("foo-2", "2")
|
||||
pod3 := mkPod("foo-3", "3")
|
||||
|
||||
lastExpectedRV := "3"
|
||||
events := []watch.Event{
|
||||
{Type: watch.Added, Object: preExisting1},
|
||||
{Type: watch.Added, Object: preExisting2},
|
||||
{Type: watch.Bookmark, Object: &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
ResourceVersion: lastExpectedRV,
|
||||
Annotations: map[string]string{
|
||||
metav1.InitialEventsAnnotationKey: "true",
|
||||
},
|
||||
items: func(rs ReflectorStore) []interface{} {
|
||||
store := rs.(*RealFIFO)
|
||||
objects := make(map[string]interface{})
|
||||
for _, item := range store.getItems() {
|
||||
key, _ := store.keyFunc(item.Object)
|
||||
if item.Type == Deleted {
|
||||
delete(objects, key)
|
||||
} else {
|
||||
objects[key] = item.Object
|
||||
}
|
||||
}
|
||||
return slices.Collect(maps.Values(objects))
|
||||
},
|
||||
}},
|
||||
{Type: watch.Added, Object: pod3},
|
||||
}
|
||||
|
||||
s := NewFIFO(MetaNamespaceKeyFunc)
|
||||
var replaceInvoked atomic.Int32
|
||||
store := &fakeStore{
|
||||
Store: s,
|
||||
beforeReplace: func(list []interface{}, rv string) {
|
||||
replaceInvoked.Add(1)
|
||||
// Only two pods are present at the point when Replace is called.
|
||||
if len(list) != 2 {
|
||||
t.Errorf("unexpected nb of objects: expected 2 received %d", len(list))
|
||||
},
|
||||
"delta-fifo": {
|
||||
storeBuilder: func(counter *atomic.Int32) ReflectorStore {
|
||||
return NewDeltaFIFOWithOptions(DeltaFIFOOptions{
|
||||
KeyFunction: MetaNamespaceKeyFunc,
|
||||
Transformer: func(i interface{}) (interface{}, error) {
|
||||
counter.Add(1)
|
||||
cast := i.(*v1.Pod)
|
||||
cast.Spec.Hostname = "transformed"
|
||||
return cast, nil
|
||||
},
|
||||
})
|
||||
},
|
||||
items: func(rs ReflectorStore) []interface{} {
|
||||
return rs.(*DeltaFIFO).list()
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
mkPod := func(id string, rv string) *v1.Pod {
|
||||
return &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: id, ResourceVersion: rv},
|
||||
Spec: v1.PodSpec{
|
||||
Hostname: "test",
|
||||
},
|
||||
}
|
||||
}
|
||||
for _, obj := range list {
|
||||
cast := obj.(*v1.Pod)
|
||||
|
||||
preExisting1 := mkPod("foo-1", "1")
|
||||
preExisting2 := mkPod("foo-2", "2")
|
||||
pod3 := mkPod("foo-3", "3")
|
||||
|
||||
lastExpectedRV := "3"
|
||||
events := []watch.Event{
|
||||
{Type: watch.Added, Object: preExisting1},
|
||||
{Type: watch.Added, Object: preExisting2},
|
||||
{Type: watch.Bookmark, Object: &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
ResourceVersion: lastExpectedRV,
|
||||
Annotations: map[string]string{
|
||||
metav1.InitialEventsAnnotationKey: "true",
|
||||
},
|
||||
},
|
||||
}},
|
||||
{Type: watch.Added, Object: pod3},
|
||||
}
|
||||
|
||||
var transformerInvoked atomic.Int32
|
||||
s := test.storeBuilder(&transformerInvoked)
|
||||
|
||||
var once sync.Once
|
||||
lw := &ListWatch{
|
||||
WatchFunc: func(metav1.ListOptions) (watch.Interface, error) {
|
||||
fw := watch.NewFake()
|
||||
go func() {
|
||||
once.Do(func() {
|
||||
for _, e := range events {
|
||||
fw.Action(e.Type, e.Object)
|
||||
}
|
||||
})
|
||||
}()
|
||||
return fw, nil
|
||||
},
|
||||
// ListFunc should never be used in WatchList mode
|
||||
ListFunc: func(metav1.ListOptions) (runtime.Object, error) {
|
||||
return nil, errors.New("list call not expected in WatchList mode")
|
||||
},
|
||||
}
|
||||
|
||||
clientfeaturestesting.SetFeatureDuringTest(t, clientfeatures.WatchListClient, true)
|
||||
r := NewReflector(lw, &v1.Pod{}, s, 0)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
doneCh := make(chan struct{})
|
||||
go func() {
|
||||
defer close(doneCh)
|
||||
r.RunWithContext(ctx)
|
||||
}()
|
||||
|
||||
// wait for the RV to sync to the version returned by the final list
|
||||
err := wait.PollUntilContextTimeout(context.Background(), 100*time.Millisecond, 5*time.Second, true, func(ctx context.Context) (done bool, err error) {
|
||||
if rv := r.LastSyncResourceVersion(); rv == lastExpectedRV {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("reflector never caught up with expected revision: %q, err: %v", lastExpectedRV, err)
|
||||
}
|
||||
|
||||
if want, got := lastExpectedRV, r.LastSyncResourceVersion(); want != got {
|
||||
t.Errorf("expected LastSyncResourceVersion to be %q, but got: %q", want, got)
|
||||
}
|
||||
|
||||
informerItems := test.items(s)
|
||||
if want, got := 3, len(informerItems); want != got {
|
||||
t.Errorf("expected informer to contain %d objects, but got: %d", want, got)
|
||||
}
|
||||
for _, item := range informerItems {
|
||||
cast := item.(*v1.Pod)
|
||||
if cast.Spec.Hostname != "transformed" {
|
||||
t.Error("Object was not transformed prior to replacement")
|
||||
}
|
||||
}
|
||||
},
|
||||
afterReplace: func(rv string, err error) {},
|
||||
transformer: func(i interface{}) (interface{}, error) {
|
||||
cast := i.(*v1.Pod)
|
||||
cast.Spec.Hostname = "transformed"
|
||||
return cast, nil
|
||||
},
|
||||
}
|
||||
|
||||
var once sync.Once
|
||||
lw := &ListWatch{
|
||||
WatchFunc: func(metav1.ListOptions) (watch.Interface, error) {
|
||||
fw := watch.NewFake()
|
||||
go func() {
|
||||
once.Do(func() {
|
||||
for _, e := range events {
|
||||
fw.Action(e.Type, e.Object)
|
||||
}
|
||||
})
|
||||
}()
|
||||
return fw, nil
|
||||
},
|
||||
// ListFunc should never be used in WatchList mode
|
||||
ListFunc: func(metav1.ListOptions) (runtime.Object, error) {
|
||||
return nil, errors.New("list call not expected in WatchList mode")
|
||||
},
|
||||
}
|
||||
// Transformer should have been invoked twice for the initial sync in the informer on the temporary store,
|
||||
// then twice on replace, then once on the following update.
|
||||
if want, got := 5, int(transformerInvoked.Load()); want != got {
|
||||
t.Errorf("expected transformer to be invoked %d times, but got: %d", want, got)
|
||||
}
|
||||
|
||||
clientfeaturestesting.SetFeatureDuringTest(t, clientfeatures.WatchListClient, true)
|
||||
r := NewReflector(lw, &v1.Pod{}, store, 0)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
doneCh := make(chan struct{})
|
||||
go func() {
|
||||
defer close(doneCh)
|
||||
r.RunWithContext(ctx)
|
||||
}()
|
||||
|
||||
// wait for the RV to sync to the version returned by the final list
|
||||
err := wait.PollUntilContextTimeout(context.Background(), 100*time.Millisecond, 5*time.Second, true, func(ctx context.Context) (done bool, err error) {
|
||||
if rv := r.LastSyncResourceVersion(); rv == lastExpectedRV {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("reflector never caught up with expected revision: %q, err: %v", lastExpectedRV, err)
|
||||
}
|
||||
|
||||
if want, got := lastExpectedRV, r.LastSyncResourceVersion(); want != got {
|
||||
t.Errorf("expected LastSyncResourceVersion to be %q, but got: %q", want, got)
|
||||
}
|
||||
if want, got := 1, int(replaceInvoked.Load()); want != got {
|
||||
t.Errorf("expected replace to be invoked %d times, but got: %d", want, got)
|
||||
}
|
||||
|
||||
cancel()
|
||||
select {
|
||||
case <-doneCh:
|
||||
case <-time.After(wait.ForeverTestTimeout):
|
||||
t.Errorf("timed out waiting for Run to return")
|
||||
cancel()
|
||||
select {
|
||||
case <-doneCh:
|
||||
case <-time.After(wait.ForeverTestTimeout):
|
||||
t.Errorf("timed out waiting for Run to return")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type fakeStore struct {
|
||||
Store
|
||||
ReflectorStore
|
||||
beforeReplace func(list []interface{}, s string)
|
||||
afterReplace func(rv string, err error)
|
||||
transformer TransformFunc
|
||||
}
|
||||
|
||||
func (f *fakeStore) Replace(list []interface{}, rv string) error {
|
||||
f.beforeReplace(list, rv)
|
||||
err := f.Store.Replace(list, rv)
|
||||
err := f.ReflectorStore.Replace(list, rv)
|
||||
f.afterReplace(rv, err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *fakeStore) Transformer() TransformFunc {
|
||||
return f.transformer
|
||||
}
|
||||
|
||||
func BenchmarkExtractList(b *testing.B) {
|
||||
_, _, podList := getPodListItems(0, fakeItemsNum)
|
||||
_, _, configMapList := getConfigmapListItems(0, fakeItemsNum)
|
||||
|
||||
15
tools/cache/shared_informer.go
vendored
15
tools/cache/shared_informer.go
vendored
@@ -539,20 +539,7 @@ func (s *sharedIndexInformer) RunWithContext(ctx context.Context) {
|
||||
s.startedLock.Lock()
|
||||
defer s.startedLock.Unlock()
|
||||
|
||||
var fifo Queue
|
||||
if clientgofeaturegate.FeatureGates().Enabled(clientgofeaturegate.InOrderInformers) {
|
||||
fifo = NewRealFIFOWithOptions(RealFIFOOptions{
|
||||
KeyFunction: MetaNamespaceKeyFunc,
|
||||
KnownObjects: s.indexer,
|
||||
Transformer: s.transform,
|
||||
})
|
||||
} else {
|
||||
fifo = NewDeltaFIFOWithOptions(DeltaFIFOOptions{
|
||||
KnownObjects: s.indexer,
|
||||
EmitDeltaTypeReplaced: true,
|
||||
Transformer: s.transform,
|
||||
})
|
||||
}
|
||||
fifo := newQueueFIFO(s.indexer, s.transform)
|
||||
|
||||
cfg := &Config{
|
||||
Queue: fifo,
|
||||
|
||||
3
tools/cache/the_real_fifo.go
vendored
3
tools/cache/the_real_fifo.go
vendored
@@ -89,7 +89,8 @@ type RealFIFO struct {
|
||||
}
|
||||
|
||||
var (
|
||||
_ = Queue(&RealFIFO{}) // RealFIFO is a Queue
|
||||
_ = Queue(&RealFIFO{}) // RealFIFO is a Queue
|
||||
_ = TransformingStore(&RealFIFO{}) // RealFIFO implements TransformingStore to allow memory optimizations
|
||||
)
|
||||
|
||||
// Close the queue.
|
||||
|
||||
@@ -283,8 +283,57 @@ type ExecConfig struct {
|
||||
// read user instructions might set this to "used by my-program to read user instructions".
|
||||
// +k8s:conversion-gen=false
|
||||
StdinUnavailableMessage string `json:"-"`
|
||||
|
||||
// PluginPolicy is the policy governing whether or not the configured
|
||||
// `Command` may run.
|
||||
// +k8s:conversion-gen=false
|
||||
PluginPolicy PluginPolicy `json:"-"`
|
||||
}
|
||||
|
||||
// AllowlistEntry is an entry in the allowlist. For each allowlist item, at
|
||||
// least one field must be nonempty. A struct with all empty fields is
|
||||
// considered a misconfiguration error. Each field is a criterion for
|
||||
// execution. If multiple fields are specified, then the criteria of all
|
||||
// specified fields must be met. That is, the result of an individual entry is
|
||||
// the logical AND of all checks corresponding to the specified fields within
|
||||
// the entry.
|
||||
type AllowlistEntry struct {
|
||||
// Name matching is performed by first resolving the absolute path of both
|
||||
// the plugin and the name in the allowlist entry using `exec.LookPath`. It
|
||||
// will be called on both, and the resulting strings must be equal. If
|
||||
// either call to `exec.LookPath` results in an error, the `Name` check
|
||||
// will be considered a failure.
|
||||
Name string `json:"-"`
|
||||
}
|
||||
|
||||
// PluginPolicy describes the policy type and allowlist (if any) for client-go
|
||||
// credential plugins.
|
||||
type PluginPolicy struct {
|
||||
// PolicyType specifies the policy governing which, if any, client-go
|
||||
// credential plugins may be executed. It MUST be one of { "", "AllowAll", "DenyAll", "Allowlist" }.
|
||||
// If the policy is "", then it falls back to "AllowAll" (this is required
|
||||
// to maintain backward compatibility). If the policy is DenyAll, no
|
||||
// credential plugins may run. If the policy is Allowlist, only those
|
||||
// plugins meeting the criteria specified in the `credentialPluginAllowlist`
|
||||
// field may run. If the policy is not `Allowlist` but one is provided, it
|
||||
// is considered a configuration error.
|
||||
PolicyType PolicyType `json:"-"`
|
||||
|
||||
// Allowlist is a slice of allowlist entries. If any of them is a match,
|
||||
// then the executable in question may execute. That is, the result is the
|
||||
// logical OR of all entries in the allowlist. This list MUST be nil
|
||||
// whenever the policy is not "Allowlist".
|
||||
Allowlist []AllowlistEntry `json:"-"`
|
||||
}
|
||||
|
||||
type PolicyType string
|
||||
|
||||
const (
|
||||
PluginPolicyAllowAll PolicyType = "AllowAll"
|
||||
PluginPolicyDenyAll PolicyType = "DenyAll"
|
||||
PluginPolicyAllowlist PolicyType = "Allowlist"
|
||||
)
|
||||
|
||||
var _ fmt.Stringer = new(ExecConfig)
|
||||
var _ fmt.GoStringer = new(ExecConfig)
|
||||
|
||||
|
||||
@@ -401,6 +401,7 @@ func autoConvert_api_ExecConfig_To_v1_ExecConfig(in *api.ExecConfig, out *ExecCo
|
||||
out.InteractiveMode = ExecInteractiveMode(in.InteractiveMode)
|
||||
// INFO: in.StdinUnavailable opted out of conversion generation
|
||||
// INFO: in.StdinUnavailableMessage opted out of conversion generation
|
||||
// INFO: in.PluginPolicy opted out of conversion generation
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,22 @@ 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 *AllowlistEntry) DeepCopyInto(out *AllowlistEntry) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowlistEntry.
|
||||
func (in *AllowlistEntry) DeepCopy() *AllowlistEntry {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AllowlistEntry)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AuthInfo) DeepCopyInto(out *AuthInfo) {
|
||||
*out = *in
|
||||
@@ -271,6 +287,7 @@ func (in *ExecConfig) DeepCopyInto(out *ExecConfig) {
|
||||
if in.Config != nil {
|
||||
out.Config = in.Config.DeepCopyObject()
|
||||
}
|
||||
in.PluginPolicy.DeepCopyInto(&out.PluginPolicy)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -300,6 +317,27 @@ func (in *ExecEnvVar) DeepCopy() *ExecEnvVar {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PluginPolicy) DeepCopyInto(out *PluginPolicy) {
|
||||
*out = *in
|
||||
if in.Allowlist != nil {
|
||||
in, out := &in.Allowlist, &out.Allowlist
|
||||
*out = make([]AllowlistEntry, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PluginPolicy.
|
||||
func (in *PluginPolicy) DeepCopy() *PluginPolicy {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PluginPolicy)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Preferences) DeepCopyInto(out *Preferences) {
|
||||
*out = *in
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
authexec "k8s.io/client-go/plugin/pkg/client/auth/exec"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
@@ -327,6 +328,10 @@ func validateAuthInfo(authInfoName string, authInfo clientcmdapi.AuthInfo) []err
|
||||
default:
|
||||
validationErrors = append(validationErrors, fmt.Errorf("invalid interactiveMode for %v: %q", authInfoName, authInfo.Exec.InteractiveMode))
|
||||
}
|
||||
|
||||
if err := authexec.ValidatePluginPolicy(authInfo.Exec.PluginPolicy); err != nil {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("allowlist misconfiguration: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
// authPath also provides information for the client to identify the server, so allow multiple auth methods in that case
|
||||
|
||||
@@ -62,6 +62,12 @@ type CallsMetric interface {
|
||||
Increment(exitCode int, callStatus string)
|
||||
}
|
||||
|
||||
// CallsMetric counts the success or failure of execution for exec plugins.
|
||||
type PolicyCallsMetric interface {
|
||||
// Increment increments a counter per status { "allowed", "denied" }
|
||||
Increment(status string)
|
||||
}
|
||||
|
||||
// RetryMetric counts the number of retries sent to the server
|
||||
// partitioned by code, method, and host.
|
||||
type RetryMetric interface {
|
||||
@@ -99,6 +105,9 @@ var (
|
||||
// ExecPluginCalls is the number of calls made to an exec plugin, partitioned by
|
||||
// exit code and call status.
|
||||
ExecPluginCalls CallsMetric = noopCalls{}
|
||||
// ExecPluginPolicyCalls is the number of plugin policy check calls, partitioned
|
||||
// by {"allowed", "denied"}
|
||||
ExecPluginPolicyCalls PolicyCallsMetric = noopPolicy{}
|
||||
// RequestRetry is the retry metric that tracks the number of
|
||||
// retries sent to the server.
|
||||
RequestRetry RetryMetric = noopRetry{}
|
||||
@@ -121,6 +130,7 @@ type RegisterOpts struct {
|
||||
RateLimiterLatency LatencyMetric
|
||||
RequestResult ResultMetric
|
||||
ExecPluginCalls CallsMetric
|
||||
ExecPluginPolicyCalls PolicyCallsMetric
|
||||
RequestRetry RetryMetric
|
||||
TransportCacheEntries TransportCacheMetric
|
||||
TransportCreateCalls TransportCreateCallsMetric
|
||||
@@ -157,6 +167,9 @@ func Register(opts RegisterOpts) {
|
||||
if opts.ExecPluginCalls != nil {
|
||||
ExecPluginCalls = opts.ExecPluginCalls
|
||||
}
|
||||
if opts.ExecPluginPolicyCalls != nil {
|
||||
ExecPluginCalls = opts.ExecPluginCalls
|
||||
}
|
||||
if opts.RequestRetry != nil {
|
||||
RequestRetry = opts.RequestRetry
|
||||
}
|
||||
@@ -198,6 +211,10 @@ type noopCalls struct{}
|
||||
|
||||
func (noopCalls) Increment(int, string) {}
|
||||
|
||||
type noopPolicy struct{}
|
||||
|
||||
func (noopPolicy) Increment(string) {}
|
||||
|
||||
type noopRetry struct{}
|
||||
|
||||
func (noopRetry) IncrementRetry(context.Context, string, string, string) {}
|
||||
|
||||
@@ -45,16 +45,28 @@ func IsDataConsistencyDetectionForWatchListEnabled() bool {
|
||||
return dataConsistencyDetectionForWatchListEnabled
|
||||
}
|
||||
|
||||
// SetDataConsistencyDetectionForWatchListEnabledForTest allows to enable/disable data consistency detection for testing purposes.
|
||||
// It returns a function that restores the original value.
|
||||
func SetDataConsistencyDetectionForWatchListEnabledForTest(enabled bool) func() {
|
||||
original := dataConsistencyDetectionForWatchListEnabled
|
||||
dataConsistencyDetectionForWatchListEnabled = enabled
|
||||
return func() {
|
||||
dataConsistencyDetectionForWatchListEnabled = original
|
||||
}
|
||||
}
|
||||
|
||||
type RetrieveItemsFunc[U any] func() []U
|
||||
|
||||
type ListFunc[T runtime.Object] func(ctx context.Context, options metav1.ListOptions) (T, error)
|
||||
|
||||
type TransformFunc func(interface{}) (interface{}, error)
|
||||
|
||||
// CheckDataConsistency exists solely for testing purposes.
|
||||
// we cannot use checkWatchListDataConsistencyIfRequested because
|
||||
// it is guarded by an environmental variable.
|
||||
// we cannot manipulate the environmental variable because
|
||||
// it will affect other tests in this package.
|
||||
func CheckDataConsistency[T runtime.Object, U any](ctx context.Context, identity string, lastSyncedResourceVersion string, listFn ListFunc[T], listOptions metav1.ListOptions, retrieveItemsFn RetrieveItemsFunc[U]) {
|
||||
func CheckDataConsistency[T runtime.Object, U any](ctx context.Context, identity string, lastSyncedResourceVersion string, listFn ListFunc[T], listItemTransformFunc TransformFunc, listOptions metav1.ListOptions, retrieveItemsFn RetrieveItemsFunc[U]) {
|
||||
if !canFormAdditionalListCall(lastSyncedResourceVersion, listOptions) {
|
||||
klog.V(4).Infof("data consistency check for %s is enabled but the parameters (RV, ListOptions) doesn't allow for creating a valid LIST request. Skipping the data consistency check.", identity)
|
||||
return
|
||||
@@ -84,6 +96,15 @@ func CheckDataConsistency[T runtime.Object, U any](ctx context.Context, identity
|
||||
if err != nil {
|
||||
panic(err) // this should never happen
|
||||
}
|
||||
if listItemTransformFunc != nil {
|
||||
for i := range rawListItems {
|
||||
obj, err := listItemTransformFunc(rawListItems[i])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rawListItems[i] = obj.(runtime.Object)
|
||||
}
|
||||
}
|
||||
listItems := toMetaObjectSliceOrDie(rawListItems)
|
||||
|
||||
sort.Sort(byUID(listItems))
|
||||
|
||||
@@ -215,10 +215,10 @@ func TestDataConsistencyChecker(t *testing.T) {
|
||||
|
||||
if scenario.expectPanic {
|
||||
require.Panics(t, func() {
|
||||
CheckDataConsistency(ctx, "", scenario.lastSyncedResourceVersion, fakeLister.List, scenario.requestOptions, retrievedItemsFunc)
|
||||
CheckDataConsistency(ctx, "", scenario.lastSyncedResourceVersion, fakeLister.List, nil, scenario.requestOptions, retrievedItemsFunc)
|
||||
})
|
||||
} else {
|
||||
CheckDataConsistency(ctx, "", scenario.lastSyncedResourceVersion, fakeLister.List, scenario.requestOptions, retrievedItemsFunc)
|
||||
CheckDataConsistency(ctx, "", scenario.lastSyncedResourceVersion, fakeLister.List, nil, scenario.requestOptions, retrievedItemsFunc)
|
||||
}
|
||||
|
||||
require.Equal(t, scenario.expectedListRequests, fakeLister.counter)
|
||||
@@ -235,7 +235,7 @@ func TestDataConsistencyCheckerRetry(t *testing.T) {
|
||||
stopListErrorAfter := 5
|
||||
fakeErrLister := &errorLister{stopErrorAfter: stopListErrorAfter}
|
||||
|
||||
CheckDataConsistency(ctx, "", "", fakeErrLister.List, metav1.ListOptions{}, retrievedItemsFunc)
|
||||
CheckDataConsistency(ctx, "", "", fakeErrLister.List, nil, metav1.ListOptions{}, retrievedItemsFunc)
|
||||
require.Equal(t, fakeErrLister.listCounter, fakeErrLister.stopErrorAfter)
|
||||
}
|
||||
|
||||
|
||||
@@ -169,12 +169,13 @@ func newQueue[T comparable](c clock.WithTicker, queue Queue[T], metrics queueMet
|
||||
cond: sync.NewCond(&sync.Mutex{}),
|
||||
metrics: metrics,
|
||||
unfinishedWorkUpdatePeriod: updatePeriod,
|
||||
stopCh: make(chan struct{}),
|
||||
}
|
||||
|
||||
// Don't start the goroutine for a type of noMetrics so we don't consume
|
||||
// resources unnecessarily
|
||||
if _, ok := metrics.(noMetrics[T]); !ok {
|
||||
go t.updateUnfinishedWorkLoop()
|
||||
t.wg.Go(t.updateUnfinishedWorkLoop)
|
||||
}
|
||||
|
||||
return t
|
||||
@@ -210,6 +211,14 @@ type Typed[t comparable] struct {
|
||||
|
||||
unfinishedWorkUpdatePeriod time.Duration
|
||||
clock clock.WithTicker
|
||||
|
||||
// wg manages goroutines started by the queue to allow graceful shutdown
|
||||
// ShutDown() will wait for goroutines to exit before returning.
|
||||
wg sync.WaitGroup
|
||||
|
||||
stopCh chan struct{}
|
||||
// stopOnce guarantees we only signal shutdown a single time
|
||||
stopOnce sync.Once
|
||||
}
|
||||
|
||||
// Add marks item as needing processing. When the queue is shutdown new
|
||||
@@ -296,6 +305,11 @@ func (q *Typed[T]) Done(item T) {
|
||||
// goroutines will continue processing items in the queue until it is
|
||||
// empty and then receive the shutdown signal.
|
||||
func (q *Typed[T]) ShutDown() {
|
||||
defer q.wg.Wait()
|
||||
q.stopOnce.Do(func() {
|
||||
defer close(q.stopCh)
|
||||
})
|
||||
|
||||
q.cond.L.Lock()
|
||||
defer q.cond.L.Unlock()
|
||||
|
||||
@@ -311,6 +325,10 @@ func (q *Typed[T]) ShutDown() {
|
||||
// Workers must call Done on an item after processing it, otherwise
|
||||
// ShutDownWithDrain will block indefinitely.
|
||||
func (q *Typed[T]) ShutDownWithDrain() {
|
||||
defer q.wg.Wait()
|
||||
q.stopOnce.Do(func() {
|
||||
defer close(q.stopCh)
|
||||
})
|
||||
q.cond.L.Lock()
|
||||
defer q.cond.L.Unlock()
|
||||
|
||||
@@ -330,20 +348,22 @@ func (q *Typed[T]) ShuttingDown() bool {
|
||||
return q.shuttingDown
|
||||
}
|
||||
|
||||
func (q *Typed[T]) updateUnfinishedWork() {
|
||||
q.cond.L.Lock()
|
||||
defer q.cond.L.Unlock()
|
||||
if !q.shuttingDown {
|
||||
q.metrics.updateUnfinishedWork()
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Typed[T]) updateUnfinishedWorkLoop() {
|
||||
t := q.clock.NewTicker(q.unfinishedWorkUpdatePeriod)
|
||||
defer t.Stop()
|
||||
for range t.C() {
|
||||
if !func() bool {
|
||||
q.cond.L.Lock()
|
||||
defer q.cond.L.Unlock()
|
||||
if !q.shuttingDown {
|
||||
q.metrics.updateUnfinishedWork()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
}() {
|
||||
for {
|
||||
select {
|
||||
case <-t.C():
|
||||
q.updateUnfinishedWork()
|
||||
case <-q.stopCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user