From b01b0982f248890f00f8e0584d9bd54062df6de1 Mon Sep 17 00:00:00 2001 From: Ellis Tarn Date: Wed, 12 Oct 2022 14:24:34 -0700 Subject: [PATCH] chore: Migrate pkg/api from github.com/aws/karpenter --- .gitignore | 11 + Makefile | 26 + chart/crds/karpenter.sh_provisioners.yaml | 348 ++++++++++++++ go.mod | 39 ++ go.sum | 153 ++++++ hack/boilerplate.go.txt | 13 + hack/boilerplate.sh | 10 + pkg/apis/provisioning/v1alpha5/doc.go | 19 + pkg/apis/provisioning/v1alpha5/labels.go | 122 +++++ pkg/apis/provisioning/v1alpha5/limits.go | 41 ++ pkg/apis/provisioning/v1alpha5/provisioner.go | 179 +++++++ .../v1alpha5/provisioner_defaults.go | 24 + .../v1alpha5/provisioner_status.go | 51 ++ .../v1alpha5/provisioner_validation.go | 308 ++++++++++++ pkg/apis/provisioning/v1alpha5/register.go | 50 ++ pkg/apis/provisioning/v1alpha5/suite_test.go | 453 ++++++++++++++++++ .../v1alpha5/zz_generated.deepcopy.go | 335 +++++++++++++ 17 files changed, 2182 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 chart/crds/karpenter.sh_provisioners.yaml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 hack/boilerplate.go.txt create mode 100755 hack/boilerplate.sh create mode 100644 pkg/apis/provisioning/v1alpha5/doc.go create mode 100644 pkg/apis/provisioning/v1alpha5/labels.go create mode 100644 pkg/apis/provisioning/v1alpha5/limits.go create mode 100644 pkg/apis/provisioning/v1alpha5/provisioner.go create mode 100644 pkg/apis/provisioning/v1alpha5/provisioner_defaults.go create mode 100644 pkg/apis/provisioning/v1alpha5/provisioner_status.go create mode 100644 pkg/apis/provisioning/v1alpha5/provisioner_validation.go create mode 100644 pkg/apis/provisioning/v1alpha5/register.go create mode 100644 pkg/apis/provisioning/v1alpha5/suite_test.go create mode 100644 pkg/apis/provisioning/v1alpha5/zz_generated.deepcopy.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..95f28cff3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# Go toolchain +coverage.out +coverage.html +*.test +*.cpuprofile +*.heapprofile + +# Common in OSs and IDEs +.idea +.vscode +.DS_Store diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..21672c79e --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +help: ## Display help + @awk 'BEGIN {FS = ":.*##"; printf "Usage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +ci: verify test ## Run all steps used by continuous integration + +test: ## Run tests + go test -run=${TEST_FILTER} ./... + +verify: codegen ## Verify code. Includes dependencies, linting, formatting, etc + go mod tidy + go vet ./... + @git diff --quiet ||\ + { echo "New file modification detected in the Git working tree. Please check in before commit."; git --no-pager diff --name-only | uniq | awk '{print " - " $$0}'; \ + if [ $(MAKECMDGOALS) = 'ci' ]; then\ + exit 1;\ + fi;} + +codegen: ## Generate code. Must be run if changes are made to ./pkg/apis/... + controller-gen \ + object:headerFile="hack/boilerplate.go.txt" \ + crd \ + paths="./pkg/..." \ + output:crd:artifacts:config=chart/crds + hack/boilerplate.sh + +.PHONY: help ci test verify codegen diff --git a/chart/crds/karpenter.sh_provisioners.yaml b/chart/crds/karpenter.sh_provisioners.yaml new file mode 100644 index 000000000..79e65badb --- /dev/null +++ b/chart/crds/karpenter.sh_provisioners.yaml @@ -0,0 +1,348 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: provisioners.karpenter.sh +spec: + group: karpenter.sh + names: + categories: + - karpenter + kind: Provisioner + listKind: ProvisionerList + plural: provisioners + singular: provisioner + scope: Cluster + versions: + - name: v1alpha5 + schema: + openAPIV3Schema: + description: Provisioner is the Schema for the Provisioners API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ProvisionerSpec is the top level provisioner specification. + Provisioners launch nodes in response to pods that are unschedulable. + A single provisioner is capable of managing a diverse set of nodes. + Node properties are determined from a combination of provisioner and + pod scheduling constraints. + properties: + consolidation: + description: Consolidation are the consolidation parameters + properties: + enabled: + description: Enabled enables consolidation if it has been set + type: boolean + type: object + kubeletConfiguration: + description: KubeletConfiguration are options passed to the kubelet + when provisioning nodes + properties: + clusterDNS: + description: clusterDNS is a list of IP addresses for the cluster + DNS server. Note that not all providers may use all addresses. + items: + type: string + type: array + containerRuntime: + description: ContainerRuntime is the container runtime to be used + with your worker nodes. + type: string + evictionHard: + additionalProperties: + type: string + description: EvictionHard is the map of signal names to quantities + that define hard eviction thresholds + type: object + evictionMaxPodGracePeriod: + description: EvictionMaxPodGracePeriod is the maximum allowed + grace period (in seconds) to use when terminating pods in response + to soft eviction thresholds being met. + format: int32 + type: integer + evictionSoft: + additionalProperties: + type: string + description: EvictionSoft is the map of signal names to quantities + that define soft eviction thresholds + type: object + evictionSoftGracePeriod: + additionalProperties: + type: string + description: EvictionSoftGracePeriod is the map of signal names + to quantities that define grace periods for each eviction signal + type: object + kubeReserved: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: KubeReserved contains resources reserved for Kubernetes + system components. + type: object + maxPods: + description: MaxPods is an override for the maximum number of + pods that can run on a worker node instance. + format: int32 + minimum: 0 + type: integer + podsPerCore: + description: PodsPerCore is an override for the number of pods + that can run on a worker node instance based on the number of + cpu cores. This value cannot exceed MaxPods, so, if MaxPods + is a lower value, that value will be used. + format: int32 + minimum: 0 + type: integer + systemReserved: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: SystemReserved contains resources reserved for OS + system daemons and kernel memory. + type: object + type: object + labels: + additionalProperties: + type: string + description: Labels are layered with Requirements and applied to every + node. + type: object + limits: + description: Limits define a set of bounds for provisioning capacity. + properties: + resources: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Resources contains all the allocatable resources + that Karpenter supports for limiting. + type: object + type: object + provider: + description: Provider contains fields specific to your cloudprovider. + type: object + x-kubernetes-preserve-unknown-fields: true + providerRef: + description: ProviderRef is a reference to a dedicated CRD for the + chosen provider, that holds additional configuration options + properties: + apiVersion: + description: API version of the referent + type: string + kind: + description: 'Kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds"' + type: string + name: + description: 'Name of the referent; More info: http://kubernetes.io/docs/user-guide/identifiers#names' + type: string + type: object + requirements: + description: Requirements are layered with Labels and applied to every + node. + items: + description: A node selector requirement is a selector that contains + values, a key, and an operator that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: An array of string values. If the operator is In + or NotIn, the values array must be non-empty. If the operator + is Exists or DoesNotExist, the values array must be empty. + If the operator is Gt or Lt, the values array must have a + single element, which will be interpreted as an integer. This + array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + startupTaints: + description: StartupTaints are taints that are applied to nodes upon + startup which are expected to be removed automatically within a + short period of time, typically by a DaemonSet that tolerates the + taint. These are commonly used by daemonsets to allow initialization + and enforce startup ordering. StartupTaints are ignored for provisioning + purposes in that pods are not required to tolerate a StartupTaint + in order to have nodes provisioned for them. + items: + description: The node this Taint is attached to has the "effect" + on any pod that does not tolerate the Taint. + properties: + effect: + description: Required. The effect of the taint on pods that + do not tolerate the taint. Valid effects are NoSchedule, PreferNoSchedule + and NoExecute. + type: string + key: + description: Required. The taint key to be applied to a node. + type: string + timeAdded: + description: TimeAdded represents the time at which the taint + was added. It is only written for NoExecute taints. + format: date-time + type: string + value: + description: The taint value corresponding to the taint key. + type: string + required: + - effect + - key + type: object + type: array + taints: + description: Taints will be applied to every node launched by the + Provisioner. If specified, the provisioner will not provision nodes + for pods that do not have matching tolerations. Additional taints + will be created that match pod tolerations on a per-node basis. + items: + description: The node this Taint is attached to has the "effect" + on any pod that does not tolerate the Taint. + properties: + effect: + description: Required. The effect of the taint on pods that + do not tolerate the taint. Valid effects are NoSchedule, PreferNoSchedule + and NoExecute. + type: string + key: + description: Required. The taint key to be applied to a node. + type: string + timeAdded: + description: TimeAdded represents the time at which the taint + was added. It is only written for NoExecute taints. + format: date-time + type: string + value: + description: The taint value corresponding to the taint key. + type: string + required: + - effect + - key + type: object + type: array + ttlSecondsAfterEmpty: + description: "TTLSecondsAfterEmpty is the number of seconds the controller + will wait before attempting to delete a node, measured from when + the node is detected to be empty. A Node is considered to be empty + when it does not have pods scheduled to it, excluding daemonsets. + \n Termination due to no utilization is disabled if this field is + not set." + format: int64 + type: integer + ttlSecondsUntilExpired: + description: "TTLSecondsUntilExpired is the number of seconds the + controller will wait before terminating a node, measured from when + the node is created. This is useful to implement features like eventually + consistent node upgrade, memory leak protection, and disruption + testing. \n Termination due to expiration is disabled if this field + is not set." + format: int64 + type: integer + weight: + description: Weight is the priority given to the provisioner during + scheduling. A higher numerical weight indicates that this provisioner + will be ordered ahead of other provisioners with lower weights. + A provisioner with no weight will be treated as if it is a provisioner + with a weight of 0. + format: int32 + maximum: 100 + minimum: 1 + type: integer + type: object + status: + description: ProvisionerStatus defines the observed state of Provisioner + properties: + conditions: + description: Conditions is the set of conditions required for this + provisioner to scale its target, and indicates whether or not those + conditions are met. + items: + description: 'Condition defines a readiness condition for a Knative + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. We use VolatileTime + in place of metav1.Time to exclude this from creating equality.Semantic + differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: Severity with which to treat failures of this type + of condition. When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastScaleTime: + description: LastScaleTime is the last time the Provisioner scaled + the number of nodes + format: date-time + type: string + resources: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Resources is the list of resources that have been provisioned. + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..337206ef9 --- /dev/null +++ b/go.mod @@ -0,0 +1,39 @@ +module github.com/aws/karpenter-core + +go 1.19 + +require ( + github.com/Pallinder/go-randomdata v1.2.0 + github.com/aws/aws-sdk-go v1.44.114 + github.com/onsi/ginkgo/v2 v2.1.6 + github.com/onsi/gomega v1.20.1 + github.com/samber/lo v1.32.0 + go.uber.org/multierr v1.8.0 + k8s.io/api v0.25.2 + k8s.io/apimachinery v0.25.2 + knative.dev/pkg v0.0.0-20221011175852-714b7630a836 +) + +require ( + github.com/blendle/zapdriver v1.3.1 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/go-cmp v0.5.8 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/zap v1.19.1 // indirect + golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect + golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect + golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect + golang.org/x/text v0.3.7 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/klog/v2 v2.70.2-0.20220707122935-0990e81f1a8f // indirect + k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect + sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..97fc762a5 --- /dev/null +++ b/go.sum @@ -0,0 +1,153 @@ +github.com/Pallinder/go-randomdata v1.2.0 h1:DZ41wBchNRb/0GfsePLiSwb0PHZmT67XY00lCDlaYPg= +github.com/Pallinder/go-randomdata v1.2.0/go.mod h1:yHmJgulpD2Nfrm0cR9tI/+oAgRqCQQixsA8HyRZfV9Y= +github.com/aws/aws-sdk-go v1.44.114 h1:plIkWc/RsHr3DXBj4MEw9sEW4CcL/e2ryokc+CKyq1I= +github.com/aws/aws-sdk-go v1.44.114/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHfpE= +github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/onsi/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU= +github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= +github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= +github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/samber/lo v1.32.0 h1:MjbngaDxbQ+ockKTEoF0IQtW2lX1VgqZ5IBhxi4fmTU= +github.com/samber/lo v1.32.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= +go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= +go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= +golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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.25.2 h1:v6G8RyFcwf0HR5jQGIAYlvtRNrxMJQG1xJzaSeVnIS8= +k8s.io/api v0.25.2/go.mod h1:qP1Rn4sCVFwx/xIhe+we2cwBLTXNcheRyYXwajonhy0= +k8s.io/apimachinery v0.25.2 h1:WbxfAjCx+AeN8Ilp9joWnyJ6xu9OMeS/fsfjK/5zaQs= +k8s.io/apimachinery v0.25.2/go.mod h1:hqqA1X0bsgsxI6dXsJ4HnNTBOmJNxyPp8dw3u2fSHwA= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.70.2-0.20220707122935-0990e81f1a8f h1:dltw7bAn8bCrQ2CmzzhgoieUZEbWqrvIGVdHGioP5nY= +k8s.io/klog/v2 v2.70.2-0.20220707122935-0990e81f1a8f/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= +k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +knative.dev/pkg v0.0.0-20221011175852-714b7630a836 h1:0N7Zo/O+xeUUebJPm9keBaGclrUoEbljr3J1MsqtaIM= +knative.dev/pkg v0.0.0-20221011175852-714b7630a836/go.mod h1:DMTRDJ5WRxf/DrlOPzohzfhSuJggscLZ8EavOq9O/x8= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt new file mode 100644 index 000000000..b4ede5d4b --- /dev/null +++ b/hack/boilerplate.go.txt @@ -0,0 +1,13 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ diff --git a/hack/boilerplate.sh b/hack/boilerplate.sh new file mode 100755 index 000000000..d2c24ed36 --- /dev/null +++ b/hack/boilerplate.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -eu -o pipefail + +for i in $( + find ./ -name "*.go" +); do + if ! grep -q "Apache License" $i; then + cat hack/boilerplate.go.txt $i >$i.new && mv $i.new $i + fi +done diff --git a/pkg/apis/provisioning/v1alpha5/doc.go b/pkg/apis/provisioning/v1alpha5/doc.go new file mode 100644 index 000000000..ce68d0b7a --- /dev/null +++ b/pkg/apis/provisioning/v1alpha5/doc.go @@ -0,0 +1,19 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// +k8s:openapi-gen=true +// +k8s:deepcopy-gen=package,register +// +k8s:defaulter-gen=TypeMeta +// +groupName=karpenter.sh +package v1alpha5 // doc.go is discovered by codegen diff --git a/pkg/apis/provisioning/v1alpha5/labels.go b/pkg/apis/provisioning/v1alpha5/labels.go new file mode 100644 index 000000000..b3ddf0a51 --- /dev/null +++ b/pkg/apis/provisioning/v1alpha5/labels.go @@ -0,0 +1,122 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha5 + +import ( + "fmt" + "strings" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/sets" +) + +var ( + ArchitectureAmd64 = "amd64" + ArchitectureArm64 = "arm64" + OperatingSystemLinux = "linux" + + // Karpenter specific domains and labels + KarpenterLabelDomain = "karpenter.sh" + + ProvisionerNameLabelKey = Group + "/provisioner-name" + DoNotEvictPodAnnotationKey = Group + "/do-not-evict" + DoNotConsolidateNodeAnnotationKey = KarpenterLabelDomain + "/do-not-consolidate" + EmptinessTimestampAnnotationKey = Group + "/emptiness-timestamp" + TerminationFinalizer = Group + "/termination" + + LabelCapacityType = KarpenterLabelDomain + "/capacity-type" + LabelNodeInitialized = KarpenterLabelDomain + "/initialized" + + // RestrictedLabelDomains are either prohibited by the kubelet or reserved by karpenter + RestrictedLabelDomains = sets.NewString( + "kubernetes.io", + "k8s.io", + KarpenterLabelDomain, + ) + + // LabelDomainException are sub-domains of the RestrictedLabelDomains but allowed because + // they are not used in a context where they may be passed as argument to kubelet. + LabelDomainExceptions = sets.NewString( + "kops.k8s.io", + v1.LabelNamespaceSuffixNode, + ) + + // WellKnownLabels are labels that belong to the RestrictedLabelDomains but allowed. + // Karpenter is aware of these labels, and they can be used to further narrow down + // the range of the corresponding values by either provisioner or pods. + WellKnownLabels = sets.NewString( + ProvisionerNameLabelKey, + v1.LabelTopologyZone, + v1.LabelTopologyRegion, + v1.LabelInstanceTypeStable, + v1.LabelArchStable, + v1.LabelOSStable, + LabelCapacityType, + ) + + // RestrictedLabels are labels that should not be used + // because they may interfere with the internal provisioning logic. + RestrictedLabels = sets.NewString( + EmptinessTimestampAnnotationKey, + v1.LabelHostname, + ) + + // NormalizedLabels translate aliased concepts into the controller's + // WellKnownLabels. Pod requirements are translated for compatibility. + NormalizedLabels = map[string]string{ + v1.LabelFailureDomainBetaZone: v1.LabelTopologyZone, + "beta.kubernetes.io/arch": v1.LabelArchStable, + "beta.kubernetes.io/os": v1.LabelOSStable, + v1.LabelInstanceType: v1.LabelInstanceTypeStable, + v1.LabelFailureDomainBetaRegion: v1.LabelTopologyRegion, + } +) + +// IsRestrictedLabel returns an error if the label is restricted. +func IsRestrictedLabel(key string) error { + if WellKnownLabels.Has(key) { + return nil + } + if IsRestrictedNodeLabel(key) { + return fmt.Errorf("label %s is restricted; specify a well known label: %v, or a custom label that does not use a restricted domain: %v", key, WellKnownLabels.List(), RestrictedLabelDomains.List()) + } + return nil +} + +// IsRestrictedNodeLabel returns true if a node label should not be injected by Karpenter. +// They are either known labels that will be injected by cloud providers, +// or label domain managed by other software (e.g., kops.k8s.io managed by kOps). +func IsRestrictedNodeLabel(key string) bool { + if WellKnownLabels.Has(key) { + return true + } + labelDomain := getLabelDomain(key) + if LabelDomainExceptions.Has(labelDomain) { + return false + } + for restrictedLabelDomain := range RestrictedLabelDomains { + if strings.HasSuffix(labelDomain, restrictedLabelDomain) { + return true + } + } + return RestrictedLabels.Has(key) +} + +func getLabelDomain(key string) string { + if parts := strings.SplitN(key, "/", 2); len(parts) == 2 { + return parts[0] + } + return "" +} diff --git a/pkg/apis/provisioning/v1alpha5/limits.go b/pkg/apis/provisioning/v1alpha5/limits.go new file mode 100644 index 000000000..49da47da5 --- /dev/null +++ b/pkg/apis/provisioning/v1alpha5/limits.go @@ -0,0 +1,41 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha5 + +import ( + "fmt" + + v1 "k8s.io/api/core/v1" +) + +// Limits define bounds on the resources being provisioned by Karpenter +type Limits struct { + // Resources contains all the allocatable resources that Karpenter supports for limiting. + Resources v1.ResourceList `json:"resources,omitempty"` +} + +func (l *Limits) ExceededBy(resources v1.ResourceList) error { + if l == nil || l.Resources == nil { + return nil + } + for resourceName, usage := range resources { + if limit, ok := l.Resources[resourceName]; ok { + if usage.Cmp(limit) >= 0 { + return fmt.Errorf("%s resource usage of %v exceeds limit of %v", resourceName, usage.AsDec(), limit.AsDec()) + } + } + } + return nil +} diff --git a/pkg/apis/provisioning/v1alpha5/provisioner.go b/pkg/apis/provisioning/v1alpha5/provisioner.go new file mode 100644 index 000000000..555761a34 --- /dev/null +++ b/pkg/apis/provisioning/v1alpha5/provisioner.go @@ -0,0 +1,179 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha5 + +import ( + "sort" + + "knative.dev/pkg/ptr" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// ProvisionerSpec is the top level provisioner specification. Provisioners +// launch nodes in response to pods that are unschedulable. A single provisioner +// is capable of managing a diverse set of nodes. Node properties are determined +// from a combination of provisioner and pod scheduling constraints. +type ProvisionerSpec struct { + // Labels are layered with Requirements and applied to every node. + //+optional + Labels map[string]string `json:"labels,omitempty"` + // Taints will be applied to every node launched by the Provisioner. If + // specified, the provisioner will not provision nodes for pods that do not + // have matching tolerations. Additional taints will be created that match + // pod tolerations on a per-node basis. + // +optional + Taints []v1.Taint `json:"taints,omitempty"` + // StartupTaints are taints that are applied to nodes upon startup which are expected to be removed automatically + // within a short period of time, typically by a DaemonSet that tolerates the taint. These are commonly used by + // daemonsets to allow initialization and enforce startup ordering. StartupTaints are ignored for provisioning + // purposes in that pods are not required to tolerate a StartupTaint in order to have nodes provisioned for them. + // +optional + StartupTaints []v1.Taint `json:"startupTaints,omitempty"` + // Requirements are layered with Labels and applied to every node. + Requirements []v1.NodeSelectorRequirement `json:"requirements,omitempty"` + // KubeletConfiguration are options passed to the kubelet when provisioning nodes + //+optional + KubeletConfiguration *KubeletConfiguration `json:"kubeletConfiguration,omitempty"` + // Provider contains fields specific to your cloudprovider. + // +kubebuilder:pruning:PreserveUnknownFields + Provider *Provider `json:"provider,omitempty"` + // ProviderRef is a reference to a dedicated CRD for the chosen provider, that holds + // additional configuration options + // +optional + ProviderRef *ProviderRef `json:"providerRef,omitempty"` + // TTLSecondsAfterEmpty is the number of seconds the controller will wait + // before attempting to delete a node, measured from when the node is + // detected to be empty. A Node is considered to be empty when it does not + // have pods scheduled to it, excluding daemonsets. + // + // Termination due to no utilization is disabled if this field is not set. + // +optional + TTLSecondsAfterEmpty *int64 `json:"ttlSecondsAfterEmpty,omitempty"` + // TTLSecondsUntilExpired is the number of seconds the controller will wait + // before terminating a node, measured from when the node is created. This + // is useful to implement features like eventually consistent node upgrade, + // memory leak protection, and disruption testing. + // + // Termination due to expiration is disabled if this field is not set. + // +optional + TTLSecondsUntilExpired *int64 `json:"ttlSecondsUntilExpired,omitempty"` + // Limits define a set of bounds for provisioning capacity. + Limits *Limits `json:"limits,omitempty"` + // Weight is the priority given to the provisioner during scheduling. A higher + // numerical weight indicates that this provisioner will be ordered + // ahead of other provisioners with lower weights. A provisioner with no weight + // will be treated as if it is a provisioner with a weight of 0. + // +kubebuilder:validation:Minimum:=1 + // +kubebuilder:validation:Maximum:=100 + // +optional + Weight *int32 `json:"weight,omitempty"` + // Consolidation are the consolidation parameters + // +optional + Consolidation *Consolidation `json:"consolidation,omitempty"` +} + +type Consolidation struct { + // Enabled enables consolidation if it has been set + Enabled *bool `json:"enabled,omitempty"` +} + +// +kubebuilder:object:generate=false +type Provider = runtime.RawExtension + +type ProviderRef struct { + // Kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + Kind string `json:"kind,omitempty"` + // Name of the referent; More info: http://kubernetes.io/docs/user-guide/identifiers#names + Name string `json:"name,omitempty"` + // API version of the referent + // +optional + APIVersion string `json:"apiVersion,omitempty"` +} + +// KubeletConfiguration defines args to be used when configuring kubelet on provisioned nodes. +// They are a subset of the upstream types, recognizing not all options may be supported. +// Wherever possible, the types and names should reflect the upstream kubelet types. +// https://pkg.go.dev/k8s.io/kubelet/config/v1beta1#KubeletConfiguration +// https://github.com/kubernetes/kubernetes/blob/9f82d81e55cafdedab619ea25cabf5d42736dacf/cmd/kubelet/app/options/options.go#L53 +type KubeletConfiguration struct { + // clusterDNS is a list of IP addresses for the cluster DNS server. + // Note that not all providers may use all addresses. + //+optional + ClusterDNS []string `json:"clusterDNS,omitempty"` + // ContainerRuntime is the container runtime to be used with your worker nodes. + // +optional + ContainerRuntime *string `json:"containerRuntime,omitempty"` + // MaxPods is an override for the maximum number of pods that can run on + // a worker node instance. + // +kubebuilder:validation:Minimum:=0 + // +optional + MaxPods *int32 `json:"maxPods,omitempty"` + // PodsPerCore is an override for the number of pods that can run on a worker node + // instance based on the number of cpu cores. This value cannot exceed MaxPods, so, if + // MaxPods is a lower value, that value will be used. + // +kubebuilder:validation:Minimum:=0 + // +optional + PodsPerCore *int32 `json:"podsPerCore,omitempty"` + // SystemReserved contains resources reserved for OS system daemons and kernel memory. + // +optional + SystemReserved v1.ResourceList `json:"systemReserved,omitempty"` + // KubeReserved contains resources reserved for Kubernetes system components. + // +optional + KubeReserved v1.ResourceList `json:"kubeReserved,omitempty"` + // EvictionHard is the map of signal names to quantities that define hard eviction thresholds + // +optional + EvictionHard map[string]string `json:"evictionHard,omitempty"` + // EvictionSoft is the map of signal names to quantities that define soft eviction thresholds + // +optional + EvictionSoft map[string]string `json:"evictionSoft,omitempty"` + // EvictionSoftGracePeriod is the map of signal names to quantities that define grace periods for each eviction signal + // +optional + EvictionSoftGracePeriod map[string]metav1.Duration `json:"evictionSoftGracePeriod,omitempty"` + // EvictionMaxPodGracePeriod is the maximum allowed grace period (in seconds) to use when terminating pods in + // response to soft eviction thresholds being met. + // +optional + EvictionMaxPodGracePeriod *int32 `json:"evictionMaxPodGracePeriod,omitempty"` +} + +// Provisioner is the Schema for the Provisioners API +// +kubebuilder:object:root=true +// +kubebuilder:resource:path=provisioners,scope=Cluster,categories=karpenter +// +kubebuilder:subresource:status +type Provisioner struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ProvisionerSpec `json:"spec,omitempty"` + Status ProvisionerStatus `json:"status,omitempty"` +} + +// ProvisionerList contains a list of Provisioner +// +kubebuilder:object:root=true +type ProvisionerList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Provisioner `json:"items"` +} + +// OrderByWeight orders the provisioners in the ProvisionerList +// by their priority weight in-place +func (pl *ProvisionerList) OrderByWeight() { + sort.Slice(pl.Items, func(a, b int) bool { + return ptr.Int32Value(pl.Items[a].Spec.Weight) > ptr.Int32Value(pl.Items[b].Spec.Weight) + }) +} diff --git a/pkg/apis/provisioning/v1alpha5/provisioner_defaults.go b/pkg/apis/provisioning/v1alpha5/provisioner_defaults.go new file mode 100644 index 000000000..e12262c72 --- /dev/null +++ b/pkg/apis/provisioning/v1alpha5/provisioner_defaults.go @@ -0,0 +1,24 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha5 + +import ( + "context" +) + +// SetDefaults for the provisioner +func (p *Provisioner) SetDefaults(ctx context.Context) { + DefaultHook(ctx, p) +} diff --git a/pkg/apis/provisioning/v1alpha5/provisioner_status.go b/pkg/apis/provisioning/v1alpha5/provisioner_status.go new file mode 100644 index 000000000..1f16fff52 --- /dev/null +++ b/pkg/apis/provisioning/v1alpha5/provisioner_status.go @@ -0,0 +1,51 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha5 + +import ( + v1 "k8s.io/api/core/v1" + "knative.dev/pkg/apis" +) + +// ProvisionerStatus defines the observed state of Provisioner +type ProvisionerStatus struct { + // LastScaleTime is the last time the Provisioner scaled the number + // of nodes + // +optional + // +kubebuilder:validation:Format="date-time" + LastScaleTime *apis.VolatileTime `json:"lastScaleTime,omitempty"` + + // Conditions is the set of conditions required for this provisioner to scale + // its target, and indicates whether or not those conditions are met. + // +optional + Conditions apis.Conditions `json:"conditions,omitempty"` + + // Resources is the list of resources that have been provisioned. + Resources v1.ResourceList `json:"resources,omitempty"` +} + +func (p *Provisioner) StatusConditions() apis.ConditionManager { + return apis.NewLivingConditionSet( + Active, + ).Manage(p) +} + +func (p *Provisioner) GetConditions() apis.Conditions { + return p.Status.Conditions +} + +func (p *Provisioner) SetConditions(conditions apis.Conditions) { + p.Status.Conditions = conditions +} diff --git a/pkg/apis/provisioning/v1alpha5/provisioner_validation.go b/pkg/apis/provisioning/v1alpha5/provisioner_validation.go new file mode 100644 index 000000000..551dc9e27 --- /dev/null +++ b/pkg/apis/provisioning/v1alpha5/provisioner_validation.go @@ -0,0 +1,308 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha5 + +import ( + "context" + "fmt" + "strconv" + "strings" + + "github.com/samber/lo" + "go.uber.org/multierr" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation" + "knative.dev/pkg/apis" + "knative.dev/pkg/ptr" +) + +var ( + SupportedNodeSelectorOps = sets.NewString( + string(v1.NodeSelectorOpIn), + string(v1.NodeSelectorOpNotIn), + string(v1.NodeSelectorOpGt), + string(v1.NodeSelectorOpLt), + string(v1.NodeSelectorOpExists), + string(v1.NodeSelectorOpDoesNotExist), + ) + + SupportedReservedResources = sets.NewString( + v1.ResourceCPU.String(), + v1.ResourceMemory.String(), + v1.ResourceEphemeralStorage.String(), + "pid", + ) + + SupportedEvictionSignals = sets.NewString( + "memory.available", + "nodefs.available", + "nodefs.inodesFree", + "imagefs.available", + "imagefs.inodesFree", + "pid.available", + ) +) + +const ( + providerPath = "provider" + providerRefPath = "providerRef" +) + +func (p *Provisioner) Validate(ctx context.Context) (errs *apis.FieldError) { + return errs.Also( + apis.ValidateObjectMetadata(p).ViaField("metadata"), + p.Spec.validate(ctx).ViaField("spec"), + ValidateHook(ctx, p), + ) +} + +func (s *ProvisionerSpec) validate(ctx context.Context) (errs *apis.FieldError) { + return errs.Also( + s.validateTTLSecondsUntilExpired(), + s.validateTTLSecondsAfterEmpty(), + s.Validate(ctx), + ) +} + +func (s *ProvisionerSpec) validateTTLSecondsUntilExpired() (errs *apis.FieldError) { + if ptr.Int64Value(s.TTLSecondsUntilExpired) < 0 { + return errs.Also(apis.ErrInvalidValue("cannot be negative", "ttlSecondsUntilExpired")) + } + return errs +} + +func (s *ProvisionerSpec) validateTTLSecondsAfterEmpty() (errs *apis.FieldError) { + if ptr.Int64Value(s.TTLSecondsAfterEmpty) < 0 { + return errs.Also(apis.ErrInvalidValue("cannot be negative", "ttlSecondsAfterEmpty")) + } + // TTLSecondsAfterEmpty and consolidation are mutually exclusive + if s.Consolidation != nil && ptr.BoolValue(s.Consolidation.Enabled) && s.TTLSecondsAfterEmpty != nil { + return errs.Also(apis.ErrMultipleOneOf("ttlSecondsAfterEmpty", "consolidation.enabled")) + } + return errs +} + +// Validate the constraints +func (s *ProvisionerSpec) Validate(ctx context.Context) (errs *apis.FieldError) { + return errs.Also( + s.validateProvider(), + s.validateLabels(), + s.validateTaints(), + s.validateRequirements(), + s.validateKubeletConfiguration().ViaField("kubeletConfiguration"), + ) +} + +func (s *ProvisionerSpec) validateLabels() (errs *apis.FieldError) { + for key, value := range s.Labels { + if key == ProvisionerNameLabelKey { + errs = errs.Also(apis.ErrInvalidKeyName(key, "labels", "restricted")) + } + for _, err := range validation.IsQualifiedName(key) { + errs = errs.Also(apis.ErrInvalidKeyName(key, "labels", err)) + } + for _, err := range validation.IsValidLabelValue(value) { + errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("%s, %s", value, err), fmt.Sprintf("labels[%s]", key))) + } + if err := IsRestrictedLabel(key); err != nil { + errs = errs.Also(apis.ErrInvalidKeyName(key, "labels", err.Error())) + } + } + return errs +} + +type taintKeyEffect struct { + Key string + Effect v1.TaintEffect +} + +func (s *ProvisionerSpec) validateTaints() (errs *apis.FieldError) { + existing := map[taintKeyEffect]struct{}{} + errs = errs.Also(s.validateTaintsField(s.Taints, existing, "taints")) + errs = errs.Also(s.validateTaintsField(s.StartupTaints, existing, "startupTaints")) + return errs +} + +func (s *ProvisionerSpec) validateTaintsField(taints []v1.Taint, existing map[taintKeyEffect]struct{}, fieldName string) *apis.FieldError { + var errs *apis.FieldError + for i, taint := range taints { + // Validate Key + if len(taint.Key) == 0 { + errs = errs.Also(apis.ErrInvalidArrayValue(errs, fieldName, i)) + } + for _, err := range validation.IsQualifiedName(taint.Key) { + errs = errs.Also(apis.ErrInvalidArrayValue(err, fieldName, i)) + } + // Validate Value + if len(taint.Value) != 0 { + for _, err := range validation.IsQualifiedName(taint.Value) { + errs = errs.Also(apis.ErrInvalidArrayValue(err, fieldName, i)) + } + } + // Validate effect + switch taint.Effect { + case v1.TaintEffectNoSchedule, v1.TaintEffectPreferNoSchedule, v1.TaintEffectNoExecute, "": + default: + errs = errs.Also(apis.ErrInvalidArrayValue(taint.Effect, "effect", i)) + } + + // Check for duplicate Key/Effect pairs + key := taintKeyEffect{Key: taint.Key, Effect: taint.Effect} + if _, ok := existing[key]; ok { + errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("duplicate taint Key/Effect pair %s=%s", taint.Key, taint.Effect), apis.CurrentField). + ViaFieldIndex("taints", i)) + } + existing[key] = struct{}{} + } + return errs +} + +// This function is used by the provisioner validation webhook to verify the provisioner requirements. +// When this function is called, the provisioner's requirments do not include the requirements from labels. +// Provisioner requirements only support well known labels. +func (s *ProvisionerSpec) validateRequirements() (errs *apis.FieldError) { + for i, requirement := range s.Requirements { + if requirement.Key == ProvisionerNameLabelKey { + errs = errs.Also(apis.ErrInvalidArrayValue(fmt.Sprintf("%s is restricted", requirement.Key), "requirements", i)) + } + if err := ValidateRequirement(requirement); err != nil { + errs = errs.Also(apis.ErrInvalidArrayValue(err, "requirements", i)) + } + } + return errs +} + +func (s *ProvisionerSpec) validateProvider() *apis.FieldError { + if s.Provider != nil && s.ProviderRef != nil { + return apis.ErrMultipleOneOf(providerPath, providerRefPath) + } + return nil +} + +func (s *ProvisionerSpec) validateKubeletConfiguration() (errs *apis.FieldError) { + if s.KubeletConfiguration == nil { + return + } + return errs.Also( + validateEvictionThresholds(s.KubeletConfiguration.EvictionHard, "evictionHard"), + validateEvictionThresholds(s.KubeletConfiguration.EvictionSoft, "evictionSoft"), + validateReservedResources(s.KubeletConfiguration.KubeReserved, "kubeReserved"), + validateReservedResources(s.KubeletConfiguration.SystemReserved, "systemReserved"), + s.KubeletConfiguration.validateEvictionSoftGracePeriod(), + s.KubeletConfiguration.validateEvictionSoftPairs(), + ) +} + +func (kc *KubeletConfiguration) validateEvictionSoftGracePeriod() (errs *apis.FieldError) { + for k := range kc.EvictionSoftGracePeriod { + if !SupportedEvictionSignals.Has(k) { + errs = errs.Also(apis.ErrInvalidKeyName(k, "evictionSoftGracePeriod")) + } + } + return errs +} + +func (kc *KubeletConfiguration) validateEvictionSoftPairs() (errs *apis.FieldError) { + evictionSoftKeys := sets.NewString(lo.Keys(kc.EvictionSoft)...) + evictionSoftGracePeriodKeys := sets.NewString(lo.Keys(kc.EvictionSoftGracePeriod)...) + + evictionSoftDiff := evictionSoftKeys.Difference(evictionSoftGracePeriodKeys) + for k := range evictionSoftDiff { + errs = errs.Also(apis.ErrInvalidKeyName(k, "evictionSoft", "Key does not have a matching evictionSoftGracePeriod")) + } + evictionSoftGracePeriodDiff := evictionSoftGracePeriodKeys.Difference(evictionSoftKeys) + for k := range evictionSoftGracePeriodDiff { + errs = errs.Also(apis.ErrInvalidKeyName(k, "evictionSoftGracePeriod", "Key does not have a matching evictionSoft threshold value")) + } + return errs +} + +func validateReservedResources(m v1.ResourceList, fieldName string) (errs *apis.FieldError) { + for k, v := range m { + if !SupportedReservedResources.Has(k.String()) { + errs = errs.Also(apis.ErrInvalidKeyName(k.String(), fieldName)) + } + if v.Value() < 0 { + errs = errs.Also(apis.ErrInvalidValue(v.String(), fmt.Sprintf(`%s["%s"]`, fieldName, k), "Value cannot be a negative resource quantity")) + } + } + return errs +} + +func validateEvictionThresholds(m map[string]string, fieldName string) (errs *apis.FieldError) { + if m == nil { + return + } + for k, v := range m { + if !SupportedEvictionSignals.Has(k) { + errs = errs.Also(apis.ErrInvalidKeyName(k, fieldName)) + } + if strings.HasSuffix(v, "%") { + p, err := strconv.ParseFloat(strings.Trim(v, "%"), 64) + if err != nil { + errs = errs.Also(apis.ErrInvalidValue(v, fmt.Sprintf(`%s["%s"]`, fieldName, k), fmt.Sprintf("Value could not be parsed as a percentage value, %v", err.Error()))) + } + if p < 0 { + errs = errs.Also(apis.ErrInvalidValue(v, fmt.Sprintf(`%s["%s"]`, fieldName, k), "Percentage values cannot be negative")) + } + if p > 100 { + errs = errs.Also(apis.ErrInvalidValue(v, fmt.Sprintf(`%s["%s"]`, fieldName, k), "Percentage values cannot be greater than 100")) + } + } else { + _, err := resource.ParseQuantity(v) + if err != nil { + errs = errs.Also(apis.ErrInvalidValue(v, fmt.Sprintf("%s[%s]", fieldName, k), fmt.Sprintf("Value could not be parsed as a resource quantity, %v", err.Error()))) + } + } + } + return errs +} + +func ValidateRequirement(requirement v1.NodeSelectorRequirement) error { //nolint:gocyclo + var errs error + if normalized, ok := NormalizedLabels[requirement.Key]; ok { + requirement.Key = normalized + } + if !SupportedNodeSelectorOps.Has(string(requirement.Operator)) { + errs = multierr.Append(errs, fmt.Errorf("key %s has an unsupported operator %s not in %s", requirement.Key, requirement.Operator, SupportedNodeSelectorOps.UnsortedList())) + } + if e := IsRestrictedLabel(requirement.Key); e != nil { + errs = multierr.Append(errs, e) + } + for _, err := range validation.IsQualifiedName(requirement.Key) { + errs = multierr.Append(errs, fmt.Errorf("key %s is not a qualified name, %s", requirement.Key, err)) + } + for _, value := range requirement.Values { + for _, err := range validation.IsValidLabelValue(value) { + errs = multierr.Append(errs, fmt.Errorf("invalid value %s for key %s, %s", value, requirement.Key, err)) + } + } + if requirement.Operator == v1.NodeSelectorOpIn && len(requirement.Values) == 0 { + errs = multierr.Append(errs, fmt.Errorf("key %s with operator %s must have a value defined", requirement.Key, requirement.Operator)) + } + if requirement.Operator == v1.NodeSelectorOpGt || requirement.Operator == v1.NodeSelectorOpLt { + if len(requirement.Values) != 1 { + errs = multierr.Append(errs, fmt.Errorf("key %s with operator %s must have a single positive integer value", requirement.Key, requirement.Operator)) + } else { + value, err := strconv.Atoi(requirement.Values[0]) + if err != nil || value < 0 { + errs = multierr.Append(errs, fmt.Errorf("key %s with operator %s must have a single positive integer value", requirement.Key, requirement.Operator)) + } + } + } + return errs +} diff --git a/pkg/apis/provisioning/v1alpha5/register.go b/pkg/apis/provisioning/v1alpha5/register.go new file mode 100644 index 000000000..501838311 --- /dev/null +++ b/pkg/apis/provisioning/v1alpha5/register.go @@ -0,0 +1,50 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha5 + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "knative.dev/pkg/apis" +) + +var ( + DefaultHook = func(ctx context.Context, provisoner *Provisioner) {} + ValidateHook = func(ctx context.Context, provisoner *Provisioner) *apis.FieldError { return nil } +) + +var ( + Group = "karpenter.sh" + ExtensionsGroup = "extensions." + Group + SchemeGroupVersion = schema.GroupVersion{Group: Group, Version: "v1alpha5"} + SchemeBuilder = runtime.NewSchemeBuilder(func(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &Provisioner{}, + &ProvisionerList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil + }) +) + +const ( + // Active is a condition implemented by all resources. It indicates that the + // controller is able to take actions: it's correctly configured, can make + // necessary API calls, and isn't disabled. + Active apis.ConditionType = "Active" +) diff --git a/pkg/apis/provisioning/v1alpha5/suite_test.go b/pkg/apis/provisioning/v1alpha5/suite_test.go new file mode 100644 index 000000000..c1712f7af --- /dev/null +++ b/pkg/apis/provisioning/v1alpha5/suite_test.go @@ -0,0 +1,453 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha5 + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws" + "k8s.io/apimachinery/pkg/api/resource" + + "github.com/Pallinder/go-randomdata" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "knative.dev/pkg/logging/testing" + "knative.dev/pkg/ptr" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" +) + +var ctx context.Context + +func TestAPIs(t *testing.T) { + ctx = TestContextWithLogger(t) + RegisterFailHandler(Fail) + RunSpecs(t, "Validation") +} + +var _ = Describe("Validation", func() { + var provisioner *Provisioner + + BeforeEach(func() { + provisioner = &Provisioner{ + ObjectMeta: metav1.ObjectMeta{Name: strings.ToLower(randomdata.SillyName())}, + Spec: ProvisionerSpec{}, + } + }) + + It("should fail on negative expiry ttl", func() { + provisioner.Spec.TTLSecondsUntilExpired = ptr.Int64(-1) + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + It("should succeed on a missing expiry ttl", func() { + // this already is true, but to be explicit + provisioner.Spec.TTLSecondsUntilExpired = nil + Expect(provisioner.Validate(ctx)).To(Succeed()) + }) + It("should fail on negative empty ttl", func() { + provisioner.Spec.TTLSecondsAfterEmpty = ptr.Int64(-1) + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + It("should succeed on a missing empty ttl", func() { + provisioner.Spec.TTLSecondsAfterEmpty = nil + Expect(provisioner.Validate(ctx)).To(Succeed()) + }) + It("should succeed on a valid empty ttl", func() { + provisioner.Spec.TTLSecondsAfterEmpty = aws.Int64(30) + Expect(provisioner.Validate(ctx)).To(Succeed()) + }) + It("should fail if both consolidation and TTLSecondsAfterEmpty are enabled", func() { + provisioner.Spec.TTLSecondsAfterEmpty = ptr.Int64(30) + provisioner.Spec.Consolidation = &Consolidation{Enabled: aws.Bool(true)} + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + It("should succeed if consolidation is off and TTLSecondsAfterEmpty is set", func() { + provisioner.Spec.TTLSecondsAfterEmpty = ptr.Int64(30) + provisioner.Spec.Consolidation = &Consolidation{Enabled: aws.Bool(false)} + Expect(provisioner.Validate(ctx)).To(Succeed()) + }) + It("should succeed if consolidation is on and TTLSecondsAfterEmpty is not set", func() { + provisioner.Spec.TTLSecondsAfterEmpty = nil + provisioner.Spec.Consolidation = &Consolidation{Enabled: aws.Bool(true)} + Expect(provisioner.Validate(ctx)).To(Succeed()) + }) + + Context("Limits", func() { + It("should allow undefined limits", func() { + provisioner.Spec.Limits = &Limits{} + Expect(provisioner.Validate(ctx)).To(Succeed()) + }) + It("should allow empty limits", func() { + provisioner.Spec.Limits = &Limits{Resources: v1.ResourceList{}} + Expect(provisioner.Validate(ctx)).To(Succeed()) + }) + }) + Context("Provider", func() { + It("should not allow provider and providerRef", func() { + provisioner.Spec.Provider = &Provider{} + provisioner.Spec.ProviderRef = &ProviderRef{Name: "providerRef"} + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + }) + Context("Labels", func() { + It("should allow unrecognized labels", func() { + provisioner.Spec.Labels = map[string]string{"foo": randomdata.SillyName()} + Expect(provisioner.Validate(ctx)).To(Succeed()) + }) + It("should fail for the provisioner name label", func() { + provisioner.Spec.Labels = map[string]string{ProvisionerNameLabelKey: randomdata.SillyName()} + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + It("should fail for invalid label keys", func() { + provisioner.Spec.Labels = map[string]string{"spaces are not allowed": randomdata.SillyName()} + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + It("should fail for invalid label values", func() { + provisioner.Spec.Labels = map[string]string{randomdata.SillyName(): "/ is not allowed"} + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + It("should fail for restricted label domains", func() { + for label := range RestrictedLabelDomains { + provisioner.Spec.Labels = map[string]string{label + "/unknown": randomdata.SillyName()} + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + } + }) + It("should allow labels kOps require", func() { + provisioner.Spec.Labels = map[string]string{ + "kops.k8s.io/instancegroup": "karpenter-nodes", + "kops.k8s.io/gpu": "1", + } + Expect(provisioner.Validate(ctx)).To(Succeed()) + }) + It("should allow labels in restricted domains exceptions list", func() { + for label := range LabelDomainExceptions { + provisioner.Spec.Labels = map[string]string{ + label: "test-value", + } + Expect(provisioner.Validate(ctx)).To(Succeed()) + } + }) + }) + Context("Taints", func() { + It("should succeed for valid taints", func() { + provisioner.Spec.Taints = []v1.Taint{ + {Key: "a", Value: "b", Effect: v1.TaintEffectNoSchedule}, + {Key: "c", Value: "d", Effect: v1.TaintEffectNoExecute}, + {Key: "e", Value: "f", Effect: v1.TaintEffectPreferNoSchedule}, + {Key: "key-only", Effect: v1.TaintEffectNoExecute}, + } + Expect(provisioner.Validate(ctx)).To(Succeed()) + }) + It("should fail for invalid taint keys", func() { + provisioner.Spec.Taints = []v1.Taint{{Key: "???"}} + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + It("should fail for missing taint key", func() { + provisioner.Spec.Taints = []v1.Taint{{Effect: v1.TaintEffectNoSchedule}} + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + It("should fail for invalid taint value", func() { + provisioner.Spec.Taints = []v1.Taint{{Key: "invalid-value", Effect: v1.TaintEffectNoSchedule, Value: "???"}} + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + It("should fail for invalid taint effect", func() { + provisioner.Spec.Taints = []v1.Taint{{Key: "invalid-effect", Effect: "???"}} + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + It("should not fail for same key with different effects", func() { + provisioner.Spec.Taints = []v1.Taint{ + {Key: "a", Effect: v1.TaintEffectNoSchedule}, + {Key: "a", Effect: v1.TaintEffectNoExecute}, + } + Expect(provisioner.Validate(ctx)).To(Succeed()) + }) + It("should fail for duplicate taint key/effect pairs", func() { + provisioner.Spec.Taints = []v1.Taint{ + {Key: "a", Effect: v1.TaintEffectNoSchedule}, + {Key: "a", Effect: v1.TaintEffectNoSchedule}, + } + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + provisioner.Spec.Taints = []v1.Taint{ + {Key: "a", Effect: v1.TaintEffectNoSchedule}, + } + provisioner.Spec.StartupTaints = []v1.Taint{ + {Key: "a", Effect: v1.TaintEffectNoSchedule}, + } + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + }) + Context("Requirements", func() { + It("should fail for the provisioner name label", func() { + provisioner.Spec.Requirements = []v1.NodeSelectorRequirement{ + {Key: ProvisionerNameLabelKey, Operator: v1.NodeSelectorOpIn, Values: []string{randomdata.SillyName()}}, + } + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + It("should allow supported ops", func() { + provisioner.Spec.Requirements = []v1.NodeSelectorRequirement{ + {Key: v1.LabelTopologyZone, Operator: v1.NodeSelectorOpIn, Values: []string{"test"}}, + {Key: v1.LabelTopologyZone, Operator: v1.NodeSelectorOpGt, Values: []string{"1"}}, + {Key: v1.LabelTopologyZone, Operator: v1.NodeSelectorOpLt, Values: []string{"1"}}, + {Key: v1.LabelTopologyZone, Operator: v1.NodeSelectorOpNotIn}, + {Key: v1.LabelTopologyZone, Operator: v1.NodeSelectorOpExists}, + } + Expect(provisioner.Validate(ctx)).To(Succeed()) + }) + It("should fail for unsupported ops", func() { + for _, op := range []v1.NodeSelectorOperator{"unknown"} { + provisioner.Spec.Requirements = []v1.NodeSelectorRequirement{ + {Key: v1.LabelTopologyZone, Operator: op, Values: []string{"test"}}, + } + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + } + }) + It("should fail for restricted domains", func() { + for label := range RestrictedLabelDomains { + provisioner.Spec.Requirements = []v1.NodeSelectorRequirement{ + {Key: label + "/test", Operator: v1.NodeSelectorOpIn, Values: []string{"test"}}, + } + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + } + }) + It("should allow restricted domains exceptions", func() { + for label := range LabelDomainExceptions { + provisioner.Spec.Requirements = []v1.NodeSelectorRequirement{ + {Key: label + "/test", Operator: v1.NodeSelectorOpIn, Values: []string{"test"}}, + } + Expect(provisioner.Validate(ctx)).To(Succeed()) + } + }) + It("should allow well known label exceptions", func() { + for label := range WellKnownLabels.Difference(sets.NewString(ProvisionerNameLabelKey)) { + provisioner.Spec.Requirements = []v1.NodeSelectorRequirement{ + {Key: label, Operator: v1.NodeSelectorOpIn, Values: []string{"test"}}, + } + Expect(provisioner.Validate(ctx)).To(Succeed()) + } + }) + It("should allow non-empty set after removing overlapped value", func() { + provisioner.Spec.Requirements = []v1.NodeSelectorRequirement{ + {Key: v1.LabelTopologyZone, Operator: v1.NodeSelectorOpIn, Values: []string{"test", "foo"}}, + {Key: v1.LabelTopologyZone, Operator: v1.NodeSelectorOpNotIn, Values: []string{"test", "bar"}}, + } + Expect(provisioner.Validate(ctx)).To(Succeed()) + }) + It("should allow empty requirements", func() { + provisioner.Spec.Requirements = []v1.NodeSelectorRequirement{} + Expect(provisioner.Validate(ctx)).To(Succeed()) + }) + It("should fail with invalid GT or LT values", func() { + for _, requirement := range []v1.NodeSelectorRequirement{ + {Key: v1.LabelTopologyZone, Operator: v1.NodeSelectorOpGt, Values: []string{}}, + {Key: v1.LabelTopologyZone, Operator: v1.NodeSelectorOpGt, Values: []string{"1", "2"}}, + {Key: v1.LabelTopologyZone, Operator: v1.NodeSelectorOpGt, Values: []string{"a"}}, + {Key: v1.LabelTopologyZone, Operator: v1.NodeSelectorOpGt, Values: []string{"-1"}}, + {Key: v1.LabelTopologyZone, Operator: v1.NodeSelectorOpLt, Values: []string{}}, + {Key: v1.LabelTopologyZone, Operator: v1.NodeSelectorOpLt, Values: []string{"1", "2"}}, + {Key: v1.LabelTopologyZone, Operator: v1.NodeSelectorOpLt, Values: []string{"a"}}, + {Key: v1.LabelTopologyZone, Operator: v1.NodeSelectorOpLt, Values: []string{"-1"}}, + } { + provisioner.Spec.Requirements = []v1.NodeSelectorRequirement{requirement} + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + } + }) + }) + Context("KubeletConfiguration", func() { + It("should fail on kubeReserved with invalid keys", func() { + provisioner.Spec.KubeletConfiguration = &KubeletConfiguration{ + KubeReserved: v1.ResourceList{ + v1.ResourcePods: resource.MustParse("2"), + }, + } + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + It("should fail on systemReserved with invalid keys", func() { + provisioner.Spec.KubeletConfiguration = &KubeletConfiguration{ + SystemReserved: v1.ResourceList{ + v1.ResourcePods: resource.MustParse("2"), + }, + } + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + Context("Eviction Signals", func() { + Context("Eviction Hard", func() { + It("should succeed on evictionHard with valid keys", func() { + provisioner.Spec.KubeletConfiguration = &KubeletConfiguration{ + EvictionHard: map[string]string{ + "memory.available": "5%", + "nodefs.available": "10%", + "nodefs.inodesFree": "15%", + "imagefs.available": "5%", + "imagefs.inodesFree": "5%", + "pid.available": "5%", + }, + } + Expect(provisioner.Validate(ctx)).To(Succeed()) + }) + It("should fail on evictionHard with invalid keys", func() { + provisioner.Spec.KubeletConfiguration = &KubeletConfiguration{ + EvictionHard: map[string]string{ + "memory": "5%", + }, + } + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + It("should fail on invalid formatted percentage value in evictionHard", func() { + provisioner.Spec.KubeletConfiguration = &KubeletConfiguration{ + EvictionHard: map[string]string{ + "memory.available": "5%3", + }, + } + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + It("should fail on invalid percentage value (too large) in evictionHard", func() { + provisioner.Spec.KubeletConfiguration = &KubeletConfiguration{ + EvictionHard: map[string]string{ + "memory.available": "110%", + }, + } + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + It("should fail on invalid quantity value in evictionHard", func() { + provisioner.Spec.KubeletConfiguration = &KubeletConfiguration{ + EvictionHard: map[string]string{ + "memory.available": "110GB", + }, + } + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + }) + }) + Context("Eviction Soft", func() { + It("should succeed on evictionSoft with valid keys", func() { + provisioner.Spec.KubeletConfiguration = &KubeletConfiguration{ + EvictionSoft: map[string]string{ + "memory.available": "5%", + "nodefs.available": "10%", + "nodefs.inodesFree": "15%", + "imagefs.available": "5%", + "imagefs.inodesFree": "5%", + "pid.available": "5%", + }, + EvictionSoftGracePeriod: map[string]metav1.Duration{ + "memory.available": {Duration: time.Minute}, + "nodefs.available": {Duration: time.Second * 90}, + "nodefs.inodesFree": {Duration: time.Minute * 5}, + "imagefs.available": {Duration: time.Hour}, + "imagefs.inodesFree": {Duration: time.Hour * 24}, + "pid.available": {Duration: time.Minute}, + }, + } + Expect(provisioner.Validate(ctx)).To(Succeed()) + }) + It("should fail on evictionSoft with invalid keys", func() { + provisioner.Spec.KubeletConfiguration = &KubeletConfiguration{ + EvictionSoft: map[string]string{ + "memory": "5%", + }, + EvictionSoftGracePeriod: map[string]metav1.Duration{ + "memory": {Duration: time.Minute}, + }, + } + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + It("should fail on invalid formatted percentage value in evictionSoft", func() { + provisioner.Spec.KubeletConfiguration = &KubeletConfiguration{ + EvictionSoft: map[string]string{ + "memory.available": "5%3", + }, + EvictionSoftGracePeriod: map[string]metav1.Duration{ + "memory.available": {Duration: time.Minute}, + }, + } + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + It("should fail on invalid percentage value (too large) in evictionSoft", func() { + provisioner.Spec.KubeletConfiguration = &KubeletConfiguration{ + EvictionSoft: map[string]string{ + "memory.available": "110%", + }, + EvictionSoftGracePeriod: map[string]metav1.Duration{ + "memory.available": {Duration: time.Minute}, + }, + } + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + It("should fail on invalid quantity value in evictionSoft", func() { + provisioner.Spec.KubeletConfiguration = &KubeletConfiguration{ + EvictionSoft: map[string]string{ + "memory.available": "110GB", + }, + EvictionSoftGracePeriod: map[string]metav1.Duration{ + "memory.available": {Duration: time.Minute}, + }, + } + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + It("should fail when eviction soft doesn't have matching grace period", func() { + provisioner.Spec.KubeletConfiguration = &KubeletConfiguration{ + EvictionSoft: map[string]string{ + "memory.available": "200Mi", + }, + } + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + }) + Context("Eviction Soft Grace Period", func() { + It("should succeed on evictionSoftGracePeriod with valid keys", func() { + provisioner.Spec.KubeletConfiguration = &KubeletConfiguration{ + EvictionSoft: map[string]string{ + "memory.available": "5%", + "nodefs.available": "10%", + "nodefs.inodesFree": "15%", + "imagefs.available": "5%", + "imagefs.inodesFree": "5%", + "pid.available": "5%", + }, + EvictionSoftGracePeriod: map[string]metav1.Duration{ + "memory.available": {Duration: time.Minute}, + "nodefs.available": {Duration: time.Second * 90}, + "nodefs.inodesFree": {Duration: time.Minute * 5}, + "imagefs.available": {Duration: time.Hour}, + "imagefs.inodesFree": {Duration: time.Hour * 24}, + "pid.available": {Duration: time.Minute}, + }, + } + Expect(provisioner.Validate(ctx)).To(Succeed()) + }) + It("should fail on evictionSoftGracePeriod with invalid keys", func() { + provisioner.Spec.KubeletConfiguration = &KubeletConfiguration{ + EvictionSoftGracePeriod: map[string]metav1.Duration{ + "memory": {Duration: time.Minute}, + }, + } + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + It("should fail when eviction soft grace period doesn't have matching threshold", func() { + provisioner.Spec.KubeletConfiguration = &KubeletConfiguration{ + EvictionSoftGracePeriod: map[string]metav1.Duration{ + "memory.available": {Duration: time.Minute}, + }, + } + Expect(provisioner.Validate(ctx)).ToNot(Succeed()) + }) + }) + }) +}) diff --git a/pkg/apis/provisioning/v1alpha5/zz_generated.deepcopy.go b/pkg/apis/provisioning/v1alpha5/zz_generated.deepcopy.go new file mode 100644 index 000000000..469c02d99 --- /dev/null +++ b/pkg/apis/provisioning/v1alpha5/zz_generated.deepcopy.go @@ -0,0 +1,335 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 controller-gen. DO NOT EDIT. + +package v1alpha5 + +import ( + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "knative.dev/pkg/apis" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Consolidation) DeepCopyInto(out *Consolidation) { + *out = *in + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Consolidation. +func (in *Consolidation) DeepCopy() *Consolidation { + if in == nil { + return nil + } + out := new(Consolidation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) { + *out = *in + if in.ClusterDNS != nil { + in, out := &in.ClusterDNS, &out.ClusterDNS + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ContainerRuntime != nil { + in, out := &in.ContainerRuntime, &out.ContainerRuntime + *out = new(string) + **out = **in + } + if in.MaxPods != nil { + in, out := &in.MaxPods, &out.MaxPods + *out = new(int32) + **out = **in + } + if in.PodsPerCore != nil { + in, out := &in.PodsPerCore, &out.PodsPerCore + *out = new(int32) + **out = **in + } + if in.SystemReserved != nil { + in, out := &in.SystemReserved, &out.SystemReserved + *out = make(v1.ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + if in.KubeReserved != nil { + in, out := &in.KubeReserved, &out.KubeReserved + *out = make(v1.ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + if in.EvictionHard != nil { + in, out := &in.EvictionHard, &out.EvictionHard + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.EvictionSoft != nil { + in, out := &in.EvictionSoft, &out.EvictionSoft + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.EvictionSoftGracePeriod != nil { + in, out := &in.EvictionSoftGracePeriod, &out.EvictionSoftGracePeriod + *out = make(map[string]metav1.Duration, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.EvictionMaxPodGracePeriod != nil { + in, out := &in.EvictionMaxPodGracePeriod, &out.EvictionMaxPodGracePeriod + *out = new(int32) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeletConfiguration. +func (in *KubeletConfiguration) DeepCopy() *KubeletConfiguration { + if in == nil { + return nil + } + out := new(KubeletConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Limits) DeepCopyInto(out *Limits) { + *out = *in + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = make(v1.ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Limits. +func (in *Limits) DeepCopy() *Limits { + if in == nil { + return nil + } + out := new(Limits) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProviderRef) DeepCopyInto(out *ProviderRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderRef. +func (in *ProviderRef) DeepCopy() *ProviderRef { + if in == nil { + return nil + } + out := new(ProviderRef) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Provisioner) DeepCopyInto(out *Provisioner) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Provisioner. +func (in *Provisioner) DeepCopy() *Provisioner { + if in == nil { + return nil + } + out := new(Provisioner) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Provisioner) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProvisionerList) DeepCopyInto(out *ProvisionerList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Provisioner, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProvisionerList. +func (in *ProvisionerList) DeepCopy() *ProvisionerList { + if in == nil { + return nil + } + out := new(ProvisionerList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ProvisionerList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProvisionerSpec) DeepCopyInto(out *ProvisionerSpec) { + *out = *in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Taints != nil { + in, out := &in.Taints, &out.Taints + *out = make([]v1.Taint, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.StartupTaints != nil { + in, out := &in.StartupTaints, &out.StartupTaints + *out = make([]v1.Taint, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Requirements != nil { + in, out := &in.Requirements, &out.Requirements + *out = make([]v1.NodeSelectorRequirement, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.KubeletConfiguration != nil { + in, out := &in.KubeletConfiguration, &out.KubeletConfiguration + *out = new(KubeletConfiguration) + (*in).DeepCopyInto(*out) + } + if in.Provider != nil { + in, out := &in.Provider, &out.Provider + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.ProviderRef != nil { + in, out := &in.ProviderRef, &out.ProviderRef + *out = new(ProviderRef) + **out = **in + } + if in.TTLSecondsAfterEmpty != nil { + in, out := &in.TTLSecondsAfterEmpty, &out.TTLSecondsAfterEmpty + *out = new(int64) + **out = **in + } + if in.TTLSecondsUntilExpired != nil { + in, out := &in.TTLSecondsUntilExpired, &out.TTLSecondsUntilExpired + *out = new(int64) + **out = **in + } + if in.Limits != nil { + in, out := &in.Limits, &out.Limits + *out = new(Limits) + (*in).DeepCopyInto(*out) + } + if in.Weight != nil { + in, out := &in.Weight, &out.Weight + *out = new(int32) + **out = **in + } + if in.Consolidation != nil { + in, out := &in.Consolidation, &out.Consolidation + *out = new(Consolidation) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProvisionerSpec. +func (in *ProvisionerSpec) DeepCopy() *ProvisionerSpec { + if in == nil { + return nil + } + out := new(ProvisionerSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProvisionerStatus) DeepCopyInto(out *ProvisionerStatus) { + *out = *in + if in.LastScaleTime != nil { + in, out := &in.LastScaleTime, &out.LastScaleTime + *out = new(apis.VolatileTime) + (*in).DeepCopyInto(*out) + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make(apis.Conditions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = make(v1.ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProvisionerStatus. +func (in *ProvisionerStatus) DeepCopy() *ProvisionerStatus { + if in == nil { + return nil + } + out := new(ProvisionerStatus) + in.DeepCopyInto(out) + return out +}