From ac0ce04e258c6c96d2546e976129eb47dd00845b Mon Sep 17 00:00:00 2001 From: Jerad C Date: Fri, 1 Apr 2022 14:33:32 -0500 Subject: [PATCH 01/13] core functionality * Add SQS parameters to Terminator spec * Add drain parameters to Terminator spec * Fetch messages from SQS queue * Cordon and drain nodes targeted in SQS messages --- BUILD.md | 60 +- src/Makefile | 18 +- src/api/v1alpha1/terminator_logging.go | 59 + src/api/v1alpha1/terminator_types.go | 32 +- src/api/v1alpha1/terminator_validation.go | 90 + src/api/v1alpha1/zz_generated.deepcopy.go | 44 +- .../crds/node.k8s.aws_terminators.yaml | 56 - .../templates/clusterrole.yaml | 8 +- .../templates/configmap_logging.yaml | 26 +- .../templates/deployment.yaml | 12 + .../templates/node.k8s.aws_terminators.yaml | 209 ++ .../templates/service.yaml | 4 + .../values.yaml | 46 +- src/cmd/controller/main.go | 237 ++- src/controllers/suite_test.go | 80 - src/controllers/terminator_controller.go | 62 - src/go.mod | 47 +- src/go.sum | 199 +- src/pkg/event/asgterminate/v1/awsevent.go | 55 + src/pkg/event/asgterminate/v1/event.go | 58 + src/pkg/event/asgterminate/v1/parser.go | 72 + src/pkg/event/asgterminate/v1/types.go | 27 + src/pkg/event/asgterminate/v2/awsevent.go | 58 + src/pkg/event/asgterminate/v2/event.go | 58 + src/pkg/event/asgterminate/v2/parser.go | 72 + src/pkg/event/asgterminate/v2/types.go | 27 + src/pkg/event/metadata.go | 51 + src/pkg/event/noop.go | 39 + src/pkg/event/parser.go | 53 + src/pkg/event/parserfunc.go | 27 + .../rebalancerecommendation/v0/awsevent.go | 47 + .../event/rebalancerecommendation/v0/event.go | 39 + .../rebalancerecommendation/v0/parser.go | 53 + src/pkg/event/scheduledchange/v1/awsevent.go | 91 + src/pkg/event/scheduledchange/v1/event.go | 43 + src/pkg/event/scheduledchange/v1/parser.go | 71 + src/pkg/event/spotinterruption/v1/awsevent.go | 49 + src/pkg/event/spotinterruption/v1/event.go | 39 + src/pkg/event/spotinterruption/v1/parser.go | 53 + src/pkg/event/statechange/v1/awsevent.go | 49 + src/pkg/event/statechange/v1/event.go | 39 + src/pkg/event/statechange/v1/parser.go | 65 + src/pkg/event/types.go | 36 + src/pkg/logging/alias.go | 34 + src/pkg/logging/sqs/deletemessageinput.go | 45 + src/pkg/logging/sqs/message.go | 55 + src/pkg/logging/sqs/receivemessagesinput.go | 70 + src/pkg/logging/writer.go | 56 + src/pkg/node/cordondrain/kubectl/builder.go | 68 + .../node/cordondrain/kubectl/cordondrainer.go | 61 + src/pkg/node/cordondrain/kubectl/cordoner.go | 68 + src/pkg/node/cordondrain/kubectl/drainer.go | 67 + src/pkg/node/cordondrain/kubectl/types.go | 34 + src/pkg/node/cordondrain/types.go | 50 + src/pkg/node/getter.go | 63 + src/pkg/node/name/getter.go | 90 + src/pkg/sqsmessage/client.go | 79 + src/pkg/terminator/cordondrainbuilder.go | 49 + src/pkg/terminator/getter.go | 63 + src/pkg/terminator/parser.go | 44 + src/pkg/terminator/reconciler.go | 178 ++ src/pkg/terminator/sqsclient.go | 101 + src/resources/controller-iam-role.yaml | 23 + src/resources/eks-cluster.yaml.tmpl | 17 + src/scripts/docker-login-ecr.sh | 8 + src/test/app_integ_suite_test.go | 29 + src/test/asgclient.go | 33 + src/test/ec2client.go | 33 + src/test/kubeclient.go | 33 + src/test/reconciliation_test.go | 1690 +++++++++++++++++ src/test/sqsclient.go | 41 + 71 files changed, 5430 insertions(+), 312 deletions(-) create mode 100644 src/api/v1alpha1/terminator_logging.go create mode 100644 src/api/v1alpha1/terminator_validation.go delete mode 100644 src/charts/aws-node-termination-handler-2/crds/node.k8s.aws_terminators.yaml create mode 100644 src/charts/aws-node-termination-handler-2/templates/node.k8s.aws_terminators.yaml delete mode 100644 src/controllers/suite_test.go delete mode 100644 src/controllers/terminator_controller.go create mode 100644 src/pkg/event/asgterminate/v1/awsevent.go create mode 100644 src/pkg/event/asgterminate/v1/event.go create mode 100644 src/pkg/event/asgterminate/v1/parser.go create mode 100644 src/pkg/event/asgterminate/v1/types.go create mode 100644 src/pkg/event/asgterminate/v2/awsevent.go create mode 100644 src/pkg/event/asgterminate/v2/event.go create mode 100644 src/pkg/event/asgterminate/v2/parser.go create mode 100644 src/pkg/event/asgterminate/v2/types.go create mode 100644 src/pkg/event/metadata.go create mode 100644 src/pkg/event/noop.go create mode 100644 src/pkg/event/parser.go create mode 100644 src/pkg/event/parserfunc.go create mode 100644 src/pkg/event/rebalancerecommendation/v0/awsevent.go create mode 100644 src/pkg/event/rebalancerecommendation/v0/event.go create mode 100644 src/pkg/event/rebalancerecommendation/v0/parser.go create mode 100644 src/pkg/event/scheduledchange/v1/awsevent.go create mode 100644 src/pkg/event/scheduledchange/v1/event.go create mode 100644 src/pkg/event/scheduledchange/v1/parser.go create mode 100644 src/pkg/event/spotinterruption/v1/awsevent.go create mode 100644 src/pkg/event/spotinterruption/v1/event.go create mode 100644 src/pkg/event/spotinterruption/v1/parser.go create mode 100644 src/pkg/event/statechange/v1/awsevent.go create mode 100644 src/pkg/event/statechange/v1/event.go create mode 100644 src/pkg/event/statechange/v1/parser.go create mode 100644 src/pkg/event/types.go create mode 100644 src/pkg/logging/alias.go create mode 100644 src/pkg/logging/sqs/deletemessageinput.go create mode 100644 src/pkg/logging/sqs/message.go create mode 100644 src/pkg/logging/sqs/receivemessagesinput.go create mode 100644 src/pkg/logging/writer.go create mode 100644 src/pkg/node/cordondrain/kubectl/builder.go create mode 100644 src/pkg/node/cordondrain/kubectl/cordondrainer.go create mode 100644 src/pkg/node/cordondrain/kubectl/cordoner.go create mode 100644 src/pkg/node/cordondrain/kubectl/drainer.go create mode 100644 src/pkg/node/cordondrain/kubectl/types.go create mode 100644 src/pkg/node/cordondrain/types.go create mode 100644 src/pkg/node/getter.go create mode 100644 src/pkg/node/name/getter.go create mode 100644 src/pkg/sqsmessage/client.go create mode 100644 src/pkg/terminator/cordondrainbuilder.go create mode 100644 src/pkg/terminator/getter.go create mode 100644 src/pkg/terminator/parser.go create mode 100644 src/pkg/terminator/reconciler.go create mode 100644 src/pkg/terminator/sqsclient.go create mode 100644 src/resources/controller-iam-role.yaml create mode 100644 src/resources/eks-cluster.yaml.tmpl create mode 100755 src/scripts/docker-login-ecr.sh create mode 100644 src/test/app_integ_suite_test.go create mode 100644 src/test/asgclient.go create mode 100644 src/test/ec2client.go create mode 100644 src/test/kubeclient.go create mode 100644 src/test/reconciliation_test.go create mode 100644 src/test/sqsclient.go diff --git a/BUILD.md b/BUILD.md index c00ee1ef..55374f49 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,30 +1,78 @@ # Setup Development Environment -Clone the repo: +## Clone the repo ```sh git clone --branch v2 https://github.com/aws/aws-node-termination-handler.git +cd aws-node-termination-handler ``` -Install build tools +## Install build tools ```sh make toolchain ``` -Configure image repository location +## Set environment variables ```sh -export KO_DOCKER_REPO=my.image.repo/path +export AWS_REGION= +export AWS_ACCOUNT_ID= +export CLUSTER_NAME= ``` -Build and deploy controller to Kubernetes cluster +## Create Image Repositories + +```sh +aws ecr create-repository \ + --repository-name nthv2/controller \ + --image-scanning-configuration scanOnPush=true \ + --region "${AWS_REGION}" + +aws ecr create-repository \ + --repository-name nthv2/webhook \ + --image-scanning-configuration scanOnPush=true \ + --region "${AWS_REGION}" + +export KO_DOCKER_REPO="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/nthv2" + +./scripts/docker-login-ecr.sh +``` + +## Create an EKS Cluster + +```sh +envsubst src/resources/eks-cluster.yaml.tmpl | eksctl create cluster -f - +``` + +### Create the Controller IAM Role + +```sh +aws cloudformation deploy \ + --template-file src/resources/controller-iam-role.yaml \ + --stack-name "nthv2-${CLUSTER_NAME}" \ + --capabilities CAPABILITY_NAMED_IAM \ + --parameter-overrides "ClusterName=${CLUSTER_NAME}" + +eksctl create iamserviceaccount \ + --cluster "${CLUSTER_NAME}" \ + --name nthv2 \ + --namespace nthv2 \ + --role-name "${CLUSTER_NAME}-nthv2" \ + --attach-policy-arn "arn:aws:iam::${AWS_ACCOUNT_ID}:policy/Nthv2ControllerPolicy-${CLUSTER_NAME}" \ + --role-only \ + --approve + +export NTHV2_IAM_ROLE_ARN="arn:aws:iam::${AWS_ACCOUNT_ID}:role/${CLUSTER_NAME}-nthv2 +``` + +## Build and deploy controller to Kubernetes cluster ```sh make apply ``` -Remove deployed controller from Kubernetes cluster +## Remove deployed controller from Kubernetes cluster ```sh make delete diff --git a/src/Makefile b/src/Makefile index 4cd6808b..120c15f5 100644 --- a/src/Makefile +++ b/src/Makefile @@ -2,6 +2,8 @@ PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) CONTROLLER_GEN = $(PROJECT_DIR)/bin/controller-gen KO = $(PROJECT_DIR)/bin/ko ENVTEST = $(PROJECT_DIR)/bin/setup-envtest +HELM_BASE_OPTS ?= --set serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn=${NTHV2_IAM_ROLE_ARN} +GINKGO_BASE_OPTS ?= --coverpkg $(shell head -n 1 $(PROJECT_DIR)/go.mod | cut -s -d ' ' -f 2)/pkg/... # Image URL to use all building/pushing image targets @@ -45,27 +47,24 @@ toolchain: ## Download additional tools. ##@ Development -.PHONY: manifests -manifests: ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. - $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases - .PHONY: generate generate: ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." .PHONY: verify verify: ## Run go fmt and go vet against code. - go fmt ./... - go vet ./... + go fmt $(PROJECT_DIR)/... + go vet $(PROJECT_DIR)/... .PHONY: test -test: manifests generate fmt vet envtest ## Run tests. - KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -coverprofile cover.out +test: ## Run tests. + go vet $(PROJECT_DIR)/... + ginkgo run $(GINKGO_BASE_OPTS) $(GINKGO_OPTS) $(PROJECT_DIR)/test/ ##@ Build .PHONY: run -run: manifests generate fmt vet ## Run a controller from your host. +run: ## Run a controller from your host. go run ./main.go ##@ Deployment @@ -73,6 +72,7 @@ run: manifests generate fmt vet ## Run a controller from your host. .PHONY: apply apply: ## Deploy the controller into the current kubernetes cluster. helm upgrade --install dev charts/aws-node-termination-handler-2 --namespace nthv2 --create-namespace \ + $(HELM_BASE_OPTS) \ $(HELM_OPTS) \ --set controller.image=$(shell $(KO) publish -B github.com/aws/aws-node-termination-handler/cmd/controller) \ --set webhook.image=$(shell $(KO) publish -B github.com/aws/aws-node-termination-handler/cmd/webhook) \ diff --git a/src/api/v1alpha1/terminator_logging.go b/src/api/v1alpha1/terminator_logging.go new file mode 100644 index 00000000..c563a2b1 --- /dev/null +++ b/src/api/v1alpha1/terminator_logging.go @@ -0,0 +1,59 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "go.uber.org/zap/zapcore" +) + +func (t *TerminatorSpec) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddObject("sqs", t.Sqs) + enc.AddObject("drain", t.Drain) + return nil +} + +func (s SqsSpec) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddArray("attributeNames", zapcore.ArrayMarshalerFunc(func(enc zapcore.ArrayEncoder) error { + for _, attrName := range s.AttributeNames { + enc.AppendString(attrName) + } + return nil + })) + + enc.AddInt64("maxNumberOfMessages", s.MaxNumberOfMessages) + + enc.AddArray("messageAttributeNames", zapcore.ArrayMarshalerFunc(func(enc zapcore.ArrayEncoder) error { + for _, attrName := range s.MessageAttributeNames { + enc.AppendString(attrName) + } + return nil + })) + + enc.AddString("queueUrl", s.QueueUrl) + enc.AddInt64("visibilityTimeoutSeconds", s.VisibilityTimeoutSeconds) + enc.AddInt64("waitTimeSeconds", s.WaitTimeSeconds) + return nil +} + +func (d DrainSpec) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddBool("force", d.Force) + enc.AddInt("gracePeriodSeconds", d.GracePeriodSeconds) + enc.AddBool("ignoreAllDaemonSets", d.IgnoreAllDaemonSets) + enc.AddBool("deleteEmptyDirData", d.DeleteEmptyDirData) + enc.AddInt("timeoutSeconds", d.TimeoutSeconds) + return nil +} diff --git a/src/api/v1alpha1/terminator_types.go b/src/api/v1alpha1/terminator_types.go index 78b75851..40723fd9 100644 --- a/src/api/v1alpha1/terminator_types.go +++ b/src/api/v1alpha1/terminator_types.go @@ -20,7 +20,6 @@ import ( "context" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "knative.dev/pkg/apis" ) // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! @@ -31,8 +30,29 @@ type TerminatorSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file - // Foo is an example field of Terminator. Edit terminator_types.go to remove/update - Foo string `json:"foo,omitempty"` + Sqs SqsSpec `json:"sqs,omitempty"` + Drain DrainSpec `json:"drain,omitempty"` +} + +// SqsSpec defines inputs to SQS "receive messages" requests. +type SqsSpec struct { + // https://pkg.go.dev/github.com/aws/aws-sdk-go@v1.38.55/service/sqs#ReceiveMessageInput + AttributeNames []string `json:"attributeNames,omitempty"` + MaxNumberOfMessages int64 `json:"maxNumberOfMessages,omitempty"` + MessageAttributeNames []string `json:"messageAttributeNames,omitempty"` + QueueUrl string `json:"queueUrl,omitempty"` + VisibilityTimeoutSeconds int64 `json:"visibilityTimeoutSeconds,omitempty"` + WaitTimeSeconds int64 `json:"waitTimeSeconds,omitempty"` +} + +// DrainSpec defines inputs to the cordon and drain operations. +type DrainSpec struct { + // https://pkg.go.dev/k8s.io/kubectl@v0.21.4/pkg/drain#Helper + Force bool `json:"force,omitempty"` + GracePeriodSeconds int `json:"gracePeriodSeconds,omitempty"` + IgnoreAllDaemonSets bool `json:"ignoreAllDaemonSets,omitempty"` + DeleteEmptyDirData bool `json:"deleteEmptyDirData,omitempty"` + TimeoutSeconds int `json:"timeoutSeconds,omitempty"` } // TerminatorStatus defines the observed state of Terminator @@ -58,12 +78,6 @@ func (t *Terminator) SetDefaults(_ context.Context) { // TODO: actually set defaults } -func (t *Terminator) Validate(_ context.Context) *apis.FieldError { - // Stubbed to satisfy interface requirements. - // TODO: actually validate - return nil -} - // TerminatorList contains a list of Terminator //+kubebuilder:object:root=true type TerminatorList struct { diff --git a/src/api/v1alpha1/terminator_validation.go b/src/api/v1alpha1/terminator_validation.go new file mode 100644 index 00000000..2f79fd83 --- /dev/null +++ b/src/api/v1alpha1/terminator_validation.go @@ -0,0 +1,90 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "context" + "net/url" + "strings" + + "github.com/aws/aws-sdk-go/service/sqs" + + "k8s.io/apimachinery/pkg/util/sets" + + "knative.dev/pkg/apis" +) + +var ( + // https://github.com/aws/aws-sdk-go/blob/v1.38.55/service/sqs/api.go#L3966-L3994 + knownSqsAttributeNames = sets.NewString(sqs.MessageSystemAttributeName_Values()...) +) + +func (t *Terminator) Validate(_ context.Context) (errs *apis.FieldError) { + return errs.Also( + apis.ValidateObjectMetadata(t).ViaField("metadata"), + t.Spec.validate().ViaField("spec"), + ) +} + +func (t *TerminatorSpec) validate() (errs *apis.FieldError) { + return t.Sqs.validate().ViaField("sqs") +} + +func (s *SqsSpec) validate() (errs *apis.FieldError) { + for _, attrName := range s.AttributeNames { + if !knownSqsAttributeNames.Has(attrName) { + errs = errs.Also(apis.ErrInvalidValue(attrName, "attributeNames")) + } + } + + // https://github.com/aws/aws-sdk-go/blob/v1.38.55/service/sqs/api.go#L3996-L3999 + if s.MaxNumberOfMessages < 1 || 10 < s.MaxNumberOfMessages { + errs = errs.Also(apis.ErrInvalidValue(s.MaxNumberOfMessages, "maxNumberOfMessages", "must be in range 1-10")) + } + + // https://github.com/aws/aws-sdk-go/blob/v1.38.55/service/sqs/api.go#L4001-L4021 + // + // Simple checks are done below. More indepth checks are left to the SQS client/service. + for _, attrName := range s.MessageAttributeNames { + if len(attrName) > 256 { + errs = errs.Also(apis.ErrInvalidValue(attrName, "messageAttributeNames", "must be 256 characters or less")) + } + + lcAttrName := strings.ToLower(attrName) + if strings.HasPrefix(lcAttrName, "aws") || strings.HasPrefix(lcAttrName, "amazon") { + errs = errs.Also(apis.ErrInvalidValue(attrName, "messageAttributeNames", `must not use reserved prefixes "AWS" or "Amazon"`)) + } + + if strings.HasPrefix(attrName, ".") || strings.HasSuffix(attrName, ".") { + errs = errs.Also(apis.ErrInvalidValue(attrName, "messageAttributeNames", "must not begin or end with a period (.)")) + } + } + + if _, err := url.Parse(s.QueueUrl); err != nil { + errs = errs.Also(apis.ErrInvalidValue(s.QueueUrl, "queueUrl", "must be a valid URL")) + } + + if s.VisibilityTimeoutSeconds < 0 { + errs = errs.Also(apis.ErrInvalidValue(s.VisibilityTimeoutSeconds, "visibilityTimeoutSeconds", "must be zero or greater")) + } + + if s.WaitTimeSeconds < 0 { + errs = errs.Also(apis.ErrInvalidValue(s.WaitTimeSeconds, "waitTimeSeconds", "must be zero or greater")) + } + + return errs +} diff --git a/src/api/v1alpha1/zz_generated.deepcopy.go b/src/api/v1alpha1/zz_generated.deepcopy.go index 13e3bae8..20b76cc7 100644 --- a/src/api/v1alpha1/zz_generated.deepcopy.go +++ b/src/api/v1alpha1/zz_generated.deepcopy.go @@ -25,12 +25,52 @@ 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 *DrainSpec) DeepCopyInto(out *DrainSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DrainSpec. +func (in *DrainSpec) DeepCopy() *DrainSpec { + if in == nil { + return nil + } + out := new(DrainSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SqsSpec) DeepCopyInto(out *SqsSpec) { + *out = *in + if in.AttributeNames != nil { + in, out := &in.AttributeNames, &out.AttributeNames + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.MessageAttributeNames != nil { + in, out := &in.MessageAttributeNames, &out.MessageAttributeNames + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SqsSpec. +func (in *SqsSpec) DeepCopy() *SqsSpec { + if in == nil { + return nil + } + out := new(SqsSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Terminator) DeepCopyInto(out *Terminator) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -87,6 +127,8 @@ func (in *TerminatorList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TerminatorSpec) DeepCopyInto(out *TerminatorSpec) { *out = *in + in.Sqs.DeepCopyInto(&out.Sqs) + out.Drain = in.Drain } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TerminatorSpec. diff --git a/src/charts/aws-node-termination-handler-2/crds/node.k8s.aws_terminators.yaml b/src/charts/aws-node-termination-handler-2/crds/node.k8s.aws_terminators.yaml deleted file mode 100644 index 6f609776..00000000 --- a/src/charts/aws-node-termination-handler-2/crds/node.k8s.aws_terminators.yaml +++ /dev/null @@ -1,56 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: terminators.node.k8s.aws -spec: - group: node.k8s.aws - names: - kind: Terminator - listKind: TerminatorList - plural: terminators - singular: terminator - scope: Namespaced - versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - description: Terminator is the Schema for the terminators 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: TerminatorSpec defines the desired state of Terminator - properties: - foo: - description: Foo is an example field of Terminator. Edit terminator_types.go - to remove/update - type: string - type: object - status: - description: TerminatorStatus defines the observed state of Terminator - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/src/charts/aws-node-termination-handler-2/templates/clusterrole.yaml b/src/charts/aws-node-termination-handler-2/templates/clusterrole.yaml index fb5358c7..d84f4d8e 100644 --- a/src/charts/aws-node-termination-handler-2/templates/clusterrole.yaml +++ b/src/charts/aws-node-termination-handler-2/templates/clusterrole.yaml @@ -20,11 +20,15 @@ rules: - apiGroups: [""] resources: ["nodes"] - verbs: ["get", "list", "patch", "update"] + verbs: ["get", "list", "patch", "update", "watch"] - apiGroups: [""] resources: ["pods"] - verbs: ["get", "list"] + verbs: ["get", "list", "watch"] + + - apiGroups: [""] + resources: ["configmaps"] + verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["pods/eviction"] diff --git a/src/charts/aws-node-termination-handler-2/templates/configmap_logging.yaml b/src/charts/aws-node-termination-handler-2/templates/configmap_logging.yaml index 789b4993..8608e7db 100644 --- a/src/charts/aws-node-termination-handler-2/templates/configmap_logging.yaml +++ b/src/charts/aws-node-termination-handler-2/templates/configmap_logging.yaml @@ -10,31 +10,7 @@ metadata: {{- toYaml . | nindent 8 }} {{- end }} data: - # https://github.com/uber-go/zap/blob/aa3e73ec0896f8b066ddf668597a02f89628ee50/config.go - zap-logger-config: | - { - "level": "{{ .Values.logLevel }}", - "development": false, - "disableStacktrace": true, - "disableCaller": true, - "sampling": { - "initial": 100, - "thereafter": 100 - }, - "outputPaths": ["stdout"], - "errorOutputPaths": ["stderr"], - "encoding": "console", - "encoderConfig": { - "timeKey": "time", - "levelKey": "level", - "nameKey": "logger", - "callerKey": "caller", - "messageKey": "message", - "stacktraceKey": "stacktrace", - "levelEncoder": "capital", - "timeEncoder": "iso8601" - } - } + zap-logger-config: {{ toJson .Values.logging | quote }} {{- with .Values.controller.logLevel }} loglevel.controller: {{ . | quote }} {{- end }} diff --git a/src/charts/aws-node-termination-handler-2/templates/deployment.yaml b/src/charts/aws-node-termination-handler-2/templates/deployment.yaml index 680df31f..011ae430 100644 --- a/src/charts/aws-node-termination-handler-2/templates/deployment.yaml +++ b/src/charts/aws-node-termination-handler-2/templates/deployment.yaml @@ -69,6 +69,18 @@ spec: {{- toYaml . | nindent 22 }} {{- end }} env: + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace - name: SYSTEM_NAMESPACE valueFrom: fieldRef: diff --git a/src/charts/aws-node-termination-handler-2/templates/node.k8s.aws_terminators.yaml b/src/charts/aws-node-termination-handler-2/templates/node.k8s.aws_terminators.yaml new file mode 100644 index 00000000..cdaf7a73 --- /dev/null +++ b/src/charts/aws-node-termination-handler-2/templates/node.k8s.aws_terminators.yaml @@ -0,0 +1,209 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: terminators.node.k8s.aws +spec: + group: node.k8s.aws + names: + kind: Terminator + listKind: TerminatorList + plural: terminators + singular: terminator + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Terminator is the Schema for the terminators 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: TerminatorSpec defines the desired state of Terminator + type: object + properties: + sqs: + description: AWS SQS queue configuration. + type: object + properties: + attributeNames: + description: | + A list of attributes that need to be returned along with each message. These + attributes include: + + * All – Returns all values. + + * ApproximateFirstReceiveTimestamp – Returns the time the message was + first received from the queue (epoch time (http://en.wikipedia.org/wiki/Unix_time) + in milliseconds). + + * ApproximateReceiveCount – Returns the number of times a message has + been received across all queues but not deleted. + + * AWSTraceHeader – Returns the AWS X-Ray trace header string. + + * SenderId For an IAM user, returns the IAM user ID, for example ABCDEFGHI1JKLMNOPQ23R. + For an IAM role, returns the IAM role ID, for example ABCDE1F2GH3I4JK5LMNOP:i-a123b456. + + * SentTimestamp – Returns the time the message was sent to the queue + (epoch time (http://en.wikipedia.org/wiki/Unix_time) in milliseconds). + + * MessageDeduplicationId – Returns the value provided by the producer + that calls the SendMessage action. + + * MessageGroupId – Returns the value provided by the producer that calls + the SendMessage action. Messages with the same MessageGroupId are returned + in sequence. + + * SequenceNumber – Returns the value provided by Amazon SQS. + type: array + items: + type: string + {{- with .Values.terminator.defaults.sqs.attributeNames }} + default: + {{- toYaml . | nindent 22 }} + {{- end }} + maxNumberOfMessages: + description: | + The maximum number of messages to return. Amazon SQS never returns more messages + than this value (however, fewer messages might be returned). Valid values: + 1 to 10. + type: integer + format: int64 + {{- with .Values.terminator.defaults.sqs.maxNumberOfMessages }} + default: {{ . }} + {{- end }} + messageAttributeNames: + description: | + The name of the message attribute, where N is the index. + + * The name can contain alphanumeric characters and the underscore (_), + hyphen (-), and period (.). + + * The name is case-sensitive and must be unique among all attribute names + for the message. + + * The name must not start with AWS-reserved prefixes such as AWS. or Amazon. + (or any casing variants). + + * The name must not start or end with a period (.), and it should not + have periods in succession (..). + + * The name can be up to 256 characters long. + + When using ReceiveMessage, you can send a list of attribute names to receive, + or you can return all of the attributes by specifying All or .* in your request. + You can also use all message attributes starting with a prefix, for example + bar.*. + type: array + items: + type: string + {{- with .Values.terminator.defaults.sqs.messageAttributeNames }} + default: + {{- toYaml . | nindent 22 }} + {{- end }} + queueUrl: + description: | + The URL of the Amazon SQS queue from which messages are received. + + * Queue URLs and names are case-sensitive. + + * QueueUrl is a required field + type: string + visibilityTimeoutSeconds: + description: | + The duration (in seconds) that the received messages are hidden from subsequent + retrieve requests after being retrieved by a ReceiveMessage request. + type: integer + format: int64 + {{- with .Values.terminator.defaults.sqs.visibilityTimeoutSeconds }} + default: {{ . }} + {{- end }} + waitTimeSeconds: + description: | + The duration (in seconds) for which the call waits for a message to arrive + in the queue before returning. If a message is available, the call returns + sooner than WaitTimeSeconds. If no messages are available and the wait time + expires, the call returns successfully with an empty list of messages. + + To avoid HTTP errors, ensure that the HTTP response timeout for ReceiveMessage + requests is longer than the WaitTimeSeconds parameter. For example, with + the Java SDK, you can set HTTP transport settings using the NettyNioAsyncHttpClient + (https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClient.html) + for asynchronous clients, or the ApacheHttpClient (https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/apache/ApacheHttpClient.html) + for synchronous clients. + type: integer + format: int64 + {{- with .Values.terminator.defaults.sqs.waitTimeSeconds }} + default: {{ . }} + {{- end }} + drain: + description: TBD + type: object + properties: + force: + description: TBD + type: boolean + {{- with .Values.terminator.defaults.drain.force }} + default: {{ . }} + {{- end }} + gracePeriodSeconds: + description: | + Time to wait for a pod to terminate. + + * A value of 0 will cause pods to be deleted immediately. + + * A negative value will use the pod's terminationGracePeriodSeconds. + type: integer + {{- with .Values.terminator.defaults.drain.gracePeriodSeconds }} + default: {{ . }} + {{- end }} + ignoreAllDaemonSets: + description: Do not terminate pods managed by a daemon set. + type: boolean + {{- with .Values.terminator.defaults.drain.ignoreAllDaemonSets }} + default: {{ . }} + {{- end }} + deleteEmptyDirData: + description: Terminate pods using emptyDir. The local data will be deleted. + type: boolean + {{- with .Values.terminator.defaults.drain.deleteEmptyDirData }} + default: {{ . }} + {{- end }} + timeoutSeconds: + description: | + Time to wait for a "delete" command to complete. + + * A value of 0 means to wait indefinitly. + type: integer + {{- with .Values.terminator.defaults.drain.timeoutSeconds }} + default: {{ . }} + {{- end }} + status: + description: TerminatorStatus defines the observed state of Terminator + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/src/charts/aws-node-termination-handler-2/templates/service.yaml b/src/charts/aws-node-termination-handler-2/templates/service.yaml index 04e31b30..7089aa08 100644 --- a/src/charts/aws-node-termination-handler-2/templates/service.yaml +++ b/src/charts/aws-node-termination-handler-2/templates/service.yaml @@ -22,3 +22,7 @@ spec: port: 8081 protocol: TCP targetPort: http-probes + - name: https-webhook + port: 443 + protocol: TCP + targetPort: https-webhook diff --git a/src/charts/aws-node-termination-handler-2/values.yaml b/src/charts/aws-node-termination-handler-2/values.yaml index 7cf655a8..2d662f74 100644 --- a/src/charts/aws-node-termination-handler-2/values.yaml +++ b/src/charts/aws-node-termination-handler-2/values.yaml @@ -10,8 +10,50 @@ labels: {} # Annotations to add to Kubernetes objects created by Helm deployment. annotations: {} -# Global default log message level. -logLevel: info +terminator: + defaults: + sqs: + attributeNames: + - SentTimestamp + maxNumberOfMessages: 10 + messageAttributeNames: + - All + visibilityTimeoutSeconds: 20 + waitTimeSeconds: 20 + drain: + force: true + gracePeriodSeconds: -1 + ignoreAllDaemonSets: true + deleteEmptyDirData: true + timeoutSeconds: 120 + + + +# Global logging configuration. +# See https://github.com/uber-go/zap/blob/2314926ec34c23ee21f3dd4399438469668f8097/config.go#L58-L94 +# for descriptions of each option. +logging: + level: info + development: false + disableStacktrace: true + disableCaller: true + sampling: + initial: 100 + thereafter: 100 + outputPaths: + - stdout + errorOutputPaths: + - stderr + encoding: console + encoderConfig: + timeKey: time + levelKey: level + nameKey: logger + callerKey: caller + messageKey: message + stacktraceKey: stacktrace + levelEncoder: capital + timeEncoder: iso8601 pod: # Number of aws-node-termination-handler controller instance pods. diff --git a/src/cmd/controller/main.go b/src/cmd/controller/main.go index 8de87b18..0340dc39 100644 --- a/src/cmd/controller/main.go +++ b/src/cmd/controller/main.go @@ -17,36 +17,70 @@ limitations under the License. package main import ( + "context" "flag" + "fmt" + "time" // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. - _ "k8s.io/client-go/plugin/pkg/client/auth" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/kubernetes" clientgoscheme "k8s.io/client-go/kubernetes/scheme" + _ "k8s.io/client-go/plugin/pkg/client/auth" "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "knative.dev/pkg/configmap/informer" knativeinjection "knative.dev/pkg/injection" "knative.dev/pkg/injection/sharedmain" - "knative.dev/pkg/logging" + knativelogging "knative.dev/pkg/logging" "knative.dev/pkg/signals" "knative.dev/pkg/system" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/healthz" nodev1alpha1 "github.com/aws/aws-node-termination-handler/api/v1alpha1" - "github.com/aws/aws-node-termination-handler/controllers" + "github.com/aws/aws-node-termination-handler/pkg/event" + asgterminateeventv1 "github.com/aws/aws-node-termination-handler/pkg/event/asgterminate/v1" + asgterminateeventv2 "github.com/aws/aws-node-termination-handler/pkg/event/asgterminate/v2" + rebalancerecommendationeventv0 "github.com/aws/aws-node-termination-handler/pkg/event/rebalancerecommendation/v0" + scheduledchangeeventv1 "github.com/aws/aws-node-termination-handler/pkg/event/scheduledchange/v1" + spotinterruptioneventv1 "github.com/aws/aws-node-termination-handler/pkg/event/spotinterruption/v1" + statechangeeventv1 "github.com/aws/aws-node-termination-handler/pkg/event/statechange/v1" + "github.com/aws/aws-node-termination-handler/pkg/logging" + "github.com/aws/aws-node-termination-handler/pkg/node" + kubectlcordondrainer "github.com/aws/aws-node-termination-handler/pkg/node/cordondrain/kubectl" + nodename "github.com/aws/aws-node-termination-handler/pkg/node/name" + "github.com/aws/aws-node-termination-handler/pkg/sqsmessage" + "github.com/aws/aws-node-termination-handler/pkg/terminator" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/ec2metadata" + "github.com/aws/aws-sdk-go/aws/endpoints" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/autoscaling" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/sqs" + "github.com/go-logr/zapr" //+kubebuilder:scaffold:imports ) -const component = "controller" +const componentName = "controller" var scheme = runtime.NewScheme() +type Options struct { + MetricsAddr string + EnableLeaderElection bool + ProbeAddr string +} + func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) @@ -55,63 +89,190 @@ func init() { } func main() { - var metricsAddr string - var enableLeaderElection bool - var probeAddr string - flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") - flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") - flag.BoolVar(&enableLeaderElection, "leader-elect", false, - "Enable leader election for controller manager. "+ - "Enabling this will ensure there is only one active controller manager.") - flag.Parse() - + options := parseOptions() config := ctrl.GetConfigOrDie() config.UserAgent = "aws-node-termination-handler" ctx, startInformers := knativeinjection.EnableInjectionOrDie(signals.NewContext(), config) - rootLogger, atomicLevel := sharedmain.SetupLoggerOrDie(ctx, component) - ctx = logging.WithLogger(ctx, rootLogger) - ctrlLogger := rootLogger.With("component", component) - cmw := informer.NewInformedWatcher(kubernetes.NewForConfigOrDie(config), system.Namespace()) + logger, atomicLevel := sharedmain.SetupLoggerOrDie(ctx, componentName) + ctx = logging.WithLogger(ctx, logger) + clientSet := kubernetes.NewForConfigOrDie(config) + cmw := informer.NewInformedWatcher(clientSet, system.Namespace()) - ctrl.SetLogger(zapr.NewLogger(rootLogger.Desugar())) - rest.SetDefaultWarningHandler(&logging.WarningHandler{Logger: rootLogger}) - sharedmain.WatchLoggingConfigOrDie(ctx, cmw, rootLogger, atomicLevel, component) + ctrl.SetLogger(zapr.NewLogger(logger.Desugar())) + rest.SetDefaultWarningHandler(&knativelogging.WarningHandler{Logger: logger}) + sharedmain.WatchLoggingConfigOrDie(ctx, cmw, logger, atomicLevel, componentName) if err := cmw.Start(ctx.Done()); err != nil { - ctrlLogger.With("error", err).Fatal("failed to watch logging configuration") + logger.With("error", err).Fatal("failed to watch logging configuration") } startInformers() mgr, err := ctrl.NewManager(config, ctrl.Options{ Scheme: scheme, - MetricsBindAddress: metricsAddr, + MetricsBindAddress: options.MetricsAddr, Port: 9443, - HealthProbeBindAddress: probeAddr, - LeaderElection: enableLeaderElection, + HealthProbeBindAddress: options.ProbeAddr, + LeaderElection: options.EnableLeaderElection, LeaderElectionID: "aws-node-termination-handler.k8s.aws", Logger: zapr.NewLogger(logging.FromContext(ctx).Desugar()), }) if err != nil { - ctrlLogger.With("error", err).Fatal("unable to start manager") + logger.With("error", err).Fatal("failed to create manager") + } + if err = indexNodeName(ctx, mgr.GetFieldIndexer()); err != nil { + logger.With("error", err). + With("type", "Pod"). + With("field", "spec.nodeName"). + Fatal("failed to add field index") + } + kubeClient := mgr.GetClient() + + awsSession, err := newAwsSession() + if err != nil { + logger.With("error", err).Fatal("failed to initialize AWS session") + } + sqsClient := sqs.New(awsSession) + if sqsClient == nil { + logger.Fatal("failed to create SQS client") + } + asgClient := autoscaling.New(awsSession) + if asgClient == nil { + logger.Fatal("failed to create ASG client") + } + ec2Client := ec2.New(awsSession) + if ec2Client == nil { + logger.Fatal("failed to create EC2 client") + } + + nodeGetter, err := node.NewGetter(kubeClient) + if err != nil { + logger.With("error", err).Fatal("failed to create node getter") + } + nodeNameGetter, err := nodename.NewGetter(ec2Client) + if err != nil { + logger.With("error", err).Fatal("failed to create node name getter") + } + + asgTerminateEventV1Parser, err := asgterminateeventv1.NewParser(asgClient) + if err != nil { + logger.With("error", err).Fatal("failed to create ASG instance-terminate lifecycle event v1 parser") + } + asgTerminateEventV2Parser, err := asgterminateeventv2.NewParser(asgClient) + if err != nil { + logger.With("error", err).Fatal("failed to create ASG instance-terminate lifecycle event v2 parser") + } + sqsMessageParser, err := terminator.NewSqsMessageParser(event.NewParser( + asgTerminateEventV1Parser, + asgTerminateEventV2Parser, + rebalancerecommendationeventv0.NewParser(), + scheduledchangeeventv1.NewParser(), + spotinterruptioneventv1.NewParser(), + statechangeeventv1.NewParser(), + )) + if err != nil { + logger.With("error", err).Fatal("failed to create SQS message parser") + } + + terminatorGetter, err := terminator.NewGetter(kubeClient) + if err != nil { + logger.With("error", err).Fatal("failed to create terminator getter") } - if err = (&controllers.TerminatorReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - ctrlLogger.With("error", err).Fatalw("unable to create controller") + sqsMessageClient, err := sqsmessage.NewClient(sqsClient) + if err != nil { + logger.With("error", err).Fatal("failed to create SQS message client") + } + terminatorSqsClientBuilder, err := terminator.NewSqsClientBuilder(sqsMessageClient) + if err != nil { + logger.With("error", err).Fatal("failed to create terminator SQS message client builder") + } + + cordonDrainerBuilder, err := kubectlcordondrainer.NewBuilder( + clientSet, + kubectlcordondrainer.DefaultCordoner, + kubectlcordondrainer.DefaultDrainer, + ) + if err != nil { + logger.With("error", err).Fatal("failed to create kubectl cordon/drain client") + } + terminatorCordonDrainerBuilder, err := terminator.NewCordonDrainerBuilder(cordonDrainerBuilder) + if err != nil { + logger.With("error", err).Fatal("failed to create terminator cordon/drain client") + } + + rec := terminator.Reconciler{ + Name: "terminator", + RequeueInterval: time.Duration(10) * time.Second, + NodeGetter: nodeGetter, + NodeNameGetter: nodeNameGetter, + SqsClientBuilder: terminatorSqsClientBuilder, + SqsMessageParser: sqsMessageParser, + Getter: terminatorGetter, + CordonDrainerBuilder: terminatorCordonDrainerBuilder, + } + if err = rec.BuildController( + ctrl.NewControllerManagedBy(mgr). + WithOptions(controller.Options{MaxConcurrentReconciles: 10}), + ); err != nil { + logger.With("error", err). + With("name", rec.Name). + Fatal("failed to create controller") } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { - ctrlLogger.With("error", err).Fatal("unable to set up health check") + logger.With("error", err).Fatal("failed to set up health check") } if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { - ctrlLogger.With("error", err).Fatal("unable to set up ready check") + logger.With("error", err).Fatal("failed to set up ready check") } - ctrlLogger.Info("starting manager") - if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { - ctrlLogger.With("error", err).Fatal("problem running manager") + if err := mgr.Start(ctx); err != nil { + logger.With("error", err).Fatal("failure from manager") } } + +func parseOptions() Options { + options := Options{} + + flag.StringVar(&options.MetricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") + flag.StringVar(&options.ProbeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + flag.BoolVar(&options.EnableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. "+ + "Enabling this will ensure there is only one active controller manager.") + flag.Parse() + + return options +} + +func newAwsSession() (*session.Session, error) { + config := aws.NewConfig().WithSTSRegionalEndpoint(endpoints.RegionalSTSEndpoint) + sess, err := session.NewSessionWithOptions(session.Options{ + Config: *config, + SharedConfigState: session.SharedConfigEnable, + }) + if err != nil { + return nil, fmt.Errorf("failed to create AWS session: %w", err) + } + + region, err := ec2metadata.New(sess).Region() + if err != nil { + return nil, fmt.Errorf("failed to get AWS region: %w", err) + } + + sess.Config.Region = aws.String(region) + _, err = sess.Config.Credentials.Get() + if err != nil { + return nil, fmt.Errorf("failed to get AWS session credentials: %w", err) + } + + return sess, nil +} + +func indexNodeName(ctx context.Context, indexer client.FieldIndexer) error { + return indexer.IndexField(ctx, &v1.Pod{}, "spec.nodeName", func(o client.Object) []string { + if o == nil { + return nil + } + return []string{o.(*v1.Pod).Spec.NodeName} + }) +} diff --git a/src/controllers/suite_test.go b/src/controllers/suite_test.go deleted file mode 100644 index 487cf775..00000000 --- a/src/controllers/suite_test.go +++ /dev/null @@ -1,80 +0,0 @@ -/* -Copyright 2022. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package controllers - -import ( - "path/filepath" - "testing" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/envtest" - "sigs.k8s.io/controller-runtime/pkg/envtest/printer" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - - nodev1alpha1 "github.com/aws/aws-node-termination-handler/api/v1alpha1" - //+kubebuilder:scaffold:imports -) - -// These tests use Ginkgo (BDD-style Go testing framework). Refer to -// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. - -var cfg *rest.Config -var k8sClient client.Client -var testEnv *envtest.Environment - -func TestAPIs(t *testing.T) { - RegisterFailHandler(Fail) - - RunSpecsWithDefaultAndCustomReporters(t, - "Controller Suite", - []Reporter{printer.NewlineReporter{}}) -} - -var _ = BeforeSuite(func() { - logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) - - By("bootstrapping test environment") - testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, - ErrorIfCRDPathMissing: true, - } - - cfg, err := testEnv.Start() - Expect(err).NotTo(HaveOccurred()) - Expect(cfg).NotTo(BeNil()) - - err = nodev1alpha1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) - - //+kubebuilder:scaffold:scheme - - k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) - Expect(err).NotTo(HaveOccurred()) - Expect(k8sClient).NotTo(BeNil()) - -}, 60) - -var _ = AfterSuite(func() { - By("tearing down the test environment") - err := testEnv.Stop() - Expect(err).NotTo(HaveOccurred()) -}) diff --git a/src/controllers/terminator_controller.go b/src/controllers/terminator_controller.go deleted file mode 100644 index 724cc2b2..00000000 --- a/src/controllers/terminator_controller.go +++ /dev/null @@ -1,62 +0,0 @@ -/* -Copyright 2022. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package controllers - -import ( - "context" - - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" - - nodev1alpha1 "github.com/aws/aws-node-termination-handler/api/v1alpha1" -) - -// TerminatorReconciler reconciles a Terminator object -type TerminatorReconciler struct { - client.Client - Scheme *runtime.Scheme -} - -//+kubebuilder:rbac:groups=node.k8s.aws,resources=terminators,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=node.k8s.aws,resources=terminators/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=node.k8s.aws,resources=terminators/finalizers,verbs=update - -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the Terminator object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.11.0/pkg/reconcile -func (r *TerminatorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = log.FromContext(ctx) - - // TODO(user): your logic here - - return ctrl.Result{}, nil -} - -// SetupWithManager sets up the controller with the Manager. -func (r *TerminatorReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&nodev1alpha1.Terminator{}). - Complete(r) -} diff --git a/src/go.mod b/src/go.mod index 4c924631..3cfa9840 100644 --- a/src/go.mod +++ b/src/go.mod @@ -3,10 +3,13 @@ module github.com/aws/aws-node-termination-handler go 1.17 require ( + github.com/go-logr/zapr v0.4.0 github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.18.1 + k8s.io/api v0.21.4 k8s.io/apimachinery v0.21.4 k8s.io/client-go v0.21.4 + k8s.io/kubectl v0.21.4 knative.dev/pkg v0.0.0-20211120133512-d016976f2567 sigs.k8s.io/controller-runtime v0.9.7 ) @@ -15,12 +18,17 @@ require ( cloud.google.com/go v0.98.0 // indirect contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d // indirect contrib.go.opencensus.io/exporter/prometheus v0.4.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.18 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/aws/aws-sdk-go v1.38.55 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/blendle/zapdriver v1.3.1 // indirect @@ -29,47 +37,72 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect + github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect + github.com/go-errors/errors v1.0.1 // indirect github.com/go-kit/log v0.1.0 // indirect github.com/go-logfmt/logfmt v0.5.0 // indirect github.com/go-logr/logr v0.4.0 // indirect - github.com/go-logr/zapr v0.4.0 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.5 // indirect + github.com/go-openapi/spec v0.19.5 // indirect + github.com/go-openapi/swag v0.19.15 // indirect github.com/gobuffalo/flect v0.2.4 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/google/btree v1.0.1 // indirect github.com/google/go-cmp v0.5.6 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/gnostic v0.5.5 // indirect + github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/imdario/mergo v0.3.12 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kelseyhightower/envconfig v1.4.0 // indirect + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/mitchellh/go-wordwrap v1.0.0 // indirect + github.com/moby/spdystream v0.2.0 // indirect + github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/nxadm/tail v1.4.8 // indirect + github.com/onsi/ginkgo/v2 v2.1.3 // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.11.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.6.0 // indirect github.com/prometheus/statsd_exporter v0.21.0 // indirect + github.com/russross/blackfriday v1.5.2 // indirect + github.com/spf13/cobra v1.2.1 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/testify v1.7.0 // indirect + github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect go.opencensus.io v0.23.0 // indirect + go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/automaxprocs v1.4.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.19.1 // indirect - golang.org/x/crypto v0.0.0-20210920023735-84f357641f63 // indirect - golang.org/x/net v0.0.0-20211209124913-491a49abca63 // indirect + golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect + golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect - golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect + golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect @@ -82,13 +115,15 @@ require ( gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect - k8s.io/api v0.21.4 // indirect k8s.io/apiextensions-apiserver v0.21.4 // indirect + k8s.io/cli-runtime v0.21.4 // indirect k8s.io/component-base v0.21.4 // indirect k8s.io/klog v1.0.0 // indirect k8s.io/klog/v2 v2.10.0 // indirect k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect + sigs.k8s.io/kustomize/api v0.10.1 // indirect + sigs.k8s.io/kustomize/kyaml v0.13.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/src/go.sum b/src/go.sum index e88dbf8c..e7c8f978 100644 --- a/src/go.sum +++ b/src/go.sum @@ -53,6 +53,9 @@ contrib.go.opencensus.io/exporter/prometheus v0.4.0/go.mod h1:o7cosnyfuPVK0tB8q0 contrib.go.opencensus.io/exporter/zipkin v0.1.2/go.mod h1:mP5xM3rrgOjpn79MM8fZbj3gsxcuytSqtH0dxSWW1RE= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= @@ -72,26 +75,37 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= +github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/sarama v1.30.0/go.mod h1:zujlQQx1kzHsh4jfV1USnptCQrHAEZ2Hk8fTKCulPVs= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/cvHQkZ1fst0EmZnA5dFtiQdWCNCFYzb+uE2vqVgvx0= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/alecthomas/jsonschema v0.0.0-20180308105923-f2c93856175a/go.mod h1:qpebaTNSsyUn5rPSJMsfqEtDw71TTggXM6stUDI16HA= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aws/aws-sdk-go v1.38.55 h1:1Wv5CE1Zy0hJ6MJUQ1ekFiCsNKBK5W69+towYQ1P4Vs= +github.com/aws/aws-sdk-go v1.38.55/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -100,7 +114,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= @@ -111,10 +125,10 @@ github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9cop github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0 h1:t/LhUZLVitR1Ow2YOnduCsavhwFUklBMoGVYUCqmCqk= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -129,27 +143,34 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-gk v0.0.0-20140819190930-201884a44051/go.mod h1:qm+vckxRlDt0aOla0RYJJVeqHZlWfOm2UIxHaqPB46E= github.com/dgryski/go-gk v0.0.0-20200319235926-a69029f61654/go.mod h1:qm+vckxRlDt0aOla0RYJJVeqHZlWfOm2UIxHaqPB46E= github.com/dgryski/go-lttb v0.0.0-20180810165845-318fcdf10a77/go.mod h1:Va5MyIzkU0rAM92tn3hb3Anb7oz7KcnixF49+2wOMe4= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -157,6 +178,7 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= @@ -171,12 +193,16 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= @@ -187,8 +213,13 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -204,29 +235,64 @@ github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7 github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/zapr v0.4.0 h1:uc1uML3hRYL9/ZZPdgHS/n8Nzo+eaYL/Efxkkamf7OM= github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= -github.com/go-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk= -github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= +github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= +github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= +github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= +github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= +github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= +github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= +github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= +github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.19.5 h1:Xm0Ao53uqnk9QE/LlYV5DEU09UAgpliA85QoT9LzqPw= github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= +github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= +github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= +github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gobuffalo/flect v0.2.4 h1:BSYA8+T60cdyq+vynaSUjqSVI9mDEg9ZfQUXKmfjo4I= github.com/gobuffalo/flect v0.2.4/go.mod h1:1ZyCLIbg0YD7sDkzvFdPoOydPtD8y9JQnrOROolUcM8= +github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -271,6 +337,7 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac/go.mod h1:P32wAyui1PQ58Oce/KYkOqQv8cVw1zAapXOl+dRFGbc= github.com/gonum/diff v0.0.0-20181124234638-500114f11e71/go.mod h1:22dM4PLscQl+Nzf64qNBurVJvfyvZELT0iRW2l/NN70= github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82/go.mod h1:PxC8OnwL11+aosOB5+iEPoV3picfs8tUpkVd0pDo+Kg= @@ -282,6 +349,8 @@ github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9/go.mod h1:0EXg4mc1CNP github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b/go.mod h1:Z4GIJBJO3Wa4gD4vbwQxXXZ+WHmW6E9ixmNrwvs0iZs= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -320,8 +389,11 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -342,7 +414,9 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7 github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -382,6 +456,7 @@ github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/tdigest v0.0.0-20180711151920-a7d76c6f093a/go.mod h1:9GkyshztGufsdPQWjH+ifgnIr3xNUL5syI70g2dzU1o= github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= @@ -392,7 +467,11 @@ github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxy github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +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/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -415,6 +494,7 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -425,17 +505,27 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= @@ -445,12 +535,18 @@ github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceT github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= +github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 h1:yH0SvLzcbZxcJXho2yh7CqdENGMQe73Cw3woZBpPli0= +github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= 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= @@ -458,6 +554,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 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/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -469,6 +567,7 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -477,8 +576,9 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -486,13 +586,18 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.3.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= @@ -501,6 +606,7 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -540,9 +646,14 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqn github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -555,21 +666,30 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25/go.mod h1:lbP8tGiBjZ5YWIc2fzuRpTaz0b/53vT6PEs3QuAWzuU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -579,16 +699,22 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tsenart/go-tsz v0.0.0-20180814232043-cdeb9e1e981e/go.mod h1:SWZznP1z5Ki7hDT2ioqiFKEse8K9tU2OUvaRI0NeGQo= github.com/tsenart/vegeta/v12 v12.8.4/go.mod h1:ZiJtwLn/9M4fTPdMY7bdbIeyNeFVE8/AHbWFqCsUuho= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -599,6 +725,12 @@ go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -608,6 +740,8 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 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= @@ -622,23 +756,28 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/ go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= 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-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210920023735-84f357641f63 h1:kETrAMYZq6WVGPa8IIixL0CaEcIUNi+1WX7grUoi3y8= golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -680,6 +819,7 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -687,9 +827,11 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -728,8 +870,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211101193420-4a448f8816b3/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY= -golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -741,6 +883,7 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= @@ -770,6 +913,7 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -781,6 +925,7 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -816,6 +961,7 @@ golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -833,13 +979,14 @@ golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE= -golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -861,6 +1008,7 @@ golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -871,6 +1019,7 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -953,6 +1102,7 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= @@ -1042,6 +1192,7 @@ google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ6 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -1096,6 +1247,7 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= @@ -1107,6 +1259,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -1116,6 +1269,7 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1131,11 +1285,14 @@ k8s.io/apiextensions-apiserver v0.21.4/go.mod h1:OoC8LhI9LnV+wKjZkXIBbLUwtnOGJiT k8s.io/apimachinery v0.21.4 h1:KDq0lWZVslHkuE5I7iGAQHwpK0aDTlar1E7IWEc4CNw= k8s.io/apimachinery v0.21.4/go.mod h1:H/IM+5vH9kZRNJ4l3x/fXP/5bOPJaVP/guptnZPeCFI= k8s.io/apiserver v0.21.4/go.mod h1:SErUuFBBPZUcD2nsUU8hItxoYheqyYr2o/pCINEPW8g= +k8s.io/cli-runtime v0.21.4 h1:kvOzx6dKg+9wRuHTzSqo8tfTV6ixZCkmi+ag54s7mn8= +k8s.io/cli-runtime v0.21.4/go.mod h1:eRbLHYkdVWzvG87yrkgGd8CqX6/+fAG9DTdAqTXmlRY= k8s.io/client-go v0.21.4 h1:tcwj167If+v+pIGrCjaPG7hFo6SqFPFCCgMJy+Vm8Jc= k8s.io/client-go v0.21.4/go.mod h1:t0/eMKyUAq/DoQ7vW8NVVA00/nomlwC+eInsS8PxSew= k8s.io/code-generator v0.21.4/go.mod h1:K3y0Bv9Cz2cOW2vXUrNZlFbflhuPvuadW6JdnN6gGKo= k8s.io/component-base v0.21.4 h1:Bc0AttSyhJFVXEIHz+VX+D11j/5z7SPPhl6whiXaRzs= k8s.io/component-base v0.21.4/go.mod h1:ZKG0eHVX+tUDcaoIGpU3Vtk4TIjMddN9uhEWDmW6Nyg= +k8s.io/component-helpers v0.21.4/go.mod h1:/5TBNWmxaAymZweO1JWv3Pt5rcYJV1LbWWY0x1rDdVU= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= @@ -1147,11 +1304,13 @@ k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.10.0 h1:R2HDMDJsHVTHA2n4RjwbeYXdOcBymXdX/JRb1v0VGhE= k8s.io/klog/v2 v2.10.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/klog/v2 v2.40.1 h1:P4RRucWk/lFOlDdkAr3mc7iWFkgKrZY9qZMAgek06S4= -k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4= k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= +k8s.io/kubectl v0.21.4 h1:ODXpSKpi5C6XnJmGg96E/36KAry513v4Jr9Efg3ePJI= +k8s.io/kubectl v0.21.4/go.mod h1:rRYB5HeScoGQKxZDQmus17pTSVIuqfm0D31ApET/qSM= +k8s.io/metrics v0.21.4/go.mod h1:uhWoVuVumUMSeCa1B1p2tm4Y4XuZIg0n24QEtB54wuA= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20211116205334-6203023598ed h1:ck1fRPWPJWsMd8ZRFsWc6mh/zHp5fZ/shhbrgPUxDAE= @@ -1166,6 +1325,14 @@ rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/controller-runtime v0.9.7 h1:DlHMlAyLpgEITVvNsuZqMbf8/sJl9HirmCZIeR5H9mQ= sigs.k8s.io/controller-runtime v0.9.7/go.mod h1:nExcHcQ2zvLMeoO9K7rOesGCmgu32srN5SENvpAEbGA= +sigs.k8s.io/kustomize/api v0.8.8/go.mod h1:He1zoK0nk43Pc6NlV085xDXDXTNprtcyKZVm3swsdNY= +sigs.k8s.io/kustomize/api v0.10.1 h1:KgU7hfYoscuqag84kxtzKdEC3mKMb99DPI3a0eaV1d0= +sigs.k8s.io/kustomize/api v0.10.1/go.mod h1:2FigT1QN6xKdcnGS2Ppp1uIWrtWN28Ms8A3OZUZhwr8= +sigs.k8s.io/kustomize/cmd/config v0.9.10/go.mod h1:Mrby0WnRH7hA6OwOYnYpfpiY0WJIMgYrEDfwOeFdMK0= +sigs.k8s.io/kustomize/kustomize/v4 v4.1.2/go.mod h1:PxBvo4WGYlCLeRPL+ziT64wBXqbgfcalOS/SXa/tcyo= +sigs.k8s.io/kustomize/kyaml v0.10.17/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg= +sigs.k8s.io/kustomize/kyaml v0.13.0 h1:9c+ETyNfSrVhxvphs+K2dzT3dh5oVPPEqPOE/cUpScY= +sigs.k8s.io/kustomize/kyaml v0.13.0/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLCK30D47m4E= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= diff --git a/src/pkg/event/asgterminate/v1/awsevent.go b/src/pkg/event/asgterminate/v1/awsevent.go new file mode 100644 index 00000000..6ca917ca --- /dev/null +++ b/src/pkg/event/asgterminate/v1/awsevent.go @@ -0,0 +1,55 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "github.com/aws/aws-node-termination-handler/pkg/event" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// AwsEvent contains the properties defined in AWS EventBridge schema +// aws.autoscaling@EC2InstanceTerminateLifecycleAction v1. +type AwsEvent struct { + event.AwsMetadata + + Detail Ec2InstanceTerminateLifecycleActionDetail `json:"detail"` +} + +func (e AwsEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(e.AwsMetadata).AddTo(enc) + enc.AddObject("detail", e.Detail) + return nil +} + +type Ec2InstanceTerminateLifecycleActionDetail struct { + LifecycleHookName string `json:"LifecycleHookName"` + LifecycleTransition string `json:"LifecycleTransition"` + AutoScalingGroupName string `json:"AutoScalingGroupName"` + Ec2InstanceId string `json:"EC2InstanceId"` + LifecycleActionToken string `json:"LifecycleActionToken"` +} + +func (e Ec2InstanceTerminateLifecycleActionDetail) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddString("LifecycleHookName", e.LifecycleHookName) + enc.AddString("LifecycleTransition", e.LifecycleTransition) + enc.AddString("AutoScalingGroupName", e.AutoScalingGroupName) + enc.AddString("EC2InstanceId", e.Ec2InstanceId) + enc.AddString("LifecycleActionToken", e.LifecycleActionToken) + return nil +} diff --git a/src/pkg/event/asgterminate/v1/event.go b/src/pkg/event/asgterminate/v1/event.go new file mode 100644 index 00000000..65e5d375 --- /dev/null +++ b/src/pkg/event/asgterminate/v1/event.go @@ -0,0 +1,58 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "context" + "errors" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/autoscaling" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type Ec2InstanceTerminateLifecycleAction struct { + AsgLifecycleActionCompleter + AwsEvent +} + +func (e Ec2InstanceTerminateLifecycleAction) Ec2InstanceIds() []string { + return []string{e.Detail.Ec2InstanceId} +} + +func (e Ec2InstanceTerminateLifecycleAction) Done(ctx context.Context) (bool, error) { + if _, err := e.CompleteLifecycleActionWithContext(ctx, &autoscaling.CompleteLifecycleActionInput{ + AutoScalingGroupName: aws.String(e.Detail.AutoScalingGroupName), + LifecycleActionResult: aws.String("CONTINUE"), + LifecycleHookName: aws.String(e.Detail.LifecycleHookName), + LifecycleActionToken: aws.String(e.Detail.LifecycleActionToken), + InstanceId: aws.String(e.Detail.Ec2InstanceId), + }); err != nil { + var f awserr.RequestFailure + return errors.As(err, &f) && f.StatusCode() != 400, err + } + + return false, nil +} + +func (e Ec2InstanceTerminateLifecycleAction) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(e.AwsEvent).AddTo(enc) + return nil +} diff --git a/src/pkg/event/asgterminate/v1/parser.go b/src/pkg/event/asgterminate/v1/parser.go new file mode 100644 index 00000000..175e99a7 --- /dev/null +++ b/src/pkg/event/asgterminate/v1/parser.go @@ -0,0 +1,72 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/aws/aws-node-termination-handler/pkg/event" + "github.com/aws/aws-node-termination-handler/pkg/logging" +) + +const ( + source = "aws.autoscaling" + detailType = "EC2 Instance-terminate Lifecycle Action" + version = "1" + acceptedTransition = "autoscaling:EC2_INSTANCE_TERMINATING" +) + +type parser struct { + AsgLifecycleActionCompleter +} + +func NewParser(completer AsgLifecycleActionCompleter) (event.Parser, error) { + if completer == nil { + return nil, fmt.Errorf("argument 'completer' is nil") + } + return parser{AsgLifecycleActionCompleter: completer}, nil +} + +func (p parser) Parse(ctx context.Context, str string) event.Event { + ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("asgTerminateLifecycleAction.v1")) + + evt := Ec2InstanceTerminateLifecycleAction{ + AsgLifecycleActionCompleter: p.AsgLifecycleActionCompleter, + } + if err := json.Unmarshal([]byte(str), &evt); err != nil { + logging.FromContext(ctx). + With("error", err). + Error("failed to unmarshal EC2 instance-terminate lifecycle action v1 event") + return nil + } + + if evt.Source != source || evt.DetailType != detailType || evt.Version != version { + return nil + } + + if evt.Detail.LifecycleTransition != acceptedTransition { + logging.FromContext(ctx). + With("awsEvent", evt). + With("acceptedTransitions", []string{acceptedTransition}). + Warn("ignorning EC2 instance-terminate lifecycle action event") + return nil + } + + return evt +} diff --git a/src/pkg/event/asgterminate/v1/types.go b/src/pkg/event/asgterminate/v1/types.go new file mode 100644 index 00000000..3d7e30c0 --- /dev/null +++ b/src/pkg/event/asgterminate/v1/types.go @@ -0,0 +1,27 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/service/autoscaling" +) + +type AsgLifecycleActionCompleter interface { + CompleteLifecycleActionWithContext(aws.Context, *autoscaling.CompleteLifecycleActionInput, ...request.Option) (*autoscaling.CompleteLifecycleActionOutput, error) +} diff --git a/src/pkg/event/asgterminate/v2/awsevent.go b/src/pkg/event/asgterminate/v2/awsevent.go new file mode 100644 index 00000000..230c817b --- /dev/null +++ b/src/pkg/event/asgterminate/v2/awsevent.go @@ -0,0 +1,58 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "github.com/aws/aws-node-termination-handler/pkg/event" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// AwsEvent contains the properties defined in AWS EventBridge schema +// aws.autoscaling@EC2InstanceTerminateLifecycleAction v2. +type AwsEvent struct { + event.AwsMetadata + + Detail Ec2InstanceTerminateLifecycleActionDetail `json:"detail"` +} + +func (e AwsEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(e.AwsMetadata).AddTo(enc) + enc.AddObject("detail", e.Detail) + return nil +} + +type Ec2InstanceTerminateLifecycleActionDetail struct { + LifecycleHookName string `json:"LifecycleHookName"` + LifecycleTransition string `json:"LifecycleTransition"` + AutoScalingGroupName string `json:"AutoScalingGroupName"` + Ec2InstanceId string `json:"EC2InstanceId"` + LifecycleActionToken string `json:"LifecycleActionToken"` + NotificationMetadata string `json:"NotificationMetadata"` + Origin string `json:"Origin"` + Destination string `json:"Destination"` +} + +func (e Ec2InstanceTerminateLifecycleActionDetail) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddString("LifecycleHookName", e.LifecycleHookName) + enc.AddString("LifecycleTransition", e.LifecycleTransition) + enc.AddString("AutoScalingGroupName", e.AutoScalingGroupName) + enc.AddString("EC2InstanceId", e.Ec2InstanceId) + enc.AddString("LifecycleActionToken", e.LifecycleActionToken) + return nil +} diff --git a/src/pkg/event/asgterminate/v2/event.go b/src/pkg/event/asgterminate/v2/event.go new file mode 100644 index 00000000..c68670e7 --- /dev/null +++ b/src/pkg/event/asgterminate/v2/event.go @@ -0,0 +1,58 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "context" + "errors" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/autoscaling" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type Ec2InstanceTerminateLifecycleAction struct { + AsgLifecycleActionCompleter + AwsEvent +} + +func (e Ec2InstanceTerminateLifecycleAction) Ec2InstanceIds() []string { + return []string{e.Detail.Ec2InstanceId} +} + +func (e Ec2InstanceTerminateLifecycleAction) Done(ctx context.Context) (bool, error) { + if _, err := e.CompleteLifecycleActionWithContext(ctx, &autoscaling.CompleteLifecycleActionInput{ + AutoScalingGroupName: aws.String(e.Detail.AutoScalingGroupName), + LifecycleActionResult: aws.String("CONTINUE"), + LifecycleHookName: aws.String(e.Detail.LifecycleHookName), + LifecycleActionToken: aws.String(e.Detail.LifecycleActionToken), + InstanceId: aws.String(e.Detail.Ec2InstanceId), + }); err != nil { + var f awserr.RequestFailure + return errors.As(err, &f) && f.StatusCode() != 400, err + } + + return false, nil +} + +func (e Ec2InstanceTerminateLifecycleAction) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(e.AwsEvent).AddTo(enc) + return nil +} diff --git a/src/pkg/event/asgterminate/v2/parser.go b/src/pkg/event/asgterminate/v2/parser.go new file mode 100644 index 00000000..ae2a1715 --- /dev/null +++ b/src/pkg/event/asgterminate/v2/parser.go @@ -0,0 +1,72 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/aws/aws-node-termination-handler/pkg/event" + "github.com/aws/aws-node-termination-handler/pkg/logging" +) + +const ( + source = "aws.autoscaling" + detailType = "EC2 Instance-terminate Lifecycle Action" + version = "2" + acceptedTransition = "autoscaling:EC2_INSTANCE_TERMINATING" +) + +type parser struct { + AsgLifecycleActionCompleter +} + +func NewParser(completer AsgLifecycleActionCompleter) (event.Parser, error) { + if completer == nil { + return nil, fmt.Errorf("argument 'completer' is nil") + } + return parser{AsgLifecycleActionCompleter: completer}, nil +} + +func (p parser) Parse(ctx context.Context, str string) event.Event { + ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("asgTerminateLifecycleAction.v2")) + + evt := Ec2InstanceTerminateLifecycleAction{ + AsgLifecycleActionCompleter: p.AsgLifecycleActionCompleter, + } + if err := json.Unmarshal([]byte(str), &evt); err != nil { + logging.FromContext(ctx). + With("error", err). + Error("failed to unmarshal EC2 instance-terminate lifecycle action v2 event") + return nil + } + + if evt.Source != source || evt.DetailType != detailType || evt.Version != version { + return nil + } + + if evt.Detail.LifecycleTransition != acceptedTransition { + logging.FromContext(ctx). + With("awsEvent", evt). + With("acceptedTransitions", []string{acceptedTransition}). + Warn("ignorning EC2 instance-terminate lifecycle action event") + return nil + } + + return evt +} diff --git a/src/pkg/event/asgterminate/v2/types.go b/src/pkg/event/asgterminate/v2/types.go new file mode 100644 index 00000000..645838e9 --- /dev/null +++ b/src/pkg/event/asgterminate/v2/types.go @@ -0,0 +1,27 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/service/autoscaling" +) + +type AsgLifecycleActionCompleter interface { + CompleteLifecycleActionWithContext(aws.Context, *autoscaling.CompleteLifecycleActionInput, ...request.Option) (*autoscaling.CompleteLifecycleActionOutput, error) +} diff --git a/src/pkg/event/metadata.go b/src/pkg/event/metadata.go new file mode 100644 index 00000000..16fbf95a --- /dev/null +++ b/src/pkg/event/metadata.go @@ -0,0 +1,51 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package event + +import ( + "time" + + "go.uber.org/zap/zapcore" +) + +type AwsMetadata struct { + Account string `json:"account"` + DetailType string `json:"detail-type"` + Id string `json:"id"` + Region string `json:"region"` + Resources []string `json:"resources"` + Source string `json:"source"` + Time time.Time `json:"time"` + Version string `json:"version"` +} + +func (e AwsMetadata) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddString("source", e.Source) + enc.AddString("detail-type", e.DetailType) + enc.AddString("id", e.Id) + enc.AddTime("time", e.Time) + enc.AddString("region", e.Region) + enc.AddArray("resources", zapcore.ArrayMarshalerFunc(func(enc zapcore.ArrayEncoder) error { + for _, resource := range e.Resources { + enc.AppendString(resource) + } + return nil + })) + enc.AddString("version", e.Version) + enc.AddString("account", e.Account) + return nil +} diff --git a/src/pkg/event/noop.go b/src/pkg/event/noop.go new file mode 100644 index 00000000..353675c1 --- /dev/null +++ b/src/pkg/event/noop.go @@ -0,0 +1,39 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package event + +import ( + "context" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type noop AwsMetadata + +func (n noop) Ec2InstanceIds() []string { + return []string{} +} + +func (n noop) Done(_ context.Context) (bool, error) { + return false, nil +} + +func (n noop) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(AwsMetadata(n)).AddTo(enc) + return nil +} diff --git a/src/pkg/event/parser.go b/src/pkg/event/parser.go new file mode 100644 index 00000000..a7b19c12 --- /dev/null +++ b/src/pkg/event/parser.go @@ -0,0 +1,53 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package event + +import ( + "context" + "encoding/json" + + "github.com/aws/aws-node-termination-handler/pkg/logging" +) + +type parser []Parser + +func NewParser(parsers ...Parser) Parser { + return parser(parsers) +} + +func (p parser) Parse(ctx context.Context, str string) Event { + ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("event.parser")) + + if str == "" { + return noop{} + } + + for _, parser := range p { + if a := parser.Parse(ctx, str); a != nil { + return a + } + } + + md := AwsMetadata{} + if err := json.Unmarshal([]byte(str), &md); err != nil { + logging.FromContext(ctx). + With("error", err). + Error("failed to parse SQS message metadata") + return noop{} + } + return noop(md) +} diff --git a/src/pkg/event/parserfunc.go b/src/pkg/event/parserfunc.go new file mode 100644 index 00000000..254ae1b5 --- /dev/null +++ b/src/pkg/event/parserfunc.go @@ -0,0 +1,27 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package event + +import ( + "context" +) + +type ParserFunc func(context.Context, string) Event + +func (pf ParserFunc) Parse(ctx context.Context, str string) Event { + return pf(ctx, str) +} diff --git a/src/pkg/event/rebalancerecommendation/v0/awsevent.go b/src/pkg/event/rebalancerecommendation/v0/awsevent.go new file mode 100644 index 00000000..9760f9f4 --- /dev/null +++ b/src/pkg/event/rebalancerecommendation/v0/awsevent.go @@ -0,0 +1,47 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v0 + +import ( + "github.com/aws/aws-node-termination-handler/pkg/event" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// AwsEvent contains the properties defined by +// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/rebalance-recommendations.html#monitor-rebalance-recommendations +type AwsEvent struct { + event.AwsMetadata + + Detail Ec2InstanceRebalanceRecommendationDetail `json:"detail"` +} + +func (e AwsEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(e.AwsMetadata).AddTo(enc) + enc.AddObject("detail", e.Detail) + return nil +} + +type Ec2InstanceRebalanceRecommendationDetail struct { + InstanceId string `json:"instance-id"` +} + +func (e Ec2InstanceRebalanceRecommendationDetail) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddString("instance-id", e.InstanceId) + return nil +} diff --git a/src/pkg/event/rebalancerecommendation/v0/event.go b/src/pkg/event/rebalancerecommendation/v0/event.go new file mode 100644 index 00000000..ea41d301 --- /dev/null +++ b/src/pkg/event/rebalancerecommendation/v0/event.go @@ -0,0 +1,39 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v0 + +import ( + "context" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type Ec2InstanceRebalanceRecommendation AwsEvent + +func (e Ec2InstanceRebalanceRecommendation) Ec2InstanceIds() []string { + return []string{e.Detail.InstanceId} +} + +func (e Ec2InstanceRebalanceRecommendation) Done(_ context.Context) (bool, error) { + return false, nil +} + +func (e Ec2InstanceRebalanceRecommendation) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(AwsEvent(e)).AddTo(enc) + return nil +} diff --git a/src/pkg/event/rebalancerecommendation/v0/parser.go b/src/pkg/event/rebalancerecommendation/v0/parser.go new file mode 100644 index 00000000..c82c78e8 --- /dev/null +++ b/src/pkg/event/rebalancerecommendation/v0/parser.go @@ -0,0 +1,53 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v0 + +import ( + "context" + "encoding/json" + + "github.com/aws/aws-node-termination-handler/pkg/event" + "github.com/aws/aws-node-termination-handler/pkg/logging" +) + +const ( + source = "aws.ec2" + detailType = "EC2 Instance Rebalance Recommendation" + version = "0" +) + +func NewParser() event.Parser { + return event.ParserFunc(parse) +} + +func parse(ctx context.Context, str string) event.Event { + ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("rebalanceRecommendation.v0")) + + evt := Ec2InstanceRebalanceRecommendation{} + if err := json.Unmarshal([]byte(str), &evt); err != nil { + logging.FromContext(ctx). + With("error", err). + Error("failed to unmarshal EC2 instance rebalance recommendation event") + return nil + } + + if evt.Source != source || evt.DetailType != detailType || evt.Version != version { + return nil + } + + return evt +} diff --git a/src/pkg/event/scheduledchange/v1/awsevent.go b/src/pkg/event/scheduledchange/v1/awsevent.go new file mode 100644 index 00000000..28f8a466 --- /dev/null +++ b/src/pkg/event/scheduledchange/v1/awsevent.go @@ -0,0 +1,91 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "github.com/aws/aws-node-termination-handler/pkg/event" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// AwsEvent contains the properties defined in AWS EventBridge schema +// aws.health@AWSHealthEvent v1. +type AwsEvent struct { + event.AwsMetadata + + Detail AwsHealthEventDetail `json:"detail"` +} + +func (e AwsEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(e.AwsMetadata).AddTo(enc) + enc.AddObject("detail", e.Detail) + return nil +} + +type AwsHealthEventDetail struct { + EventArn string `json:"eventArn"` + EventTypeCode string `json:"eventTypeCode"` + Service string `json:"service"` + EventDescription []EventDescription `json:"eventDescription"` + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` + EventTypeCategory string `json:"eventTypeCategory"` + AffectedEntities []AffectedEntity `json:"affectedEntities"` +} + +func (e AwsHealthEventDetail) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddString("eventArn", e.EventArn) + enc.AddString("eventTypeCode", e.EventTypeCode) + enc.AddString("eventTypeCategory", e.EventTypeCategory) + enc.AddString("service", e.Service) + enc.AddString("startTime", e.StartTime) + enc.AddString("endTime", e.EndTime) + enc.AddArray("eventDescription", zapcore.ArrayMarshalerFunc(func(enc zapcore.ArrayEncoder) error { + for _, desc := range e.EventDescription { + enc.AppendObject(desc) + } + return nil + })) + enc.AddArray("affectedEntities", zapcore.ArrayMarshalerFunc(func(enc zapcore.ArrayEncoder) error { + for _, entity := range e.AffectedEntities { + enc.AppendObject(entity) + } + return nil + })) + return nil +} + +type EventDescription struct { + LatestDescription string `json:"latestDescription"` + Language string `json:"language"` +} + +func (e EventDescription) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddString("latestDescription", e.LatestDescription) + enc.AddString("language", e.Language) + return nil +} + +type AffectedEntity struct { + EntityValue string `json:"entityValue"` +} + +func (e AffectedEntity) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddString("entityValue", e.EntityValue) + return nil +} diff --git a/src/pkg/event/scheduledchange/v1/event.go b/src/pkg/event/scheduledchange/v1/event.go new file mode 100644 index 00000000..1aa110da --- /dev/null +++ b/src/pkg/event/scheduledchange/v1/event.go @@ -0,0 +1,43 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "context" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type AwsHealthEvent AwsEvent + +func (e AwsHealthEvent) Ec2InstanceIds() []string { + ids := make([]string, len(e.Detail.AffectedEntities)) + for i, entity := range e.Detail.AffectedEntities { + ids[i] = entity.EntityValue + } + return ids +} + +func (e AwsHealthEvent) Done(_ context.Context) (bool, error) { + return false, nil +} + +func (e AwsHealthEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(AwsEvent(e)).AddTo(enc) + return nil +} diff --git a/src/pkg/event/scheduledchange/v1/parser.go b/src/pkg/event/scheduledchange/v1/parser.go new file mode 100644 index 00000000..dec051ee --- /dev/null +++ b/src/pkg/event/scheduledchange/v1/parser.go @@ -0,0 +1,71 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "context" + "encoding/json" + + "github.com/aws/aws-node-termination-handler/pkg/event" + "github.com/aws/aws-node-termination-handler/pkg/logging" +) + +const ( + source = "aws.health" + detailType = "AWS Health Event" + version = "1" + acceptedService = "EC2" + acceptedEventTypeCategory = "scheduledChange" +) + +func NewParser() event.Parser { + return event.ParserFunc(parse) +} + +func parse(ctx context.Context, str string) event.Event { + ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("scheduledChange.v1")) + + evt := AwsHealthEvent{} + if err := json.Unmarshal([]byte(str), &evt); err != nil { + logging.FromContext(ctx). + With("error", err). + Error("failed to unmarshal AWS health event") + return nil + } + + if evt.Source != source || evt.DetailType != detailType || evt.Version != version { + return nil + } + + if evt.Detail.Service != acceptedService { + logging.FromContext(ctx). + With("eventDetails", evt). + With("acceptedService", acceptedService). + Warn("ignoring AWS health event") + return nil + } + + if evt.Detail.EventTypeCategory != acceptedEventTypeCategory { + logging.FromContext(ctx). + With("eventDetails", evt). + With("acceptedEventTypeCategory", acceptedEventTypeCategory). + Warn("ignoring AWS health event") + return nil + } + + return evt +} diff --git a/src/pkg/event/spotinterruption/v1/awsevent.go b/src/pkg/event/spotinterruption/v1/awsevent.go new file mode 100644 index 00000000..f8755369 --- /dev/null +++ b/src/pkg/event/spotinterruption/v1/awsevent.go @@ -0,0 +1,49 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "github.com/aws/aws-node-termination-handler/pkg/event" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// AwsEvent contains the properties defined in AWS EventBridge schema +// aws.ec2@EC2SpotInstanceInterruptionWarning v1. +type AwsEvent struct { + event.AwsMetadata + + Detail Ec2SpotInstanceInterruptionWarningDetail `json:"detail"` +} + +func (e AwsEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(e.AwsMetadata).AddTo(enc) + enc.AddObject("detail", e.Detail) + return nil +} + +type Ec2SpotInstanceInterruptionWarningDetail struct { + InstanceId string `json:"instance-id"` + InstanceAction string `json:"instance-action"` +} + +func (e Ec2SpotInstanceInterruptionWarningDetail) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddString("instance-id", e.InstanceId) + enc.AddString("instance-action", e.InstanceAction) + return nil +} diff --git a/src/pkg/event/spotinterruption/v1/event.go b/src/pkg/event/spotinterruption/v1/event.go new file mode 100644 index 00000000..511ee047 --- /dev/null +++ b/src/pkg/event/spotinterruption/v1/event.go @@ -0,0 +1,39 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "context" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type Ec2SpotInstanceInterruptionWarning AwsEvent + +func (e Ec2SpotInstanceInterruptionWarning) Ec2InstanceIds() []string { + return []string{e.Detail.InstanceId} +} + +func (e Ec2SpotInstanceInterruptionWarning) Done(_ context.Context) (bool, error) { + return false, nil +} + +func (e Ec2SpotInstanceInterruptionWarning) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(AwsEvent(e)).AddTo(enc) + return nil +} diff --git a/src/pkg/event/spotinterruption/v1/parser.go b/src/pkg/event/spotinterruption/v1/parser.go new file mode 100644 index 00000000..94e82b4a --- /dev/null +++ b/src/pkg/event/spotinterruption/v1/parser.go @@ -0,0 +1,53 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "context" + "encoding/json" + + "github.com/aws/aws-node-termination-handler/pkg/event" + "github.com/aws/aws-node-termination-handler/pkg/logging" +) + +const ( + source = "aws.ec2" + detailType = "EC2 Spot Instance Interruption Warning" + version = "1" +) + +func NewParser() event.Parser { + return event.ParserFunc(parse) +} + +func parse(ctx context.Context, str string) event.Event { + ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("spotInterruption.v1")) + + evt := Ec2SpotInstanceInterruptionWarning{} + if err := json.Unmarshal([]byte(str), &evt); err != nil { + logging.FromContext(ctx). + With("error", err). + Error("failed to unmarshal EC2 spot instance interruption event") + return nil + } + + if evt.Source != source || evt.DetailType != detailType || evt.Version != version { + return nil + } + + return evt +} diff --git a/src/pkg/event/statechange/v1/awsevent.go b/src/pkg/event/statechange/v1/awsevent.go new file mode 100644 index 00000000..20c14439 --- /dev/null +++ b/src/pkg/event/statechange/v1/awsevent.go @@ -0,0 +1,49 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "github.com/aws/aws-node-termination-handler/pkg/event" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// AwsEvent contains the properties defined in AWS EventBridge schema +// aws.ec2@EC2InstanceStateChangeNotification v1. +type AwsEvent struct { + event.AwsMetadata + + Detail Ec2InstanceStateChangeNotificationDetail `json:"detail"` +} + +func (e AwsEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(e.AwsMetadata).AddTo(enc) + enc.AddObject("detail", e.Detail) + return nil +} + +type Ec2InstanceStateChangeNotificationDetail struct { + InstanceId string `json:"instance-id"` + State string `json:"state"` +} + +func (e Ec2InstanceStateChangeNotificationDetail) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddString("instance-id", e.InstanceId) + enc.AddString("state", e.State) + return nil +} diff --git a/src/pkg/event/statechange/v1/event.go b/src/pkg/event/statechange/v1/event.go new file mode 100644 index 00000000..6769ceae --- /dev/null +++ b/src/pkg/event/statechange/v1/event.go @@ -0,0 +1,39 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "context" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type Ec2InstanceStateChangeNotification AwsEvent + +func (e Ec2InstanceStateChangeNotification) Ec2InstanceIds() []string { + return []string{e.Detail.InstanceId} +} + +func (e Ec2InstanceStateChangeNotification) Done(_ context.Context) (bool, error) { + return false, nil +} + +func (e Ec2InstanceStateChangeNotification) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(AwsEvent(e)).AddTo(enc) + return nil +} diff --git a/src/pkg/event/statechange/v1/parser.go b/src/pkg/event/statechange/v1/parser.go new file mode 100644 index 00000000..4b5edc30 --- /dev/null +++ b/src/pkg/event/statechange/v1/parser.go @@ -0,0 +1,65 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "context" + "encoding/json" + "strings" + + "github.com/aws/aws-node-termination-handler/pkg/event" + "github.com/aws/aws-node-termination-handler/pkg/logging" +) + +const ( + source = "aws.ec2" + detailType = "EC2 Instance State-change Notification" + version = "1" + acceptedStates = "stopping,stopped,shutting-down,terminated" +) + +var acceptedStatesList = strings.Split(acceptedStates, ",") + +func NewParser() event.Parser { + return event.ParserFunc(parse) +} + +func parse(ctx context.Context, str string) event.Event { + ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("stateChange.v1")) + + evt := Ec2InstanceStateChangeNotification{} + if err := json.Unmarshal([]byte(str), &evt); err != nil { + logging.FromContext(ctx). + With("error", err). + Error("failed to unmarshal EC2 state-change event") + return nil + } + + if evt.Source != source || evt.DetailType != detailType || evt.Version != version { + return nil + } + + if !strings.Contains(acceptedStates, strings.ToLower(evt.Detail.State)) { + logging.FromContext(ctx). + With("eventDetails", evt). + With("acceptedStates", acceptedStatesList). + Warn("ignorning EC2 state-change notification") + return nil + } + + return evt +} diff --git a/src/pkg/event/types.go b/src/pkg/event/types.go new file mode 100644 index 00000000..a700fc55 --- /dev/null +++ b/src/pkg/event/types.go @@ -0,0 +1,36 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package event + +import ( + "context" + + "go.uber.org/zap/zapcore" +) + +type ( + Event interface { + zapcore.ObjectMarshaler + + Done(context.Context) (tryAgain bool, err error) + Ec2InstanceIds() []string + } + + Parser interface { + Parse(context.Context, string) Event + } +) diff --git a/src/pkg/logging/alias.go b/src/pkg/logging/alias.go new file mode 100644 index 00000000..0c74b08a --- /dev/null +++ b/src/pkg/logging/alias.go @@ -0,0 +1,34 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package logging + +import ( + "github.com/aws/aws-node-termination-handler/pkg/logging/sqs" + + knativelogging "knative.dev/pkg/logging" +) + +var ( + NewDeleteMessageInputMarshaler = sqs.NewDeleteMessageInputMarshaler + NewMessageMarshaler = sqs.NewMessageMarshaler + NewReceiveMessageInputMarshaler = sqs.NewReceiveMessageInputMarshaler + + // Alias these so the codebase doesn't have to import two separate packages + // for logging. + WithLogger = knativelogging.WithLogger + FromContext = knativelogging.FromContext +) diff --git a/src/pkg/logging/sqs/deletemessageinput.go b/src/pkg/logging/sqs/deletemessageinput.go new file mode 100644 index 00000000..9cc71193 --- /dev/null +++ b/src/pkg/logging/sqs/deletemessageinput.go @@ -0,0 +1,45 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package sqs + +import ( + awssqs "github.com/aws/aws-sdk-go/service/sqs" + + "go.uber.org/zap/zapcore" +) + +type deleteMessageInputMarshaler struct { + *awssqs.DeleteMessageInput +} + +func NewDeleteMessageInputMarshaler(input *awssqs.DeleteMessageInput) zapcore.ObjectMarshaler { + return deleteMessageInputMarshaler{DeleteMessageInput: input} +} + +func (d deleteMessageInputMarshaler) MarshalLogObject(enc zapcore.ObjectEncoder) error { + if d.DeleteMessageInput == nil { + return nil + } + + if d.QueueUrl != nil { + enc.AddString("queueUrl", *d.QueueUrl) + } + if d.ReceiptHandle != nil { + enc.AddString("receiptHandle", *d.ReceiptHandle) + } + return nil +} diff --git a/src/pkg/logging/sqs/message.go b/src/pkg/logging/sqs/message.go new file mode 100644 index 00000000..1934f6ed --- /dev/null +++ b/src/pkg/logging/sqs/message.go @@ -0,0 +1,55 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package sqs + +import ( + awssqs "github.com/aws/aws-sdk-go/service/sqs" + + "go.uber.org/zap/zapcore" +) + +type messageMarshaler struct { + *awssqs.Message +} + +func NewMessageMarshaler(msg *awssqs.Message) zapcore.ObjectMarshaler { + return messageMarshaler{Message: msg} +} + +func (s messageMarshaler) MarshalLogObject(enc zapcore.ObjectEncoder) error { + if s.Message == nil { + return nil + } + + if s.MessageId != nil { + enc.AddString("messageId", *s.MessageId) + } + enc.AddObject("attributes", zapcore.ObjectMarshalerFunc(func(enc zapcore.ObjectEncoder) error { + for key, value := range s.Attributes { + enc.AddString(key, *value) + } + return nil + })) + enc.AddObject("messageAttributes", zapcore.ObjectMarshalerFunc(func(enc zapcore.ObjectEncoder) error { + for key, value := range s.MessageAttributes { + enc.AddString(key, *value.StringValue) + } + return nil + })) + + return nil +} diff --git a/src/pkg/logging/sqs/receivemessagesinput.go b/src/pkg/logging/sqs/receivemessagesinput.go new file mode 100644 index 00000000..24a417a6 --- /dev/null +++ b/src/pkg/logging/sqs/receivemessagesinput.go @@ -0,0 +1,70 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package sqs + +import ( + awssqs "github.com/aws/aws-sdk-go/service/sqs" + + "go.uber.org/zap/zapcore" +) + +type receiveMessageInputMarshaler struct { + *awssqs.ReceiveMessageInput +} + +func NewReceiveMessageInputMarshaler(input *awssqs.ReceiveMessageInput) zapcore.ObjectMarshaler { + return receiveMessageInputMarshaler{ReceiveMessageInput: input} +} + +func (r receiveMessageInputMarshaler) MarshalLogObject(enc zapcore.ObjectEncoder) error { + if r.ReceiveMessageInput == nil { + return nil + } + + if r.QueueUrl != nil { + enc.AddString("queueUrl", *r.QueueUrl) + } + if r.ReceiveRequestAttemptId != nil { + enc.AddString("receiveAttemptId", *r.ReceiveRequestAttemptId) + } + if r.VisibilityTimeout != nil { + enc.AddInt64("visibilityTimeout", *r.VisibilityTimeout) + } + if r.WaitTimeSeconds != nil { + enc.AddInt64("waitTimeSeconds", *r.WaitTimeSeconds) + } + if r.MaxNumberOfMessages != nil { + enc.AddInt64("maxNumberOfMessages", *r.MaxNumberOfMessages) + } + enc.AddArray("attributeNames", zapcore.ArrayMarshalerFunc(func(enc zapcore.ArrayEncoder) error { + for _, attr := range r.AttributeNames { + if attr != nil { + enc.AppendString(*attr) + } + } + return nil + })) + enc.AddArray("messageAttributeNames", zapcore.ArrayMarshalerFunc(func(enc zapcore.ArrayEncoder) error { + for _, attr := range r.MessageAttributeNames { + if attr != nil { + enc.AppendString(*attr) + } + } + return nil + })) + return nil +} diff --git a/src/pkg/logging/writer.go b/src/pkg/logging/writer.go new file mode 100644 index 00000000..d7321aa7 --- /dev/null +++ b/src/pkg/logging/writer.go @@ -0,0 +1,56 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package logging + +import ( + "io" + "strings" + + "go.uber.org/zap" +) + +// loggerWriter adapts a logger to an `io.Writer`. +type loggerWriter struct { + *zap.SugaredLogger +} + +func NewWriter(logger *zap.SugaredLogger) io.Writer { + return loggerWriter{SugaredLogger: logger} +} + +// Write converts `buf` to a string and sends it to the underlying logger. +// If the string beings with "warn" or "error" (case in-sensitive) the message +// will be logged at the corresponding level; otherwise the level will be +// "info". +func (w loggerWriter) Write(buf []byte) (int, error) { + if w.SugaredLogger == nil { + return len(buf), nil + } + + msg := string(buf) + m := strings.ToLower(msg) + switch { + case strings.HasPrefix(m, "error"): + w.Error(msg) + case strings.HasPrefix(m, "warn"): + w.Warn(msg) + default: + w.Info(msg) + } + + return len(buf), nil +} diff --git a/src/pkg/node/cordondrain/kubectl/builder.go b/src/pkg/node/cordondrain/kubectl/builder.go new file mode 100644 index 00000000..47950e21 --- /dev/null +++ b/src/pkg/node/cordondrain/kubectl/builder.go @@ -0,0 +1,68 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kubectl + +import ( + "fmt" + "time" + + "github.com/aws/aws-node-termination-handler/pkg/node/cordondrain" + "go.uber.org/multierr" + + "k8s.io/client-go/kubernetes" + "k8s.io/kubectl/pkg/drain" +) + +type builder struct { + kubernetes.Interface + Cordoner + Drainer +} + +func NewBuilder(kubeClient kubernetes.Interface, cordoner Cordoner, drainer Drainer) (cordondrain.Builder, error) { + var err error + if kubeClient == nil { + err = multierr.Append(err, fmt.Errorf("argument 'kubeClient' is nil")) + } + if cordoner == nil { + err = multierr.Append(err, fmt.Errorf("argument 'cordoner' is nil")) + } + if drainer == nil { + err = multierr.Append(err, fmt.Errorf("argument 'drainer' is nil")) + } + if err != nil { + return nil, err + } + + return builder{ + Cordoner: cordoner, + Drainer: drainer, + Interface: kubeClient, + }, nil +} + +func (c builder) Build(config cordondrain.Config) (cordondrain.CordonDrainer, error) { + helper := drain.Helper{ + Client: c, + Force: config.Force, + GracePeriodSeconds: config.GracePeriodSeconds, + IgnoreAllDaemonSets: config.IgnoreAllDaemonSets, + DeleteEmptyDirData: config.DeleteEmptyDirData, + Timeout: time.Duration(config.TimeoutSeconds) * time.Second, + } + return NewCordonDrainer(helper, c.Cordoner, c.Drainer) +} diff --git a/src/pkg/node/cordondrain/kubectl/cordondrainer.go b/src/pkg/node/cordondrain/kubectl/cordondrainer.go new file mode 100644 index 00000000..4e397953 --- /dev/null +++ b/src/pkg/node/cordondrain/kubectl/cordondrainer.go @@ -0,0 +1,61 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kubectl + +import ( + "context" + "fmt" + + "github.com/aws/aws-node-termination-handler/pkg/node/cordondrain" + "go.uber.org/multierr" + + v1 "k8s.io/api/core/v1" + "k8s.io/kubectl/pkg/drain" +) + +type cordonDrainer struct { + Cordoner + Drainer + drain.Helper +} + +func NewCordonDrainer(helper drain.Helper, cordoner Cordoner, drainer Drainer) (cordondrain.CordonDrainer, error) { + var err error + if cordoner == nil { + err = multierr.Append(err, fmt.Errorf("argument 'cordoner' is nil")) + } + if drainer == nil { + err = multierr.Append(err, fmt.Errorf("arguemnt 'drainer' is nil")) + } + if err != nil { + return nil, err + } + + return cordonDrainer{ + Cordoner: cordoner, + Drainer: drainer, + Helper: helper, + }, nil +} + +func (c cordonDrainer) Cordon(ctx context.Context, node *v1.Node) error { + return c.Cordoner.Cordon(ctx, node, c.Helper) +} + +func (c cordonDrainer) Drain(ctx context.Context, node *v1.Node) error { + return c.Drainer.Drain(ctx, node, c.Helper) +} diff --git a/src/pkg/node/cordondrain/kubectl/cordoner.go b/src/pkg/node/cordondrain/kubectl/cordoner.go new file mode 100644 index 00000000..5f589841 --- /dev/null +++ b/src/pkg/node/cordondrain/kubectl/cordoner.go @@ -0,0 +1,68 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kubectl + +import ( + "context" + "fmt" + + "github.com/aws/aws-node-termination-handler/pkg/logging" + + v1 "k8s.io/api/core/v1" + "k8s.io/kubectl/pkg/drain" +) + +var DefaultCordoner = cordoner(drain.RunCordonOrUncordon) + +type ( + RunCordonFunc = func(*drain.Helper, *v1.Node, bool) error + + cordoner RunCordonFunc +) + +func NewCordoner(cordonFunc RunCordonFunc) (Cordoner, error) { + if cordonFunc == nil { + return nil, fmt.Errorf("argument 'cordonFunc' is nil") + } + return cordoner(cordonFunc), nil +} + +func (c cordoner) Cordon(ctx context.Context, node *v1.Node, helper drain.Helper) error { + ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("cordon")) + + if node == nil { + err := fmt.Errorf("argument 'node' is nil") + logging.FromContext(ctx). + With("error", err). + Error("failed to cordon node") + return err + } + + helper.Ctx = ctx + helper.Out = logging.NewWriter(logging.FromContext(ctx)) + helper.ErrOut = helper.Out + + const updateNodeUnschedulable = true + if err := c(&helper, node, updateNodeUnschedulable); err != nil { + logging.FromContext(ctx). + With("error", err). + Error("failed to cordon node") + return err + } + + return nil +} diff --git a/src/pkg/node/cordondrain/kubectl/drainer.go b/src/pkg/node/cordondrain/kubectl/drainer.go new file mode 100644 index 00000000..c0262994 --- /dev/null +++ b/src/pkg/node/cordondrain/kubectl/drainer.go @@ -0,0 +1,67 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kubectl + +import ( + "context" + "fmt" + + "github.com/aws/aws-node-termination-handler/pkg/logging" + + v1 "k8s.io/api/core/v1" + "k8s.io/kubectl/pkg/drain" +) + +var DefaultDrainer = drainer(drain.RunNodeDrain) + +type ( + RunDrainFunc = func(*drain.Helper, string) error + + drainer RunDrainFunc +) + +func NewDrainer(drainFunc RunDrainFunc) (Drainer, error) { + if drainFunc == nil { + return nil, fmt.Errorf("argument 'drainFunc' is nil") + } + return drainer(drainFunc), nil +} + +func (d drainer) Drain(ctx context.Context, node *v1.Node, helper drain.Helper) error { + ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("drain")) + + if node == nil { + err := fmt.Errorf("argument 'node' is nil") + logging.FromContext(ctx). + With("error", err). + Error("failed to drain node") + return err + } + + helper.Ctx = ctx + helper.Out = logging.NewWriter(logging.FromContext(ctx)) + helper.ErrOut = helper.Out + + if err := d(&helper, node.Name); err != nil { + logging.FromContext(ctx). + With("error", err). + Error("failed to drain node") + return err + } + + return nil +} diff --git a/src/pkg/node/cordondrain/kubectl/types.go b/src/pkg/node/cordondrain/kubectl/types.go new file mode 100644 index 00000000..86dcfecf --- /dev/null +++ b/src/pkg/node/cordondrain/kubectl/types.go @@ -0,0 +1,34 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kubectl + +import ( + "context" + + v1 "k8s.io/api/core/v1" + "k8s.io/kubectl/pkg/drain" +) + +type ( + Cordoner interface { + Cordon(context.Context, *v1.Node, drain.Helper) error + } + + Drainer interface { + Drain(context.Context, *v1.Node, drain.Helper) error + } +) diff --git a/src/pkg/node/cordondrain/types.go b/src/pkg/node/cordondrain/types.go new file mode 100644 index 00000000..32931564 --- /dev/null +++ b/src/pkg/node/cordondrain/types.go @@ -0,0 +1,50 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cordondrain + +import ( + "context" + + v1 "k8s.io/api/core/v1" +) + +type ( + Config struct { + Force bool + GracePeriodSeconds int + IgnoreAllDaemonSets bool + DeleteEmptyDirData bool + TimeoutSeconds int + } + + Builder interface { + Build(Config) (CordonDrainer, error) + } + + Cordoner interface { + Cordon(context.Context, *v1.Node) error + } + + Drainer interface { + Drain(context.Context, *v1.Node) error + } + + CordonDrainer interface { + Cordoner + Drainer + } +) diff --git a/src/pkg/node/getter.go b/src/pkg/node/getter.go new file mode 100644 index 00000000..33db4f20 --- /dev/null +++ b/src/pkg/node/getter.go @@ -0,0 +1,63 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package node + +import ( + "context" + "fmt" + + "github.com/aws/aws-node-termination-handler/pkg/logging" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type ( + Getter interface { + GetNode(context.Context, string) (*v1.Node, error) + } + + KubeGetter interface { + Get(context.Context, client.ObjectKey, client.Object) error + } + + getter struct { + KubeGetter + } +) + +func NewGetter(kubeGetter KubeGetter) (Getter, error) { + if kubeGetter == nil { + return nil, fmt.Errorf("argument 'kubeGetter' is nil") + } + return getter{KubeGetter: kubeGetter}, nil +} + +func (n getter) GetNode(ctx context.Context, nodeName string) (*v1.Node, error) { + ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("node")) + + node := &v1.Node{} + if err := n.Get(ctx, types.NamespacedName{Name: nodeName}, node); err != nil { + logging.FromContext(ctx). + With("error", err). + Error("failed to retrieve node") + return nil, err + } + + return node, nil +} diff --git a/src/pkg/node/name/getter.go b/src/pkg/node/name/getter.go new file mode 100644 index 00000000..c37ef15d --- /dev/null +++ b/src/pkg/node/name/getter.go @@ -0,0 +1,90 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package name + +import ( + "context" + "fmt" + + "github.com/aws/aws-node-termination-handler/pkg/logging" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/service/ec2" +) + +type ( + Ec2InstancesDescriber interface { + DescribeInstancesWithContext(aws.Context, *ec2.DescribeInstancesInput, ...request.Option) (*ec2.DescribeInstancesOutput, error) + } + + Getter interface { + GetNodeName(context.Context, string) (string, error) + } + + getter struct { + Ec2InstancesDescriber + } +) + +func NewGetter(describer Ec2InstancesDescriber) (Getter, error) { + if describer == nil { + return nil, fmt.Errorf("argument 'describer' is nil") + } + return getter{Ec2InstancesDescriber: describer}, nil +} + +func (n getter) GetNodeName(ctx context.Context, instanceId string) (string, error) { + ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("nodeName")) + + result, err := n.DescribeInstancesWithContext(ctx, &ec2.DescribeInstancesInput{ + InstanceIds: []*string{aws.String(instanceId)}, + }) + if err != nil { + logging.FromContext(ctx). + With("error", err). + Error("DescribeInstances API call failed") + return "", err + } + + if len(result.Reservations) == 0 || len(result.Reservations[0].Instances) == 0 { + err = fmt.Errorf("no EC2 reservation for instance ID %s", instanceId) + logging.FromContext(ctx). + With("error", err). + Error("EC2 instance not found") + return "", err + } + + if result.Reservations[0].Instances[0].PrivateDnsName == nil { + err = fmt.Errorf("no PrivateDnsName for instance %s", instanceId) + logging.FromContext(ctx). + With("error", err). + Error("EC2 instance has no PrivateDnsName") + return "", err + } + + nodeName := *result.Reservations[0].Instances[0].PrivateDnsName + if nodeName == "" { + err = fmt.Errorf("empty PrivateDnsName for instance %s", instanceId) + logging.FromContext(ctx). + With("error", err). + Error("EC2 instance's PrivateDnsName is empty") + return "", err + } + + return nodeName, nil +} diff --git a/src/pkg/sqsmessage/client.go b/src/pkg/sqsmessage/client.go new file mode 100644 index 00000000..3ef305cd --- /dev/null +++ b/src/pkg/sqsmessage/client.go @@ -0,0 +1,79 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package sqsmessage + +import ( + "context" + "fmt" + + "github.com/aws/aws-node-termination-handler/pkg/logging" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/service/sqs" +) + +type ( + SqsClient interface { + ReceiveMessageWithContext(aws.Context, *sqs.ReceiveMessageInput, ...request.Option) (*sqs.ReceiveMessageOutput, error) + DeleteMessageWithContext(aws.Context, *sqs.DeleteMessageInput, ...request.Option) (*sqs.DeleteMessageOutput, error) + } + + Client interface { + GetSqsMessages(context.Context, *sqs.ReceiveMessageInput) ([]*sqs.Message, error) + DeleteSqsMessage(context.Context, *sqs.DeleteMessageInput) error + } + + sqsClient struct { + SqsClient + } +) + +func NewClient(client SqsClient) (Client, error) { + if client == nil { + return nil, fmt.Errorf("argument 'client' is nil") + } + return sqsClient{SqsClient: client}, nil +} + +func (s sqsClient) GetSqsMessages(ctx context.Context, params *sqs.ReceiveMessageInput) ([]*sqs.Message, error) { + ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("sqsClient.getMessages")) + + result, err := s.ReceiveMessageWithContext(ctx, params) + if err != nil { + logging.FromContext(ctx). + With("error", err). + Error("failed to fetch messages") + return nil, err + } + + return result.Messages, nil +} + +func (s sqsClient) DeleteSqsMessage(ctx context.Context, params *sqs.DeleteMessageInput) error { + ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("sqsClient.deleteMessage")) + + _, err := s.DeleteMessageWithContext(ctx, params) + if err != nil { + logging.FromContext(ctx). + With("error", err). + Error("failed to delete message") + return err + } + + return nil +} diff --git a/src/pkg/terminator/cordondrainbuilder.go b/src/pkg/terminator/cordondrainbuilder.go new file mode 100644 index 00000000..3b89172a --- /dev/null +++ b/src/pkg/terminator/cordondrainbuilder.go @@ -0,0 +1,49 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package terminator + +import ( + "fmt" + + "github.com/aws/aws-node-termination-handler/api/v1alpha1" + "github.com/aws/aws-node-termination-handler/pkg/node/cordondrain" +) + +type cordonDrainerBuilderAdapter struct { + cordondrain.Builder +} + +func NewCordonDrainerBuilder(builder cordondrain.Builder) (CordonDrainerBuilder, error) { + if builder == nil { + return nil, fmt.Errorf("argument 'builder' is nil") + } + return cordonDrainerBuilderAdapter{Builder: builder}, nil +} + +func (b cordonDrainerBuilderAdapter) NewCordonDrainer(terminator *v1alpha1.Terminator) (cordondrain.CordonDrainer, error) { + if terminator == nil { + return nil, fmt.Errorf("argument 'terminator' is nil") + } + + return b.Builder.Build(cordondrain.Config{ + Force: terminator.Spec.Drain.Force, + GracePeriodSeconds: terminator.Spec.Drain.GracePeriodSeconds, + IgnoreAllDaemonSets: terminator.Spec.Drain.IgnoreAllDaemonSets, + DeleteEmptyDirData: terminator.Spec.Drain.DeleteEmptyDirData, + TimeoutSeconds: terminator.Spec.Drain.TimeoutSeconds, + }) +} diff --git a/src/pkg/terminator/getter.go b/src/pkg/terminator/getter.go new file mode 100644 index 00000000..5fbb5bd7 --- /dev/null +++ b/src/pkg/terminator/getter.go @@ -0,0 +1,63 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package terminator + +import ( + "context" + "fmt" + + "github.com/aws/aws-node-termination-handler/api/v1alpha1" + "github.com/aws/aws-node-termination-handler/pkg/logging" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type ( + KubeGetter interface { + Get(context.Context, client.ObjectKey, client.Object) error + } + + getter struct { + KubeGetter + } +) + +func NewGetter(kubeGetter KubeGetter) (Getter, error) { + if kubeGetter == nil { + return nil, fmt.Errorf("argument 'kubeGetter' is nil") + } + return getter{KubeGetter: kubeGetter}, nil +} + +func (g getter) GetTerminator(ctx context.Context, name types.NamespacedName) (*v1alpha1.Terminator, error) { + terminator := &v1alpha1.Terminator{} + if err := g.Get(ctx, name, terminator); err != nil { + if errors.IsNotFound(err) { + logging.FromContext(ctx).Warn("terminator not found") + return nil, nil + } + + logging.FromContext(ctx). + With("error", err). + Error("failed to retrieve terminator") + return nil, err + } + + return terminator, nil +} diff --git a/src/pkg/terminator/parser.go b/src/pkg/terminator/parser.go new file mode 100644 index 00000000..3f61b746 --- /dev/null +++ b/src/pkg/terminator/parser.go @@ -0,0 +1,44 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package terminator + +import ( + "context" + "fmt" + + "github.com/aws/aws-node-termination-handler/pkg/event" + + "github.com/aws/aws-sdk-go/service/sqs" +) + +type eventParserAdapter struct { + event.Parser +} + +func NewSqsMessageParser(parser event.Parser) (SqsMessageParser, error) { + if parser == nil { + return nil, fmt.Errorf("argument 'parser' is nil") + } + return eventParserAdapter{Parser: parser}, nil +} + +func (a eventParserAdapter) Parse(ctx context.Context, msg *sqs.Message) event.Event { + if msg == nil || msg.Body == nil { + return a.Parser.Parse(ctx, "") + } + return a.Parser.Parse(ctx, *msg.Body) +} diff --git a/src/pkg/terminator/reconciler.go b/src/pkg/terminator/reconciler.go new file mode 100644 index 00000000..d508cda5 --- /dev/null +++ b/src/pkg/terminator/reconciler.go @@ -0,0 +1,178 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package terminator + +import ( + "context" + "fmt" + "time" + + "github.com/aws/aws-node-termination-handler/api/v1alpha1" + "github.com/aws/aws-node-termination-handler/pkg/event" + "github.com/aws/aws-node-termination-handler/pkg/logging" + "github.com/aws/aws-node-termination-handler/pkg/node/cordondrain" + + "github.com/aws/aws-sdk-go/service/sqs" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + + "go.uber.org/multierr" + + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +type ( + NodeGetter interface { + GetNode(context.Context, string) (*v1.Node, error) + } + + NodeNameGetter interface { + GetNodeName(context.Context, string) (string, error) + } + + SqsMessageParser interface { + Parse(context.Context, *sqs.Message) event.Event + } + + CordonDrainerBuilder interface { + NewCordonDrainer(*v1alpha1.Terminator) (cordondrain.CordonDrainer, error) + } + + Getter interface { + GetTerminator(context.Context, types.NamespacedName) (*v1alpha1.Terminator, error) + } + + SqsClient interface { + GetSqsMessages(context.Context) ([]*sqs.Message, error) + DeleteSqsMessage(context.Context, *sqs.Message) error + } + + SqsClientBuilder interface { + NewSqsClient(*v1alpha1.Terminator) (SqsClient, error) + } + + Reconciler struct { + NodeGetter + NodeNameGetter + SqsClientBuilder + SqsMessageParser + CordonDrainerBuilder + Getter + + Name string + RequeueInterval time.Duration + } +) + +func (r Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { + ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named(r.Name).With("terminator", req.Name)) + + terminator, err := r.GetTerminator(ctx, req.NamespacedName) + if err != nil { + return reconcile.Result{}, err + } + if terminator == nil { + return reconcile.Result{}, nil + } + + cordondrainer, err := r.NewCordonDrainer(terminator) + if err != nil { + return reconcile.Result{}, err + } + + sqsClient, err := r.NewSqsClient(terminator) + if err != nil { + return reconcile.Result{}, err + } + + sqsMessages, err := sqsClient.GetSqsMessages(ctx) + if err != nil { + return reconcile.Result{}, err + } + + origCtx := ctx + for _, msg := range sqsMessages { + ctx = logging.WithLogger(origCtx, logging.FromContext(origCtx). + With("sqsMessage", logging.NewMessageMarshaler(msg)), + ) + + evt := r.Parse(ctx, msg) + ctx = logging.WithLogger(ctx, logging.FromContext(ctx).With("event", evt)) + + savedCtx := ctx + for _, ec2InstanceId := range evt.Ec2InstanceIds() { + ctx = logging.WithLogger(savedCtx, logging.FromContext(savedCtx). + With("ec2InstanceId", ec2InstanceId), + ) + + nodeName, e := r.GetNodeName(ctx, ec2InstanceId) + if e != nil { + err = multierr.Append(err, e) + continue + } + + ctx = logging.WithLogger(ctx, logging.FromContext(ctx).With("node", nodeName)) + + node, e := r.GetNode(ctx, nodeName) + if e != nil { + err = multierr.Append(err, e) + continue + } + + if e = cordondrainer.Cordon(ctx, node); e != nil { + err = multierr.Append(err, e) + continue + } + + if e = cordondrainer.Drain(ctx, node); e != nil { + err = multierr.Append(err, e) + continue + } + } + ctx = savedCtx + + tryAgain, e := evt.Done(ctx) + if e != nil { + err = multierr.Append(err, e) + } + + if tryAgain { + continue + } + + err = multierr.Append(err, sqsClient.DeleteSqsMessage(ctx, msg)) + } + ctx = origCtx + + if err != nil { + return reconcile.Result{}, err + } + return reconcile.Result{RequeueAfter: r.RequeueInterval}, nil +} + +func (r Reconciler) BuildController(builder *builder.Builder) error { + if builder == nil { + return fmt.Errorf("argument 'builder' is nil") + } + + return builder. + Named(r.Name). + For(&v1alpha1.Terminator{}). + Complete(r) +} diff --git a/src/pkg/terminator/sqsclient.go b/src/pkg/terminator/sqsclient.go new file mode 100644 index 00000000..dc40f593 --- /dev/null +++ b/src/pkg/terminator/sqsclient.go @@ -0,0 +1,101 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package terminator + +import ( + "context" + "fmt" + + "github.com/aws/aws-node-termination-handler/api/v1alpha1" + "github.com/aws/aws-node-termination-handler/pkg/logging" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/sqs" +) + +type ( + SqsMessageClient interface { + GetSqsMessages(context.Context, *sqs.ReceiveMessageInput) ([]*sqs.Message, error) + DeleteSqsMessage(context.Context, *sqs.DeleteMessageInput) error + } + + sqsMessageClientAdapterBuilder struct { + SqsMessageClient + } + + sqsMessageClientAdapter struct { + sqs.DeleteMessageInput + sqs.ReceiveMessageInput + SqsMessageClient + } +) + +func NewSqsClientBuilder(client SqsMessageClient) (SqsClientBuilder, error) { + if client == nil { + return nil, fmt.Errorf("argument 'client' is nil") + } + return sqsMessageClientAdapterBuilder{SqsMessageClient: client}, nil +} + +func (s sqsMessageClientAdapterBuilder) NewSqsClient(terminator *v1alpha1.Terminator) (SqsClient, error) { + if terminator == nil { + return nil, fmt.Errorf("argument 'terminator' is nil") + } + + receiveMessageInput := sqs.ReceiveMessageInput{ + MaxNumberOfMessages: aws.Int64(terminator.Spec.Sqs.MaxNumberOfMessages), + QueueUrl: aws.String(terminator.Spec.Sqs.QueueUrl), + VisibilityTimeout: aws.Int64(terminator.Spec.Sqs.VisibilityTimeoutSeconds), + WaitTimeSeconds: aws.Int64(terminator.Spec.Sqs.WaitTimeSeconds), + } + receiveMessageInput.AttributeNames = make([]*string, len(terminator.Spec.Sqs.AttributeNames)) + for i, attrName := range terminator.Spec.Sqs.AttributeNames { + receiveMessageInput.AttributeNames[i] = aws.String(attrName) + } + receiveMessageInput.MessageAttributeNames = make([]*string, len(terminator.Spec.Sqs.MessageAttributeNames)) + for i, attrName := range terminator.Spec.Sqs.MessageAttributeNames { + receiveMessageInput.MessageAttributeNames[i] = aws.String(attrName) + } + + deleteMessageInput := sqs.DeleteMessageInput{ + QueueUrl: aws.String(terminator.Spec.Sqs.QueueUrl), + } + + return sqsMessageClientAdapter{ + DeleteMessageInput: deleteMessageInput, + ReceiveMessageInput: receiveMessageInput, + SqsMessageClient: s.SqsMessageClient, + }, nil +} + +func (a sqsMessageClientAdapter) GetSqsMessages(ctx context.Context) ([]*sqs.Message, error) { + ctx = logging.WithLogger(ctx, logging.FromContext(ctx). + With("params", logging.NewReceiveMessageInputMarshaler(&a.ReceiveMessageInput)), + ) + + return a.SqsMessageClient.GetSqsMessages(ctx, &a.ReceiveMessageInput) +} + +func (a sqsMessageClientAdapter) DeleteSqsMessage(ctx context.Context, msg *sqs.Message) error { + a.DeleteMessageInput.ReceiptHandle = msg.ReceiptHandle + + ctx = logging.WithLogger(ctx, logging.FromContext(ctx). + With("params", logging.NewDeleteMessageInputMarshaler(&a.DeleteMessageInput)), + ) + + return a.SqsMessageClient.DeleteSqsMessage(ctx, &a.DeleteMessageInput) +} diff --git a/src/resources/controller-iam-role.yaml b/src/resources/controller-iam-role.yaml new file mode 100644 index 00000000..ee89e4f2 --- /dev/null +++ b/src/resources/controller-iam-role.yaml @@ -0,0 +1,23 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: Resources used by AWS Node Termination Handler v2 +Parameters: + ClusterName: + Type: String + Description: "EKS cluster name" +Resources: + Nthv2ControllerPolicy: + Type: AWS::IAM::ManagedPolicy + Properties: + ManagedPolicyName: !Sub "Nthv2ControllerPolicy-${ClusterName}" + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Resource: "*" + Action: + - autoscaling:CompleteLifecycleAction + - autoscaling:DescribeAutoScalingInstances + - autoscaling:DescribeTags + - ec2:DescribeInstances + - sqs:DeleteMessage + - sqs:ReceiveMessage diff --git a/src/resources/eks-cluster.yaml.tmpl b/src/resources/eks-cluster.yaml.tmpl new file mode 100644 index 00000000..9f6de787 --- /dev/null +++ b/src/resources/eks-cluster.yaml.tmpl @@ -0,0 +1,17 @@ +apiVersion: eksctl.io/v1alpha5 +kind: ClusterConfig +metadata: + name: ${CLUSTER_NAME} + region: ${AWS_REGION} + version: "1.21" + tags: + karpenter.sh/discovery: ${CLUSTER_NAME} +managedNodeGroups: + - instanceType: m5.large + amiFamily: AmazonLinux2 + name: ${CLUSTER_NAME}-ng + desiredCapacity: 1 + minSize: 1 + maxSize: 10 +iam: + withOIDC: true diff --git a/src/scripts/docker-login-ecr.sh b/src/scripts/docker-login-ecr.sh new file mode 100755 index 00000000..63093be1 --- /dev/null +++ b/src/scripts/docker-login-ecr.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +aws ecr get-login-password \ + --region "${AWS_REGION:?AWS_REGION is undefined or empty}" | \ + docker login \ + --username AWS \ + --password-stdin \ + "${KO_DOCKER_REPO:?KO_DOCKER_REPO is undefined or empty}" diff --git a/src/test/app_integ_suite_test.go b/src/test/app_integ_suite_test.go new file mode 100644 index 00000000..957efc1c --- /dev/null +++ b/src/test/app_integ_suite_test.go @@ -0,0 +1,29 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestAppIntegration(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "App Integration Suite") +} diff --git a/src/test/asgclient.go b/src/test/asgclient.go new file mode 100644 index 00000000..b02cd3c4 --- /dev/null +++ b/src/test/asgclient.go @@ -0,0 +1,33 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package test + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/service/autoscaling" +) + +type ( + CompleteAsgLifecycleActionFunc = func(aws.Context, *autoscaling.CompleteLifecycleActionInput, ...request.Option) (*autoscaling.CompleteLifecycleActionOutput, error) + + AsgClient CompleteAsgLifecycleActionFunc +) + +func (a AsgClient) CompleteLifecycleActionWithContext(ctx aws.Context, input *autoscaling.CompleteLifecycleActionInput, options ...request.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { + return a(ctx, input, options...) +} diff --git a/src/test/ec2client.go b/src/test/ec2client.go new file mode 100644 index 00000000..36ab5d03 --- /dev/null +++ b/src/test/ec2client.go @@ -0,0 +1,33 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package test + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/service/ec2" +) + +type ( + DescribeEc2InstancesFunc = func(aws.Context, *ec2.DescribeInstancesInput, ...request.Option) (*ec2.DescribeInstancesOutput, error) + + Ec2Client DescribeEc2InstancesFunc +) + +func (e Ec2Client) DescribeInstancesWithContext(ctx aws.Context, input *ec2.DescribeInstancesInput, options ...request.Option) (*ec2.DescribeInstancesOutput, error) { + return e(ctx, input, options...) +} diff --git a/src/test/kubeclient.go b/src/test/kubeclient.go new file mode 100644 index 00000000..f8fbee90 --- /dev/null +++ b/src/test/kubeclient.go @@ -0,0 +1,33 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package test + +import ( + "context" + + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type ( + KubeGetFunc = func(context.Context, client.ObjectKey, client.Object) error + + KubeClient KubeGetFunc +) + +func (k KubeClient) Get(ctx context.Context, key client.ObjectKey, object client.Object) error { + return k(ctx, key, object) +} diff --git a/src/test/reconciliation_test.go b/src/test/reconciliation_test.go new file mode 100644 index 00000000..8a630c5d --- /dev/null +++ b/src/test/reconciliation_test.go @@ -0,0 +1,1690 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package test + +import ( + "context" + "errors" + "fmt" + "reflect" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "go.uber.org/zap" + + v1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" + kubectl "k8s.io/kubectl/pkg/drain" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/aws/aws-node-termination-handler/api/v1alpha1" + "github.com/aws/aws-node-termination-handler/pkg/event" + asgterminateeventv1 "github.com/aws/aws-node-termination-handler/pkg/event/asgterminate/v1" + asgterminateeventv2 "github.com/aws/aws-node-termination-handler/pkg/event/asgterminate/v2" + rebalancerecommendationeventv0 "github.com/aws/aws-node-termination-handler/pkg/event/rebalancerecommendation/v0" + scheduledchangeeventv1 "github.com/aws/aws-node-termination-handler/pkg/event/scheduledchange/v1" + spotinterruptioneventv1 "github.com/aws/aws-node-termination-handler/pkg/event/spotinterruption/v1" + statechangeeventv1 "github.com/aws/aws-node-termination-handler/pkg/event/statechange/v1" + "github.com/aws/aws-node-termination-handler/pkg/logging" + "github.com/aws/aws-node-termination-handler/pkg/node" + kubectlcordondrain "github.com/aws/aws-node-termination-handler/pkg/node/cordondrain/kubectl" + nodename "github.com/aws/aws-node-termination-handler/pkg/node/name" + "github.com/aws/aws-node-termination-handler/pkg/sqsmessage" + "github.com/aws/aws-node-termination-handler/pkg/terminator" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + awsrequest "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/service/autoscaling" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/sqs" +) + +type ( + Ec2InstanceId = string + SqsQueueUrl = string + NodeName = string + + State string +) + +const ( + StatePending = State("pending") + StateComplete = State("complete") +) + +var _ = Describe("Reconciliation", func() { + const ( + errMsg = "test error" + queueUrl = "http://fake-queue.sqs.aws" + ) + + var ( + // Input variables + // These variables are assigned default values during test setup but may be + // modified to represent different cluster states or AWS service responses. + + // Terminators currently in the cluster. + terminators map[types.NamespacedName]*v1alpha1.Terminator + // Nodes currently in the cluster. + nodes map[types.NamespacedName]*v1.Node + // Maps an EC2 instance id to an ASG lifecycle action state value. + asgLifecycleActions map[Ec2InstanceId]State + // Maps an EC2 instance id to the corresponding reservation for a node + // in the cluster. + ec2Reservations map[Ec2InstanceId]*ec2.Reservation + // Maps a queue URL to a list of messages waiting to be fetched. + sqsQueues map[SqsQueueUrl][]*sqs.Message + + // Output variables + // These variables may be modified during reconciliation and should be + // used to verify the resulting cluster state. + + // A lookup table for nodes that were cordoned. + cordonedNodes map[NodeName]bool + // A lookup table for nodes that were drained. + drainedNodes map[NodeName]bool + // Result information returned by the reconciler. + result reconcile.Result + // Error information returned by the reconciler. + err error + + // Other variables + + // Names of all nodes currently in cluster. + nodeNames []NodeName + // Instance IDs for all nodes currently in cluster. + instanceIds []Ec2InstanceId + // Change count of nodes in cluster. + resizeCluster func(nodeCount uint) + // Create an ASG lifecycle action state entry for an EC2 instance ID. + createPendingAsgLifecycleAction func(Ec2InstanceId) + + // Name of default terminator. + terminatorNamespaceName types.NamespacedName + // Inputs to .Reconcile() + ctx context.Context + request reconcile.Request + + // The reconciler instance under test. + reconciler terminator.Reconciler + + // Stubs + // Default implementations interract with the backing variables listed + // above. A test may put in place alternate behavior when needed. + completeAsgLifecycleActionFunc CompleteAsgLifecycleActionFunc + describeEc2InstancesFunc DescribeEc2InstancesFunc + kubeGetFunc KubeGetFunc + receiveSqsMessageFunc ReceiveSqsMessageFunc + deleteSqsMessageFunc DeleteSqsMessageFunc + cordonFunc kubectlcordondrain.RunCordonFunc + drainFunc kubectlcordondrain.RunDrainFunc + ) + + When("the SQS queue is empty", func() { + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) + + It("does not cordon or drain any nodes", func() { + Expect(cordonedNodes).To(BeEmpty()) + Expect(drainedNodes).To(BeEmpty()) + }) + }) + + When("the SQS queue contains an ASG Lifecycle Notification v1", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.autoscaling", + "detail-type": "EC2 Instance-terminate Lifecycle Action", + "version": "1", + "detail": { + "EC2InstanceId": "%s", + "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING" + } + }`, instanceIds[1])), + }) + + createPendingAsgLifecycleAction(instanceIds[1]) + }) + + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) + + It("cordons and drains only the targeted node", func() { + Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + }) + + It("completes the ASG lifecycle action", func() { + Expect(asgLifecycleActions).To(And(HaveKeyWithValue(instanceIds[1], Equal(StateComplete)), HaveLen(1))) + }) + + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueUrl]).To(BeEmpty()) + }) + }) + + When("the SQS queue contains an ASG Lifecycle Notification v2", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.autoscaling", + "detail-type": "EC2 Instance-terminate Lifecycle Action", + "version": "2", + "detail": { + "EC2InstanceId": "%s", + "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING" + } + }`, instanceIds[1])), + }) + + createPendingAsgLifecycleAction(instanceIds[1]) + }) + + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) + + It("cordons and drains only the targeted node", func() { + Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + }) + + It("completes the ASG lifecycle action", func() { + Expect(asgLifecycleActions).To(And(HaveKeyWithValue(instanceIds[1], Equal(StateComplete)), HaveLen(1))) + }) + + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueUrl]).To(BeEmpty()) + }) + }) + + When("the SQS queue contains a Rebalance Recommendation Notification", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Instance Rebalance Recommendation", + "version": "0", + "detail": { + "instance-id": "%s" + } + }`, instanceIds[1])), + }) + }) + + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) + + It("cordons and drains only the targeted node", func() { + Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + }) + + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueUrl]).To(BeEmpty()) + }) + }) + + When("the SQS queue contains a Scheduled Change Notification", func() { + BeforeEach(func() { + resizeCluster(4) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.health", + "detail-type": "AWS Health Event", + "version": "1", + "detail": { + "service": "EC2", + "eventTypeCategory": "scheduledChange", + "affectedEntities": [ + {"entityValue": "%s"}, + {"entityValue": "%s"} + ] + } + }`, instanceIds[1], instanceIds[2])), + }) + }) + + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) + + It("cordons and drains only the targeted nodes", func() { + Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveKey(nodeNames[2]), HaveLen(2))) + Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveKey(nodeNames[2]), HaveLen(2))) + }) + + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueUrl]).To(BeEmpty()) + }) + }) + + When("the SQS queue contains a Spot Interruption Notification", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Spot Instance Interruption Warning", + "version": "1", + "detail": { + "instance-id": "%s" + } + }`, instanceIds[1])), + }) + }) + + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) + + It("cordons and drains only the targeted node", func() { + Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + }) + + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueUrl]).To(BeEmpty()) + }) + }) + + When("the SQS queue contains a State Change (stopping) Notification", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Instance State-change Notification", + "version": "1", + "detail": { + "instance-id": "%s", + "state": "stopping" + } + }`, instanceIds[1])), + }) + }) + + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) + + It("cordons and drains only the targeted nodes", func() { + Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + }) + + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueUrl]).To(BeEmpty()) + }) + }) + + When("the SQS queue contains a State Change (stopped) Notification", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Instance State-change Notification", + "version": "1", + "detail": { + "instance-id": "%s", + "state": "stopped" + } + }`, instanceIds[1])), + }) + }) + + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) + + It("cordons and drains only the targeted node", func() { + Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + }) + + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueUrl]).To(BeEmpty()) + }) + }) + + When("the SQS queue contains a State Change (shutting-down) Notification", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Instance State-change Notification", + "version": "1", + "detail": { + "instance-id": "%s", + "state": "shutting-down" + } + }`, instanceIds[1])), + }) + }) + + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) + + It("cordons and drains only the targeted node", func() { + Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + }) + + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueUrl]).To(BeEmpty()) + }) + }) + + When("the SQS queue contains a State Change (terminated) Notification", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Instance State-change Notification", + "version": "1", + "detail": { + "instance-id": "%s", + "state": "terminated" + } + }`, instanceIds[1])), + }) + }) + + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) + + It("cordons and drains only the targeted node", func() { + Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + }) + + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueUrl]).To(BeEmpty()) + }) + }) + + When("the SQS queue contains multiple messages", func() { + BeforeEach(func() { + resizeCluster(12) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], + &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.autoscaling", + "detail-type": "EC2 Instance-terminate Lifecycle Action", + "version": "1", + "detail": { + "EC2InstanceId": "%s", + "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING" + } + }`, instanceIds[1])), + }, + &sqs.Message{ + ReceiptHandle: aws.String("msg-2"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.autoscaling", + "detail-type": "EC2 Instance-terminate Lifecycle Action", + "version": "2", + "detail": { + "EC2InstanceId": "%s", + "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING" + } + }`, instanceIds[2])), + }, + &sqs.Message{ + ReceiptHandle: aws.String("msg-3"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Instance Rebalance Recommendation", + "version": "0", + "detail": { + "instance-id": "%s" + } + }`, instanceIds[3])), + }, + &sqs.Message{ + ReceiptHandle: aws.String("msg-4"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.health", + "detail-type": "AWS Health Event", + "version": "1", + "detail": { + "service": "EC2", + "eventTypeCategory": "scheduledChange", + "affectedEntities": [ + {"entityValue": "%s"}, + {"entityValue": "%s"} + ] + } + }`, instanceIds[4], instanceIds[5])), + }, + &sqs.Message{ + ReceiptHandle: aws.String("msg-5"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Spot Instance Interruption Warning", + "version": "1", + "detail": { + "instance-id": "%s" + } + }`, instanceIds[6])), + }, + &sqs.Message{ + ReceiptHandle: aws.String("msg-6"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Instance State-change Notification", + "version": "1", + "detail": { + "instance-id": "%s", + "state": "stopping" + } + }`, instanceIds[7])), + }, + &sqs.Message{ + ReceiptHandle: aws.String("msg-7"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Instance State-change Notification", + "version": "1", + "detail": { + "instance-id": "%s", + "state": "stopped" + } + }`, instanceIds[8])), + }, + &sqs.Message{ + ReceiptHandle: aws.String("msg-8"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Instance State-change Notification", + "version": "1", + "detail": { + "instance-id": "%s", + "state": "shutting-down" + } + }`, instanceIds[9])), + }, + &sqs.Message{ + ReceiptHandle: aws.String("msg-9"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Instance State-change Notification", + "version": "1", + "detail": { + "instance-id": "%s", + "state": "terminated" + } + }`, instanceIds[10])), + }, + ) + }) + + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) + + It("cordons and drains only the targeted nodes", func() { + Expect(cordonedNodes).To(And( + HaveKey(nodeNames[1]), + HaveKey(nodeNames[2]), + HaveKey(nodeNames[3]), + HaveKey(nodeNames[4]), + HaveKey(nodeNames[5]), + HaveKey(nodeNames[6]), + HaveKey(nodeNames[7]), + HaveKey(nodeNames[8]), + HaveKey(nodeNames[9]), + HaveKey(nodeNames[10]), + HaveLen(10), + )) + Expect(drainedNodes).To(And( + HaveKey(nodeNames[1]), + HaveKey(nodeNames[2]), + HaveKey(nodeNames[3]), + HaveKey(nodeNames[4]), + HaveKey(nodeNames[5]), + HaveKey(nodeNames[6]), + HaveKey(nodeNames[7]), + HaveKey(nodeNames[8]), + HaveKey(nodeNames[9]), + HaveKey(nodeNames[10]), + HaveLen(10), + )) + }) + + It("deletes the messages from the SQS queue", func() { + Expect(sqsQueues[queueUrl]).To(BeEmpty()) + }) + }) + + When("the SQS queue contains an unrecognized message", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(`{ + "source": "test.suite", + "detail-type": "Not a real notification", + "version": "1", + "detail": {} + }`), + }) + }) + + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) + + It("does not cordon or drain any nodes", func() { + Expect(cordonedNodes).To(BeEmpty()) + Expect(drainedNodes).To(BeEmpty()) + }) + + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueUrl]).To(BeEmpty()) + }) + }) + + When("the SQS message cannot be parsed", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(`{ + "source": "test.suite", + "detail-type": "Mal-formed notification", + `), + }) + }) + + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) + + It("does not cordon or drain any nodes", func() { + Expect(cordonedNodes).To(BeEmpty()) + Expect(drainedNodes).To(BeEmpty()) + }) + + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueUrl]).To(BeEmpty()) + }) + }) + + When("the terminator cannot be found", func() { + BeforeEach(func() { + delete(terminators, terminatorNamespaceName) + }) + + It("returns success but does not requeue the request", func() { + Expect(result, err).To(BeZero()) + }) + + It("does not cordon or drain any nodes", func() { + Expect(cordonedNodes).To(BeEmpty()) + Expect(drainedNodes).To(BeEmpty()) + }) + }) + + When("there is an error getting the terminator", func() { + BeforeEach(func() { + defaultKubeGetFunc := kubeGetFunc + kubeGetFunc = func(ctx context.Context, key client.ObjectKey, object client.Object) error { + switch object.(type) { + case *v1alpha1.Terminator: + return errors.New(errMsg) + default: + return defaultKubeGetFunc(ctx, key, object) + } + } + }) + + It("does not requeue the request", func() { + Expect(result).To(BeZero()) + }) + + It("returns an error", func() { + Expect(err).To(MatchError(ContainSubstring(errMsg))) + }) + }) + + When("there is an error getting SQS messages", func() { + BeforeEach(func() { + receiveSqsMessageFunc = func(_ aws.Context, _ *sqs.ReceiveMessageInput, _ ...awsrequest.Option) (*sqs.ReceiveMessageOutput, error) { + return nil, errors.New(errMsg) + } + }) + + It("does not requeue the request", func() { + Expect(result).To(BeZero()) + }) + + It("returns an error", func() { + Expect(err).To(MatchError(ContainSubstring(errMsg))) + }) + }) + + When("there is an error getting the EC2 reservation for the instance ID", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Spot Instance Interruption Warning", + "version": "1", + "detail": { + "instance-id": "%s" + } + }`, instanceIds[1])), + }) + + describeEc2InstancesFunc = func(_ aws.Context, _ *ec2.DescribeInstancesInput, _ ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { + return nil, errors.New(errMsg) + } + }) + + It("does not requeue the request", func() { + Expect(result).To(BeZero()) + }) + + It("returns an error", func() { + Expect(err).To(MatchError(ContainSubstring(errMsg))) + }) + + It("does not cordon or drain any nodes", func() { + Expect(cordonedNodes).To(BeEmpty()) + Expect(drainedNodes).To(BeEmpty()) + }) + }) + + When("there is no EC2 reservation for the instance ID", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Spot Instance Interruption Warning", + "version": "1", + "detail": { + "instance-id": "%s" + } + }`, instanceIds[1])), + }) + + describeEc2InstancesFunc = func(_ aws.Context, _ *ec2.DescribeInstancesInput, _ ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { + return &ec2.DescribeInstancesOutput{ + Reservations: []*ec2.Reservation{}, + }, nil + } + }) + + It("does not requeue the request", func() { + Expect(result).To(BeZero()) + }) + + It("returns an error", func() { + Expect(err).To(HaveOccurred()) + }) + + It("does not cordon or drain any nodes", func() { + Expect(cordonedNodes).To(BeEmpty()) + Expect(drainedNodes).To(BeEmpty()) + }) + }) + + When("the EC2 reservation contains no instances", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Spot Instance Interruption Warning", + "version": "1", + "detail": { + "instance-id": "%s" + } + }`, instanceIds[1])), + }) + + describeEc2InstancesFunc = func(_ aws.Context, _ *ec2.DescribeInstancesInput, _ ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { + return &ec2.DescribeInstancesOutput{ + Reservations: []*ec2.Reservation{ + {Instances: []*ec2.Instance{}}, + }, + }, nil + } + }) + + It("does not requeue the request", func() { + Expect(result).To(BeZero()) + }) + + It("returns an error", func() { + Expect(err).To(HaveOccurred()) + }) + + It("does not cordon or drain any nodes", func() { + Expect(cordonedNodes).To(BeEmpty()) + Expect(drainedNodes).To(BeEmpty()) + }) + }) + + When("the EC2 reservation's instance has no PrivateDnsName", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Spot Instance Interruption Warning", + "version": "1", + "detail": { + "instance-id": "%s" + } + }`, instanceIds[1])), + }) + + describeEc2InstancesFunc = func(_ aws.Context, _ *ec2.DescribeInstancesInput, _ ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { + return &ec2.DescribeInstancesOutput{ + Reservations: []*ec2.Reservation{ + { + Instances: []*ec2.Instance{ + {PrivateDnsName: nil}, + }, + }, + }, + }, nil + } + }) + + It("does not requeue the request", func() { + Expect(result).To(BeZero()) + }) + + It("returns an error", func() { + Expect(err).To(HaveOccurred()) + }) + + It("does not cordon or drain any nodes", func() { + Expect(cordonedNodes).To(BeEmpty()) + Expect(drainedNodes).To(BeEmpty()) + }) + }) + + When("the EC2 reservation's instance's PrivateDnsName empty", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Spot Instance Interruption Warning", + "version": "1", + "detail": { + "instance-id": "%s" + } + }`, instanceIds[1])), + }) + + describeEc2InstancesFunc = func(_ aws.Context, _ *ec2.DescribeInstancesInput, _ ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { + return &ec2.DescribeInstancesOutput{ + Reservations: []*ec2.Reservation{ + { + Instances: []*ec2.Instance{ + {PrivateDnsName: aws.String("")}, + }, + }, + }, + }, nil + } + }) + + It("does not requeue the request", func() { + Expect(result).To(BeZero()) + }) + + It("returns an error", func() { + Expect(err).To(HaveOccurred()) + }) + + It("does not cordon or drain any nodes", func() { + Expect(cordonedNodes).To(BeEmpty()) + Expect(drainedNodes).To(BeEmpty()) + }) + }) + + When("there is an error getting the cluster node name for an EC2 instance ID", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Spot Instance Interruption Warning", + "version": "1", + "detail": { + "instance-id": "%s" + } + }`, instanceIds[1])), + }) + + defaultKubeGetFunc := kubeGetFunc + kubeGetFunc = func(ctx context.Context, key client.ObjectKey, object client.Object) error { + switch object.(type) { + case *v1.Node: + return errors.New(errMsg) + default: + return defaultKubeGetFunc(ctx, key, object) + } + } + }) + + It("does not requeue the request", func() { + Expect(result).To(BeZero()) + }) + + It("returns an error", func() { + Expect(err).To(MatchError(ContainSubstring(errMsg))) + }) + + It("does not cordon or drain any nodes", func() { + Expect(cordonedNodes).To(BeEmpty()) + Expect(drainedNodes).To(BeEmpty()) + }) + }) + + When("cordoning a node fails", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Spot Instance Interruption Warning", + "version": "1", + "detail": { + "instance-id": "%s" + } + }`, instanceIds[1])), + }) + + cordonFunc = func(_ *kubectl.Helper, _ *v1.Node, _ bool) error { + return errors.New(errMsg) + } + }) + + It("does not requeue the request", func() { + Expect(result).To(BeZero()) + }) + + It("returns an error", func() { + Expect(err).To(MatchError(ContainSubstring(errMsg))) + }) + + It("does not cordon or drain any nodes", func() { + Expect(cordonedNodes).To(BeEmpty()) + Expect(drainedNodes).To(BeEmpty()) + }) + }) + + When("draining a node fails", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Spot Instance Interruption Warning", + "version": "1", + "detail": { + "instance-id": "%s" + } + }`, instanceIds[1])), + }) + + drainFunc = func(_ *kubectl.Helper, _ string) error { + return errors.New(errMsg) + } + }) + + It("does not requeue the request", func() { + Expect(result).To(BeZero()) + }) + + It("returns an error", func() { + Expect(err).To(MatchError(ContainSubstring(errMsg))) + }) + + It("cordons the target node", func() { + Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + }) + + It("does not drain the target node", func() { + Expect(drainedNodes).To(BeEmpty()) + }) + }) + + When("completing an ASG Lifecycle Action (v1) fails", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.autoscaling", + "detail-type": "EC2 Instance-terminate Lifecycle Action", + "version": "1", + "detail": { + "EC2InstanceId": "%s", + "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING" + } + }`, instanceIds[1])), + }) + + completeAsgLifecycleActionFunc = func(_ aws.Context, _ *autoscaling.CompleteLifecycleActionInput, _ ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { + return nil, errors.New(errMsg) + } + }) + + It("does not requeue the request", func() { + Expect(result).To(BeZero()) + }) + + It("returns an error", func() { + Expect(err).To(MatchError(ContainSubstring(errMsg))) + }) + + It("cordons and drains only the targeted node", func() { + Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + }) + + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueUrl]).To(BeEmpty()) + }) + }) + + When("the request to complete the ASG Lifecycle Action (v1) fails with a status != 400", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.autoscaling", + "detail-type": "EC2 Instance-terminate Lifecycle Action", + "version": "1", + "detail": { + "EC2InstanceId": "%s", + "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING" + } + }`, instanceIds[1])), + }) + + completeAsgLifecycleActionFunc = func(_ aws.Context, _ *autoscaling.CompleteLifecycleActionInput, _ ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { + return nil, awserr.NewRequestFailure(awserr.New("", errMsg, errors.New(errMsg)), 404, "") + } + }) + + It("does not requeue the request", func() { + Expect(result).To(BeZero()) + }) + + It("returns an error", func() { + Expect(err).To(MatchError(ContainSubstring(errMsg))) + }) + + It("cordons and drains only the targeted node", func() { + Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + }) + + It("does not delete the message from the SQS queue", func() { + Expect(sqsQueues[queueUrl]).To(HaveLen(1)) + }) + }) + + When("completing an ASG Lifecycle Action (v2) fails", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.autoscaling", + "detail-type": "EC2 Instance-terminate Lifecycle Action", + "version": "2", + "detail": { + "EC2InstanceId": "%s", + "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING" + } + }`, instanceIds[1])), + }) + + completeAsgLifecycleActionFunc = func(_ aws.Context, _ *autoscaling.CompleteLifecycleActionInput, _ ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { + return nil, errors.New(errMsg) + } + }) + + It("does not requeue the request", func() { + Expect(result).To(BeZero()) + }) + + It("returns an error", func() { + Expect(err).To(MatchError(ContainSubstring(errMsg))) + }) + + It("cordons and drains only the targeted node", func() { + Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + }) + + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueUrl]).To(BeEmpty()) + }) + }) + + When("the request to complete the ASG Lifecycle Action (v2) fails with a status != 400", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.autoscaling", + "detail-type": "EC2 Instance-terminate Lifecycle Action", + "version": "2", + "detail": { + "EC2InstanceId": "%s", + "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING" + } + }`, instanceIds[1])), + }) + + completeAsgLifecycleActionFunc = func(_ aws.Context, _ *autoscaling.CompleteLifecycleActionInput, _ ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { + return nil, awserr.NewRequestFailure(awserr.New("", errMsg, errors.New(errMsg)), 404, "") + } + }) + + It("does not requeue the request", func() { + Expect(result).To(BeZero()) + }) + + It("returns an error", func() { + Expect(err).To(MatchError(ContainSubstring(errMsg))) + }) + + It("cordons and drains only the targeted node", func() { + Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + }) + + It("does not delete the message from the SQS queue", func() { + Expect(sqsQueues[queueUrl]).To(HaveLen(1)) + }) + }) + + When("getting messages from a terminator's SQS queue", func() { + const ( + maxNumberOfMessages = int64(4) + visibilityTimeoutSeconds = int64(17) + waitTimeSeconds = int64(31) + ) + var ( + attributeNames = []string{"TestAttributeName1", "TestAttributeName2"} + messageAttributeNames = []string{"TestMsgAttributeName1", "TestMsgAttributeName2"} + input *sqs.ReceiveMessageInput + ) + + BeforeEach(func() { + terminator, found := terminators[terminatorNamespaceName] + Expect(found).To(BeTrue()) + + terminator.Spec.Sqs.MaxNumberOfMessages = maxNumberOfMessages + terminator.Spec.Sqs.QueueUrl = queueUrl + terminator.Spec.Sqs.VisibilityTimeoutSeconds = visibilityTimeoutSeconds + terminator.Spec.Sqs.WaitTimeSeconds = waitTimeSeconds + terminator.Spec.Sqs.AttributeNames = append([]string{}, attributeNames...) + terminator.Spec.Sqs.MessageAttributeNames = append([]string{}, messageAttributeNames...) + + defaultReceiveSqsMessageFunc := receiveSqsMessageFunc + receiveSqsMessageFunc = func(ctx aws.Context, in *sqs.ReceiveMessageInput, options ...awsrequest.Option) (*sqs.ReceiveMessageOutput, error) { + input = in + return defaultReceiveSqsMessageFunc(ctx, in, options...) + } + }) + + It("sends the input values from the terminator", func() { + Expect(input).ToNot(BeNil()) + + for i, attrName := range input.AttributeNames { + Expect(attrName).ToNot(BeNil()) + Expect(*attrName).To(Equal(attributeNames[i])) + } + for i, attrName := range input.MessageAttributeNames { + Expect(attrName).ToNot(BeNil()) + Expect(*attrName).To(Equal(messageAttributeNames[i])) + } + + Expect(input.MaxNumberOfMessages).ToNot(BeNil()) + Expect(*input.MaxNumberOfMessages).To(Equal(maxNumberOfMessages)) + + Expect(input.QueueUrl).ToNot(BeNil()) + Expect(*input.QueueUrl).To(Equal(queueUrl)) + + Expect(input.VisibilityTimeout).ToNot(BeNil()) + Expect(*input.VisibilityTimeout).To(Equal(visibilityTimeoutSeconds)) + + Expect(input.WaitTimeSeconds).ToNot(BeNil()) + Expect(*input.WaitTimeSeconds).To(Equal(waitTimeSeconds)) + }) + }) + + When("cordoning a node", func() { + const ( + force = true + gracePeriodSeconds = 31 + ignoreAllDaemonSets = true + deleteEmptyDirData = true + ) + var helper *kubectl.Helper + var timeout time.Duration + + BeforeEach(func() { + timeout = 42 * time.Second + + terminator, found := terminators[terminatorNamespaceName] + Expect(found).To(BeTrue()) + + terminator.Spec.Drain.DeleteEmptyDirData = deleteEmptyDirData + terminator.Spec.Drain.Force = force + terminator.Spec.Drain.GracePeriodSeconds = gracePeriodSeconds + terminator.Spec.Drain.IgnoreAllDaemonSets = ignoreAllDaemonSets + terminator.Spec.Drain.TimeoutSeconds = int(timeout.Seconds()) + + defaultCordonFunc := cordonFunc + cordonFunc = func(h *kubectl.Helper, node *v1.Node, desired bool) error { + helper = h + return defaultCordonFunc(h, node, desired) + } + + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Spot Instance Interruption Warning", + "version": "1", + "detail": { + "instance-id": "%s" + } + }`, instanceIds[1])), + }) + }) + + It("sends the input values from the terminator", func() { + Expect(helper).ToNot(BeNil()) + + Expect(helper).To(And( + HaveField("DeleteEmptyDirData", Equal(deleteEmptyDirData)), + HaveField("Force", Equal(force)), + HaveField("GracePeriodSeconds", Equal(gracePeriodSeconds)), + HaveField("IgnoreAllDaemonSets", Equal(ignoreAllDaemonSets)), + HaveField("Timeout", Equal(timeout)), + )) + }) + + It("sends additional input values", func() { + Expect(helper).ToNot(BeNil()) + + Expect(helper).To(And( + HaveField("Client", Not(BeNil())), + HaveField("Ctx", Not(BeNil())), + HaveField("Out", Not(BeNil())), + HaveField("ErrOut", Not(BeNil())), + )) + }) + }) + + When("draining a node", func() { + const ( + force = true + gracePeriodSeconds = 31 + ignoreAllDaemonSets = true + deleteEmptyDirData = true + ) + var helper *kubectl.Helper + var timeout time.Duration + + BeforeEach(func() { + timeout = 42 * time.Second + + terminator, found := terminators[terminatorNamespaceName] + Expect(found).To(BeTrue()) + + terminator.Spec.Drain.DeleteEmptyDirData = deleteEmptyDirData + terminator.Spec.Drain.Force = force + terminator.Spec.Drain.GracePeriodSeconds = gracePeriodSeconds + terminator.Spec.Drain.IgnoreAllDaemonSets = ignoreAllDaemonSets + terminator.Spec.Drain.TimeoutSeconds = int(timeout.Seconds()) + + defaultDrainFunc := drainFunc + drainFunc = func(h *kubectl.Helper, nodeName string) error { + helper = h + return defaultDrainFunc(h, nodeName) + } + + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Spot Instance Interruption Warning", + "version": "1", + "detail": { + "instance-id": "%s" + } + }`, instanceIds[1])), + }) + }) + + It("sends the input values from the terminator", func() { + Expect(helper).ToNot(BeNil()) + + Expect(helper).To(And( + HaveField("DeleteEmptyDirData", Equal(deleteEmptyDirData)), + HaveField("Force", Equal(force)), + HaveField("GracePeriodSeconds", Equal(gracePeriodSeconds)), + HaveField("IgnoreAllDaemonSets", Equal(ignoreAllDaemonSets)), + HaveField("Timeout", Equal(timeout)), + )) + }) + + It("sends additional values", func() { + Expect(helper).ToNot(BeNil()) + + Expect(helper).To(And( + HaveField("Client", Not(BeNil())), + HaveField("Ctx", Not(BeNil())), + HaveField("Out", Not(BeNil())), + HaveField("ErrOut", Not(BeNil())), + )) + }) + }) + + When("completing an ASG Complete Lifecycle Action", func() { + const ( + autoScalingGroupName = "testAutoScalingGroupName" + lifecycleActionResult = "CONTINUE" + lifecycleHookName = "testLifecycleHookName" + lifecycleActionToken = "testLifecycleActionToken" + ) + var input *autoscaling.CompleteLifecycleActionInput + + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.autoscaling", + "detail-type": "EC2 Instance-terminate Lifecycle Action", + "version": "1", + "detail": { + "AutoScalingGroupName": "%s", + "EC2InstanceId": "%s", + "LifecycleActionToken": "%s", + "LifecycleHookName": "%s", + "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING" + } + }`, autoScalingGroupName, instanceIds[1], lifecycleActionToken, lifecycleHookName)), + }) + + defaultCompleteAsgLifecycleActionFunc := completeAsgLifecycleActionFunc + completeAsgLifecycleActionFunc = func(ctx aws.Context, in *autoscaling.CompleteLifecycleActionInput, options ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { + input = in + return defaultCompleteAsgLifecycleActionFunc(ctx, in, options...) + } + }) + + It("sends the expected input values", func() { + Expect(input).ToNot(BeNil()) + + Expect(input.AutoScalingGroupName).ToNot(BeNil()) + Expect(*input.AutoScalingGroupName).To(Equal(autoScalingGroupName)) + + Expect(input.LifecycleActionResult).ToNot(BeNil()) + Expect(*input.LifecycleActionResult).To(Equal(lifecycleActionResult)) + + Expect(input.LifecycleHookName).ToNot(BeNil()) + Expect(*input.LifecycleHookName).To(Equal(lifecycleHookName)) + + Expect(input.LifecycleActionToken).ToNot(BeNil()) + Expect(*input.LifecycleActionToken).To(Equal(lifecycleActionToken)) + + Expect(input.InstanceId).ToNot(BeNil()) + Expect(*input.InstanceId).To(Equal(instanceIds[1])) + }) + }) + + // Setup the starter state: + // * One terminator (terminatorNamedspacedName) + // * The terminator references an empty sqs queue (queueUrl) + // * Zero nodes (use resizeCluster()) + // + // Tests should modify the cluster/aws service states as needed. + BeforeEach(func() { + // 1. Initialize variables. + + ctx = logging.WithLogger(context.Background(), zap.NewNop().Sugar()) + terminatorNamespaceName = types.NamespacedName{Namespace: "test", Name: "foo"} + request = reconcile.Request{NamespacedName: terminatorNamespaceName} + sqsQueues = map[SqsQueueUrl][]*sqs.Message{queueUrl: {}} + terminators = map[types.NamespacedName]*v1alpha1.Terminator{ + // For convenience create a terminator that points to the sqs queue. + terminatorNamespaceName: { + Spec: v1alpha1.TerminatorSpec{ + Sqs: v1alpha1.SqsSpec{ + QueueUrl: queueUrl, + }, + }, + }, + } + nodes = map[types.NamespacedName]*v1.Node{} + ec2Reservations = map[Ec2InstanceId]*ec2.Reservation{} + cordonedNodes = map[NodeName]bool{} + drainedNodes = map[NodeName]bool{} + + nodeNames = []NodeName{} + instanceIds = []Ec2InstanceId{} + resizeCluster = func(newNodeCount uint) { + for currNodeCount := uint(len(nodes)); currNodeCount < newNodeCount; currNodeCount++ { + nodeName := fmt.Sprintf("node-%d", currNodeCount) + nodeNames = append(nodeNames, nodeName) + nodes[types.NamespacedName{Name: nodeName}] = &v1.Node{ + ObjectMeta: metav1.ObjectMeta{Name: nodeName}, + } + + instanceId := fmt.Sprintf("instanceId-%d", currNodeCount) + instanceIds = append(instanceIds, instanceId) + ec2Reservations[instanceId] = &ec2.Reservation{ + Instances: []*ec2.Instance{ + {PrivateDnsName: aws.String(nodeName)}, + }, + } + } + + nodeNames = nodeNames[:newNodeCount] + instanceIds = instanceIds[:newNodeCount] + } + + asgLifecycleActions = map[Ec2InstanceId]State{} + createPendingAsgLifecycleAction = func(instanceId Ec2InstanceId) { + Expect(asgLifecycleActions).ToNot(HaveKey(instanceId)) + asgLifecycleActions[instanceId] = StatePending + } + + // 2. Setup stub clients. + + describeEc2InstancesFunc = func(ctx aws.Context, input *ec2.DescribeInstancesInput, _ ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { + if err := ctx.Err(); err != nil { + return nil, err + } + + output := ec2.DescribeInstancesOutput{} + for _, instanceId := range input.InstanceIds { + if instanceId == nil { + continue + } + if reservation, found := ec2Reservations[*instanceId]; found { + output.Reservations = append(output.Reservations, reservation) + } + } + return &output, nil + } + + ec2Client := Ec2Client(func(ctx aws.Context, input *ec2.DescribeInstancesInput, options ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { + return describeEc2InstancesFunc(ctx, input, options...) + }) + + completeAsgLifecycleActionFunc = func(ctx aws.Context, input *autoscaling.CompleteLifecycleActionInput, _ ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { + if err := ctx.Err(); err != nil { + return nil, err + } + Expect(input.InstanceId).ToNot(BeNil()) + if state, found := asgLifecycleActions[*input.InstanceId]; found { + Expect(state).ToNot(Equal(StateComplete)) + asgLifecycleActions[*input.InstanceId] = StateComplete + } + return &autoscaling.CompleteLifecycleActionOutput{}, nil + } + + asgClient := AsgClient(func(ctx aws.Context, input *autoscaling.CompleteLifecycleActionInput, options ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { + return completeAsgLifecycleActionFunc(ctx, input, options...) + }) + + receiveSqsMessageFunc = func(ctx aws.Context, input *sqs.ReceiveMessageInput, options ...awsrequest.Option) (*sqs.ReceiveMessageOutput, error) { + if err := ctx.Err(); err != nil { + return nil, err + } + Expect(input.QueueUrl).ToNot(BeNil()) + + messages, found := sqsQueues[*input.QueueUrl] + Expect(found).To(BeTrue(), "SQS queue does not exist: %q", *input.QueueUrl) + + return &sqs.ReceiveMessageOutput{Messages: append([]*sqs.Message{}, messages...)}, nil + } + + deleteSqsMessageFunc = func(ctx aws.Context, input *sqs.DeleteMessageInput, options ...awsrequest.Option) (*sqs.DeleteMessageOutput, error) { + if err := ctx.Err(); err != nil { + return nil, err + } + Expect(input.QueueUrl).ToNot(BeNil()) + + queue, found := sqsQueues[*input.QueueUrl] + Expect(found).To(BeTrue(), "SQS queue does not exist: %q", *input.QueueUrl) + + updatedQueue := make([]*sqs.Message, 0, len(queue)) + for i, m := range queue { + if m.ReceiptHandle == input.ReceiptHandle { + updatedQueue = append(updatedQueue, queue[:i]...) + updatedQueue = append(updatedQueue, queue[i+1:]...) + break + } + } + sqsQueues[*input.QueueUrl] = updatedQueue + + return &sqs.DeleteMessageOutput{}, nil + } + + sqsClient := SqsClient{ + ReceiveSqsMessageFunc: func(ctx aws.Context, input *sqs.ReceiveMessageInput, options ...awsrequest.Option) (*sqs.ReceiveMessageOutput, error) { + return receiveSqsMessageFunc(ctx, input, options...) + }, + DeleteSqsMessageFunc: func(ctx aws.Context, input *sqs.DeleteMessageInput, options ...awsrequest.Option) (*sqs.DeleteMessageOutput, error) { + return deleteSqsMessageFunc(ctx, input, options...) + }, + } + + kubeGetFunc = func(ctx context.Context, key client.ObjectKey, object client.Object) error { + if err := ctx.Err(); err != nil { + return err + } + + switch out := object.(type) { + case *v1.Node: + n, found := nodes[key] + if !found { + return k8serrors.NewNotFound(schema.GroupResource{}, key.String()) + } + *out = *n + + case *v1alpha1.Terminator: + t, found := terminators[key] + if !found { + return k8serrors.NewNotFound(schema.GroupResource{}, key.String()) + } + *out = *t + + default: + return fmt.Errorf("unknown type: %s", reflect.TypeOf(object).Name()) + } + return nil + } + + kubeClient := KubeClient(func(ctx context.Context, key client.ObjectKey, object client.Object) error { + return kubeGetFunc(ctx, key, object) + }) + + cordonFunc = func(_ *kubectl.Helper, node *v1.Node, desired bool) error { + if _, found := nodes[types.NamespacedName{Name: node.Name}]; !found { + return fmt.Errorf("node does not exist: %q", node.Name) + } + cordonedNodes[node.Name] = true + return nil + } + + drainFunc = func(_ *kubectl.Helper, nodeName string) error { + if _, found := nodes[types.NamespacedName{Name: nodeName}]; !found { + return fmt.Errorf("node does not exist: %q", nodeName) + } + drainedNodes[nodeName] = true + return nil + } + + // 3. Construct the reconciler. + + nodeGetter, err := node.NewGetter(kubeClient) + Expect(nodeGetter, err).ToNot(BeNil()) + + nodeNameGetter, err := nodename.NewGetter(ec2Client) + Expect(nodeNameGetter, err).ToNot(BeNil()) + + asgTerminateEventV1Parser, err := asgterminateeventv1.NewParser(asgClient) + Expect(asgTerminateEventV1Parser, err).ToNot(BeNil()) + + asgTerminateEventV2Parser, err := asgterminateeventv2.NewParser(asgClient) + Expect(asgTerminateEventV2Parser, err).ToNot(BeNil()) + + sqsMessageParser, err := terminator.NewSqsMessageParser(event.NewParser( + asgTerminateEventV1Parser, + asgTerminateEventV2Parser, + rebalancerecommendationeventv0.NewParser(), + scheduledchangeeventv1.NewParser(), + spotinterruptioneventv1.NewParser(), + statechangeeventv1.NewParser(), + )) + Expect(sqsMessageParser, err).ToNot(BeNil()) + + terminatorGetter, err := terminator.NewGetter(kubeClient) + Expect(terminatorGetter, err).ToNot(BeNil()) + + sqsMessageClient, err := sqsmessage.NewClient(sqsClient) + Expect(sqsMessageClient, err).ToNot(BeNil()) + + terminatorSqsClientBuilder, err := terminator.NewSqsClientBuilder(sqsMessageClient) + Expect(terminatorSqsClientBuilder, err).ToNot(BeNil()) + + cordoner, err := kubectlcordondrain.NewCordoner(func(h *kubectl.Helper, n *v1.Node, d bool) error { + return cordonFunc(h, n, d) + }) + Expect(cordoner, err).ToNot(BeNil()) + + drainer, err := kubectlcordondrain.NewDrainer(func(h *kubectl.Helper, n string) error { + return drainFunc(h, n) + }) + Expect(drainer, err).ToNot(BeNil()) + + cordonDrainerBuilder, err := kubectlcordondrain.NewBuilder(&kubernetes.Clientset{}, cordoner, drainer) + Expect(cordonDrainerBuilder, err).ToNot(BeNil()) + + terminatorCordonDrainerBuilder, err := terminator.NewCordonDrainerBuilder(cordonDrainerBuilder) + Expect(terminatorCordonDrainerBuilder, err).ToNot(BeNil()) + + reconciler = terminator.Reconciler{ + Name: "terminator", + RequeueInterval: time.Duration(10) * time.Second, + NodeGetter: nodeGetter, + NodeNameGetter: nodeNameGetter, + SqsClientBuilder: terminatorSqsClientBuilder, + SqsMessageParser: sqsMessageParser, + Getter: terminatorGetter, + CordonDrainerBuilder: terminatorCordonDrainerBuilder, + } + }) + + // Run the reconciliation before each test subject. + JustBeforeEach(func() { + result, err = reconciler.Reconcile(ctx, request) + }) +}) diff --git a/src/test/sqsclient.go b/src/test/sqsclient.go new file mode 100644 index 00000000..31ac0397 --- /dev/null +++ b/src/test/sqsclient.go @@ -0,0 +1,41 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package test + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/service/sqs" +) + +type ( + ReceiveSqsMessageFunc = func(aws.Context, *sqs.ReceiveMessageInput, ...request.Option) (*sqs.ReceiveMessageOutput, error) + DeleteSqsMessageFunc = func(aws.Context, *sqs.DeleteMessageInput, ...request.Option) (*sqs.DeleteMessageOutput, error) + + SqsClient struct { + ReceiveSqsMessageFunc + DeleteSqsMessageFunc + } +) + +func (s SqsClient) ReceiveMessageWithContext(ctx aws.Context, input *sqs.ReceiveMessageInput, options ...request.Option) (*sqs.ReceiveMessageOutput, error) { + return s.ReceiveSqsMessageFunc(ctx, input, options...) +} + +func (s SqsClient) DeleteMessageWithContext(ctx aws.Context, input *sqs.DeleteMessageInput, options ...request.Option) (*sqs.DeleteMessageOutput, error) { + return s.DeleteSqsMessageFunc(ctx, input, options...) +} From 68b973e27233612dc34bdf9a5fedec05cf9aa776 Mon Sep 17 00:00:00 2001 From: Jerad C Date: Mon, 4 Apr 2022 13:02:40 -0500 Subject: [PATCH 02/13] fix capitalization * "Url" to "URL" * "Aws" to "AWS" * "Asg" to "ASG" * "Ec2" to "EC2" * "Sqs" to "SQS" --- src/api/v1alpha1/terminator_logging.go | 6 +- src/api/v1alpha1/terminator_types.go | 8 +- src/api/v1alpha1/terminator_validation.go | 12 +- src/api/v1alpha1/zz_generated.deepcopy.go | 10 +- .../templates/node.k8s.aws_terminators.yaml | 4 +- src/cmd/controller/main.go | 12 +- src/pkg/event/asgterminate/v1/awsevent.go | 20 +- src/pkg/event/asgterminate/v1/event.go | 18 +- src/pkg/event/asgterminate/v1/parser.go | 10 +- src/pkg/event/asgterminate/v1/types.go | 2 +- src/pkg/event/asgterminate/v2/awsevent.go | 20 +- src/pkg/event/asgterminate/v2/event.go | 18 +- src/pkg/event/asgterminate/v2/parser.go | 10 +- src/pkg/event/asgterminate/v2/types.go | 2 +- src/pkg/event/metadata.go | 4 +- src/pkg/event/noop.go | 6 +- src/pkg/event/parser.go | 2 +- .../rebalancerecommendation/v0/awsevent.go | 16 +- .../event/rebalancerecommendation/v0/event.go | 10 +- .../rebalancerecommendation/v0/parser.go | 2 +- src/pkg/event/scheduledchange/v1/awsevent.go | 16 +- src/pkg/event/scheduledchange/v1/event.go | 10 +- src/pkg/event/scheduledchange/v1/parser.go | 2 +- src/pkg/event/spotinterruption/v1/awsevent.go | 16 +- src/pkg/event/spotinterruption/v1/event.go | 10 +- src/pkg/event/spotinterruption/v1/parser.go | 2 +- src/pkg/event/statechange/v1/awsevent.go | 16 +- src/pkg/event/statechange/v1/event.go | 10 +- src/pkg/event/statechange/v1/parser.go | 2 +- src/pkg/event/types.go | 2 +- src/pkg/node/name/getter.go | 8 +- src/pkg/sqsmessage/client.go | 16 +- src/pkg/terminator/parser.go | 2 +- src/pkg/terminator/reconciler.go | 24 +- src/pkg/terminator/sqsclient.go | 44 ++-- src/test/asgclient.go | 6 +- src/test/ec2client.go | 6 +- src/test/reconciliation_test.go | 212 +++++++++--------- src/test/sqsclient.go | 18 +- 39 files changed, 307 insertions(+), 307 deletions(-) diff --git a/src/api/v1alpha1/terminator_logging.go b/src/api/v1alpha1/terminator_logging.go index c563a2b1..7ba8c822 100644 --- a/src/api/v1alpha1/terminator_logging.go +++ b/src/api/v1alpha1/terminator_logging.go @@ -21,12 +21,12 @@ import ( ) func (t *TerminatorSpec) MarshalLogObject(enc zapcore.ObjectEncoder) error { - enc.AddObject("sqs", t.Sqs) + enc.AddObject("sqs", t.SQS) enc.AddObject("drain", t.Drain) return nil } -func (s SqsSpec) MarshalLogObject(enc zapcore.ObjectEncoder) error { +func (s SQSSpec) MarshalLogObject(enc zapcore.ObjectEncoder) error { enc.AddArray("attributeNames", zapcore.ArrayMarshalerFunc(func(enc zapcore.ArrayEncoder) error { for _, attrName := range s.AttributeNames { enc.AppendString(attrName) @@ -43,7 +43,7 @@ func (s SqsSpec) MarshalLogObject(enc zapcore.ObjectEncoder) error { return nil })) - enc.AddString("queueUrl", s.QueueUrl) + enc.AddString("queueURL", s.QueueURL) enc.AddInt64("visibilityTimeoutSeconds", s.VisibilityTimeoutSeconds) enc.AddInt64("waitTimeSeconds", s.WaitTimeSeconds) return nil diff --git a/src/api/v1alpha1/terminator_types.go b/src/api/v1alpha1/terminator_types.go index 40723fd9..7063dc0a 100644 --- a/src/api/v1alpha1/terminator_types.go +++ b/src/api/v1alpha1/terminator_types.go @@ -30,17 +30,17 @@ type TerminatorSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file - Sqs SqsSpec `json:"sqs,omitempty"` + SQS SQSSpec `json:"sqs,omitempty"` Drain DrainSpec `json:"drain,omitempty"` } -// SqsSpec defines inputs to SQS "receive messages" requests. -type SqsSpec struct { +// SQSSpec defines inputs to SQS "receive messages" requests. +type SQSSpec struct { // https://pkg.go.dev/github.com/aws/aws-sdk-go@v1.38.55/service/sqs#ReceiveMessageInput AttributeNames []string `json:"attributeNames,omitempty"` MaxNumberOfMessages int64 `json:"maxNumberOfMessages,omitempty"` MessageAttributeNames []string `json:"messageAttributeNames,omitempty"` - QueueUrl string `json:"queueUrl,omitempty"` + QueueURL string `json:"queueURL,omitempty"` VisibilityTimeoutSeconds int64 `json:"visibilityTimeoutSeconds,omitempty"` WaitTimeSeconds int64 `json:"waitTimeSeconds,omitempty"` } diff --git a/src/api/v1alpha1/terminator_validation.go b/src/api/v1alpha1/terminator_validation.go index 2f79fd83..5ec54aee 100644 --- a/src/api/v1alpha1/terminator_validation.go +++ b/src/api/v1alpha1/terminator_validation.go @@ -30,7 +30,7 @@ import ( var ( // https://github.com/aws/aws-sdk-go/blob/v1.38.55/service/sqs/api.go#L3966-L3994 - knownSqsAttributeNames = sets.NewString(sqs.MessageSystemAttributeName_Values()...) + knownSQSAttributeNames = sets.NewString(sqs.MessageSystemAttributeName_Values()...) ) func (t *Terminator) Validate(_ context.Context) (errs *apis.FieldError) { @@ -41,12 +41,12 @@ func (t *Terminator) Validate(_ context.Context) (errs *apis.FieldError) { } func (t *TerminatorSpec) validate() (errs *apis.FieldError) { - return t.Sqs.validate().ViaField("sqs") + return t.SQS.validate().ViaField("sqs") } -func (s *SqsSpec) validate() (errs *apis.FieldError) { +func (s *SQSSpec) validate() (errs *apis.FieldError) { for _, attrName := range s.AttributeNames { - if !knownSqsAttributeNames.Has(attrName) { + if !knownSQSAttributeNames.Has(attrName) { errs = errs.Also(apis.ErrInvalidValue(attrName, "attributeNames")) } } @@ -74,8 +74,8 @@ func (s *SqsSpec) validate() (errs *apis.FieldError) { } } - if _, err := url.Parse(s.QueueUrl); err != nil { - errs = errs.Also(apis.ErrInvalidValue(s.QueueUrl, "queueUrl", "must be a valid URL")) + if _, err := url.Parse(s.QueueURL); err != nil { + errs = errs.Also(apis.ErrInvalidValue(s.QueueURL, "queueURL", "must be a valid URL")) } if s.VisibilityTimeoutSeconds < 0 { diff --git a/src/api/v1alpha1/zz_generated.deepcopy.go b/src/api/v1alpha1/zz_generated.deepcopy.go index 20b76cc7..63e32546 100644 --- a/src/api/v1alpha1/zz_generated.deepcopy.go +++ b/src/api/v1alpha1/zz_generated.deepcopy.go @@ -41,7 +41,7 @@ func (in *DrainSpec) DeepCopy() *DrainSpec { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SqsSpec) DeepCopyInto(out *SqsSpec) { +func (in *SQSSpec) DeepCopyInto(out *SQSSpec) { *out = *in if in.AttributeNames != nil { in, out := &in.AttributeNames, &out.AttributeNames @@ -55,12 +55,12 @@ func (in *SqsSpec) DeepCopyInto(out *SqsSpec) { } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SqsSpec. -func (in *SqsSpec) DeepCopy() *SqsSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SQSSpec. +func (in *SQSSpec) DeepCopy() *SQSSpec { if in == nil { return nil } - out := new(SqsSpec) + out := new(SQSSpec) in.DeepCopyInto(out) return out } @@ -127,7 +127,7 @@ func (in *TerminatorList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TerminatorSpec) DeepCopyInto(out *TerminatorSpec) { *out = *in - in.Sqs.DeepCopyInto(&out.Sqs) + in.SQS.DeepCopyInto(&out.SQS) out.Drain = in.Drain } diff --git a/src/charts/aws-node-termination-handler-2/templates/node.k8s.aws_terminators.yaml b/src/charts/aws-node-termination-handler-2/templates/node.k8s.aws_terminators.yaml index cdaf7a73..5b49a24e 100644 --- a/src/charts/aws-node-termination-handler-2/templates/node.k8s.aws_terminators.yaml +++ b/src/charts/aws-node-termination-handler-2/templates/node.k8s.aws_terminators.yaml @@ -116,13 +116,13 @@ spec: default: {{- toYaml . | nindent 22 }} {{- end }} - queueUrl: + queueURL: description: | The URL of the Amazon SQS queue from which messages are received. * Queue URLs and names are case-sensitive. - * QueueUrl is a required field + * QueueURL is a required field type: string visibilityTimeoutSeconds: description: | diff --git a/src/cmd/controller/main.go b/src/cmd/controller/main.go index 0340dc39..854604bb 100644 --- a/src/cmd/controller/main.go +++ b/src/cmd/controller/main.go @@ -127,7 +127,7 @@ func main() { } kubeClient := mgr.GetClient() - awsSession, err := newAwsSession() + awsSession, err := newAWSSession() if err != nil { logger.With("error", err).Fatal("failed to initialize AWS session") } @@ -161,7 +161,7 @@ func main() { if err != nil { logger.With("error", err).Fatal("failed to create ASG instance-terminate lifecycle event v2 parser") } - sqsMessageParser, err := terminator.NewSqsMessageParser(event.NewParser( + sqsMessageParser, err := terminator.NewSQSMessageParser(event.NewParser( asgTerminateEventV1Parser, asgTerminateEventV2Parser, rebalancerecommendationeventv0.NewParser(), @@ -182,7 +182,7 @@ func main() { if err != nil { logger.With("error", err).Fatal("failed to create SQS message client") } - terminatorSqsClientBuilder, err := terminator.NewSqsClientBuilder(sqsMessageClient) + terminatorSQSClientBuilder, err := terminator.NewSQSClientBuilder(sqsMessageClient) if err != nil { logger.With("error", err).Fatal("failed to create terminator SQS message client builder") } @@ -205,8 +205,8 @@ func main() { RequeueInterval: time.Duration(10) * time.Second, NodeGetter: nodeGetter, NodeNameGetter: nodeNameGetter, - SqsClientBuilder: terminatorSqsClientBuilder, - SqsMessageParser: sqsMessageParser, + SQSClientBuilder: terminatorSQSClientBuilder, + SQSMessageParser: sqsMessageParser, Getter: terminatorGetter, CordonDrainerBuilder: terminatorCordonDrainerBuilder, } @@ -244,7 +244,7 @@ func parseOptions() Options { return options } -func newAwsSession() (*session.Session, error) { +func newAWSSession() (*session.Session, error) { config := aws.NewConfig().WithSTSRegionalEndpoint(endpoints.RegionalSTSEndpoint) sess, err := session.NewSessionWithOptions(session.Options{ Config: *config, diff --git a/src/pkg/event/asgterminate/v1/awsevent.go b/src/pkg/event/asgterminate/v1/awsevent.go index 6ca917ca..89cfea5d 100644 --- a/src/pkg/event/asgterminate/v1/awsevent.go +++ b/src/pkg/event/asgterminate/v1/awsevent.go @@ -23,33 +23,33 @@ import ( "go.uber.org/zap/zapcore" ) -// AwsEvent contains the properties defined in AWS EventBridge schema +// AWSEvent contains the properties defined in AWS EventBridge schema // aws.autoscaling@EC2InstanceTerminateLifecycleAction v1. -type AwsEvent struct { - event.AwsMetadata +type AWSEvent struct { + event.AWSMetadata - Detail Ec2InstanceTerminateLifecycleActionDetail `json:"detail"` + Detail EC2InstanceTerminateLifecycleActionDetail `json:"detail"` } -func (e AwsEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { - zap.Inline(e.AwsMetadata).AddTo(enc) +func (e AWSEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(e.AWSMetadata).AddTo(enc) enc.AddObject("detail", e.Detail) return nil } -type Ec2InstanceTerminateLifecycleActionDetail struct { +type EC2InstanceTerminateLifecycleActionDetail struct { LifecycleHookName string `json:"LifecycleHookName"` LifecycleTransition string `json:"LifecycleTransition"` AutoScalingGroupName string `json:"AutoScalingGroupName"` - Ec2InstanceId string `json:"EC2InstanceId"` + EC2InstanceId string `json:"EC2InstanceId"` LifecycleActionToken string `json:"LifecycleActionToken"` } -func (e Ec2InstanceTerminateLifecycleActionDetail) MarshalLogObject(enc zapcore.ObjectEncoder) error { +func (e EC2InstanceTerminateLifecycleActionDetail) MarshalLogObject(enc zapcore.ObjectEncoder) error { enc.AddString("LifecycleHookName", e.LifecycleHookName) enc.AddString("LifecycleTransition", e.LifecycleTransition) enc.AddString("AutoScalingGroupName", e.AutoScalingGroupName) - enc.AddString("EC2InstanceId", e.Ec2InstanceId) + enc.AddString("EC2InstanceId", e.EC2InstanceId) enc.AddString("LifecycleActionToken", e.LifecycleActionToken) return nil } diff --git a/src/pkg/event/asgterminate/v1/event.go b/src/pkg/event/asgterminate/v1/event.go index 65e5d375..754bd231 100644 --- a/src/pkg/event/asgterminate/v1/event.go +++ b/src/pkg/event/asgterminate/v1/event.go @@ -28,22 +28,22 @@ import ( "go.uber.org/zap/zapcore" ) -type Ec2InstanceTerminateLifecycleAction struct { - AsgLifecycleActionCompleter - AwsEvent +type EC2InstanceTerminateLifecycleAction struct { + ASGLifecycleActionCompleter + AWSEvent } -func (e Ec2InstanceTerminateLifecycleAction) Ec2InstanceIds() []string { - return []string{e.Detail.Ec2InstanceId} +func (e EC2InstanceTerminateLifecycleAction) EC2InstanceIds() []string { + return []string{e.Detail.EC2InstanceId} } -func (e Ec2InstanceTerminateLifecycleAction) Done(ctx context.Context) (bool, error) { +func (e EC2InstanceTerminateLifecycleAction) Done(ctx context.Context) (bool, error) { if _, err := e.CompleteLifecycleActionWithContext(ctx, &autoscaling.CompleteLifecycleActionInput{ AutoScalingGroupName: aws.String(e.Detail.AutoScalingGroupName), LifecycleActionResult: aws.String("CONTINUE"), LifecycleHookName: aws.String(e.Detail.LifecycleHookName), LifecycleActionToken: aws.String(e.Detail.LifecycleActionToken), - InstanceId: aws.String(e.Detail.Ec2InstanceId), + InstanceId: aws.String(e.Detail.EC2InstanceId), }); err != nil { var f awserr.RequestFailure return errors.As(err, &f) && f.StatusCode() != 400, err @@ -52,7 +52,7 @@ func (e Ec2InstanceTerminateLifecycleAction) Done(ctx context.Context) (bool, er return false, nil } -func (e Ec2InstanceTerminateLifecycleAction) MarshalLogObject(enc zapcore.ObjectEncoder) error { - zap.Inline(e.AwsEvent).AddTo(enc) +func (e EC2InstanceTerminateLifecycleAction) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(e.AWSEvent).AddTo(enc) return nil } diff --git a/src/pkg/event/asgterminate/v1/parser.go b/src/pkg/event/asgterminate/v1/parser.go index 175e99a7..cf041eac 100644 --- a/src/pkg/event/asgterminate/v1/parser.go +++ b/src/pkg/event/asgterminate/v1/parser.go @@ -33,21 +33,21 @@ const ( ) type parser struct { - AsgLifecycleActionCompleter + ASGLifecycleActionCompleter } -func NewParser(completer AsgLifecycleActionCompleter) (event.Parser, error) { +func NewParser(completer ASGLifecycleActionCompleter) (event.Parser, error) { if completer == nil { return nil, fmt.Errorf("argument 'completer' is nil") } - return parser{AsgLifecycleActionCompleter: completer}, nil + return parser{ASGLifecycleActionCompleter: completer}, nil } func (p parser) Parse(ctx context.Context, str string) event.Event { ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("asgTerminateLifecycleAction.v1")) - evt := Ec2InstanceTerminateLifecycleAction{ - AsgLifecycleActionCompleter: p.AsgLifecycleActionCompleter, + evt := EC2InstanceTerminateLifecycleAction{ + ASGLifecycleActionCompleter: p.ASGLifecycleActionCompleter, } if err := json.Unmarshal([]byte(str), &evt); err != nil { logging.FromContext(ctx). diff --git a/src/pkg/event/asgterminate/v1/types.go b/src/pkg/event/asgterminate/v1/types.go index 3d7e30c0..423f7dbb 100644 --- a/src/pkg/event/asgterminate/v1/types.go +++ b/src/pkg/event/asgterminate/v1/types.go @@ -22,6 +22,6 @@ import ( "github.com/aws/aws-sdk-go/service/autoscaling" ) -type AsgLifecycleActionCompleter interface { +type ASGLifecycleActionCompleter interface { CompleteLifecycleActionWithContext(aws.Context, *autoscaling.CompleteLifecycleActionInput, ...request.Option) (*autoscaling.CompleteLifecycleActionOutput, error) } diff --git a/src/pkg/event/asgterminate/v2/awsevent.go b/src/pkg/event/asgterminate/v2/awsevent.go index 230c817b..ad2fd98a 100644 --- a/src/pkg/event/asgterminate/v2/awsevent.go +++ b/src/pkg/event/asgterminate/v2/awsevent.go @@ -23,36 +23,36 @@ import ( "go.uber.org/zap/zapcore" ) -// AwsEvent contains the properties defined in AWS EventBridge schema +// AWSEvent contains the properties defined in AWS EventBridge schema // aws.autoscaling@EC2InstanceTerminateLifecycleAction v2. -type AwsEvent struct { - event.AwsMetadata +type AWSEvent struct { + event.AWSMetadata - Detail Ec2InstanceTerminateLifecycleActionDetail `json:"detail"` + Detail EC2InstanceTerminateLifecycleActionDetail `json:"detail"` } -func (e AwsEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { - zap.Inline(e.AwsMetadata).AddTo(enc) +func (e AWSEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(e.AWSMetadata).AddTo(enc) enc.AddObject("detail", e.Detail) return nil } -type Ec2InstanceTerminateLifecycleActionDetail struct { +type EC2InstanceTerminateLifecycleActionDetail struct { LifecycleHookName string `json:"LifecycleHookName"` LifecycleTransition string `json:"LifecycleTransition"` AutoScalingGroupName string `json:"AutoScalingGroupName"` - Ec2InstanceId string `json:"EC2InstanceId"` + EC2InstanceId string `json:"EC2InstanceId"` LifecycleActionToken string `json:"LifecycleActionToken"` NotificationMetadata string `json:"NotificationMetadata"` Origin string `json:"Origin"` Destination string `json:"Destination"` } -func (e Ec2InstanceTerminateLifecycleActionDetail) MarshalLogObject(enc zapcore.ObjectEncoder) error { +func (e EC2InstanceTerminateLifecycleActionDetail) MarshalLogObject(enc zapcore.ObjectEncoder) error { enc.AddString("LifecycleHookName", e.LifecycleHookName) enc.AddString("LifecycleTransition", e.LifecycleTransition) enc.AddString("AutoScalingGroupName", e.AutoScalingGroupName) - enc.AddString("EC2InstanceId", e.Ec2InstanceId) + enc.AddString("EC2InstanceId", e.EC2InstanceId) enc.AddString("LifecycleActionToken", e.LifecycleActionToken) return nil } diff --git a/src/pkg/event/asgterminate/v2/event.go b/src/pkg/event/asgterminate/v2/event.go index c68670e7..4257f503 100644 --- a/src/pkg/event/asgterminate/v2/event.go +++ b/src/pkg/event/asgterminate/v2/event.go @@ -28,22 +28,22 @@ import ( "go.uber.org/zap/zapcore" ) -type Ec2InstanceTerminateLifecycleAction struct { - AsgLifecycleActionCompleter - AwsEvent +type EC2InstanceTerminateLifecycleAction struct { + ASGLifecycleActionCompleter + AWSEvent } -func (e Ec2InstanceTerminateLifecycleAction) Ec2InstanceIds() []string { - return []string{e.Detail.Ec2InstanceId} +func (e EC2InstanceTerminateLifecycleAction) EC2InstanceIds() []string { + return []string{e.Detail.EC2InstanceId} } -func (e Ec2InstanceTerminateLifecycleAction) Done(ctx context.Context) (bool, error) { +func (e EC2InstanceTerminateLifecycleAction) Done(ctx context.Context) (bool, error) { if _, err := e.CompleteLifecycleActionWithContext(ctx, &autoscaling.CompleteLifecycleActionInput{ AutoScalingGroupName: aws.String(e.Detail.AutoScalingGroupName), LifecycleActionResult: aws.String("CONTINUE"), LifecycleHookName: aws.String(e.Detail.LifecycleHookName), LifecycleActionToken: aws.String(e.Detail.LifecycleActionToken), - InstanceId: aws.String(e.Detail.Ec2InstanceId), + InstanceId: aws.String(e.Detail.EC2InstanceId), }); err != nil { var f awserr.RequestFailure return errors.As(err, &f) && f.StatusCode() != 400, err @@ -52,7 +52,7 @@ func (e Ec2InstanceTerminateLifecycleAction) Done(ctx context.Context) (bool, er return false, nil } -func (e Ec2InstanceTerminateLifecycleAction) MarshalLogObject(enc zapcore.ObjectEncoder) error { - zap.Inline(e.AwsEvent).AddTo(enc) +func (e EC2InstanceTerminateLifecycleAction) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(e.AWSEvent).AddTo(enc) return nil } diff --git a/src/pkg/event/asgterminate/v2/parser.go b/src/pkg/event/asgterminate/v2/parser.go index ae2a1715..ee6ee111 100644 --- a/src/pkg/event/asgterminate/v2/parser.go +++ b/src/pkg/event/asgterminate/v2/parser.go @@ -33,21 +33,21 @@ const ( ) type parser struct { - AsgLifecycleActionCompleter + ASGLifecycleActionCompleter } -func NewParser(completer AsgLifecycleActionCompleter) (event.Parser, error) { +func NewParser(completer ASGLifecycleActionCompleter) (event.Parser, error) { if completer == nil { return nil, fmt.Errorf("argument 'completer' is nil") } - return parser{AsgLifecycleActionCompleter: completer}, nil + return parser{ASGLifecycleActionCompleter: completer}, nil } func (p parser) Parse(ctx context.Context, str string) event.Event { ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("asgTerminateLifecycleAction.v2")) - evt := Ec2InstanceTerminateLifecycleAction{ - AsgLifecycleActionCompleter: p.AsgLifecycleActionCompleter, + evt := EC2InstanceTerminateLifecycleAction{ + ASGLifecycleActionCompleter: p.ASGLifecycleActionCompleter, } if err := json.Unmarshal([]byte(str), &evt); err != nil { logging.FromContext(ctx). diff --git a/src/pkg/event/asgterminate/v2/types.go b/src/pkg/event/asgterminate/v2/types.go index 645838e9..e4ec3483 100644 --- a/src/pkg/event/asgterminate/v2/types.go +++ b/src/pkg/event/asgterminate/v2/types.go @@ -22,6 +22,6 @@ import ( "github.com/aws/aws-sdk-go/service/autoscaling" ) -type AsgLifecycleActionCompleter interface { +type ASGLifecycleActionCompleter interface { CompleteLifecycleActionWithContext(aws.Context, *autoscaling.CompleteLifecycleActionInput, ...request.Option) (*autoscaling.CompleteLifecycleActionOutput, error) } diff --git a/src/pkg/event/metadata.go b/src/pkg/event/metadata.go index 16fbf95a..a0c19228 100644 --- a/src/pkg/event/metadata.go +++ b/src/pkg/event/metadata.go @@ -22,7 +22,7 @@ import ( "go.uber.org/zap/zapcore" ) -type AwsMetadata struct { +type AWSMetadata struct { Account string `json:"account"` DetailType string `json:"detail-type"` Id string `json:"id"` @@ -33,7 +33,7 @@ type AwsMetadata struct { Version string `json:"version"` } -func (e AwsMetadata) MarshalLogObject(enc zapcore.ObjectEncoder) error { +func (e AWSMetadata) MarshalLogObject(enc zapcore.ObjectEncoder) error { enc.AddString("source", e.Source) enc.AddString("detail-type", e.DetailType) enc.AddString("id", e.Id) diff --git a/src/pkg/event/noop.go b/src/pkg/event/noop.go index 353675c1..971c454c 100644 --- a/src/pkg/event/noop.go +++ b/src/pkg/event/noop.go @@ -23,9 +23,9 @@ import ( "go.uber.org/zap/zapcore" ) -type noop AwsMetadata +type noop AWSMetadata -func (n noop) Ec2InstanceIds() []string { +func (n noop) EC2InstanceIds() []string { return []string{} } @@ -34,6 +34,6 @@ func (n noop) Done(_ context.Context) (bool, error) { } func (n noop) MarshalLogObject(enc zapcore.ObjectEncoder) error { - zap.Inline(AwsMetadata(n)).AddTo(enc) + zap.Inline(AWSMetadata(n)).AddTo(enc) return nil } diff --git a/src/pkg/event/parser.go b/src/pkg/event/parser.go index a7b19c12..5f88c93b 100644 --- a/src/pkg/event/parser.go +++ b/src/pkg/event/parser.go @@ -42,7 +42,7 @@ func (p parser) Parse(ctx context.Context, str string) Event { } } - md := AwsMetadata{} + md := AWSMetadata{} if err := json.Unmarshal([]byte(str), &md); err != nil { logging.FromContext(ctx). With("error", err). diff --git a/src/pkg/event/rebalancerecommendation/v0/awsevent.go b/src/pkg/event/rebalancerecommendation/v0/awsevent.go index 9760f9f4..6b9902e8 100644 --- a/src/pkg/event/rebalancerecommendation/v0/awsevent.go +++ b/src/pkg/event/rebalancerecommendation/v0/awsevent.go @@ -23,25 +23,25 @@ import ( "go.uber.org/zap/zapcore" ) -// AwsEvent contains the properties defined by +// AWSEvent contains the properties defined by // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/rebalance-recommendations.html#monitor-rebalance-recommendations -type AwsEvent struct { - event.AwsMetadata +type AWSEvent struct { + event.AWSMetadata - Detail Ec2InstanceRebalanceRecommendationDetail `json:"detail"` + Detail EC2InstanceRebalanceRecommendationDetail `json:"detail"` } -func (e AwsEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { - zap.Inline(e.AwsMetadata).AddTo(enc) +func (e AWSEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(e.AWSMetadata).AddTo(enc) enc.AddObject("detail", e.Detail) return nil } -type Ec2InstanceRebalanceRecommendationDetail struct { +type EC2InstanceRebalanceRecommendationDetail struct { InstanceId string `json:"instance-id"` } -func (e Ec2InstanceRebalanceRecommendationDetail) MarshalLogObject(enc zapcore.ObjectEncoder) error { +func (e EC2InstanceRebalanceRecommendationDetail) MarshalLogObject(enc zapcore.ObjectEncoder) error { enc.AddString("instance-id", e.InstanceId) return nil } diff --git a/src/pkg/event/rebalancerecommendation/v0/event.go b/src/pkg/event/rebalancerecommendation/v0/event.go index ea41d301..341bf56e 100644 --- a/src/pkg/event/rebalancerecommendation/v0/event.go +++ b/src/pkg/event/rebalancerecommendation/v0/event.go @@ -23,17 +23,17 @@ import ( "go.uber.org/zap/zapcore" ) -type Ec2InstanceRebalanceRecommendation AwsEvent +type EC2InstanceRebalanceRecommendation AWSEvent -func (e Ec2InstanceRebalanceRecommendation) Ec2InstanceIds() []string { +func (e EC2InstanceRebalanceRecommendation) EC2InstanceIds() []string { return []string{e.Detail.InstanceId} } -func (e Ec2InstanceRebalanceRecommendation) Done(_ context.Context) (bool, error) { +func (e EC2InstanceRebalanceRecommendation) Done(_ context.Context) (bool, error) { return false, nil } -func (e Ec2InstanceRebalanceRecommendation) MarshalLogObject(enc zapcore.ObjectEncoder) error { - zap.Inline(AwsEvent(e)).AddTo(enc) +func (e EC2InstanceRebalanceRecommendation) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(AWSEvent(e)).AddTo(enc) return nil } diff --git a/src/pkg/event/rebalancerecommendation/v0/parser.go b/src/pkg/event/rebalancerecommendation/v0/parser.go index c82c78e8..d07650e8 100644 --- a/src/pkg/event/rebalancerecommendation/v0/parser.go +++ b/src/pkg/event/rebalancerecommendation/v0/parser.go @@ -37,7 +37,7 @@ func NewParser() event.Parser { func parse(ctx context.Context, str string) event.Event { ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("rebalanceRecommendation.v0")) - evt := Ec2InstanceRebalanceRecommendation{} + evt := EC2InstanceRebalanceRecommendation{} if err := json.Unmarshal([]byte(str), &evt); err != nil { logging.FromContext(ctx). With("error", err). diff --git a/src/pkg/event/scheduledchange/v1/awsevent.go b/src/pkg/event/scheduledchange/v1/awsevent.go index 28f8a466..4469f2f7 100644 --- a/src/pkg/event/scheduledchange/v1/awsevent.go +++ b/src/pkg/event/scheduledchange/v1/awsevent.go @@ -23,21 +23,21 @@ import ( "go.uber.org/zap/zapcore" ) -// AwsEvent contains the properties defined in AWS EventBridge schema +// AWSEvent contains the properties defined in AWS EventBridge schema // aws.health@AWSHealthEvent v1. -type AwsEvent struct { - event.AwsMetadata +type AWSEvent struct { + event.AWSMetadata - Detail AwsHealthEventDetail `json:"detail"` + Detail AWSHealthEventDetail `json:"detail"` } -func (e AwsEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { - zap.Inline(e.AwsMetadata).AddTo(enc) +func (e AWSEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(e.AWSMetadata).AddTo(enc) enc.AddObject("detail", e.Detail) return nil } -type AwsHealthEventDetail struct { +type AWSHealthEventDetail struct { EventArn string `json:"eventArn"` EventTypeCode string `json:"eventTypeCode"` Service string `json:"service"` @@ -48,7 +48,7 @@ type AwsHealthEventDetail struct { AffectedEntities []AffectedEntity `json:"affectedEntities"` } -func (e AwsHealthEventDetail) MarshalLogObject(enc zapcore.ObjectEncoder) error { +func (e AWSHealthEventDetail) MarshalLogObject(enc zapcore.ObjectEncoder) error { enc.AddString("eventArn", e.EventArn) enc.AddString("eventTypeCode", e.EventTypeCode) enc.AddString("eventTypeCategory", e.EventTypeCategory) diff --git a/src/pkg/event/scheduledchange/v1/event.go b/src/pkg/event/scheduledchange/v1/event.go index 1aa110da..e39a0e45 100644 --- a/src/pkg/event/scheduledchange/v1/event.go +++ b/src/pkg/event/scheduledchange/v1/event.go @@ -23,9 +23,9 @@ import ( "go.uber.org/zap/zapcore" ) -type AwsHealthEvent AwsEvent +type AWSHealthEvent AWSEvent -func (e AwsHealthEvent) Ec2InstanceIds() []string { +func (e AWSHealthEvent) EC2InstanceIds() []string { ids := make([]string, len(e.Detail.AffectedEntities)) for i, entity := range e.Detail.AffectedEntities { ids[i] = entity.EntityValue @@ -33,11 +33,11 @@ func (e AwsHealthEvent) Ec2InstanceIds() []string { return ids } -func (e AwsHealthEvent) Done(_ context.Context) (bool, error) { +func (e AWSHealthEvent) Done(_ context.Context) (bool, error) { return false, nil } -func (e AwsHealthEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { - zap.Inline(AwsEvent(e)).AddTo(enc) +func (e AWSHealthEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(AWSEvent(e)).AddTo(enc) return nil } diff --git a/src/pkg/event/scheduledchange/v1/parser.go b/src/pkg/event/scheduledchange/v1/parser.go index dec051ee..85a3b5ce 100644 --- a/src/pkg/event/scheduledchange/v1/parser.go +++ b/src/pkg/event/scheduledchange/v1/parser.go @@ -39,7 +39,7 @@ func NewParser() event.Parser { func parse(ctx context.Context, str string) event.Event { ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("scheduledChange.v1")) - evt := AwsHealthEvent{} + evt := AWSHealthEvent{} if err := json.Unmarshal([]byte(str), &evt); err != nil { logging.FromContext(ctx). With("error", err). diff --git a/src/pkg/event/spotinterruption/v1/awsevent.go b/src/pkg/event/spotinterruption/v1/awsevent.go index f8755369..c5829d97 100644 --- a/src/pkg/event/spotinterruption/v1/awsevent.go +++ b/src/pkg/event/spotinterruption/v1/awsevent.go @@ -23,26 +23,26 @@ import ( "go.uber.org/zap/zapcore" ) -// AwsEvent contains the properties defined in AWS EventBridge schema +// AWSEvent contains the properties defined in AWS EventBridge schema // aws.ec2@EC2SpotInstanceInterruptionWarning v1. -type AwsEvent struct { - event.AwsMetadata +type AWSEvent struct { + event.AWSMetadata - Detail Ec2SpotInstanceInterruptionWarningDetail `json:"detail"` + Detail EC2SpotInstanceInterruptionWarningDetail `json:"detail"` } -func (e AwsEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { - zap.Inline(e.AwsMetadata).AddTo(enc) +func (e AWSEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(e.AWSMetadata).AddTo(enc) enc.AddObject("detail", e.Detail) return nil } -type Ec2SpotInstanceInterruptionWarningDetail struct { +type EC2SpotInstanceInterruptionWarningDetail struct { InstanceId string `json:"instance-id"` InstanceAction string `json:"instance-action"` } -func (e Ec2SpotInstanceInterruptionWarningDetail) MarshalLogObject(enc zapcore.ObjectEncoder) error { +func (e EC2SpotInstanceInterruptionWarningDetail) MarshalLogObject(enc zapcore.ObjectEncoder) error { enc.AddString("instance-id", e.InstanceId) enc.AddString("instance-action", e.InstanceAction) return nil diff --git a/src/pkg/event/spotinterruption/v1/event.go b/src/pkg/event/spotinterruption/v1/event.go index 511ee047..465c29fb 100644 --- a/src/pkg/event/spotinterruption/v1/event.go +++ b/src/pkg/event/spotinterruption/v1/event.go @@ -23,17 +23,17 @@ import ( "go.uber.org/zap/zapcore" ) -type Ec2SpotInstanceInterruptionWarning AwsEvent +type EC2SpotInstanceInterruptionWarning AWSEvent -func (e Ec2SpotInstanceInterruptionWarning) Ec2InstanceIds() []string { +func (e EC2SpotInstanceInterruptionWarning) EC2InstanceIds() []string { return []string{e.Detail.InstanceId} } -func (e Ec2SpotInstanceInterruptionWarning) Done(_ context.Context) (bool, error) { +func (e EC2SpotInstanceInterruptionWarning) Done(_ context.Context) (bool, error) { return false, nil } -func (e Ec2SpotInstanceInterruptionWarning) MarshalLogObject(enc zapcore.ObjectEncoder) error { - zap.Inline(AwsEvent(e)).AddTo(enc) +func (e EC2SpotInstanceInterruptionWarning) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(AWSEvent(e)).AddTo(enc) return nil } diff --git a/src/pkg/event/spotinterruption/v1/parser.go b/src/pkg/event/spotinterruption/v1/parser.go index 94e82b4a..8472c428 100644 --- a/src/pkg/event/spotinterruption/v1/parser.go +++ b/src/pkg/event/spotinterruption/v1/parser.go @@ -37,7 +37,7 @@ func NewParser() event.Parser { func parse(ctx context.Context, str string) event.Event { ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("spotInterruption.v1")) - evt := Ec2SpotInstanceInterruptionWarning{} + evt := EC2SpotInstanceInterruptionWarning{} if err := json.Unmarshal([]byte(str), &evt); err != nil { logging.FromContext(ctx). With("error", err). diff --git a/src/pkg/event/statechange/v1/awsevent.go b/src/pkg/event/statechange/v1/awsevent.go index 20c14439..68cc6702 100644 --- a/src/pkg/event/statechange/v1/awsevent.go +++ b/src/pkg/event/statechange/v1/awsevent.go @@ -23,26 +23,26 @@ import ( "go.uber.org/zap/zapcore" ) -// AwsEvent contains the properties defined in AWS EventBridge schema +// AWSEvent contains the properties defined in AWS EventBridge schema // aws.ec2@EC2InstanceStateChangeNotification v1. -type AwsEvent struct { - event.AwsMetadata +type AWSEvent struct { + event.AWSMetadata - Detail Ec2InstanceStateChangeNotificationDetail `json:"detail"` + Detail EC2InstanceStateChangeNotificationDetail `json:"detail"` } -func (e AwsEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { - zap.Inline(e.AwsMetadata).AddTo(enc) +func (e AWSEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(e.AWSMetadata).AddTo(enc) enc.AddObject("detail", e.Detail) return nil } -type Ec2InstanceStateChangeNotificationDetail struct { +type EC2InstanceStateChangeNotificationDetail struct { InstanceId string `json:"instance-id"` State string `json:"state"` } -func (e Ec2InstanceStateChangeNotificationDetail) MarshalLogObject(enc zapcore.ObjectEncoder) error { +func (e EC2InstanceStateChangeNotificationDetail) MarshalLogObject(enc zapcore.ObjectEncoder) error { enc.AddString("instance-id", e.InstanceId) enc.AddString("state", e.State) return nil diff --git a/src/pkg/event/statechange/v1/event.go b/src/pkg/event/statechange/v1/event.go index 6769ceae..766121a4 100644 --- a/src/pkg/event/statechange/v1/event.go +++ b/src/pkg/event/statechange/v1/event.go @@ -23,17 +23,17 @@ import ( "go.uber.org/zap/zapcore" ) -type Ec2InstanceStateChangeNotification AwsEvent +type EC2InstanceStateChangeNotification AWSEvent -func (e Ec2InstanceStateChangeNotification) Ec2InstanceIds() []string { +func (e EC2InstanceStateChangeNotification) EC2InstanceIds() []string { return []string{e.Detail.InstanceId} } -func (e Ec2InstanceStateChangeNotification) Done(_ context.Context) (bool, error) { +func (e EC2InstanceStateChangeNotification) Done(_ context.Context) (bool, error) { return false, nil } -func (e Ec2InstanceStateChangeNotification) MarshalLogObject(enc zapcore.ObjectEncoder) error { - zap.Inline(AwsEvent(e)).AddTo(enc) +func (e EC2InstanceStateChangeNotification) MarshalLogObject(enc zapcore.ObjectEncoder) error { + zap.Inline(AWSEvent(e)).AddTo(enc) return nil } diff --git a/src/pkg/event/statechange/v1/parser.go b/src/pkg/event/statechange/v1/parser.go index 4b5edc30..a43f8da9 100644 --- a/src/pkg/event/statechange/v1/parser.go +++ b/src/pkg/event/statechange/v1/parser.go @@ -41,7 +41,7 @@ func NewParser() event.Parser { func parse(ctx context.Context, str string) event.Event { ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("stateChange.v1")) - evt := Ec2InstanceStateChangeNotification{} + evt := EC2InstanceStateChangeNotification{} if err := json.Unmarshal([]byte(str), &evt); err != nil { logging.FromContext(ctx). With("error", err). diff --git a/src/pkg/event/types.go b/src/pkg/event/types.go index a700fc55..3797f761 100644 --- a/src/pkg/event/types.go +++ b/src/pkg/event/types.go @@ -27,7 +27,7 @@ type ( zapcore.ObjectMarshaler Done(context.Context) (tryAgain bool, err error) - Ec2InstanceIds() []string + EC2InstanceIds() []string } Parser interface { diff --git a/src/pkg/node/name/getter.go b/src/pkg/node/name/getter.go index c37ef15d..ea834006 100644 --- a/src/pkg/node/name/getter.go +++ b/src/pkg/node/name/getter.go @@ -28,7 +28,7 @@ import ( ) type ( - Ec2InstancesDescriber interface { + EC2InstancesDescriber interface { DescribeInstancesWithContext(aws.Context, *ec2.DescribeInstancesInput, ...request.Option) (*ec2.DescribeInstancesOutput, error) } @@ -37,15 +37,15 @@ type ( } getter struct { - Ec2InstancesDescriber + EC2InstancesDescriber } ) -func NewGetter(describer Ec2InstancesDescriber) (Getter, error) { +func NewGetter(describer EC2InstancesDescriber) (Getter, error) { if describer == nil { return nil, fmt.Errorf("argument 'describer' is nil") } - return getter{Ec2InstancesDescriber: describer}, nil + return getter{EC2InstancesDescriber: describer}, nil } func (n getter) GetNodeName(ctx context.Context, instanceId string) (string, error) { diff --git a/src/pkg/sqsmessage/client.go b/src/pkg/sqsmessage/client.go index 3ef305cd..ac3288c7 100644 --- a/src/pkg/sqsmessage/client.go +++ b/src/pkg/sqsmessage/client.go @@ -28,29 +28,29 @@ import ( ) type ( - SqsClient interface { + SQSClient interface { ReceiveMessageWithContext(aws.Context, *sqs.ReceiveMessageInput, ...request.Option) (*sqs.ReceiveMessageOutput, error) DeleteMessageWithContext(aws.Context, *sqs.DeleteMessageInput, ...request.Option) (*sqs.DeleteMessageOutput, error) } Client interface { - GetSqsMessages(context.Context, *sqs.ReceiveMessageInput) ([]*sqs.Message, error) - DeleteSqsMessage(context.Context, *sqs.DeleteMessageInput) error + GetSQSMessages(context.Context, *sqs.ReceiveMessageInput) ([]*sqs.Message, error) + DeleteSQSMessage(context.Context, *sqs.DeleteMessageInput) error } sqsClient struct { - SqsClient + SQSClient } ) -func NewClient(client SqsClient) (Client, error) { +func NewClient(client SQSClient) (Client, error) { if client == nil { return nil, fmt.Errorf("argument 'client' is nil") } - return sqsClient{SqsClient: client}, nil + return sqsClient{SQSClient: client}, nil } -func (s sqsClient) GetSqsMessages(ctx context.Context, params *sqs.ReceiveMessageInput) ([]*sqs.Message, error) { +func (s sqsClient) GetSQSMessages(ctx context.Context, params *sqs.ReceiveMessageInput) ([]*sqs.Message, error) { ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("sqsClient.getMessages")) result, err := s.ReceiveMessageWithContext(ctx, params) @@ -64,7 +64,7 @@ func (s sqsClient) GetSqsMessages(ctx context.Context, params *sqs.ReceiveMessag return result.Messages, nil } -func (s sqsClient) DeleteSqsMessage(ctx context.Context, params *sqs.DeleteMessageInput) error { +func (s sqsClient) DeleteSQSMessage(ctx context.Context, params *sqs.DeleteMessageInput) error { ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("sqsClient.deleteMessage")) _, err := s.DeleteMessageWithContext(ctx, params) diff --git a/src/pkg/terminator/parser.go b/src/pkg/terminator/parser.go index 3f61b746..c3aba115 100644 --- a/src/pkg/terminator/parser.go +++ b/src/pkg/terminator/parser.go @@ -29,7 +29,7 @@ type eventParserAdapter struct { event.Parser } -func NewSqsMessageParser(parser event.Parser) (SqsMessageParser, error) { +func NewSQSMessageParser(parser event.Parser) (SQSMessageParser, error) { if parser == nil { return nil, fmt.Errorf("argument 'parser' is nil") } diff --git a/src/pkg/terminator/reconciler.go b/src/pkg/terminator/reconciler.go index d508cda5..ed251302 100644 --- a/src/pkg/terminator/reconciler.go +++ b/src/pkg/terminator/reconciler.go @@ -46,7 +46,7 @@ type ( GetNodeName(context.Context, string) (string, error) } - SqsMessageParser interface { + SQSMessageParser interface { Parse(context.Context, *sqs.Message) event.Event } @@ -58,20 +58,20 @@ type ( GetTerminator(context.Context, types.NamespacedName) (*v1alpha1.Terminator, error) } - SqsClient interface { - GetSqsMessages(context.Context) ([]*sqs.Message, error) - DeleteSqsMessage(context.Context, *sqs.Message) error + SQSClient interface { + GetSQSMessages(context.Context) ([]*sqs.Message, error) + DeleteSQSMessage(context.Context, *sqs.Message) error } - SqsClientBuilder interface { - NewSqsClient(*v1alpha1.Terminator) (SqsClient, error) + SQSClientBuilder interface { + NewSQSClient(*v1alpha1.Terminator) (SQSClient, error) } Reconciler struct { NodeGetter NodeNameGetter - SqsClientBuilder - SqsMessageParser + SQSClientBuilder + SQSMessageParser CordonDrainerBuilder Getter @@ -96,12 +96,12 @@ func (r Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (recon return reconcile.Result{}, err } - sqsClient, err := r.NewSqsClient(terminator) + sqsClient, err := r.NewSQSClient(terminator) if err != nil { return reconcile.Result{}, err } - sqsMessages, err := sqsClient.GetSqsMessages(ctx) + sqsMessages, err := sqsClient.GetSQSMessages(ctx) if err != nil { return reconcile.Result{}, err } @@ -116,7 +116,7 @@ func (r Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (recon ctx = logging.WithLogger(ctx, logging.FromContext(ctx).With("event", evt)) savedCtx := ctx - for _, ec2InstanceId := range evt.Ec2InstanceIds() { + for _, ec2InstanceId := range evt.EC2InstanceIds() { ctx = logging.WithLogger(savedCtx, logging.FromContext(savedCtx). With("ec2InstanceId", ec2InstanceId), ) @@ -156,7 +156,7 @@ func (r Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (recon continue } - err = multierr.Append(err, sqsClient.DeleteSqsMessage(ctx, msg)) + err = multierr.Append(err, sqsClient.DeleteSQSMessage(ctx, msg)) } ctx = origCtx diff --git a/src/pkg/terminator/sqsclient.go b/src/pkg/terminator/sqsclient.go index dc40f593..13b91e2d 100644 --- a/src/pkg/terminator/sqsclient.go +++ b/src/pkg/terminator/sqsclient.go @@ -28,74 +28,74 @@ import ( ) type ( - SqsMessageClient interface { - GetSqsMessages(context.Context, *sqs.ReceiveMessageInput) ([]*sqs.Message, error) - DeleteSqsMessage(context.Context, *sqs.DeleteMessageInput) error + SQSMessageClient interface { + GetSQSMessages(context.Context, *sqs.ReceiveMessageInput) ([]*sqs.Message, error) + DeleteSQSMessage(context.Context, *sqs.DeleteMessageInput) error } sqsMessageClientAdapterBuilder struct { - SqsMessageClient + SQSMessageClient } sqsMessageClientAdapter struct { sqs.DeleteMessageInput sqs.ReceiveMessageInput - SqsMessageClient + SQSMessageClient } ) -func NewSqsClientBuilder(client SqsMessageClient) (SqsClientBuilder, error) { +func NewSQSClientBuilder(client SQSMessageClient) (SQSClientBuilder, error) { if client == nil { return nil, fmt.Errorf("argument 'client' is nil") } - return sqsMessageClientAdapterBuilder{SqsMessageClient: client}, nil + return sqsMessageClientAdapterBuilder{SQSMessageClient: client}, nil } -func (s sqsMessageClientAdapterBuilder) NewSqsClient(terminator *v1alpha1.Terminator) (SqsClient, error) { +func (s sqsMessageClientAdapterBuilder) NewSQSClient(terminator *v1alpha1.Terminator) (SQSClient, error) { if terminator == nil { return nil, fmt.Errorf("argument 'terminator' is nil") } receiveMessageInput := sqs.ReceiveMessageInput{ - MaxNumberOfMessages: aws.Int64(terminator.Spec.Sqs.MaxNumberOfMessages), - QueueUrl: aws.String(terminator.Spec.Sqs.QueueUrl), - VisibilityTimeout: aws.Int64(terminator.Spec.Sqs.VisibilityTimeoutSeconds), - WaitTimeSeconds: aws.Int64(terminator.Spec.Sqs.WaitTimeSeconds), + MaxNumberOfMessages: aws.Int64(terminator.Spec.SQS.MaxNumberOfMessages), + QueueUrl: aws.String(terminator.Spec.SQS.QueueURL), + VisibilityTimeout: aws.Int64(terminator.Spec.SQS.VisibilityTimeoutSeconds), + WaitTimeSeconds: aws.Int64(terminator.Spec.SQS.WaitTimeSeconds), } - receiveMessageInput.AttributeNames = make([]*string, len(terminator.Spec.Sqs.AttributeNames)) - for i, attrName := range terminator.Spec.Sqs.AttributeNames { + receiveMessageInput.AttributeNames = make([]*string, len(terminator.Spec.SQS.AttributeNames)) + for i, attrName := range terminator.Spec.SQS.AttributeNames { receiveMessageInput.AttributeNames[i] = aws.String(attrName) } - receiveMessageInput.MessageAttributeNames = make([]*string, len(terminator.Spec.Sqs.MessageAttributeNames)) - for i, attrName := range terminator.Spec.Sqs.MessageAttributeNames { + receiveMessageInput.MessageAttributeNames = make([]*string, len(terminator.Spec.SQS.MessageAttributeNames)) + for i, attrName := range terminator.Spec.SQS.MessageAttributeNames { receiveMessageInput.MessageAttributeNames[i] = aws.String(attrName) } deleteMessageInput := sqs.DeleteMessageInput{ - QueueUrl: aws.String(terminator.Spec.Sqs.QueueUrl), + QueueUrl: aws.String(terminator.Spec.SQS.QueueURL), } return sqsMessageClientAdapter{ DeleteMessageInput: deleteMessageInput, ReceiveMessageInput: receiveMessageInput, - SqsMessageClient: s.SqsMessageClient, + SQSMessageClient: s.SQSMessageClient, }, nil } -func (a sqsMessageClientAdapter) GetSqsMessages(ctx context.Context) ([]*sqs.Message, error) { +func (a sqsMessageClientAdapter) GetSQSMessages(ctx context.Context) ([]*sqs.Message, error) { ctx = logging.WithLogger(ctx, logging.FromContext(ctx). With("params", logging.NewReceiveMessageInputMarshaler(&a.ReceiveMessageInput)), ) - return a.SqsMessageClient.GetSqsMessages(ctx, &a.ReceiveMessageInput) + return a.SQSMessageClient.GetSQSMessages(ctx, &a.ReceiveMessageInput) } -func (a sqsMessageClientAdapter) DeleteSqsMessage(ctx context.Context, msg *sqs.Message) error { +func (a sqsMessageClientAdapter) DeleteSQSMessage(ctx context.Context, msg *sqs.Message) error { a.DeleteMessageInput.ReceiptHandle = msg.ReceiptHandle ctx = logging.WithLogger(ctx, logging.FromContext(ctx). With("params", logging.NewDeleteMessageInputMarshaler(&a.DeleteMessageInput)), ) - return a.SqsMessageClient.DeleteSqsMessage(ctx, &a.DeleteMessageInput) + return a.SQSMessageClient.DeleteSQSMessage(ctx, &a.DeleteMessageInput) } diff --git a/src/test/asgclient.go b/src/test/asgclient.go index b02cd3c4..92d4da26 100644 --- a/src/test/asgclient.go +++ b/src/test/asgclient.go @@ -23,11 +23,11 @@ import ( ) type ( - CompleteAsgLifecycleActionFunc = func(aws.Context, *autoscaling.CompleteLifecycleActionInput, ...request.Option) (*autoscaling.CompleteLifecycleActionOutput, error) + CompleteASGLifecycleActionFunc = func(aws.Context, *autoscaling.CompleteLifecycleActionInput, ...request.Option) (*autoscaling.CompleteLifecycleActionOutput, error) - AsgClient CompleteAsgLifecycleActionFunc + ASGClient CompleteASGLifecycleActionFunc ) -func (a AsgClient) CompleteLifecycleActionWithContext(ctx aws.Context, input *autoscaling.CompleteLifecycleActionInput, options ...request.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { +func (a ASGClient) CompleteLifecycleActionWithContext(ctx aws.Context, input *autoscaling.CompleteLifecycleActionInput, options ...request.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { return a(ctx, input, options...) } diff --git a/src/test/ec2client.go b/src/test/ec2client.go index 36ab5d03..f9cf49e0 100644 --- a/src/test/ec2client.go +++ b/src/test/ec2client.go @@ -23,11 +23,11 @@ import ( ) type ( - DescribeEc2InstancesFunc = func(aws.Context, *ec2.DescribeInstancesInput, ...request.Option) (*ec2.DescribeInstancesOutput, error) + DescribeEC2InstancesFunc = func(aws.Context, *ec2.DescribeInstancesInput, ...request.Option) (*ec2.DescribeInstancesOutput, error) - Ec2Client DescribeEc2InstancesFunc + EC2Client DescribeEC2InstancesFunc ) -func (e Ec2Client) DescribeInstancesWithContext(ctx aws.Context, input *ec2.DescribeInstancesInput, options ...request.Option) (*ec2.DescribeInstancesOutput, error) { +func (e EC2Client) DescribeInstancesWithContext(ctx aws.Context, input *ec2.DescribeInstancesInput, options ...request.Option) (*ec2.DescribeInstancesOutput, error) { return e(ctx, input, options...) } diff --git a/src/test/reconciliation_test.go b/src/test/reconciliation_test.go index 8a630c5d..d6b6215e 100644 --- a/src/test/reconciliation_test.go +++ b/src/test/reconciliation_test.go @@ -63,8 +63,8 @@ import ( ) type ( - Ec2InstanceId = string - SqsQueueUrl = string + EC2InstanceId = string + SQSQueueURL = string NodeName = string State string @@ -78,7 +78,7 @@ const ( var _ = Describe("Reconciliation", func() { const ( errMsg = "test error" - queueUrl = "http://fake-queue.sqs.aws" + queueURL = "http://fake-queue.sqs.aws" ) var ( @@ -91,12 +91,12 @@ var _ = Describe("Reconciliation", func() { // Nodes currently in the cluster. nodes map[types.NamespacedName]*v1.Node // Maps an EC2 instance id to an ASG lifecycle action state value. - asgLifecycleActions map[Ec2InstanceId]State + asgLifecycleActions map[EC2InstanceId]State // Maps an EC2 instance id to the corresponding reservation for a node // in the cluster. - ec2Reservations map[Ec2InstanceId]*ec2.Reservation + ec2Reservations map[EC2InstanceId]*ec2.Reservation // Maps a queue URL to a list of messages waiting to be fetched. - sqsQueues map[SqsQueueUrl][]*sqs.Message + sqsQueues map[SQSQueueURL][]*sqs.Message // Output variables // These variables may be modified during reconciliation and should be @@ -116,11 +116,11 @@ var _ = Describe("Reconciliation", func() { // Names of all nodes currently in cluster. nodeNames []NodeName // Instance IDs for all nodes currently in cluster. - instanceIds []Ec2InstanceId + instanceIds []EC2InstanceId // Change count of nodes in cluster. resizeCluster func(nodeCount uint) // Create an ASG lifecycle action state entry for an EC2 instance ID. - createPendingAsgLifecycleAction func(Ec2InstanceId) + createPendingASGLifecycleAction func(EC2InstanceId) // Name of default terminator. terminatorNamespaceName types.NamespacedName @@ -134,11 +134,11 @@ var _ = Describe("Reconciliation", func() { // Stubs // Default implementations interract with the backing variables listed // above. A test may put in place alternate behavior when needed. - completeAsgLifecycleActionFunc CompleteAsgLifecycleActionFunc - describeEc2InstancesFunc DescribeEc2InstancesFunc + completeASGLifecycleActionFunc CompleteASGLifecycleActionFunc + describeEC2InstancesFunc DescribeEC2InstancesFunc kubeGetFunc KubeGetFunc - receiveSqsMessageFunc ReceiveSqsMessageFunc - deleteSqsMessageFunc DeleteSqsMessageFunc + receiveSQSMessageFunc ReceiveSQSMessageFunc + deleteSQSMessageFunc DeleteSQSMessageFunc cordonFunc kubectlcordondrain.RunCordonFunc drainFunc kubectlcordondrain.RunDrainFunc ) @@ -158,7 +158,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ "source": "aws.autoscaling", @@ -171,7 +171,7 @@ var _ = Describe("Reconciliation", func() { }`, instanceIds[1])), }) - createPendingAsgLifecycleAction(instanceIds[1]) + createPendingASGLifecycleAction(instanceIds[1]) }) It("returns success and requeues the request with the reconciler's configured interval", func() { @@ -188,7 +188,7 @@ var _ = Describe("Reconciliation", func() { }) It("deletes the message from the SQS queue", func() { - Expect(sqsQueues[queueUrl]).To(BeEmpty()) + Expect(sqsQueues[queueURL]).To(BeEmpty()) }) }) @@ -196,7 +196,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ "source": "aws.autoscaling", @@ -209,7 +209,7 @@ var _ = Describe("Reconciliation", func() { }`, instanceIds[1])), }) - createPendingAsgLifecycleAction(instanceIds[1]) + createPendingASGLifecycleAction(instanceIds[1]) }) It("returns success and requeues the request with the reconciler's configured interval", func() { @@ -226,7 +226,7 @@ var _ = Describe("Reconciliation", func() { }) It("deletes the message from the SQS queue", func() { - Expect(sqsQueues[queueUrl]).To(BeEmpty()) + Expect(sqsQueues[queueURL]).To(BeEmpty()) }) }) @@ -234,7 +234,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ "source": "aws.ec2", @@ -257,7 +257,7 @@ var _ = Describe("Reconciliation", func() { }) It("deletes the message from the SQS queue", func() { - Expect(sqsQueues[queueUrl]).To(BeEmpty()) + Expect(sqsQueues[queueURL]).To(BeEmpty()) }) }) @@ -265,7 +265,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(4) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ "source": "aws.health", @@ -293,7 +293,7 @@ var _ = Describe("Reconciliation", func() { }) It("deletes the message from the SQS queue", func() { - Expect(sqsQueues[queueUrl]).To(BeEmpty()) + Expect(sqsQueues[queueURL]).To(BeEmpty()) }) }) @@ -301,7 +301,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ "source": "aws.ec2", @@ -324,7 +324,7 @@ var _ = Describe("Reconciliation", func() { }) It("deletes the message from the SQS queue", func() { - Expect(sqsQueues[queueUrl]).To(BeEmpty()) + Expect(sqsQueues[queueURL]).To(BeEmpty()) }) }) @@ -332,7 +332,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ "source": "aws.ec2", @@ -356,7 +356,7 @@ var _ = Describe("Reconciliation", func() { }) It("deletes the message from the SQS queue", func() { - Expect(sqsQueues[queueUrl]).To(BeEmpty()) + Expect(sqsQueues[queueURL]).To(BeEmpty()) }) }) @@ -364,7 +364,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ "source": "aws.ec2", @@ -388,7 +388,7 @@ var _ = Describe("Reconciliation", func() { }) It("deletes the message from the SQS queue", func() { - Expect(sqsQueues[queueUrl]).To(BeEmpty()) + Expect(sqsQueues[queueURL]).To(BeEmpty()) }) }) @@ -396,7 +396,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ "source": "aws.ec2", @@ -420,7 +420,7 @@ var _ = Describe("Reconciliation", func() { }) It("deletes the message from the SQS queue", func() { - Expect(sqsQueues[queueUrl]).To(BeEmpty()) + Expect(sqsQueues[queueURL]).To(BeEmpty()) }) }) @@ -428,7 +428,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ "source": "aws.ec2", @@ -452,7 +452,7 @@ var _ = Describe("Reconciliation", func() { }) It("deletes the message from the SQS queue", func() { - Expect(sqsQueues[queueUrl]).To(BeEmpty()) + Expect(sqsQueues[queueURL]).To(BeEmpty()) }) }) @@ -460,7 +460,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(12) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ @@ -608,7 +608,7 @@ var _ = Describe("Reconciliation", func() { }) It("deletes the messages from the SQS queue", func() { - Expect(sqsQueues[queueUrl]).To(BeEmpty()) + Expect(sqsQueues[queueURL]).To(BeEmpty()) }) }) @@ -616,7 +616,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(`{ "source": "test.suite", @@ -637,7 +637,7 @@ var _ = Describe("Reconciliation", func() { }) It("deletes the message from the SQS queue", func() { - Expect(sqsQueues[queueUrl]).To(BeEmpty()) + Expect(sqsQueues[queueURL]).To(BeEmpty()) }) }) @@ -645,7 +645,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(`{ "source": "test.suite", @@ -664,7 +664,7 @@ var _ = Describe("Reconciliation", func() { }) It("deletes the message from the SQS queue", func() { - Expect(sqsQueues[queueUrl]).To(BeEmpty()) + Expect(sqsQueues[queueURL]).To(BeEmpty()) }) }) @@ -707,7 +707,7 @@ var _ = Describe("Reconciliation", func() { When("there is an error getting SQS messages", func() { BeforeEach(func() { - receiveSqsMessageFunc = func(_ aws.Context, _ *sqs.ReceiveMessageInput, _ ...awsrequest.Option) (*sqs.ReceiveMessageOutput, error) { + receiveSQSMessageFunc = func(_ aws.Context, _ *sqs.ReceiveMessageInput, _ ...awsrequest.Option) (*sqs.ReceiveMessageOutput, error) { return nil, errors.New(errMsg) } }) @@ -725,7 +725,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ "source": "aws.ec2", @@ -737,7 +737,7 @@ var _ = Describe("Reconciliation", func() { }`, instanceIds[1])), }) - describeEc2InstancesFunc = func(_ aws.Context, _ *ec2.DescribeInstancesInput, _ ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { + describeEC2InstancesFunc = func(_ aws.Context, _ *ec2.DescribeInstancesInput, _ ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { return nil, errors.New(errMsg) } }) @@ -760,7 +760,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ "source": "aws.ec2", @@ -772,7 +772,7 @@ var _ = Describe("Reconciliation", func() { }`, instanceIds[1])), }) - describeEc2InstancesFunc = func(_ aws.Context, _ *ec2.DescribeInstancesInput, _ ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { + describeEC2InstancesFunc = func(_ aws.Context, _ *ec2.DescribeInstancesInput, _ ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { return &ec2.DescribeInstancesOutput{ Reservations: []*ec2.Reservation{}, }, nil @@ -797,7 +797,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ "source": "aws.ec2", @@ -809,7 +809,7 @@ var _ = Describe("Reconciliation", func() { }`, instanceIds[1])), }) - describeEc2InstancesFunc = func(_ aws.Context, _ *ec2.DescribeInstancesInput, _ ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { + describeEC2InstancesFunc = func(_ aws.Context, _ *ec2.DescribeInstancesInput, _ ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { return &ec2.DescribeInstancesOutput{ Reservations: []*ec2.Reservation{ {Instances: []*ec2.Instance{}}, @@ -836,7 +836,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ "source": "aws.ec2", @@ -848,7 +848,7 @@ var _ = Describe("Reconciliation", func() { }`, instanceIds[1])), }) - describeEc2InstancesFunc = func(_ aws.Context, _ *ec2.DescribeInstancesInput, _ ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { + describeEC2InstancesFunc = func(_ aws.Context, _ *ec2.DescribeInstancesInput, _ ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { return &ec2.DescribeInstancesOutput{ Reservations: []*ec2.Reservation{ { @@ -879,7 +879,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ "source": "aws.ec2", @@ -891,7 +891,7 @@ var _ = Describe("Reconciliation", func() { }`, instanceIds[1])), }) - describeEc2InstancesFunc = func(_ aws.Context, _ *ec2.DescribeInstancesInput, _ ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { + describeEC2InstancesFunc = func(_ aws.Context, _ *ec2.DescribeInstancesInput, _ ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { return &ec2.DescribeInstancesOutput{ Reservations: []*ec2.Reservation{ { @@ -922,7 +922,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ "source": "aws.ec2", @@ -963,7 +963,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ "source": "aws.ec2", @@ -998,7 +998,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ "source": "aws.ec2", @@ -1036,7 +1036,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ "source": "aws.autoscaling", @@ -1049,7 +1049,7 @@ var _ = Describe("Reconciliation", func() { }`, instanceIds[1])), }) - completeAsgLifecycleActionFunc = func(_ aws.Context, _ *autoscaling.CompleteLifecycleActionInput, _ ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { + completeASGLifecycleActionFunc = func(_ aws.Context, _ *autoscaling.CompleteLifecycleActionInput, _ ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { return nil, errors.New(errMsg) } }) @@ -1068,7 +1068,7 @@ var _ = Describe("Reconciliation", func() { }) It("deletes the message from the SQS queue", func() { - Expect(sqsQueues[queueUrl]).To(BeEmpty()) + Expect(sqsQueues[queueURL]).To(BeEmpty()) }) }) @@ -1076,7 +1076,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ "source": "aws.autoscaling", @@ -1089,7 +1089,7 @@ var _ = Describe("Reconciliation", func() { }`, instanceIds[1])), }) - completeAsgLifecycleActionFunc = func(_ aws.Context, _ *autoscaling.CompleteLifecycleActionInput, _ ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { + completeASGLifecycleActionFunc = func(_ aws.Context, _ *autoscaling.CompleteLifecycleActionInput, _ ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { return nil, awserr.NewRequestFailure(awserr.New("", errMsg, errors.New(errMsg)), 404, "") } }) @@ -1108,7 +1108,7 @@ var _ = Describe("Reconciliation", func() { }) It("does not delete the message from the SQS queue", func() { - Expect(sqsQueues[queueUrl]).To(HaveLen(1)) + Expect(sqsQueues[queueURL]).To(HaveLen(1)) }) }) @@ -1116,7 +1116,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ "source": "aws.autoscaling", @@ -1129,7 +1129,7 @@ var _ = Describe("Reconciliation", func() { }`, instanceIds[1])), }) - completeAsgLifecycleActionFunc = func(_ aws.Context, _ *autoscaling.CompleteLifecycleActionInput, _ ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { + completeASGLifecycleActionFunc = func(_ aws.Context, _ *autoscaling.CompleteLifecycleActionInput, _ ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { return nil, errors.New(errMsg) } }) @@ -1148,7 +1148,7 @@ var _ = Describe("Reconciliation", func() { }) It("deletes the message from the SQS queue", func() { - Expect(sqsQueues[queueUrl]).To(BeEmpty()) + Expect(sqsQueues[queueURL]).To(BeEmpty()) }) }) @@ -1156,7 +1156,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ "source": "aws.autoscaling", @@ -1169,7 +1169,7 @@ var _ = Describe("Reconciliation", func() { }`, instanceIds[1])), }) - completeAsgLifecycleActionFunc = func(_ aws.Context, _ *autoscaling.CompleteLifecycleActionInput, _ ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { + completeASGLifecycleActionFunc = func(_ aws.Context, _ *autoscaling.CompleteLifecycleActionInput, _ ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { return nil, awserr.NewRequestFailure(awserr.New("", errMsg, errors.New(errMsg)), 404, "") } }) @@ -1188,7 +1188,7 @@ var _ = Describe("Reconciliation", func() { }) It("does not delete the message from the SQS queue", func() { - Expect(sqsQueues[queueUrl]).To(HaveLen(1)) + Expect(sqsQueues[queueURL]).To(HaveLen(1)) }) }) @@ -1208,17 +1208,17 @@ var _ = Describe("Reconciliation", func() { terminator, found := terminators[terminatorNamespaceName] Expect(found).To(BeTrue()) - terminator.Spec.Sqs.MaxNumberOfMessages = maxNumberOfMessages - terminator.Spec.Sqs.QueueUrl = queueUrl - terminator.Spec.Sqs.VisibilityTimeoutSeconds = visibilityTimeoutSeconds - terminator.Spec.Sqs.WaitTimeSeconds = waitTimeSeconds - terminator.Spec.Sqs.AttributeNames = append([]string{}, attributeNames...) - terminator.Spec.Sqs.MessageAttributeNames = append([]string{}, messageAttributeNames...) + terminator.Spec.SQS.MaxNumberOfMessages = maxNumberOfMessages + terminator.Spec.SQS.QueueURL = queueURL + terminator.Spec.SQS.VisibilityTimeoutSeconds = visibilityTimeoutSeconds + terminator.Spec.SQS.WaitTimeSeconds = waitTimeSeconds + terminator.Spec.SQS.AttributeNames = append([]string{}, attributeNames...) + terminator.Spec.SQS.MessageAttributeNames = append([]string{}, messageAttributeNames...) - defaultReceiveSqsMessageFunc := receiveSqsMessageFunc - receiveSqsMessageFunc = func(ctx aws.Context, in *sqs.ReceiveMessageInput, options ...awsrequest.Option) (*sqs.ReceiveMessageOutput, error) { + defaultReceiveSQSMessageFunc := receiveSQSMessageFunc + receiveSQSMessageFunc = func(ctx aws.Context, in *sqs.ReceiveMessageInput, options ...awsrequest.Option) (*sqs.ReceiveMessageOutput, error) { input = in - return defaultReceiveSqsMessageFunc(ctx, in, options...) + return defaultReceiveSQSMessageFunc(ctx, in, options...) } }) @@ -1238,7 +1238,7 @@ var _ = Describe("Reconciliation", func() { Expect(*input.MaxNumberOfMessages).To(Equal(maxNumberOfMessages)) Expect(input.QueueUrl).ToNot(BeNil()) - Expect(*input.QueueUrl).To(Equal(queueUrl)) + Expect(*input.QueueUrl).To(Equal(queueURL)) Expect(input.VisibilityTimeout).ToNot(BeNil()) Expect(*input.VisibilityTimeout).To(Equal(visibilityTimeoutSeconds)) @@ -1278,7 +1278,7 @@ var _ = Describe("Reconciliation", func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ "source": "aws.ec2", @@ -1345,7 +1345,7 @@ var _ = Describe("Reconciliation", func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ "source": "aws.ec2", @@ -1394,7 +1394,7 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { resizeCluster(3) - sqsQueues[queueUrl] = append(sqsQueues[queueUrl], &sqs.Message{ + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ ReceiptHandle: aws.String("msg-1"), Body: aws.String(fmt.Sprintf(`{ "source": "aws.autoscaling", @@ -1410,10 +1410,10 @@ var _ = Describe("Reconciliation", func() { }`, autoScalingGroupName, instanceIds[1], lifecycleActionToken, lifecycleHookName)), }) - defaultCompleteAsgLifecycleActionFunc := completeAsgLifecycleActionFunc - completeAsgLifecycleActionFunc = func(ctx aws.Context, in *autoscaling.CompleteLifecycleActionInput, options ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { + defaultCompleteASGLifecycleActionFunc := completeASGLifecycleActionFunc + completeASGLifecycleActionFunc = func(ctx aws.Context, in *autoscaling.CompleteLifecycleActionInput, options ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { input = in - return defaultCompleteAsgLifecycleActionFunc(ctx, in, options...) + return defaultCompleteASGLifecycleActionFunc(ctx, in, options...) } }) @@ -1439,7 +1439,7 @@ var _ = Describe("Reconciliation", func() { // Setup the starter state: // * One terminator (terminatorNamedspacedName) - // * The terminator references an empty sqs queue (queueUrl) + // * The terminator references an empty sqs queue (queueURL) // * Zero nodes (use resizeCluster()) // // Tests should modify the cluster/aws service states as needed. @@ -1449,24 +1449,24 @@ var _ = Describe("Reconciliation", func() { ctx = logging.WithLogger(context.Background(), zap.NewNop().Sugar()) terminatorNamespaceName = types.NamespacedName{Namespace: "test", Name: "foo"} request = reconcile.Request{NamespacedName: terminatorNamespaceName} - sqsQueues = map[SqsQueueUrl][]*sqs.Message{queueUrl: {}} + sqsQueues = map[SQSQueueURL][]*sqs.Message{queueURL: {}} terminators = map[types.NamespacedName]*v1alpha1.Terminator{ // For convenience create a terminator that points to the sqs queue. terminatorNamespaceName: { Spec: v1alpha1.TerminatorSpec{ - Sqs: v1alpha1.SqsSpec{ - QueueUrl: queueUrl, + SQS: v1alpha1.SQSSpec{ + QueueURL: queueURL, }, }, }, } nodes = map[types.NamespacedName]*v1.Node{} - ec2Reservations = map[Ec2InstanceId]*ec2.Reservation{} + ec2Reservations = map[EC2InstanceId]*ec2.Reservation{} cordonedNodes = map[NodeName]bool{} drainedNodes = map[NodeName]bool{} nodeNames = []NodeName{} - instanceIds = []Ec2InstanceId{} + instanceIds = []EC2InstanceId{} resizeCluster = func(newNodeCount uint) { for currNodeCount := uint(len(nodes)); currNodeCount < newNodeCount; currNodeCount++ { nodeName := fmt.Sprintf("node-%d", currNodeCount) @@ -1488,15 +1488,15 @@ var _ = Describe("Reconciliation", func() { instanceIds = instanceIds[:newNodeCount] } - asgLifecycleActions = map[Ec2InstanceId]State{} - createPendingAsgLifecycleAction = func(instanceId Ec2InstanceId) { + asgLifecycleActions = map[EC2InstanceId]State{} + createPendingASGLifecycleAction = func(instanceId EC2InstanceId) { Expect(asgLifecycleActions).ToNot(HaveKey(instanceId)) asgLifecycleActions[instanceId] = StatePending } // 2. Setup stub clients. - describeEc2InstancesFunc = func(ctx aws.Context, input *ec2.DescribeInstancesInput, _ ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { + describeEC2InstancesFunc = func(ctx aws.Context, input *ec2.DescribeInstancesInput, _ ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { if err := ctx.Err(); err != nil { return nil, err } @@ -1513,11 +1513,11 @@ var _ = Describe("Reconciliation", func() { return &output, nil } - ec2Client := Ec2Client(func(ctx aws.Context, input *ec2.DescribeInstancesInput, options ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { - return describeEc2InstancesFunc(ctx, input, options...) + ec2Client := EC2Client(func(ctx aws.Context, input *ec2.DescribeInstancesInput, options ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { + return describeEC2InstancesFunc(ctx, input, options...) }) - completeAsgLifecycleActionFunc = func(ctx aws.Context, input *autoscaling.CompleteLifecycleActionInput, _ ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { + completeASGLifecycleActionFunc = func(ctx aws.Context, input *autoscaling.CompleteLifecycleActionInput, _ ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { if err := ctx.Err(); err != nil { return nil, err } @@ -1529,11 +1529,11 @@ var _ = Describe("Reconciliation", func() { return &autoscaling.CompleteLifecycleActionOutput{}, nil } - asgClient := AsgClient(func(ctx aws.Context, input *autoscaling.CompleteLifecycleActionInput, options ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { - return completeAsgLifecycleActionFunc(ctx, input, options...) + asgClient := ASGClient(func(ctx aws.Context, input *autoscaling.CompleteLifecycleActionInput, options ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { + return completeASGLifecycleActionFunc(ctx, input, options...) }) - receiveSqsMessageFunc = func(ctx aws.Context, input *sqs.ReceiveMessageInput, options ...awsrequest.Option) (*sqs.ReceiveMessageOutput, error) { + receiveSQSMessageFunc = func(ctx aws.Context, input *sqs.ReceiveMessageInput, options ...awsrequest.Option) (*sqs.ReceiveMessageOutput, error) { if err := ctx.Err(); err != nil { return nil, err } @@ -1545,7 +1545,7 @@ var _ = Describe("Reconciliation", func() { return &sqs.ReceiveMessageOutput{Messages: append([]*sqs.Message{}, messages...)}, nil } - deleteSqsMessageFunc = func(ctx aws.Context, input *sqs.DeleteMessageInput, options ...awsrequest.Option) (*sqs.DeleteMessageOutput, error) { + deleteSQSMessageFunc = func(ctx aws.Context, input *sqs.DeleteMessageInput, options ...awsrequest.Option) (*sqs.DeleteMessageOutput, error) { if err := ctx.Err(); err != nil { return nil, err } @@ -1567,12 +1567,12 @@ var _ = Describe("Reconciliation", func() { return &sqs.DeleteMessageOutput{}, nil } - sqsClient := SqsClient{ - ReceiveSqsMessageFunc: func(ctx aws.Context, input *sqs.ReceiveMessageInput, options ...awsrequest.Option) (*sqs.ReceiveMessageOutput, error) { - return receiveSqsMessageFunc(ctx, input, options...) + sqsClient := SQSClient{ + ReceiveSQSMessageFunc: func(ctx aws.Context, input *sqs.ReceiveMessageInput, options ...awsrequest.Option) (*sqs.ReceiveMessageOutput, error) { + return receiveSQSMessageFunc(ctx, input, options...) }, - DeleteSqsMessageFunc: func(ctx aws.Context, input *sqs.DeleteMessageInput, options ...awsrequest.Option) (*sqs.DeleteMessageOutput, error) { - return deleteSqsMessageFunc(ctx, input, options...) + DeleteSQSMessageFunc: func(ctx aws.Context, input *sqs.DeleteMessageInput, options ...awsrequest.Option) (*sqs.DeleteMessageOutput, error) { + return deleteSQSMessageFunc(ctx, input, options...) }, } @@ -1636,7 +1636,7 @@ var _ = Describe("Reconciliation", func() { asgTerminateEventV2Parser, err := asgterminateeventv2.NewParser(asgClient) Expect(asgTerminateEventV2Parser, err).ToNot(BeNil()) - sqsMessageParser, err := terminator.NewSqsMessageParser(event.NewParser( + sqsMessageParser, err := terminator.NewSQSMessageParser(event.NewParser( asgTerminateEventV1Parser, asgTerminateEventV2Parser, rebalancerecommendationeventv0.NewParser(), @@ -1652,8 +1652,8 @@ var _ = Describe("Reconciliation", func() { sqsMessageClient, err := sqsmessage.NewClient(sqsClient) Expect(sqsMessageClient, err).ToNot(BeNil()) - terminatorSqsClientBuilder, err := terminator.NewSqsClientBuilder(sqsMessageClient) - Expect(terminatorSqsClientBuilder, err).ToNot(BeNil()) + terminatorSQSClientBuilder, err := terminator.NewSQSClientBuilder(sqsMessageClient) + Expect(terminatorSQSClientBuilder, err).ToNot(BeNil()) cordoner, err := kubectlcordondrain.NewCordoner(func(h *kubectl.Helper, n *v1.Node, d bool) error { return cordonFunc(h, n, d) @@ -1676,8 +1676,8 @@ var _ = Describe("Reconciliation", func() { RequeueInterval: time.Duration(10) * time.Second, NodeGetter: nodeGetter, NodeNameGetter: nodeNameGetter, - SqsClientBuilder: terminatorSqsClientBuilder, - SqsMessageParser: sqsMessageParser, + SQSClientBuilder: terminatorSQSClientBuilder, + SQSMessageParser: sqsMessageParser, Getter: terminatorGetter, CordonDrainerBuilder: terminatorCordonDrainerBuilder, } diff --git a/src/test/sqsclient.go b/src/test/sqsclient.go index 31ac0397..a11d3b16 100644 --- a/src/test/sqsclient.go +++ b/src/test/sqsclient.go @@ -23,19 +23,19 @@ import ( ) type ( - ReceiveSqsMessageFunc = func(aws.Context, *sqs.ReceiveMessageInput, ...request.Option) (*sqs.ReceiveMessageOutput, error) - DeleteSqsMessageFunc = func(aws.Context, *sqs.DeleteMessageInput, ...request.Option) (*sqs.DeleteMessageOutput, error) + ReceiveSQSMessageFunc = func(aws.Context, *sqs.ReceiveMessageInput, ...request.Option) (*sqs.ReceiveMessageOutput, error) + DeleteSQSMessageFunc = func(aws.Context, *sqs.DeleteMessageInput, ...request.Option) (*sqs.DeleteMessageOutput, error) - SqsClient struct { - ReceiveSqsMessageFunc - DeleteSqsMessageFunc + SQSClient struct { + ReceiveSQSMessageFunc + DeleteSQSMessageFunc } ) -func (s SqsClient) ReceiveMessageWithContext(ctx aws.Context, input *sqs.ReceiveMessageInput, options ...request.Option) (*sqs.ReceiveMessageOutput, error) { - return s.ReceiveSqsMessageFunc(ctx, input, options...) +func (s SQSClient) ReceiveMessageWithContext(ctx aws.Context, input *sqs.ReceiveMessageInput, options ...request.Option) (*sqs.ReceiveMessageOutput, error) { + return s.ReceiveSQSMessageFunc(ctx, input, options...) } -func (s SqsClient) DeleteMessageWithContext(ctx aws.Context, input *sqs.DeleteMessageInput, options ...request.Option) (*sqs.DeleteMessageOutput, error) { - return s.DeleteSqsMessageFunc(ctx, input, options...) +func (s SQSClient) DeleteMessageWithContext(ctx aws.Context, input *sqs.DeleteMessageInput, options ...request.Option) (*sqs.DeleteMessageOutput, error) { + return s.DeleteSQSMessageFunc(ctx, input, options...) } From 2beddc7d02fb4bffc1a14848961b543d73fea3ae Mon Sep 17 00:00:00 2001 From: Jerad C Date: Mon, 4 Apr 2022 14:53:38 -0500 Subject: [PATCH 03/13] add more ways to specify AWS region --- .../templates/deployment.yaml | 14 ++++++++++--- .../values.yaml | 4 +++- src/cmd/controller/main.go | 21 ++++++++++++------- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/charts/aws-node-termination-handler-2/templates/deployment.yaml b/src/charts/aws-node-termination-handler-2/templates/deployment.yaml index 011ae430..5369e434 100644 --- a/src/charts/aws-node-termination-handler-2/templates/deployment.yaml +++ b/src/charts/aws-node-termination-handler-2/templates/deployment.yaml @@ -85,6 +85,10 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + {{- with .Values.aws.region }} + - name: AWS_REGION + value: {{ . | quote}} + {{- end }} {{- with .Values.controller.env }} {{- toYaml . | nindent 22 }} {{- end }} @@ -123,9 +127,13 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - {{- with .Values.webhook.env }} - {{- toYaml . | nindent 26 }} - {{- end }} + {{- with .Values.aws.region }} + - name: AWS_REGION + value: {{ . | quote}} + {{- end }} + {{- with .Values.webhook.env }} + {{- toYaml . | nindent 26 }} + {{- end }} ports: - name: https-webhook containerPort: {{ .Values.webhook.port }} diff --git a/src/charts/aws-node-termination-handler-2/values.yaml b/src/charts/aws-node-termination-handler-2/values.yaml index 2d662f74..77d7cf4f 100644 --- a/src/charts/aws-node-termination-handler-2/values.yaml +++ b/src/charts/aws-node-termination-handler-2/values.yaml @@ -27,7 +27,9 @@ terminator: deleteEmptyDirData: true timeoutSeconds: 120 - +aws: + # AWS region to use in API calls. + region: "" # Global logging configuration. # See https://github.com/uber-go/zap/blob/2314926ec34c23ee21f3dd4399438469668f8097/config.go#L58-L94 diff --git a/src/cmd/controller/main.go b/src/cmd/controller/main.go index 854604bb..911c7780 100644 --- a/src/cmd/controller/main.go +++ b/src/cmd/controller/main.go @@ -20,6 +20,7 @@ import ( "context" "flag" "fmt" + "os" "time" // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) @@ -76,6 +77,7 @@ const componentName = "controller" var scheme = runtime.NewScheme() type Options struct { + AWSRegion string MetricsAddr string EnableLeaderElection bool ProbeAddr string @@ -127,7 +129,7 @@ func main() { } kubeClient := mgr.GetClient() - awsSession, err := newAWSSession() + awsSession, err := newAWSSession(options.AWSRegion) if err != nil { logger.With("error", err).Fatal("failed to initialize AWS session") } @@ -235,6 +237,7 @@ func main() { func parseOptions() Options { options := Options{} + flag.StringVar(&options.AWSRegion, "aws-region", os.Getenv("AWS_REGION"), "The AWS region for API calls.") flag.StringVar(&options.MetricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&options.ProbeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") flag.BoolVar(&options.EnableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. "+ @@ -244,8 +247,10 @@ func parseOptions() Options { return options } -func newAWSSession() (*session.Session, error) { - config := aws.NewConfig().WithSTSRegionalEndpoint(endpoints.RegionalSTSEndpoint) +func newAWSSession(awsRegion string) (*session.Session, error) { + config := aws.NewConfig(). + WithRegion(awsRegion). + WithSTSRegionalEndpoint(endpoints.RegionalSTSEndpoint) sess, err := session.NewSessionWithOptions(session.Options{ Config: *config, SharedConfigState: session.SharedConfigEnable, @@ -254,12 +259,14 @@ func newAWSSession() (*session.Session, error) { return nil, fmt.Errorf("failed to create AWS session: %w", err) } - region, err := ec2metadata.New(sess).Region() - if err != nil { - return nil, fmt.Errorf("failed to get AWS region: %w", err) + if sess.Config.Region == nil || *sess.Config.Region == "" { + awsRegion, err := ec2metadata.New(sess).Region() + if err != nil { + return nil, fmt.Errorf("failed to get AWS region: %w", err) + } + sess.Config.Region = aws.String(awsRegion) } - sess.Config.Region = aws.String(region) _, err = sess.Config.Credentials.Get() if err != nil { return nil, fmt.Errorf("failed to get AWS session credentials: %w", err) From c4ee5fb39a54dc7ddd01aecdd67e7304ffc99d78 Mon Sep 17 00:00:00 2001 From: Jerad C Date: Mon, 4 Apr 2022 15:53:18 -0500 Subject: [PATCH 04/13] tidy up interface organization --- src/cmd/controller/main.go | 86 ++++++------------- .../event/{parser.go => aggregatedparser.go} | 15 +++- src/pkg/event/asgterminate/v1/parser.go | 14 +-- src/pkg/event/asgterminate/v2/parser.go | 14 +-- .../rebalancerecommendation/v0/parser.go | 8 +- src/pkg/event/scheduledchange/v1/parser.go | 8 +- src/pkg/event/spotinterruption/v1/parser.go | 8 +- src/pkg/event/statechange/v1/parser.go | 8 +- src/pkg/event/types.go | 36 -------- src/pkg/logging/writer.go | 14 ++- .../cordondrain/config.go} | 16 ++-- src/pkg/node/cordondrain/kubectl/builder.go | 62 ++++++------- .../node/cordondrain/kubectl/cordondrainer.go | 29 +------ .../kubectl/{cordoner.go => cordonfunc.go} | 19 +--- .../kubectl/{drainer.go => drainfunc.go} | 19 +--- src/pkg/node/cordondrain/kubectl/types.go | 34 -------- src/pkg/node/cordondrain/types.go | 50 ----------- src/pkg/node/getter.go | 18 +--- src/pkg/node/name/getter.go | 17 +--- src/pkg/sqsmessage/client.go | 23 ++--- .../{ => adapter}/cordondrainbuilder.go | 19 ++-- src/pkg/terminator/{ => adapter}/getter.go | 14 +-- src/pkg/terminator/{ => adapter}/parser.go | 19 ++-- src/pkg/terminator/{ => adapter}/sqsclient.go | 41 ++++----- src/pkg/terminator/reconciler.go | 33 ++++--- src/test/reconciliation_test.go | 79 +++++++---------- 26 files changed, 197 insertions(+), 506 deletions(-) rename src/pkg/event/{parser.go => aggregatedparser.go} (75%) delete mode 100644 src/pkg/event/types.go rename src/pkg/{event/parserfunc.go => node/cordondrain/config.go} (75%) rename src/pkg/node/cordondrain/kubectl/{cordoner.go => cordonfunc.go} (71%) rename src/pkg/node/cordondrain/kubectl/{drainer.go => drainfunc.go} (71%) delete mode 100644 src/pkg/node/cordondrain/kubectl/types.go delete mode 100644 src/pkg/node/cordondrain/types.go rename src/pkg/terminator/{ => adapter}/cordondrainbuilder.go (69%) rename src/pkg/terminator/{ => adapter}/getter.go (81%) rename src/pkg/terminator/{ => adapter}/parser.go (63%) rename src/pkg/terminator/{ => adapter}/sqsclient.go (68%) diff --git a/src/cmd/controller/main.go b/src/cmd/controller/main.go index 911c7780..495b58a7 100644 --- a/src/cmd/controller/main.go +++ b/src/cmd/controller/main.go @@ -59,6 +59,7 @@ import ( nodename "github.com/aws/aws-node-termination-handler/pkg/node/name" "github.com/aws/aws-node-termination-handler/pkg/sqsmessage" "github.com/aws/aws-node-termination-handler/pkg/terminator" + terminatoradapter "github.com/aws/aws-node-termination-handler/pkg/terminator/adapter" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/ec2metadata" @@ -146,71 +147,32 @@ func main() { logger.Fatal("failed to create EC2 client") } - nodeGetter, err := node.NewGetter(kubeClient) - if err != nil { - logger.With("error", err).Fatal("failed to create node getter") - } - nodeNameGetter, err := nodename.NewGetter(ec2Client) - if err != nil { - logger.With("error", err).Fatal("failed to create node name getter") - } - - asgTerminateEventV1Parser, err := asgterminateeventv1.NewParser(asgClient) - if err != nil { - logger.With("error", err).Fatal("failed to create ASG instance-terminate lifecycle event v1 parser") - } - asgTerminateEventV2Parser, err := asgterminateeventv2.NewParser(asgClient) - if err != nil { - logger.With("error", err).Fatal("failed to create ASG instance-terminate lifecycle event v2 parser") - } - sqsMessageParser, err := terminator.NewSQSMessageParser(event.NewParser( - asgTerminateEventV1Parser, - asgTerminateEventV2Parser, - rebalancerecommendationeventv0.NewParser(), - scheduledchangeeventv1.NewParser(), - spotinterruptioneventv1.NewParser(), - statechangeeventv1.NewParser(), - )) - if err != nil { - logger.With("error", err).Fatal("failed to create SQS message parser") - } - - terminatorGetter, err := terminator.NewGetter(kubeClient) - if err != nil { - logger.With("error", err).Fatal("failed to create terminator getter") - } - - sqsMessageClient, err := sqsmessage.NewClient(sqsClient) - if err != nil { - logger.With("error", err).Fatal("failed to create SQS message client") - } - terminatorSQSClientBuilder, err := terminator.NewSQSClientBuilder(sqsMessageClient) - if err != nil { - logger.With("error", err).Fatal("failed to create terminator SQS message client builder") - } - - cordonDrainerBuilder, err := kubectlcordondrainer.NewBuilder( - clientSet, - kubectlcordondrainer.DefaultCordoner, - kubectlcordondrainer.DefaultDrainer, + eventParser := event.NewAggregatedParser( + asgterminateeventv1.Parser{ASGLifecycleActionCompleter: asgClient}, + asgterminateeventv2.Parser{ASGLifecycleActionCompleter: asgClient}, + rebalancerecommendationeventv0.Parser{}, + scheduledchangeeventv1.Parser{}, + spotinterruptioneventv1.Parser{}, + statechangeeventv1.Parser{}, ) - if err != nil { - logger.With("error", err).Fatal("failed to create kubectl cordon/drain client") - } - terminatorCordonDrainerBuilder, err := terminator.NewCordonDrainerBuilder(cordonDrainerBuilder) - if err != nil { - logger.With("error", err).Fatal("failed to create terminator cordon/drain client") - } rec := terminator.Reconciler{ - Name: "terminator", - RequeueInterval: time.Duration(10) * time.Second, - NodeGetter: nodeGetter, - NodeNameGetter: nodeNameGetter, - SQSClientBuilder: terminatorSQSClientBuilder, - SQSMessageParser: sqsMessageParser, - Getter: terminatorGetter, - CordonDrainerBuilder: terminatorCordonDrainerBuilder, + Name: "terminator", + RequeueInterval: time.Duration(10) * time.Second, + NodeGetter: node.Getter{KubeGetter: kubeClient}, + NodeNameGetter: nodename.Getter{EC2InstancesDescriber: ec2Client}, + SQSClientBuilder: terminatoradapter.SQSMessageClientBuilder{ + SQSMessageClient: sqsmessage.Client{SQSClient: sqsClient}, + }, + SQSMessageParser: terminatoradapter.EventParser{Parser: eventParser}, + Getter: terminatoradapter.Getter{KubeGetter: kubeClient}, + CordonDrainerBuilder: terminatoradapter.CordonDrainerBuilder{ + Builder: kubectlcordondrainer.Builder{ + ClientSet: clientSet, + Cordoner: kubectlcordondrainer.DefaultCordoner, + Drainer: kubectlcordondrainer.DefaultDrainer, + }, + }, } if err = rec.BuildController( ctrl.NewControllerManagedBy(mgr). diff --git a/src/pkg/event/parser.go b/src/pkg/event/aggregatedparser.go similarity index 75% rename from src/pkg/event/parser.go rename to src/pkg/event/aggregatedparser.go index 5f88c93b..7604f500 100644 --- a/src/pkg/event/parser.go +++ b/src/pkg/event/aggregatedparser.go @@ -21,15 +21,22 @@ import ( "encoding/json" "github.com/aws/aws-node-termination-handler/pkg/logging" + "github.com/aws/aws-node-termination-handler/pkg/terminator" ) -type parser []Parser +type ( + Parser interface { + Parse(context.Context, string) terminator.Event + } + + AggregatedParser []Parser +) -func NewParser(parsers ...Parser) Parser { - return parser(parsers) +func NewAggregatedParser(parsers ...Parser) AggregatedParser { + return AggregatedParser(parsers) } -func (p parser) Parse(ctx context.Context, str string) Event { +func (p AggregatedParser) Parse(ctx context.Context, str string) terminator.Event { ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("event.parser")) if str == "" { diff --git a/src/pkg/event/asgterminate/v1/parser.go b/src/pkg/event/asgterminate/v1/parser.go index cf041eac..52c9c89c 100644 --- a/src/pkg/event/asgterminate/v1/parser.go +++ b/src/pkg/event/asgterminate/v1/parser.go @@ -19,10 +19,9 @@ package v1 import ( "context" "encoding/json" - "fmt" - "github.com/aws/aws-node-termination-handler/pkg/event" "github.com/aws/aws-node-termination-handler/pkg/logging" + "github.com/aws/aws-node-termination-handler/pkg/terminator" ) const ( @@ -32,18 +31,11 @@ const ( acceptedTransition = "autoscaling:EC2_INSTANCE_TERMINATING" ) -type parser struct { +type Parser struct { ASGLifecycleActionCompleter } -func NewParser(completer ASGLifecycleActionCompleter) (event.Parser, error) { - if completer == nil { - return nil, fmt.Errorf("argument 'completer' is nil") - } - return parser{ASGLifecycleActionCompleter: completer}, nil -} - -func (p parser) Parse(ctx context.Context, str string) event.Event { +func (p Parser) Parse(ctx context.Context, str string) terminator.Event { ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("asgTerminateLifecycleAction.v1")) evt := EC2InstanceTerminateLifecycleAction{ diff --git a/src/pkg/event/asgterminate/v2/parser.go b/src/pkg/event/asgterminate/v2/parser.go index ee6ee111..653e57cd 100644 --- a/src/pkg/event/asgterminate/v2/parser.go +++ b/src/pkg/event/asgterminate/v2/parser.go @@ -19,10 +19,9 @@ package v2 import ( "context" "encoding/json" - "fmt" - "github.com/aws/aws-node-termination-handler/pkg/event" "github.com/aws/aws-node-termination-handler/pkg/logging" + "github.com/aws/aws-node-termination-handler/pkg/terminator" ) const ( @@ -32,18 +31,11 @@ const ( acceptedTransition = "autoscaling:EC2_INSTANCE_TERMINATING" ) -type parser struct { +type Parser struct { ASGLifecycleActionCompleter } -func NewParser(completer ASGLifecycleActionCompleter) (event.Parser, error) { - if completer == nil { - return nil, fmt.Errorf("argument 'completer' is nil") - } - return parser{ASGLifecycleActionCompleter: completer}, nil -} - -func (p parser) Parse(ctx context.Context, str string) event.Event { +func (p Parser) Parse(ctx context.Context, str string) terminator.Event { ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("asgTerminateLifecycleAction.v2")) evt := EC2InstanceTerminateLifecycleAction{ diff --git a/src/pkg/event/rebalancerecommendation/v0/parser.go b/src/pkg/event/rebalancerecommendation/v0/parser.go index d07650e8..609c2a86 100644 --- a/src/pkg/event/rebalancerecommendation/v0/parser.go +++ b/src/pkg/event/rebalancerecommendation/v0/parser.go @@ -20,8 +20,8 @@ import ( "context" "encoding/json" - "github.com/aws/aws-node-termination-handler/pkg/event" "github.com/aws/aws-node-termination-handler/pkg/logging" + "github.com/aws/aws-node-termination-handler/pkg/terminator" ) const ( @@ -30,11 +30,9 @@ const ( version = "0" ) -func NewParser() event.Parser { - return event.ParserFunc(parse) -} +type Parser struct{} -func parse(ctx context.Context, str string) event.Event { +func (Parser) Parse(ctx context.Context, str string) terminator.Event { ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("rebalanceRecommendation.v0")) evt := EC2InstanceRebalanceRecommendation{} diff --git a/src/pkg/event/scheduledchange/v1/parser.go b/src/pkg/event/scheduledchange/v1/parser.go index 85a3b5ce..1781a233 100644 --- a/src/pkg/event/scheduledchange/v1/parser.go +++ b/src/pkg/event/scheduledchange/v1/parser.go @@ -20,8 +20,8 @@ import ( "context" "encoding/json" - "github.com/aws/aws-node-termination-handler/pkg/event" "github.com/aws/aws-node-termination-handler/pkg/logging" + "github.com/aws/aws-node-termination-handler/pkg/terminator" ) const ( @@ -32,11 +32,9 @@ const ( acceptedEventTypeCategory = "scheduledChange" ) -func NewParser() event.Parser { - return event.ParserFunc(parse) -} +type Parser struct{} -func parse(ctx context.Context, str string) event.Event { +func (Parser) Parse(ctx context.Context, str string) terminator.Event { ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("scheduledChange.v1")) evt := AWSHealthEvent{} diff --git a/src/pkg/event/spotinterruption/v1/parser.go b/src/pkg/event/spotinterruption/v1/parser.go index 8472c428..0249cef6 100644 --- a/src/pkg/event/spotinterruption/v1/parser.go +++ b/src/pkg/event/spotinterruption/v1/parser.go @@ -20,8 +20,8 @@ import ( "context" "encoding/json" - "github.com/aws/aws-node-termination-handler/pkg/event" "github.com/aws/aws-node-termination-handler/pkg/logging" + "github.com/aws/aws-node-termination-handler/pkg/terminator" ) const ( @@ -30,11 +30,9 @@ const ( version = "1" ) -func NewParser() event.Parser { - return event.ParserFunc(parse) -} +type Parser struct{} -func parse(ctx context.Context, str string) event.Event { +func (Parser) Parse(ctx context.Context, str string) terminator.Event { ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("spotInterruption.v1")) evt := EC2SpotInstanceInterruptionWarning{} diff --git a/src/pkg/event/statechange/v1/parser.go b/src/pkg/event/statechange/v1/parser.go index a43f8da9..3cdb013e 100644 --- a/src/pkg/event/statechange/v1/parser.go +++ b/src/pkg/event/statechange/v1/parser.go @@ -21,8 +21,8 @@ import ( "encoding/json" "strings" - "github.com/aws/aws-node-termination-handler/pkg/event" "github.com/aws/aws-node-termination-handler/pkg/logging" + "github.com/aws/aws-node-termination-handler/pkg/terminator" ) const ( @@ -34,11 +34,9 @@ const ( var acceptedStatesList = strings.Split(acceptedStates, ",") -func NewParser() event.Parser { - return event.ParserFunc(parse) -} +type Parser struct{} -func parse(ctx context.Context, str string) event.Event { +func (Parser) Parse(ctx context.Context, str string) terminator.Event { ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("stateChange.v1")) evt := EC2InstanceStateChangeNotification{} diff --git a/src/pkg/event/types.go b/src/pkg/event/types.go deleted file mode 100644 index 3797f761..00000000 --- a/src/pkg/event/types.go +++ /dev/null @@ -1,36 +0,0 @@ -/* -Copyright 2022. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package event - -import ( - "context" - - "go.uber.org/zap/zapcore" -) - -type ( - Event interface { - zapcore.ObjectMarshaler - - Done(context.Context) (tryAgain bool, err error) - EC2InstanceIds() []string - } - - Parser interface { - Parse(context.Context, string) Event - } -) diff --git a/src/pkg/logging/writer.go b/src/pkg/logging/writer.go index d7321aa7..0368e715 100644 --- a/src/pkg/logging/writer.go +++ b/src/pkg/logging/writer.go @@ -17,28 +17,24 @@ limitations under the License. package logging import ( - "io" + "fmt" "strings" "go.uber.org/zap" ) -// loggerWriter adapts a logger to an `io.Writer`. -type loggerWriter struct { +// Writer adapts a logger to an `io.Writer`. +type Writer struct { *zap.SugaredLogger } -func NewWriter(logger *zap.SugaredLogger) io.Writer { - return loggerWriter{SugaredLogger: logger} -} - // Write converts `buf` to a string and sends it to the underlying logger. // If the string beings with "warn" or "error" (case in-sensitive) the message // will be logged at the corresponding level; otherwise the level will be // "info". -func (w loggerWriter) Write(buf []byte) (int, error) { +func (w Writer) Write(buf []byte) (int, error) { if w.SugaredLogger == nil { - return len(buf), nil + return 0, fmt.Errorf("Writer's backing logger is nil") } msg := string(buf) diff --git a/src/pkg/event/parserfunc.go b/src/pkg/node/cordondrain/config.go similarity index 75% rename from src/pkg/event/parserfunc.go rename to src/pkg/node/cordondrain/config.go index 254ae1b5..dd6ee5d7 100644 --- a/src/pkg/event/parserfunc.go +++ b/src/pkg/node/cordondrain/config.go @@ -14,14 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package event +package cordondrain -import ( - "context" -) - -type ParserFunc func(context.Context, string) Event - -func (pf ParserFunc) Parse(ctx context.Context, str string) Event { - return pf(ctx, str) +type Config struct { + Force bool + GracePeriodSeconds int + IgnoreAllDaemonSets bool + DeleteEmptyDirData bool + TimeoutSeconds int } diff --git a/src/pkg/node/cordondrain/kubectl/builder.go b/src/pkg/node/cordondrain/kubectl/builder.go index 47950e21..c69651ab 100644 --- a/src/pkg/node/cordondrain/kubectl/builder.go +++ b/src/pkg/node/cordondrain/kubectl/builder.go @@ -17,52 +17,44 @@ limitations under the License. package kubectl import ( - "fmt" + "context" "time" "github.com/aws/aws-node-termination-handler/pkg/node/cordondrain" - "go.uber.org/multierr" + v1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes" "k8s.io/kubectl/pkg/drain" ) -type builder struct { - kubernetes.Interface - Cordoner - Drainer -} - -func NewBuilder(kubeClient kubernetes.Interface, cordoner Cordoner, drainer Drainer) (cordondrain.Builder, error) { - var err error - if kubeClient == nil { - err = multierr.Append(err, fmt.Errorf("argument 'kubeClient' is nil")) - } - if cordoner == nil { - err = multierr.Append(err, fmt.Errorf("argument 'cordoner' is nil")) +type ( + Cordoner interface { + Cordon(context.Context, *v1.Node, drain.Helper) error } - if drainer == nil { - err = multierr.Append(err, fmt.Errorf("argument 'drainer' is nil")) - } - if err != nil { - return nil, err + + Drainer interface { + Drain(context.Context, *v1.Node, drain.Helper) error } - return builder{ - Cordoner: cordoner, - Drainer: drainer, - Interface: kubeClient, - }, nil -} + Builder struct { + Cordoner + Drainer -func (c builder) Build(config cordondrain.Config) (cordondrain.CordonDrainer, error) { - helper := drain.Helper{ - Client: c, - Force: config.Force, - GracePeriodSeconds: config.GracePeriodSeconds, - IgnoreAllDaemonSets: config.IgnoreAllDaemonSets, - DeleteEmptyDirData: config.DeleteEmptyDirData, - Timeout: time.Duration(config.TimeoutSeconds) * time.Second, + ClientSet kubernetes.Interface } - return NewCordonDrainer(helper, c.Cordoner, c.Drainer) +) + +func (b Builder) Build(config cordondrain.Config) (CordonDrainer, error) { + return CordonDrainer{ + Cordoner: b.Cordoner, + Drainer: b.Drainer, + Helper: drain.Helper{ + Client: b.ClientSet, + Force: config.Force, + GracePeriodSeconds: config.GracePeriodSeconds, + IgnoreAllDaemonSets: config.IgnoreAllDaemonSets, + DeleteEmptyDirData: config.DeleteEmptyDirData, + Timeout: time.Duration(config.TimeoutSeconds) * time.Second, + }, + }, nil } diff --git a/src/pkg/node/cordondrain/kubectl/cordondrainer.go b/src/pkg/node/cordondrain/kubectl/cordondrainer.go index 4e397953..7a146e38 100644 --- a/src/pkg/node/cordondrain/kubectl/cordondrainer.go +++ b/src/pkg/node/cordondrain/kubectl/cordondrainer.go @@ -18,44 +18,21 @@ package kubectl import ( "context" - "fmt" - - "github.com/aws/aws-node-termination-handler/pkg/node/cordondrain" - "go.uber.org/multierr" v1 "k8s.io/api/core/v1" "k8s.io/kubectl/pkg/drain" ) -type cordonDrainer struct { +type CordonDrainer struct { Cordoner Drainer drain.Helper } -func NewCordonDrainer(helper drain.Helper, cordoner Cordoner, drainer Drainer) (cordondrain.CordonDrainer, error) { - var err error - if cordoner == nil { - err = multierr.Append(err, fmt.Errorf("argument 'cordoner' is nil")) - } - if drainer == nil { - err = multierr.Append(err, fmt.Errorf("arguemnt 'drainer' is nil")) - } - if err != nil { - return nil, err - } - - return cordonDrainer{ - Cordoner: cordoner, - Drainer: drainer, - Helper: helper, - }, nil -} - -func (c cordonDrainer) Cordon(ctx context.Context, node *v1.Node) error { +func (c CordonDrainer) Cordon(ctx context.Context, node *v1.Node) error { return c.Cordoner.Cordon(ctx, node, c.Helper) } -func (c cordonDrainer) Drain(ctx context.Context, node *v1.Node) error { +func (c CordonDrainer) Drain(ctx context.Context, node *v1.Node) error { return c.Drainer.Drain(ctx, node, c.Helper) } diff --git a/src/pkg/node/cordondrain/kubectl/cordoner.go b/src/pkg/node/cordondrain/kubectl/cordonfunc.go similarity index 71% rename from src/pkg/node/cordondrain/kubectl/cordoner.go rename to src/pkg/node/cordondrain/kubectl/cordonfunc.go index 5f589841..6109904a 100644 --- a/src/pkg/node/cordondrain/kubectl/cordoner.go +++ b/src/pkg/node/cordondrain/kubectl/cordonfunc.go @@ -26,22 +26,11 @@ import ( "k8s.io/kubectl/pkg/drain" ) -var DefaultCordoner = cordoner(drain.RunCordonOrUncordon) +var DefaultCordoner = CordonFunc(drain.RunCordonOrUncordon) -type ( - RunCordonFunc = func(*drain.Helper, *v1.Node, bool) error +type CordonFunc func(*drain.Helper, *v1.Node, bool) error - cordoner RunCordonFunc -) - -func NewCordoner(cordonFunc RunCordonFunc) (Cordoner, error) { - if cordonFunc == nil { - return nil, fmt.Errorf("argument 'cordonFunc' is nil") - } - return cordoner(cordonFunc), nil -} - -func (c cordoner) Cordon(ctx context.Context, node *v1.Node, helper drain.Helper) error { +func (c CordonFunc) Cordon(ctx context.Context, node *v1.Node, helper drain.Helper) error { ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("cordon")) if node == nil { @@ -53,7 +42,7 @@ func (c cordoner) Cordon(ctx context.Context, node *v1.Node, helper drain.Helper } helper.Ctx = ctx - helper.Out = logging.NewWriter(logging.FromContext(ctx)) + helper.Out = logging.Writer{SugaredLogger: logging.FromContext(ctx)} helper.ErrOut = helper.Out const updateNodeUnschedulable = true diff --git a/src/pkg/node/cordondrain/kubectl/drainer.go b/src/pkg/node/cordondrain/kubectl/drainfunc.go similarity index 71% rename from src/pkg/node/cordondrain/kubectl/drainer.go rename to src/pkg/node/cordondrain/kubectl/drainfunc.go index c0262994..2ad35f29 100644 --- a/src/pkg/node/cordondrain/kubectl/drainer.go +++ b/src/pkg/node/cordondrain/kubectl/drainfunc.go @@ -26,22 +26,11 @@ import ( "k8s.io/kubectl/pkg/drain" ) -var DefaultDrainer = drainer(drain.RunNodeDrain) +var DefaultDrainer = DrainFunc(drain.RunNodeDrain) -type ( - RunDrainFunc = func(*drain.Helper, string) error +type DrainFunc func(*drain.Helper, string) error - drainer RunDrainFunc -) - -func NewDrainer(drainFunc RunDrainFunc) (Drainer, error) { - if drainFunc == nil { - return nil, fmt.Errorf("argument 'drainFunc' is nil") - } - return drainer(drainFunc), nil -} - -func (d drainer) Drain(ctx context.Context, node *v1.Node, helper drain.Helper) error { +func (d DrainFunc) Drain(ctx context.Context, node *v1.Node, helper drain.Helper) error { ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("drain")) if node == nil { @@ -53,7 +42,7 @@ func (d drainer) Drain(ctx context.Context, node *v1.Node, helper drain.Helper) } helper.Ctx = ctx - helper.Out = logging.NewWriter(logging.FromContext(ctx)) + helper.Out = logging.Writer{SugaredLogger: logging.FromContext(ctx)} helper.ErrOut = helper.Out if err := d(&helper, node.Name); err != nil { diff --git a/src/pkg/node/cordondrain/kubectl/types.go b/src/pkg/node/cordondrain/kubectl/types.go deleted file mode 100644 index 86dcfecf..00000000 --- a/src/pkg/node/cordondrain/kubectl/types.go +++ /dev/null @@ -1,34 +0,0 @@ -/* -Copyright 2022. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package kubectl - -import ( - "context" - - v1 "k8s.io/api/core/v1" - "k8s.io/kubectl/pkg/drain" -) - -type ( - Cordoner interface { - Cordon(context.Context, *v1.Node, drain.Helper) error - } - - Drainer interface { - Drain(context.Context, *v1.Node, drain.Helper) error - } -) diff --git a/src/pkg/node/cordondrain/types.go b/src/pkg/node/cordondrain/types.go deleted file mode 100644 index 32931564..00000000 --- a/src/pkg/node/cordondrain/types.go +++ /dev/null @@ -1,50 +0,0 @@ -/* -Copyright 2022. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cordondrain - -import ( - "context" - - v1 "k8s.io/api/core/v1" -) - -type ( - Config struct { - Force bool - GracePeriodSeconds int - IgnoreAllDaemonSets bool - DeleteEmptyDirData bool - TimeoutSeconds int - } - - Builder interface { - Build(Config) (CordonDrainer, error) - } - - Cordoner interface { - Cordon(context.Context, *v1.Node) error - } - - Drainer interface { - Drain(context.Context, *v1.Node) error - } - - CordonDrainer interface { - Cordoner - Drainer - } -) diff --git a/src/pkg/node/getter.go b/src/pkg/node/getter.go index 33db4f20..94106182 100644 --- a/src/pkg/node/getter.go +++ b/src/pkg/node/getter.go @@ -18,7 +18,6 @@ package node import ( "context" - "fmt" "github.com/aws/aws-node-termination-handler/pkg/logging" @@ -28,31 +27,20 @@ import ( ) type ( - Getter interface { - GetNode(context.Context, string) (*v1.Node, error) - } - KubeGetter interface { Get(context.Context, client.ObjectKey, client.Object) error } - getter struct { + Getter struct { KubeGetter } ) -func NewGetter(kubeGetter KubeGetter) (Getter, error) { - if kubeGetter == nil { - return nil, fmt.Errorf("argument 'kubeGetter' is nil") - } - return getter{KubeGetter: kubeGetter}, nil -} - -func (n getter) GetNode(ctx context.Context, nodeName string) (*v1.Node, error) { +func (g Getter) GetNode(ctx context.Context, nodeName string) (*v1.Node, error) { ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("node")) node := &v1.Node{} - if err := n.Get(ctx, types.NamespacedName{Name: nodeName}, node); err != nil { + if err := g.Get(ctx, types.NamespacedName{Name: nodeName}, node); err != nil { logging.FromContext(ctx). With("error", err). Error("failed to retrieve node") diff --git a/src/pkg/node/name/getter.go b/src/pkg/node/name/getter.go index ea834006..10d8add9 100644 --- a/src/pkg/node/name/getter.go +++ b/src/pkg/node/name/getter.go @@ -32,26 +32,15 @@ type ( DescribeInstancesWithContext(aws.Context, *ec2.DescribeInstancesInput, ...request.Option) (*ec2.DescribeInstancesOutput, error) } - Getter interface { - GetNodeName(context.Context, string) (string, error) - } - - getter struct { + Getter struct { EC2InstancesDescriber } ) -func NewGetter(describer EC2InstancesDescriber) (Getter, error) { - if describer == nil { - return nil, fmt.Errorf("argument 'describer' is nil") - } - return getter{EC2InstancesDescriber: describer}, nil -} - -func (n getter) GetNodeName(ctx context.Context, instanceId string) (string, error) { +func (g Getter) GetNodeName(ctx context.Context, instanceId string) (string, error) { ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("nodeName")) - result, err := n.DescribeInstancesWithContext(ctx, &ec2.DescribeInstancesInput{ + result, err := g.DescribeInstancesWithContext(ctx, &ec2.DescribeInstancesInput{ InstanceIds: []*string{aws.String(instanceId)}, }) if err != nil { diff --git a/src/pkg/sqsmessage/client.go b/src/pkg/sqsmessage/client.go index ac3288c7..d3ad498d 100644 --- a/src/pkg/sqsmessage/client.go +++ b/src/pkg/sqsmessage/client.go @@ -18,7 +18,6 @@ package sqsmessage import ( "context" - "fmt" "github.com/aws/aws-node-termination-handler/pkg/logging" @@ -33,27 +32,15 @@ type ( DeleteMessageWithContext(aws.Context, *sqs.DeleteMessageInput, ...request.Option) (*sqs.DeleteMessageOutput, error) } - Client interface { - GetSQSMessages(context.Context, *sqs.ReceiveMessageInput) ([]*sqs.Message, error) - DeleteSQSMessage(context.Context, *sqs.DeleteMessageInput) error - } - - sqsClient struct { + Client struct { SQSClient } ) -func NewClient(client SQSClient) (Client, error) { - if client == nil { - return nil, fmt.Errorf("argument 'client' is nil") - } - return sqsClient{SQSClient: client}, nil -} - -func (s sqsClient) GetSQSMessages(ctx context.Context, params *sqs.ReceiveMessageInput) ([]*sqs.Message, error) { +func (c Client) GetSQSMessages(ctx context.Context, params *sqs.ReceiveMessageInput) ([]*sqs.Message, error) { ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("sqsClient.getMessages")) - result, err := s.ReceiveMessageWithContext(ctx, params) + result, err := c.ReceiveMessageWithContext(ctx, params) if err != nil { logging.FromContext(ctx). With("error", err). @@ -64,10 +51,10 @@ func (s sqsClient) GetSQSMessages(ctx context.Context, params *sqs.ReceiveMessag return result.Messages, nil } -func (s sqsClient) DeleteSQSMessage(ctx context.Context, params *sqs.DeleteMessageInput) error { +func (c Client) DeleteSQSMessage(ctx context.Context, params *sqs.DeleteMessageInput) error { ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("sqsClient.deleteMessage")) - _, err := s.DeleteMessageWithContext(ctx, params) + _, err := c.DeleteMessageWithContext(ctx, params) if err != nil { logging.FromContext(ctx). With("error", err). diff --git a/src/pkg/terminator/cordondrainbuilder.go b/src/pkg/terminator/adapter/cordondrainbuilder.go similarity index 69% rename from src/pkg/terminator/cordondrainbuilder.go rename to src/pkg/terminator/adapter/cordondrainbuilder.go index 3b89172a..594f9b08 100644 --- a/src/pkg/terminator/cordondrainbuilder.go +++ b/src/pkg/terminator/adapter/cordondrainbuilder.go @@ -14,32 +14,27 @@ See the License for the specific language governing permissions and limitations under the License. */ -package terminator +package adapter import ( "fmt" "github.com/aws/aws-node-termination-handler/api/v1alpha1" "github.com/aws/aws-node-termination-handler/pkg/node/cordondrain" + kubectlcordondrain "github.com/aws/aws-node-termination-handler/pkg/node/cordondrain/kubectl" + "github.com/aws/aws-node-termination-handler/pkg/terminator" ) -type cordonDrainerBuilderAdapter struct { - cordondrain.Builder +type CordonDrainerBuilder struct { + kubectlcordondrain.Builder } -func NewCordonDrainerBuilder(builder cordondrain.Builder) (CordonDrainerBuilder, error) { - if builder == nil { - return nil, fmt.Errorf("argument 'builder' is nil") - } - return cordonDrainerBuilderAdapter{Builder: builder}, nil -} - -func (b cordonDrainerBuilderAdapter) NewCordonDrainer(terminator *v1alpha1.Terminator) (cordondrain.CordonDrainer, error) { +func (b CordonDrainerBuilder) NewCordonDrainer(terminator *v1alpha1.Terminator) (terminator.CordonDrainer, error) { if terminator == nil { return nil, fmt.Errorf("argument 'terminator' is nil") } - return b.Builder.Build(cordondrain.Config{ + return b.Build(cordondrain.Config{ Force: terminator.Spec.Drain.Force, GracePeriodSeconds: terminator.Spec.Drain.GracePeriodSeconds, IgnoreAllDaemonSets: terminator.Spec.Drain.IgnoreAllDaemonSets, diff --git a/src/pkg/terminator/getter.go b/src/pkg/terminator/adapter/getter.go similarity index 81% rename from src/pkg/terminator/getter.go rename to src/pkg/terminator/adapter/getter.go index 5fbb5bd7..ddc8fa00 100644 --- a/src/pkg/terminator/getter.go +++ b/src/pkg/terminator/adapter/getter.go @@ -14,11 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -package terminator +package adapter import ( "context" - "fmt" "github.com/aws/aws-node-termination-handler/api/v1alpha1" "github.com/aws/aws-node-termination-handler/pkg/logging" @@ -33,19 +32,12 @@ type ( Get(context.Context, client.ObjectKey, client.Object) error } - getter struct { + Getter struct { KubeGetter } ) -func NewGetter(kubeGetter KubeGetter) (Getter, error) { - if kubeGetter == nil { - return nil, fmt.Errorf("argument 'kubeGetter' is nil") - } - return getter{KubeGetter: kubeGetter}, nil -} - -func (g getter) GetTerminator(ctx context.Context, name types.NamespacedName) (*v1alpha1.Terminator, error) { +func (g Getter) GetTerminator(ctx context.Context, name types.NamespacedName) (*v1alpha1.Terminator, error) { terminator := &v1alpha1.Terminator{} if err := g.Get(ctx, name, terminator); err != nil { if errors.IsNotFound(err) { diff --git a/src/pkg/terminator/parser.go b/src/pkg/terminator/adapter/parser.go similarity index 63% rename from src/pkg/terminator/parser.go rename to src/pkg/terminator/adapter/parser.go index c3aba115..eb305d44 100644 --- a/src/pkg/terminator/parser.go +++ b/src/pkg/terminator/adapter/parser.go @@ -14,31 +14,24 @@ See the License for the specific language governing permissions and limitations under the License. */ -package terminator +package adapter import ( "context" - "fmt" "github.com/aws/aws-node-termination-handler/pkg/event" + "github.com/aws/aws-node-termination-handler/pkg/terminator" "github.com/aws/aws-sdk-go/service/sqs" ) -type eventParserAdapter struct { +type EventParser struct { event.Parser } -func NewSQSMessageParser(parser event.Parser) (SQSMessageParser, error) { - if parser == nil { - return nil, fmt.Errorf("argument 'parser' is nil") - } - return eventParserAdapter{Parser: parser}, nil -} - -func (a eventParserAdapter) Parse(ctx context.Context, msg *sqs.Message) event.Event { +func (e EventParser) Parse(ctx context.Context, msg *sqs.Message) terminator.Event { if msg == nil || msg.Body == nil { - return a.Parser.Parse(ctx, "") + return e.Parser.Parse(ctx, "") } - return a.Parser.Parse(ctx, *msg.Body) + return e.Parser.Parse(ctx, *msg.Body) } diff --git a/src/pkg/terminator/sqsclient.go b/src/pkg/terminator/adapter/sqsclient.go similarity index 68% rename from src/pkg/terminator/sqsclient.go rename to src/pkg/terminator/adapter/sqsclient.go index 13b91e2d..751990eb 100644 --- a/src/pkg/terminator/sqsclient.go +++ b/src/pkg/terminator/adapter/sqsclient.go @@ -14,14 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -package terminator +package adapter import ( "context" - "fmt" "github.com/aws/aws-node-termination-handler/api/v1alpha1" "github.com/aws/aws-node-termination-handler/pkg/logging" + "github.com/aws/aws-node-termination-handler/pkg/terminator" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/sqs" @@ -33,29 +33,18 @@ type ( DeleteSQSMessage(context.Context, *sqs.DeleteMessageInput) error } - sqsMessageClientAdapterBuilder struct { - SQSMessageClient - } - - sqsMessageClientAdapter struct { + sqsMessageClient struct { sqs.DeleteMessageInput sqs.ReceiveMessageInput SQSMessageClient } -) -func NewSQSClientBuilder(client SQSMessageClient) (SQSClientBuilder, error) { - if client == nil { - return nil, fmt.Errorf("argument 'client' is nil") - } - return sqsMessageClientAdapterBuilder{SQSMessageClient: client}, nil -} - -func (s sqsMessageClientAdapterBuilder) NewSQSClient(terminator *v1alpha1.Terminator) (SQSClient, error) { - if terminator == nil { - return nil, fmt.Errorf("argument 'terminator' is nil") + SQSMessageClientBuilder struct { + SQSMessageClient } +) +func (s SQSMessageClientBuilder) NewSQSClient(terminator *v1alpha1.Terminator) (terminator.SQSClient, error) { receiveMessageInput := sqs.ReceiveMessageInput{ MaxNumberOfMessages: aws.Int64(terminator.Spec.SQS.MaxNumberOfMessages), QueueUrl: aws.String(terminator.Spec.SQS.QueueURL), @@ -75,27 +64,27 @@ func (s sqsMessageClientAdapterBuilder) NewSQSClient(terminator *v1alpha1.Termin QueueUrl: aws.String(terminator.Spec.SQS.QueueURL), } - return sqsMessageClientAdapter{ + return sqsMessageClient{ DeleteMessageInput: deleteMessageInput, ReceiveMessageInput: receiveMessageInput, SQSMessageClient: s.SQSMessageClient, }, nil } -func (a sqsMessageClientAdapter) GetSQSMessages(ctx context.Context) ([]*sqs.Message, error) { +func (s sqsMessageClient) GetSQSMessages(ctx context.Context) ([]*sqs.Message, error) { ctx = logging.WithLogger(ctx, logging.FromContext(ctx). - With("params", logging.NewReceiveMessageInputMarshaler(&a.ReceiveMessageInput)), + With("params", logging.NewReceiveMessageInputMarshaler(&s.ReceiveMessageInput)), ) - return a.SQSMessageClient.GetSQSMessages(ctx, &a.ReceiveMessageInput) + return s.SQSMessageClient.GetSQSMessages(ctx, &s.ReceiveMessageInput) } -func (a sqsMessageClientAdapter) DeleteSQSMessage(ctx context.Context, msg *sqs.Message) error { - a.DeleteMessageInput.ReceiptHandle = msg.ReceiptHandle +func (s sqsMessageClient) DeleteSQSMessage(ctx context.Context, msg *sqs.Message) error { + s.DeleteMessageInput.ReceiptHandle = msg.ReceiptHandle ctx = logging.WithLogger(ctx, logging.FromContext(ctx). - With("params", logging.NewDeleteMessageInputMarshaler(&a.DeleteMessageInput)), + With("params", logging.NewDeleteMessageInputMarshaler(&s.DeleteMessageInput)), ) - return a.SQSMessageClient.DeleteSQSMessage(ctx, &a.DeleteMessageInput) + return s.SQSMessageClient.DeleteSQSMessage(ctx, &s.DeleteMessageInput) } diff --git a/src/pkg/terminator/reconciler.go b/src/pkg/terminator/reconciler.go index ed251302..607251bb 100644 --- a/src/pkg/terminator/reconciler.go +++ b/src/pkg/terminator/reconciler.go @@ -22,9 +22,7 @@ import ( "time" "github.com/aws/aws-node-termination-handler/api/v1alpha1" - "github.com/aws/aws-node-termination-handler/pkg/event" "github.com/aws/aws-node-termination-handler/pkg/logging" - "github.com/aws/aws-node-termination-handler/pkg/node/cordondrain" "github.com/aws/aws-sdk-go/service/sqs" @@ -32,32 +30,41 @@ import ( "k8s.io/apimachinery/pkg/types" "go.uber.org/multierr" + "go.uber.org/zap/zapcore" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) type ( - NodeGetter interface { - GetNode(context.Context, string) (*v1.Node, error) + CordonDrainer interface { + Cordon(context.Context, *v1.Node) error + Drain(context.Context, *v1.Node) error } - NodeNameGetter interface { - GetNodeName(context.Context, string) (string, error) + CordonDrainerBuilder interface { + NewCordonDrainer(*v1alpha1.Terminator) (CordonDrainer, error) } - SQSMessageParser interface { - Parse(context.Context, *sqs.Message) event.Event - } + Event interface { + zapcore.ObjectMarshaler - CordonDrainerBuilder interface { - NewCordonDrainer(*v1alpha1.Terminator) (cordondrain.CordonDrainer, error) + Done(context.Context) (tryAgain bool, err error) + EC2InstanceIds() []string } Getter interface { GetTerminator(context.Context, types.NamespacedName) (*v1alpha1.Terminator, error) } + NodeGetter interface { + GetNode(context.Context, string) (*v1.Node, error) + } + + NodeNameGetter interface { + GetNodeName(context.Context, string) (string, error) + } + SQSClient interface { GetSQSMessages(context.Context) ([]*sqs.Message, error) DeleteSQSMessage(context.Context, *sqs.Message) error @@ -67,6 +74,10 @@ type ( NewSQSClient(*v1alpha1.Terminator) (SQSClient, error) } + SQSMessageParser interface { + Parse(context.Context, *sqs.Message) Event + } + Reconciler struct { NodeGetter NodeNameGetter diff --git a/src/test/reconciliation_test.go b/src/test/reconciliation_test.go index d6b6215e..82e608c3 100644 --- a/src/test/reconciliation_test.go +++ b/src/test/reconciliation_test.go @@ -53,6 +53,7 @@ import ( nodename "github.com/aws/aws-node-termination-handler/pkg/node/name" "github.com/aws/aws-node-termination-handler/pkg/sqsmessage" "github.com/aws/aws-node-termination-handler/pkg/terminator" + terminatoradapter "github.com/aws/aws-node-termination-handler/pkg/terminator/adapter" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" @@ -139,8 +140,8 @@ var _ = Describe("Reconciliation", func() { kubeGetFunc KubeGetFunc receiveSQSMessageFunc ReceiveSQSMessageFunc deleteSQSMessageFunc DeleteSQSMessageFunc - cordonFunc kubectlcordondrain.RunCordonFunc - drainFunc kubectlcordondrain.RunDrainFunc + cordonFunc kubectlcordondrain.CordonFunc + drainFunc kubectlcordondrain.DrainFunc ) When("the SQS queue is empty", func() { @@ -1624,62 +1625,42 @@ var _ = Describe("Reconciliation", func() { // 3. Construct the reconciler. - nodeGetter, err := node.NewGetter(kubeClient) - Expect(nodeGetter, err).ToNot(BeNil()) - - nodeNameGetter, err := nodename.NewGetter(ec2Client) - Expect(nodeNameGetter, err).ToNot(BeNil()) - - asgTerminateEventV1Parser, err := asgterminateeventv1.NewParser(asgClient) - Expect(asgTerminateEventV1Parser, err).ToNot(BeNil()) - - asgTerminateEventV2Parser, err := asgterminateeventv2.NewParser(asgClient) - Expect(asgTerminateEventV2Parser, err).ToNot(BeNil()) - - sqsMessageParser, err := terminator.NewSQSMessageParser(event.NewParser( - asgTerminateEventV1Parser, - asgTerminateEventV2Parser, - rebalancerecommendationeventv0.NewParser(), - scheduledchangeeventv1.NewParser(), - spotinterruptioneventv1.NewParser(), - statechangeeventv1.NewParser(), - )) - Expect(sqsMessageParser, err).ToNot(BeNil()) - - terminatorGetter, err := terminator.NewGetter(kubeClient) - Expect(terminatorGetter, err).ToNot(BeNil()) - - sqsMessageClient, err := sqsmessage.NewClient(sqsClient) - Expect(sqsMessageClient, err).ToNot(BeNil()) - - terminatorSQSClientBuilder, err := terminator.NewSQSClientBuilder(sqsMessageClient) - Expect(terminatorSQSClientBuilder, err).ToNot(BeNil()) + eventParser := event.NewAggregatedParser( + asgterminateeventv1.Parser{ASGLifecycleActionCompleter: asgClient}, + asgterminateeventv2.Parser{ASGLifecycleActionCompleter: asgClient}, + rebalancerecommendationeventv0.Parser{}, + scheduledchangeeventv1.Parser{}, + spotinterruptioneventv1.Parser{}, + statechangeeventv1.Parser{}, + ) - cordoner, err := kubectlcordondrain.NewCordoner(func(h *kubectl.Helper, n *v1.Node, d bool) error { + cordoner := kubectlcordondrain.CordonFunc(func(h *kubectl.Helper, n *v1.Node, d bool) error { return cordonFunc(h, n, d) }) - Expect(cordoner, err).ToNot(BeNil()) - drainer, err := kubectlcordondrain.NewDrainer(func(h *kubectl.Helper, n string) error { + drainer := kubectlcordondrain.DrainFunc(func(h *kubectl.Helper, n string) error { return drainFunc(h, n) }) - Expect(drainer, err).ToNot(BeNil()) - - cordonDrainerBuilder, err := kubectlcordondrain.NewBuilder(&kubernetes.Clientset{}, cordoner, drainer) - Expect(cordonDrainerBuilder, err).ToNot(BeNil()) - terminatorCordonDrainerBuilder, err := terminator.NewCordonDrainerBuilder(cordonDrainerBuilder) - Expect(terminatorCordonDrainerBuilder, err).ToNot(BeNil()) + cordonDrainerBuilder := kubectlcordondrain.Builder{ + ClientSet: &kubernetes.Clientset{}, + Cordoner: cordoner, + Drainer: drainer, + } reconciler = terminator.Reconciler{ - Name: "terminator", - RequeueInterval: time.Duration(10) * time.Second, - NodeGetter: nodeGetter, - NodeNameGetter: nodeNameGetter, - SQSClientBuilder: terminatorSQSClientBuilder, - SQSMessageParser: sqsMessageParser, - Getter: terminatorGetter, - CordonDrainerBuilder: terminatorCordonDrainerBuilder, + Name: "terminator", + RequeueInterval: time.Duration(10) * time.Second, + NodeGetter: node.Getter{KubeGetter: kubeClient}, + NodeNameGetter: nodename.Getter{EC2InstancesDescriber: ec2Client}, + SQSClientBuilder: terminatoradapter.SQSMessageClientBuilder{ + SQSMessageClient: sqsmessage.Client{SQSClient: sqsClient}, + }, + SQSMessageParser: terminatoradapter.EventParser{Parser: eventParser}, + Getter: terminatoradapter.Getter{KubeGetter: kubeClient}, + CordonDrainerBuilder: terminatoradapter.CordonDrainerBuilder{ + Builder: cordonDrainerBuilder, + }, } }) From 0407de64ed7c467ef4a30662b30e8adb70e3ec3b Mon Sep 17 00:00:00 2001 From: Jerad C Date: Tue, 5 Apr 2022 13:24:30 -0500 Subject: [PATCH 05/13] remove SQS request params from Terminator spec --- src/api/v1alpha1/terminator_logging.go | 18 --- src/api/v1alpha1/terminator_types.go | 7 +- src/api/v1alpha1/terminator_validation.go | 39 ------- src/api/v1alpha1/zz_generated.deepcopy.go | 10 -- .../templates/node.k8s.aws_terminators.yaml | 103 ------------------ .../values.yaml | 8 -- src/pkg/terminator/adapter/sqsclient.go | 20 ++-- src/test/reconciliation_test.go | 15 +-- 8 files changed, 15 insertions(+), 205 deletions(-) diff --git a/src/api/v1alpha1/terminator_logging.go b/src/api/v1alpha1/terminator_logging.go index 7ba8c822..fe23319d 100644 --- a/src/api/v1alpha1/terminator_logging.go +++ b/src/api/v1alpha1/terminator_logging.go @@ -27,25 +27,7 @@ func (t *TerminatorSpec) MarshalLogObject(enc zapcore.ObjectEncoder) error { } func (s SQSSpec) MarshalLogObject(enc zapcore.ObjectEncoder) error { - enc.AddArray("attributeNames", zapcore.ArrayMarshalerFunc(func(enc zapcore.ArrayEncoder) error { - for _, attrName := range s.AttributeNames { - enc.AppendString(attrName) - } - return nil - })) - - enc.AddInt64("maxNumberOfMessages", s.MaxNumberOfMessages) - - enc.AddArray("messageAttributeNames", zapcore.ArrayMarshalerFunc(func(enc zapcore.ArrayEncoder) error { - for _, attrName := range s.MessageAttributeNames { - enc.AppendString(attrName) - } - return nil - })) - enc.AddString("queueURL", s.QueueURL) - enc.AddInt64("visibilityTimeoutSeconds", s.VisibilityTimeoutSeconds) - enc.AddInt64("waitTimeSeconds", s.WaitTimeSeconds) return nil } diff --git a/src/api/v1alpha1/terminator_types.go b/src/api/v1alpha1/terminator_types.go index 7063dc0a..4b4579c4 100644 --- a/src/api/v1alpha1/terminator_types.go +++ b/src/api/v1alpha1/terminator_types.go @@ -37,12 +37,7 @@ type TerminatorSpec struct { // SQSSpec defines inputs to SQS "receive messages" requests. type SQSSpec struct { // https://pkg.go.dev/github.com/aws/aws-sdk-go@v1.38.55/service/sqs#ReceiveMessageInput - AttributeNames []string `json:"attributeNames,omitempty"` - MaxNumberOfMessages int64 `json:"maxNumberOfMessages,omitempty"` - MessageAttributeNames []string `json:"messageAttributeNames,omitempty"` - QueueURL string `json:"queueURL,omitempty"` - VisibilityTimeoutSeconds int64 `json:"visibilityTimeoutSeconds,omitempty"` - WaitTimeSeconds int64 `json:"waitTimeSeconds,omitempty"` + QueueURL string `json:"queueURL,omitempty"` } // DrainSpec defines inputs to the cordon and drain operations. diff --git a/src/api/v1alpha1/terminator_validation.go b/src/api/v1alpha1/terminator_validation.go index 5ec54aee..5903f283 100644 --- a/src/api/v1alpha1/terminator_validation.go +++ b/src/api/v1alpha1/terminator_validation.go @@ -19,7 +19,6 @@ package v1alpha1 import ( "context" "net/url" - "strings" "github.com/aws/aws-sdk-go/service/sqs" @@ -45,46 +44,8 @@ func (t *TerminatorSpec) validate() (errs *apis.FieldError) { } func (s *SQSSpec) validate() (errs *apis.FieldError) { - for _, attrName := range s.AttributeNames { - if !knownSQSAttributeNames.Has(attrName) { - errs = errs.Also(apis.ErrInvalidValue(attrName, "attributeNames")) - } - } - - // https://github.com/aws/aws-sdk-go/blob/v1.38.55/service/sqs/api.go#L3996-L3999 - if s.MaxNumberOfMessages < 1 || 10 < s.MaxNumberOfMessages { - errs = errs.Also(apis.ErrInvalidValue(s.MaxNumberOfMessages, "maxNumberOfMessages", "must be in range 1-10")) - } - - // https://github.com/aws/aws-sdk-go/blob/v1.38.55/service/sqs/api.go#L4001-L4021 - // - // Simple checks are done below. More indepth checks are left to the SQS client/service. - for _, attrName := range s.MessageAttributeNames { - if len(attrName) > 256 { - errs = errs.Also(apis.ErrInvalidValue(attrName, "messageAttributeNames", "must be 256 characters or less")) - } - - lcAttrName := strings.ToLower(attrName) - if strings.HasPrefix(lcAttrName, "aws") || strings.HasPrefix(lcAttrName, "amazon") { - errs = errs.Also(apis.ErrInvalidValue(attrName, "messageAttributeNames", `must not use reserved prefixes "AWS" or "Amazon"`)) - } - - if strings.HasPrefix(attrName, ".") || strings.HasSuffix(attrName, ".") { - errs = errs.Also(apis.ErrInvalidValue(attrName, "messageAttributeNames", "must not begin or end with a period (.)")) - } - } - if _, err := url.Parse(s.QueueURL); err != nil { errs = errs.Also(apis.ErrInvalidValue(s.QueueURL, "queueURL", "must be a valid URL")) } - - if s.VisibilityTimeoutSeconds < 0 { - errs = errs.Also(apis.ErrInvalidValue(s.VisibilityTimeoutSeconds, "visibilityTimeoutSeconds", "must be zero or greater")) - } - - if s.WaitTimeSeconds < 0 { - errs = errs.Also(apis.ErrInvalidValue(s.WaitTimeSeconds, "waitTimeSeconds", "must be zero or greater")) - } - return errs } diff --git a/src/api/v1alpha1/zz_generated.deepcopy.go b/src/api/v1alpha1/zz_generated.deepcopy.go index 63e32546..3d63d5d1 100644 --- a/src/api/v1alpha1/zz_generated.deepcopy.go +++ b/src/api/v1alpha1/zz_generated.deepcopy.go @@ -43,16 +43,6 @@ func (in *DrainSpec) DeepCopy() *DrainSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SQSSpec) DeepCopyInto(out *SQSSpec) { *out = *in - if in.AttributeNames != nil { - in, out := &in.AttributeNames, &out.AttributeNames - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.MessageAttributeNames != nil { - in, out := &in.MessageAttributeNames, &out.MessageAttributeNames - *out = make([]string, len(*in)) - copy(*out, *in) - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SQSSpec. diff --git a/src/charts/aws-node-termination-handler-2/templates/node.k8s.aws_terminators.yaml b/src/charts/aws-node-termination-handler-2/templates/node.k8s.aws_terminators.yaml index 5b49a24e..c0b865e9 100644 --- a/src/charts/aws-node-termination-handler-2/templates/node.k8s.aws_terminators.yaml +++ b/src/charts/aws-node-termination-handler-2/templates/node.k8s.aws_terminators.yaml @@ -40,82 +40,6 @@ spec: description: AWS SQS queue configuration. type: object properties: - attributeNames: - description: | - A list of attributes that need to be returned along with each message. These - attributes include: - - * All – Returns all values. - - * ApproximateFirstReceiveTimestamp – Returns the time the message was - first received from the queue (epoch time (http://en.wikipedia.org/wiki/Unix_time) - in milliseconds). - - * ApproximateReceiveCount – Returns the number of times a message has - been received across all queues but not deleted. - - * AWSTraceHeader – Returns the AWS X-Ray trace header string. - - * SenderId For an IAM user, returns the IAM user ID, for example ABCDEFGHI1JKLMNOPQ23R. - For an IAM role, returns the IAM role ID, for example ABCDE1F2GH3I4JK5LMNOP:i-a123b456. - - * SentTimestamp – Returns the time the message was sent to the queue - (epoch time (http://en.wikipedia.org/wiki/Unix_time) in milliseconds). - - * MessageDeduplicationId – Returns the value provided by the producer - that calls the SendMessage action. - - * MessageGroupId – Returns the value provided by the producer that calls - the SendMessage action. Messages with the same MessageGroupId are returned - in sequence. - - * SequenceNumber – Returns the value provided by Amazon SQS. - type: array - items: - type: string - {{- with .Values.terminator.defaults.sqs.attributeNames }} - default: - {{- toYaml . | nindent 22 }} - {{- end }} - maxNumberOfMessages: - description: | - The maximum number of messages to return. Amazon SQS never returns more messages - than this value (however, fewer messages might be returned). Valid values: - 1 to 10. - type: integer - format: int64 - {{- with .Values.terminator.defaults.sqs.maxNumberOfMessages }} - default: {{ . }} - {{- end }} - messageAttributeNames: - description: | - The name of the message attribute, where N is the index. - - * The name can contain alphanumeric characters and the underscore (_), - hyphen (-), and period (.). - - * The name is case-sensitive and must be unique among all attribute names - for the message. - - * The name must not start with AWS-reserved prefixes such as AWS. or Amazon. - (or any casing variants). - - * The name must not start or end with a period (.), and it should not - have periods in succession (..). - - * The name can be up to 256 characters long. - - When using ReceiveMessage, you can send a list of attribute names to receive, - or you can return all of the attributes by specifying All or .* in your request. - You can also use all message attributes starting with a prefix, for example - bar.*. - type: array - items: - type: string - {{- with .Values.terminator.defaults.sqs.messageAttributeNames }} - default: - {{- toYaml . | nindent 22 }} - {{- end }} queueURL: description: | The URL of the Amazon SQS queue from which messages are received. @@ -124,33 +48,6 @@ spec: * QueueURL is a required field type: string - visibilityTimeoutSeconds: - description: | - The duration (in seconds) that the received messages are hidden from subsequent - retrieve requests after being retrieved by a ReceiveMessage request. - type: integer - format: int64 - {{- with .Values.terminator.defaults.sqs.visibilityTimeoutSeconds }} - default: {{ . }} - {{- end }} - waitTimeSeconds: - description: | - The duration (in seconds) for which the call waits for a message to arrive - in the queue before returning. If a message is available, the call returns - sooner than WaitTimeSeconds. If no messages are available and the wait time - expires, the call returns successfully with an empty list of messages. - - To avoid HTTP errors, ensure that the HTTP response timeout for ReceiveMessage - requests is longer than the WaitTimeSeconds parameter. For example, with - the Java SDK, you can set HTTP transport settings using the NettyNioAsyncHttpClient - (https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClient.html) - for asynchronous clients, or the ApacheHttpClient (https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/apache/ApacheHttpClient.html) - for synchronous clients. - type: integer - format: int64 - {{- with .Values.terminator.defaults.sqs.waitTimeSeconds }} - default: {{ . }} - {{- end }} drain: description: TBD type: object diff --git a/src/charts/aws-node-termination-handler-2/values.yaml b/src/charts/aws-node-termination-handler-2/values.yaml index 77d7cf4f..1eac2a8f 100644 --- a/src/charts/aws-node-termination-handler-2/values.yaml +++ b/src/charts/aws-node-termination-handler-2/values.yaml @@ -12,14 +12,6 @@ annotations: {} terminator: defaults: - sqs: - attributeNames: - - SentTimestamp - maxNumberOfMessages: 10 - messageAttributeNames: - - All - visibilityTimeoutSeconds: 20 - waitTimeSeconds: 20 drain: force: true gracePeriodSeconds: -1 diff --git a/src/pkg/terminator/adapter/sqsclient.go b/src/pkg/terminator/adapter/sqsclient.go index 751990eb..2eb09a83 100644 --- a/src/pkg/terminator/adapter/sqsclient.go +++ b/src/pkg/terminator/adapter/sqsclient.go @@ -46,18 +46,16 @@ type ( func (s SQSMessageClientBuilder) NewSQSClient(terminator *v1alpha1.Terminator) (terminator.SQSClient, error) { receiveMessageInput := sqs.ReceiveMessageInput{ - MaxNumberOfMessages: aws.Int64(terminator.Spec.SQS.MaxNumberOfMessages), QueueUrl: aws.String(terminator.Spec.SQS.QueueURL), - VisibilityTimeout: aws.Int64(terminator.Spec.SQS.VisibilityTimeoutSeconds), - WaitTimeSeconds: aws.Int64(terminator.Spec.SQS.WaitTimeSeconds), - } - receiveMessageInput.AttributeNames = make([]*string, len(terminator.Spec.SQS.AttributeNames)) - for i, attrName := range terminator.Spec.SQS.AttributeNames { - receiveMessageInput.AttributeNames[i] = aws.String(attrName) - } - receiveMessageInput.MessageAttributeNames = make([]*string, len(terminator.Spec.SQS.MessageAttributeNames)) - for i, attrName := range terminator.Spec.SQS.MessageAttributeNames { - receiveMessageInput.MessageAttributeNames[i] = aws.String(attrName) + MaxNumberOfMessages: aws.Int64(10), + VisibilityTimeout: aws.Int64(20), // Seconds + WaitTimeSeconds: aws.Int64(20), // Seconds, maximum for long polling + AttributeNames: []*string{ + aws.String(sqs.MessageSystemAttributeNameSentTimestamp), + }, + MessageAttributeNames: []*string{ + aws.String(sqs.QueueAttributeNameAll), + }, } deleteMessageInput := sqs.DeleteMessageInput{ diff --git a/src/test/reconciliation_test.go b/src/test/reconciliation_test.go index 82e608c3..7be428e9 100644 --- a/src/test/reconciliation_test.go +++ b/src/test/reconciliation_test.go @@ -1195,13 +1195,13 @@ var _ = Describe("Reconciliation", func() { When("getting messages from a terminator's SQS queue", func() { const ( - maxNumberOfMessages = int64(4) - visibilityTimeoutSeconds = int64(17) - waitTimeSeconds = int64(31) + maxNumberOfMessages = int64(10) + visibilityTimeoutSeconds = int64(20) + waitTimeSeconds = int64(20) ) var ( - attributeNames = []string{"TestAttributeName1", "TestAttributeName2"} - messageAttributeNames = []string{"TestMsgAttributeName1", "TestMsgAttributeName2"} + attributeNames = []string{sqs.MessageSystemAttributeNameSentTimestamp} + messageAttributeNames = []string{sqs.QueueAttributeNameAll} input *sqs.ReceiveMessageInput ) @@ -1209,12 +1209,7 @@ var _ = Describe("Reconciliation", func() { terminator, found := terminators[terminatorNamespaceName] Expect(found).To(BeTrue()) - terminator.Spec.SQS.MaxNumberOfMessages = maxNumberOfMessages terminator.Spec.SQS.QueueURL = queueURL - terminator.Spec.SQS.VisibilityTimeoutSeconds = visibilityTimeoutSeconds - terminator.Spec.SQS.WaitTimeSeconds = waitTimeSeconds - terminator.Spec.SQS.AttributeNames = append([]string{}, attributeNames...) - terminator.Spec.SQS.MessageAttributeNames = append([]string{}, messageAttributeNames...) defaultReceiveSQSMessageFunc := receiveSQSMessageFunc receiveSQSMessageFunc = func(ctx aws.Context, in *sqs.ReceiveMessageInput, options ...awsrequest.Option) (*sqs.ReceiveMessageOutput, error) { From 69a05440aa86e22df0049f123297caace725e4e2 Mon Sep 17 00:00:00 2001 From: Jerad C Date: Wed, 6 Apr 2022 10:57:55 -0500 Subject: [PATCH 06/13] update copyright attribution --- src/api/v1alpha1/groupversion_info.go | 2 +- src/api/v1alpha1/terminator_logging.go | 2 +- src/api/v1alpha1/terminator_types.go | 2 +- src/api/v1alpha1/terminator_validation.go | 2 +- src/api/v1alpha1/zz_generated.deepcopy.go | 2 +- src/cmd/controller/main.go | 2 +- src/cmd/webhook/main.go | 2 +- src/pkg/event/aggregatedparser.go | 2 +- src/pkg/event/asgterminate/v1/awsevent.go | 2 +- src/pkg/event/asgterminate/v1/event.go | 2 +- src/pkg/event/asgterminate/v1/parser.go | 2 +- src/pkg/event/asgterminate/v1/types.go | 2 +- src/pkg/event/asgterminate/v2/awsevent.go | 2 +- src/pkg/event/asgterminate/v2/event.go | 2 +- src/pkg/event/asgterminate/v2/parser.go | 2 +- src/pkg/event/asgterminate/v2/types.go | 2 +- src/pkg/event/metadata.go | 2 +- src/pkg/event/noop.go | 2 +- src/pkg/event/rebalancerecommendation/v0/awsevent.go | 2 +- src/pkg/event/rebalancerecommendation/v0/event.go | 2 +- src/pkg/event/rebalancerecommendation/v0/parser.go | 2 +- src/pkg/event/scheduledchange/v1/awsevent.go | 2 +- src/pkg/event/scheduledchange/v1/event.go | 2 +- src/pkg/event/scheduledchange/v1/parser.go | 2 +- src/pkg/event/spotinterruption/v1/awsevent.go | 2 +- src/pkg/event/spotinterruption/v1/event.go | 2 +- src/pkg/event/spotinterruption/v1/parser.go | 2 +- src/pkg/event/statechange/v1/awsevent.go | 2 +- src/pkg/event/statechange/v1/event.go | 2 +- src/pkg/event/statechange/v1/parser.go | 2 +- src/pkg/logging/alias.go | 2 +- src/pkg/logging/sqs/deletemessageinput.go | 2 +- src/pkg/logging/sqs/message.go | 2 +- src/pkg/logging/sqs/receivemessagesinput.go | 2 +- src/pkg/logging/writer.go | 2 +- src/pkg/node/cordondrain/config.go | 2 +- src/pkg/node/cordondrain/kubectl/builder.go | 2 +- src/pkg/node/cordondrain/kubectl/cordondrainer.go | 2 +- src/pkg/node/cordondrain/kubectl/cordonfunc.go | 2 +- src/pkg/node/cordondrain/kubectl/drainfunc.go | 2 +- src/pkg/node/getter.go | 2 +- src/pkg/node/name/getter.go | 2 +- src/pkg/sqsmessage/client.go | 2 +- src/pkg/terminator/adapter/cordondrainbuilder.go | 2 +- src/pkg/terminator/adapter/getter.go | 2 +- src/pkg/terminator/adapter/parser.go | 2 +- src/pkg/terminator/adapter/sqsclient.go | 2 +- src/pkg/terminator/reconciler.go | 2 +- src/test/app_integ_suite_test.go | 2 +- src/test/asgclient.go | 2 +- src/test/ec2client.go | 2 +- src/test/kubeclient.go | 2 +- src/test/reconciliation_test.go | 2 +- src/test/sqsclient.go | 2 +- 54 files changed, 54 insertions(+), 54 deletions(-) diff --git a/src/api/v1alpha1/groupversion_info.go b/src/api/v1alpha1/groupversion_info.go index 727f452b..4dbfc0db 100644 --- a/src/api/v1alpha1/groupversion_info.go +++ b/src/api/v1alpha1/groupversion_info.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/api/v1alpha1/terminator_logging.go b/src/api/v1alpha1/terminator_logging.go index fe23319d..93135a36 100644 --- a/src/api/v1alpha1/terminator_logging.go +++ b/src/api/v1alpha1/terminator_logging.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/api/v1alpha1/terminator_types.go b/src/api/v1alpha1/terminator_types.go index 4b4579c4..f742e646 100644 --- a/src/api/v1alpha1/terminator_types.go +++ b/src/api/v1alpha1/terminator_types.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/api/v1alpha1/terminator_validation.go b/src/api/v1alpha1/terminator_validation.go index 5903f283..bf2ff401 100644 --- a/src/api/v1alpha1/terminator_validation.go +++ b/src/api/v1alpha1/terminator_validation.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/api/v1alpha1/zz_generated.deepcopy.go b/src/api/v1alpha1/zz_generated.deepcopy.go index 3d63d5d1..a98c656a 100644 --- a/src/api/v1alpha1/zz_generated.deepcopy.go +++ b/src/api/v1alpha1/zz_generated.deepcopy.go @@ -2,7 +2,7 @@ // +build !ignore_autogenerated /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/cmd/controller/main.go b/src/cmd/controller/main.go index 495b58a7..389c5d70 100644 --- a/src/cmd/controller/main.go +++ b/src/cmd/controller/main.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/cmd/webhook/main.go b/src/cmd/webhook/main.go index b092db9b..e60f087b 100644 --- a/src/cmd/webhook/main.go +++ b/src/cmd/webhook/main.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/event/aggregatedparser.go b/src/pkg/event/aggregatedparser.go index 7604f500..81b6be56 100644 --- a/src/pkg/event/aggregatedparser.go +++ b/src/pkg/event/aggregatedparser.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/event/asgterminate/v1/awsevent.go b/src/pkg/event/asgterminate/v1/awsevent.go index 89cfea5d..78d55824 100644 --- a/src/pkg/event/asgterminate/v1/awsevent.go +++ b/src/pkg/event/asgterminate/v1/awsevent.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/event/asgterminate/v1/event.go b/src/pkg/event/asgterminate/v1/event.go index 754bd231..33efe517 100644 --- a/src/pkg/event/asgterminate/v1/event.go +++ b/src/pkg/event/asgterminate/v1/event.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/event/asgterminate/v1/parser.go b/src/pkg/event/asgterminate/v1/parser.go index 52c9c89c..e749cefd 100644 --- a/src/pkg/event/asgterminate/v1/parser.go +++ b/src/pkg/event/asgterminate/v1/parser.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/event/asgterminate/v1/types.go b/src/pkg/event/asgterminate/v1/types.go index 423f7dbb..6c9aae0d 100644 --- a/src/pkg/event/asgterminate/v1/types.go +++ b/src/pkg/event/asgterminate/v1/types.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/event/asgterminate/v2/awsevent.go b/src/pkg/event/asgterminate/v2/awsevent.go index ad2fd98a..28469e51 100644 --- a/src/pkg/event/asgterminate/v2/awsevent.go +++ b/src/pkg/event/asgterminate/v2/awsevent.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/event/asgterminate/v2/event.go b/src/pkg/event/asgterminate/v2/event.go index 4257f503..c740ccfc 100644 --- a/src/pkg/event/asgterminate/v2/event.go +++ b/src/pkg/event/asgterminate/v2/event.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/event/asgterminate/v2/parser.go b/src/pkg/event/asgterminate/v2/parser.go index 653e57cd..a810d4da 100644 --- a/src/pkg/event/asgterminate/v2/parser.go +++ b/src/pkg/event/asgterminate/v2/parser.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/event/asgterminate/v2/types.go b/src/pkg/event/asgterminate/v2/types.go index e4ec3483..1ce4d963 100644 --- a/src/pkg/event/asgterminate/v2/types.go +++ b/src/pkg/event/asgterminate/v2/types.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/event/metadata.go b/src/pkg/event/metadata.go index a0c19228..e40528d5 100644 --- a/src/pkg/event/metadata.go +++ b/src/pkg/event/metadata.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/event/noop.go b/src/pkg/event/noop.go index 971c454c..53d62f19 100644 --- a/src/pkg/event/noop.go +++ b/src/pkg/event/noop.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/event/rebalancerecommendation/v0/awsevent.go b/src/pkg/event/rebalancerecommendation/v0/awsevent.go index 6b9902e8..b5ca9d93 100644 --- a/src/pkg/event/rebalancerecommendation/v0/awsevent.go +++ b/src/pkg/event/rebalancerecommendation/v0/awsevent.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/event/rebalancerecommendation/v0/event.go b/src/pkg/event/rebalancerecommendation/v0/event.go index 341bf56e..08881549 100644 --- a/src/pkg/event/rebalancerecommendation/v0/event.go +++ b/src/pkg/event/rebalancerecommendation/v0/event.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/event/rebalancerecommendation/v0/parser.go b/src/pkg/event/rebalancerecommendation/v0/parser.go index 609c2a86..52511d43 100644 --- a/src/pkg/event/rebalancerecommendation/v0/parser.go +++ b/src/pkg/event/rebalancerecommendation/v0/parser.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/event/scheduledchange/v1/awsevent.go b/src/pkg/event/scheduledchange/v1/awsevent.go index 4469f2f7..3240c438 100644 --- a/src/pkg/event/scheduledchange/v1/awsevent.go +++ b/src/pkg/event/scheduledchange/v1/awsevent.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/event/scheduledchange/v1/event.go b/src/pkg/event/scheduledchange/v1/event.go index e39a0e45..b53f587f 100644 --- a/src/pkg/event/scheduledchange/v1/event.go +++ b/src/pkg/event/scheduledchange/v1/event.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/event/scheduledchange/v1/parser.go b/src/pkg/event/scheduledchange/v1/parser.go index 1781a233..88d8a388 100644 --- a/src/pkg/event/scheduledchange/v1/parser.go +++ b/src/pkg/event/scheduledchange/v1/parser.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/event/spotinterruption/v1/awsevent.go b/src/pkg/event/spotinterruption/v1/awsevent.go index c5829d97..ad5c175a 100644 --- a/src/pkg/event/spotinterruption/v1/awsevent.go +++ b/src/pkg/event/spotinterruption/v1/awsevent.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/event/spotinterruption/v1/event.go b/src/pkg/event/spotinterruption/v1/event.go index 465c29fb..a18a8eae 100644 --- a/src/pkg/event/spotinterruption/v1/event.go +++ b/src/pkg/event/spotinterruption/v1/event.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/event/spotinterruption/v1/parser.go b/src/pkg/event/spotinterruption/v1/parser.go index 0249cef6..b1567770 100644 --- a/src/pkg/event/spotinterruption/v1/parser.go +++ b/src/pkg/event/spotinterruption/v1/parser.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/event/statechange/v1/awsevent.go b/src/pkg/event/statechange/v1/awsevent.go index 68cc6702..82392106 100644 --- a/src/pkg/event/statechange/v1/awsevent.go +++ b/src/pkg/event/statechange/v1/awsevent.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/event/statechange/v1/event.go b/src/pkg/event/statechange/v1/event.go index 766121a4..add3b1eb 100644 --- a/src/pkg/event/statechange/v1/event.go +++ b/src/pkg/event/statechange/v1/event.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/event/statechange/v1/parser.go b/src/pkg/event/statechange/v1/parser.go index 3cdb013e..2bac7578 100644 --- a/src/pkg/event/statechange/v1/parser.go +++ b/src/pkg/event/statechange/v1/parser.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/logging/alias.go b/src/pkg/logging/alias.go index 0c74b08a..1bf44110 100644 --- a/src/pkg/logging/alias.go +++ b/src/pkg/logging/alias.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/logging/sqs/deletemessageinput.go b/src/pkg/logging/sqs/deletemessageinput.go index 9cc71193..5a65a50e 100644 --- a/src/pkg/logging/sqs/deletemessageinput.go +++ b/src/pkg/logging/sqs/deletemessageinput.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/logging/sqs/message.go b/src/pkg/logging/sqs/message.go index 1934f6ed..da95227f 100644 --- a/src/pkg/logging/sqs/message.go +++ b/src/pkg/logging/sqs/message.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/logging/sqs/receivemessagesinput.go b/src/pkg/logging/sqs/receivemessagesinput.go index 24a417a6..c71ef602 100644 --- a/src/pkg/logging/sqs/receivemessagesinput.go +++ b/src/pkg/logging/sqs/receivemessagesinput.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/logging/writer.go b/src/pkg/logging/writer.go index 0368e715..74028e4f 100644 --- a/src/pkg/logging/writer.go +++ b/src/pkg/logging/writer.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/node/cordondrain/config.go b/src/pkg/node/cordondrain/config.go index dd6ee5d7..80f3e1c5 100644 --- a/src/pkg/node/cordondrain/config.go +++ b/src/pkg/node/cordondrain/config.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/node/cordondrain/kubectl/builder.go b/src/pkg/node/cordondrain/kubectl/builder.go index c69651ab..b938a173 100644 --- a/src/pkg/node/cordondrain/kubectl/builder.go +++ b/src/pkg/node/cordondrain/kubectl/builder.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/node/cordondrain/kubectl/cordondrainer.go b/src/pkg/node/cordondrain/kubectl/cordondrainer.go index 7a146e38..9b3fe07a 100644 --- a/src/pkg/node/cordondrain/kubectl/cordondrainer.go +++ b/src/pkg/node/cordondrain/kubectl/cordondrainer.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/node/cordondrain/kubectl/cordonfunc.go b/src/pkg/node/cordondrain/kubectl/cordonfunc.go index 6109904a..64ecdb6a 100644 --- a/src/pkg/node/cordondrain/kubectl/cordonfunc.go +++ b/src/pkg/node/cordondrain/kubectl/cordonfunc.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/node/cordondrain/kubectl/drainfunc.go b/src/pkg/node/cordondrain/kubectl/drainfunc.go index 2ad35f29..85e885b6 100644 --- a/src/pkg/node/cordondrain/kubectl/drainfunc.go +++ b/src/pkg/node/cordondrain/kubectl/drainfunc.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/node/getter.go b/src/pkg/node/getter.go index 94106182..5ef20af5 100644 --- a/src/pkg/node/getter.go +++ b/src/pkg/node/getter.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/node/name/getter.go b/src/pkg/node/name/getter.go index 10d8add9..3d0cc892 100644 --- a/src/pkg/node/name/getter.go +++ b/src/pkg/node/name/getter.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/sqsmessage/client.go b/src/pkg/sqsmessage/client.go index d3ad498d..6a334e60 100644 --- a/src/pkg/sqsmessage/client.go +++ b/src/pkg/sqsmessage/client.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/terminator/adapter/cordondrainbuilder.go b/src/pkg/terminator/adapter/cordondrainbuilder.go index 594f9b08..68da0db7 100644 --- a/src/pkg/terminator/adapter/cordondrainbuilder.go +++ b/src/pkg/terminator/adapter/cordondrainbuilder.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/terminator/adapter/getter.go b/src/pkg/terminator/adapter/getter.go index ddc8fa00..2d39276f 100644 --- a/src/pkg/terminator/adapter/getter.go +++ b/src/pkg/terminator/adapter/getter.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/terminator/adapter/parser.go b/src/pkg/terminator/adapter/parser.go index eb305d44..4f553573 100644 --- a/src/pkg/terminator/adapter/parser.go +++ b/src/pkg/terminator/adapter/parser.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/terminator/adapter/sqsclient.go b/src/pkg/terminator/adapter/sqsclient.go index 2eb09a83..a0abadf9 100644 --- a/src/pkg/terminator/adapter/sqsclient.go +++ b/src/pkg/terminator/adapter/sqsclient.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/pkg/terminator/reconciler.go b/src/pkg/terminator/reconciler.go index 607251bb..b30c2a87 100644 --- a/src/pkg/terminator/reconciler.go +++ b/src/pkg/terminator/reconciler.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/test/app_integ_suite_test.go b/src/test/app_integ_suite_test.go index 957efc1c..f215c7d4 100644 --- a/src/test/app_integ_suite_test.go +++ b/src/test/app_integ_suite_test.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/test/asgclient.go b/src/test/asgclient.go index 92d4da26..246c2954 100644 --- a/src/test/asgclient.go +++ b/src/test/asgclient.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/test/ec2client.go b/src/test/ec2client.go index f9cf49e0..58c6653c 100644 --- a/src/test/ec2client.go +++ b/src/test/ec2client.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/test/kubeclient.go b/src/test/kubeclient.go index f8fbee90..f45418ff 100644 --- a/src/test/kubeclient.go +++ b/src/test/kubeclient.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/test/reconciliation_test.go b/src/test/reconciliation_test.go index 7be428e9..e07c2dcf 100644 --- a/src/test/reconciliation_test.go +++ b/src/test/reconciliation_test.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/test/sqsclient.go b/src/test/sqsclient.go index a11d3b16..ee9c099a 100644 --- a/src/test/sqsclient.go +++ b/src/test/sqsclient.go @@ -1,5 +1,5 @@ /* -Copyright 2022. +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 17c2ad00329e9e56cf5523b6b517e6716dcce811 Mon Sep 17 00:00:00 2001 From: Jerad C Date: Wed, 6 Apr 2022 10:59:55 -0500 Subject: [PATCH 07/13] remove boilerplate comment --- src/cmd/controller/main.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/cmd/controller/main.go b/src/cmd/controller/main.go index 389c5d70..46d2107d 100644 --- a/src/cmd/controller/main.go +++ b/src/cmd/controller/main.go @@ -23,9 +23,6 @@ import ( "os" "time" - // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) - // to ensure that exec-entrypoint and run can make use of them. - v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" From d21781647b4f35a0997670a5fd9eeacb5cc66d47 Mon Sep 17 00:00:00 2001 From: Jerad C Date: Wed, 6 Apr 2022 11:05:05 -0500 Subject: [PATCH 08/13] expand "Addr" to "Address" --- src/cmd/controller/main.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/cmd/controller/main.go b/src/cmd/controller/main.go index 46d2107d..b6474f4e 100644 --- a/src/cmd/controller/main.go +++ b/src/cmd/controller/main.go @@ -76,9 +76,9 @@ var scheme = runtime.NewScheme() type Options struct { AWSRegion string - MetricsAddr string + MetricsAddress string EnableLeaderElection bool - ProbeAddr string + ProbeAddress string } func init() { @@ -109,9 +109,9 @@ func main() { mgr, err := ctrl.NewManager(config, ctrl.Options{ Scheme: scheme, - MetricsBindAddress: options.MetricsAddr, + MetricsBindAddress: options.MetricsAddress, Port: 9443, - HealthProbeBindAddress: options.ProbeAddr, + HealthProbeBindAddress: options.ProbeAddress, LeaderElection: options.EnableLeaderElection, LeaderElectionID: "aws-node-termination-handler.k8s.aws", Logger: zapr.NewLogger(logging.FromContext(ctx).Desugar()), @@ -197,8 +197,8 @@ func parseOptions() Options { options := Options{} flag.StringVar(&options.AWSRegion, "aws-region", os.Getenv("AWS_REGION"), "The AWS region for API calls.") - flag.StringVar(&options.MetricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") - flag.StringVar(&options.ProbeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + flag.StringVar(&options.MetricsAddress, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") + flag.StringVar(&options.ProbeAddress, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") flag.BoolVar(&options.EnableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") flag.Parse() From 87dccd7ed853a283d010ddb96fce153967713aad Mon Sep 17 00:00:00 2001 From: Jerad C Date: Wed, 6 Apr 2022 11:15:12 -0500 Subject: [PATCH 09/13] fix capitalization * "Id" to "ID" * "Arn" to "ARN" --- src/pkg/event/asgterminate/v1/awsevent.go | 4 +- src/pkg/event/asgterminate/v1/event.go | 6 +- src/pkg/event/asgterminate/v2/awsevent.go | 4 +- src/pkg/event/asgterminate/v2/event.go | 6 +- src/pkg/event/metadata.go | 4 +- src/pkg/event/noop.go | 2 +- .../rebalancerecommendation/v0/awsevent.go | 4 +- .../event/rebalancerecommendation/v0/event.go | 4 +- src/pkg/event/scheduledchange/v1/awsevent.go | 4 +- src/pkg/event/scheduledchange/v1/event.go | 2 +- src/pkg/event/spotinterruption/v1/awsevent.go | 4 +- src/pkg/event/spotinterruption/v1/event.go | 4 +- src/pkg/event/statechange/v1/awsevent.go | 4 +- src/pkg/event/statechange/v1/event.go | 4 +- src/pkg/node/name/getter.go | 10 +- src/pkg/terminator/reconciler.go | 8 +- src/test/reconciliation_test.go | 112 +++++++++--------- 17 files changed, 93 insertions(+), 93 deletions(-) diff --git a/src/pkg/event/asgterminate/v1/awsevent.go b/src/pkg/event/asgterminate/v1/awsevent.go index 78d55824..6778802a 100644 --- a/src/pkg/event/asgterminate/v1/awsevent.go +++ b/src/pkg/event/asgterminate/v1/awsevent.go @@ -41,7 +41,7 @@ type EC2InstanceTerminateLifecycleActionDetail struct { LifecycleHookName string `json:"LifecycleHookName"` LifecycleTransition string `json:"LifecycleTransition"` AutoScalingGroupName string `json:"AutoScalingGroupName"` - EC2InstanceId string `json:"EC2InstanceId"` + EC2InstanceID string `json:"EC2InstanceId"` LifecycleActionToken string `json:"LifecycleActionToken"` } @@ -49,7 +49,7 @@ func (e EC2InstanceTerminateLifecycleActionDetail) MarshalLogObject(enc zapcore. enc.AddString("LifecycleHookName", e.LifecycleHookName) enc.AddString("LifecycleTransition", e.LifecycleTransition) enc.AddString("AutoScalingGroupName", e.AutoScalingGroupName) - enc.AddString("EC2InstanceId", e.EC2InstanceId) + enc.AddString("EC2InstanceId", e.EC2InstanceID) enc.AddString("LifecycleActionToken", e.LifecycleActionToken) return nil } diff --git a/src/pkg/event/asgterminate/v1/event.go b/src/pkg/event/asgterminate/v1/event.go index 33efe517..474e5829 100644 --- a/src/pkg/event/asgterminate/v1/event.go +++ b/src/pkg/event/asgterminate/v1/event.go @@ -33,8 +33,8 @@ type EC2InstanceTerminateLifecycleAction struct { AWSEvent } -func (e EC2InstanceTerminateLifecycleAction) EC2InstanceIds() []string { - return []string{e.Detail.EC2InstanceId} +func (e EC2InstanceTerminateLifecycleAction) EC2InstanceIDs() []string { + return []string{e.Detail.EC2InstanceID} } func (e EC2InstanceTerminateLifecycleAction) Done(ctx context.Context) (bool, error) { @@ -43,7 +43,7 @@ func (e EC2InstanceTerminateLifecycleAction) Done(ctx context.Context) (bool, er LifecycleActionResult: aws.String("CONTINUE"), LifecycleHookName: aws.String(e.Detail.LifecycleHookName), LifecycleActionToken: aws.String(e.Detail.LifecycleActionToken), - InstanceId: aws.String(e.Detail.EC2InstanceId), + InstanceId: aws.String(e.Detail.EC2InstanceID), }); err != nil { var f awserr.RequestFailure return errors.As(err, &f) && f.StatusCode() != 400, err diff --git a/src/pkg/event/asgterminate/v2/awsevent.go b/src/pkg/event/asgterminate/v2/awsevent.go index 28469e51..4db5392d 100644 --- a/src/pkg/event/asgterminate/v2/awsevent.go +++ b/src/pkg/event/asgterminate/v2/awsevent.go @@ -41,7 +41,7 @@ type EC2InstanceTerminateLifecycleActionDetail struct { LifecycleHookName string `json:"LifecycleHookName"` LifecycleTransition string `json:"LifecycleTransition"` AutoScalingGroupName string `json:"AutoScalingGroupName"` - EC2InstanceId string `json:"EC2InstanceId"` + EC2InstanceID string `json:"EC2InstanceId"` LifecycleActionToken string `json:"LifecycleActionToken"` NotificationMetadata string `json:"NotificationMetadata"` Origin string `json:"Origin"` @@ -52,7 +52,7 @@ func (e EC2InstanceTerminateLifecycleActionDetail) MarshalLogObject(enc zapcore. enc.AddString("LifecycleHookName", e.LifecycleHookName) enc.AddString("LifecycleTransition", e.LifecycleTransition) enc.AddString("AutoScalingGroupName", e.AutoScalingGroupName) - enc.AddString("EC2InstanceId", e.EC2InstanceId) + enc.AddString("EC2InstanceId", e.EC2InstanceID) enc.AddString("LifecycleActionToken", e.LifecycleActionToken) return nil } diff --git a/src/pkg/event/asgterminate/v2/event.go b/src/pkg/event/asgterminate/v2/event.go index c740ccfc..36d7f3f1 100644 --- a/src/pkg/event/asgterminate/v2/event.go +++ b/src/pkg/event/asgterminate/v2/event.go @@ -33,8 +33,8 @@ type EC2InstanceTerminateLifecycleAction struct { AWSEvent } -func (e EC2InstanceTerminateLifecycleAction) EC2InstanceIds() []string { - return []string{e.Detail.EC2InstanceId} +func (e EC2InstanceTerminateLifecycleAction) EC2InstanceIDs() []string { + return []string{e.Detail.EC2InstanceID} } func (e EC2InstanceTerminateLifecycleAction) Done(ctx context.Context) (bool, error) { @@ -43,7 +43,7 @@ func (e EC2InstanceTerminateLifecycleAction) Done(ctx context.Context) (bool, er LifecycleActionResult: aws.String("CONTINUE"), LifecycleHookName: aws.String(e.Detail.LifecycleHookName), LifecycleActionToken: aws.String(e.Detail.LifecycleActionToken), - InstanceId: aws.String(e.Detail.EC2InstanceId), + InstanceId: aws.String(e.Detail.EC2InstanceID), }); err != nil { var f awserr.RequestFailure return errors.As(err, &f) && f.StatusCode() != 400, err diff --git a/src/pkg/event/metadata.go b/src/pkg/event/metadata.go index e40528d5..96ef260f 100644 --- a/src/pkg/event/metadata.go +++ b/src/pkg/event/metadata.go @@ -25,7 +25,7 @@ import ( type AWSMetadata struct { Account string `json:"account"` DetailType string `json:"detail-type"` - Id string `json:"id"` + ID string `json:"id"` Region string `json:"region"` Resources []string `json:"resources"` Source string `json:"source"` @@ -36,7 +36,7 @@ type AWSMetadata struct { func (e AWSMetadata) MarshalLogObject(enc zapcore.ObjectEncoder) error { enc.AddString("source", e.Source) enc.AddString("detail-type", e.DetailType) - enc.AddString("id", e.Id) + enc.AddString("id", e.ID) enc.AddTime("time", e.Time) enc.AddString("region", e.Region) enc.AddArray("resources", zapcore.ArrayMarshalerFunc(func(enc zapcore.ArrayEncoder) error { diff --git a/src/pkg/event/noop.go b/src/pkg/event/noop.go index 53d62f19..a4126cb0 100644 --- a/src/pkg/event/noop.go +++ b/src/pkg/event/noop.go @@ -25,7 +25,7 @@ import ( type noop AWSMetadata -func (n noop) EC2InstanceIds() []string { +func (n noop) EC2InstanceIDs() []string { return []string{} } diff --git a/src/pkg/event/rebalancerecommendation/v0/awsevent.go b/src/pkg/event/rebalancerecommendation/v0/awsevent.go index b5ca9d93..917cd94b 100644 --- a/src/pkg/event/rebalancerecommendation/v0/awsevent.go +++ b/src/pkg/event/rebalancerecommendation/v0/awsevent.go @@ -38,10 +38,10 @@ func (e AWSEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { } type EC2InstanceRebalanceRecommendationDetail struct { - InstanceId string `json:"instance-id"` + InstanceID string `json:"instance-id"` } func (e EC2InstanceRebalanceRecommendationDetail) MarshalLogObject(enc zapcore.ObjectEncoder) error { - enc.AddString("instance-id", e.InstanceId) + enc.AddString("instance-id", e.InstanceID) return nil } diff --git a/src/pkg/event/rebalancerecommendation/v0/event.go b/src/pkg/event/rebalancerecommendation/v0/event.go index 08881549..8e873f80 100644 --- a/src/pkg/event/rebalancerecommendation/v0/event.go +++ b/src/pkg/event/rebalancerecommendation/v0/event.go @@ -25,8 +25,8 @@ import ( type EC2InstanceRebalanceRecommendation AWSEvent -func (e EC2InstanceRebalanceRecommendation) EC2InstanceIds() []string { - return []string{e.Detail.InstanceId} +func (e EC2InstanceRebalanceRecommendation) EC2InstanceIDs() []string { + return []string{e.Detail.InstanceID} } func (e EC2InstanceRebalanceRecommendation) Done(_ context.Context) (bool, error) { diff --git a/src/pkg/event/scheduledchange/v1/awsevent.go b/src/pkg/event/scheduledchange/v1/awsevent.go index 3240c438..d91b488b 100644 --- a/src/pkg/event/scheduledchange/v1/awsevent.go +++ b/src/pkg/event/scheduledchange/v1/awsevent.go @@ -38,7 +38,7 @@ func (e AWSEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { } type AWSHealthEventDetail struct { - EventArn string `json:"eventArn"` + EventARN string `json:"eventArn"` EventTypeCode string `json:"eventTypeCode"` Service string `json:"service"` EventDescription []EventDescription `json:"eventDescription"` @@ -49,7 +49,7 @@ type AWSHealthEventDetail struct { } func (e AWSHealthEventDetail) MarshalLogObject(enc zapcore.ObjectEncoder) error { - enc.AddString("eventArn", e.EventArn) + enc.AddString("eventArn", e.EventARN) enc.AddString("eventTypeCode", e.EventTypeCode) enc.AddString("eventTypeCategory", e.EventTypeCategory) enc.AddString("service", e.Service) diff --git a/src/pkg/event/scheduledchange/v1/event.go b/src/pkg/event/scheduledchange/v1/event.go index b53f587f..e1f3ccf9 100644 --- a/src/pkg/event/scheduledchange/v1/event.go +++ b/src/pkg/event/scheduledchange/v1/event.go @@ -25,7 +25,7 @@ import ( type AWSHealthEvent AWSEvent -func (e AWSHealthEvent) EC2InstanceIds() []string { +func (e AWSHealthEvent) EC2InstanceIDs() []string { ids := make([]string, len(e.Detail.AffectedEntities)) for i, entity := range e.Detail.AffectedEntities { ids[i] = entity.EntityValue diff --git a/src/pkg/event/spotinterruption/v1/awsevent.go b/src/pkg/event/spotinterruption/v1/awsevent.go index ad5c175a..1c40a2a0 100644 --- a/src/pkg/event/spotinterruption/v1/awsevent.go +++ b/src/pkg/event/spotinterruption/v1/awsevent.go @@ -38,12 +38,12 @@ func (e AWSEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { } type EC2SpotInstanceInterruptionWarningDetail struct { - InstanceId string `json:"instance-id"` + InstanceID string `json:"instance-id"` InstanceAction string `json:"instance-action"` } func (e EC2SpotInstanceInterruptionWarningDetail) MarshalLogObject(enc zapcore.ObjectEncoder) error { - enc.AddString("instance-id", e.InstanceId) + enc.AddString("instance-id", e.InstanceID) enc.AddString("instance-action", e.InstanceAction) return nil } diff --git a/src/pkg/event/spotinterruption/v1/event.go b/src/pkg/event/spotinterruption/v1/event.go index a18a8eae..42c8b1e2 100644 --- a/src/pkg/event/spotinterruption/v1/event.go +++ b/src/pkg/event/spotinterruption/v1/event.go @@ -25,8 +25,8 @@ import ( type EC2SpotInstanceInterruptionWarning AWSEvent -func (e EC2SpotInstanceInterruptionWarning) EC2InstanceIds() []string { - return []string{e.Detail.InstanceId} +func (e EC2SpotInstanceInterruptionWarning) EC2InstanceIDs() []string { + return []string{e.Detail.InstanceID} } func (e EC2SpotInstanceInterruptionWarning) Done(_ context.Context) (bool, error) { diff --git a/src/pkg/event/statechange/v1/awsevent.go b/src/pkg/event/statechange/v1/awsevent.go index 82392106..f7765672 100644 --- a/src/pkg/event/statechange/v1/awsevent.go +++ b/src/pkg/event/statechange/v1/awsevent.go @@ -38,12 +38,12 @@ func (e AWSEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error { } type EC2InstanceStateChangeNotificationDetail struct { - InstanceId string `json:"instance-id"` + InstanceID string `json:"instance-id"` State string `json:"state"` } func (e EC2InstanceStateChangeNotificationDetail) MarshalLogObject(enc zapcore.ObjectEncoder) error { - enc.AddString("instance-id", e.InstanceId) + enc.AddString("instance-id", e.InstanceID) enc.AddString("state", e.State) return nil } diff --git a/src/pkg/event/statechange/v1/event.go b/src/pkg/event/statechange/v1/event.go index add3b1eb..0527f2a1 100644 --- a/src/pkg/event/statechange/v1/event.go +++ b/src/pkg/event/statechange/v1/event.go @@ -25,8 +25,8 @@ import ( type EC2InstanceStateChangeNotification AWSEvent -func (e EC2InstanceStateChangeNotification) EC2InstanceIds() []string { - return []string{e.Detail.InstanceId} +func (e EC2InstanceStateChangeNotification) EC2InstanceIDs() []string { + return []string{e.Detail.InstanceID} } func (e EC2InstanceStateChangeNotification) Done(_ context.Context) (bool, error) { diff --git a/src/pkg/node/name/getter.go b/src/pkg/node/name/getter.go index 3d0cc892..1f772265 100644 --- a/src/pkg/node/name/getter.go +++ b/src/pkg/node/name/getter.go @@ -37,11 +37,11 @@ type ( } ) -func (g Getter) GetNodeName(ctx context.Context, instanceId string) (string, error) { +func (g Getter) GetNodeName(ctx context.Context, instanceID string) (string, error) { ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("nodeName")) result, err := g.DescribeInstancesWithContext(ctx, &ec2.DescribeInstancesInput{ - InstanceIds: []*string{aws.String(instanceId)}, + InstanceIds: []*string{aws.String(instanceID)}, }) if err != nil { logging.FromContext(ctx). @@ -51,7 +51,7 @@ func (g Getter) GetNodeName(ctx context.Context, instanceId string) (string, err } if len(result.Reservations) == 0 || len(result.Reservations[0].Instances) == 0 { - err = fmt.Errorf("no EC2 reservation for instance ID %s", instanceId) + err = fmt.Errorf("no EC2 reservation for instance ID %s", instanceID) logging.FromContext(ctx). With("error", err). Error("EC2 instance not found") @@ -59,7 +59,7 @@ func (g Getter) GetNodeName(ctx context.Context, instanceId string) (string, err } if result.Reservations[0].Instances[0].PrivateDnsName == nil { - err = fmt.Errorf("no PrivateDnsName for instance %s", instanceId) + err = fmt.Errorf("no PrivateDnsName for instance %s", instanceID) logging.FromContext(ctx). With("error", err). Error("EC2 instance has no PrivateDnsName") @@ -68,7 +68,7 @@ func (g Getter) GetNodeName(ctx context.Context, instanceId string) (string, err nodeName := *result.Reservations[0].Instances[0].PrivateDnsName if nodeName == "" { - err = fmt.Errorf("empty PrivateDnsName for instance %s", instanceId) + err = fmt.Errorf("empty PrivateDnsName for instance %s", instanceID) logging.FromContext(ctx). With("error", err). Error("EC2 instance's PrivateDnsName is empty") diff --git a/src/pkg/terminator/reconciler.go b/src/pkg/terminator/reconciler.go index b30c2a87..18a6671b 100644 --- a/src/pkg/terminator/reconciler.go +++ b/src/pkg/terminator/reconciler.go @@ -50,7 +50,7 @@ type ( zapcore.ObjectMarshaler Done(context.Context) (tryAgain bool, err error) - EC2InstanceIds() []string + EC2InstanceIDs() []string } Getter interface { @@ -127,12 +127,12 @@ func (r Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (recon ctx = logging.WithLogger(ctx, logging.FromContext(ctx).With("event", evt)) savedCtx := ctx - for _, ec2InstanceId := range evt.EC2InstanceIds() { + for _, ec2InstanceID := range evt.EC2InstanceIDs() { ctx = logging.WithLogger(savedCtx, logging.FromContext(savedCtx). - With("ec2InstanceId", ec2InstanceId), + With("ec2InstanceID", ec2InstanceID), ) - nodeName, e := r.GetNodeName(ctx, ec2InstanceId) + nodeName, e := r.GetNodeName(ctx, ec2InstanceID) if e != nil { err = multierr.Append(err, e) continue diff --git a/src/test/reconciliation_test.go b/src/test/reconciliation_test.go index e07c2dcf..1fff76a6 100644 --- a/src/test/reconciliation_test.go +++ b/src/test/reconciliation_test.go @@ -64,7 +64,7 @@ import ( ) type ( - EC2InstanceId = string + EC2InstanceID = string SQSQueueURL = string NodeName = string @@ -92,10 +92,10 @@ var _ = Describe("Reconciliation", func() { // Nodes currently in the cluster. nodes map[types.NamespacedName]*v1.Node // Maps an EC2 instance id to an ASG lifecycle action state value. - asgLifecycleActions map[EC2InstanceId]State + asgLifecycleActions map[EC2InstanceID]State // Maps an EC2 instance id to the corresponding reservation for a node // in the cluster. - ec2Reservations map[EC2InstanceId]*ec2.Reservation + ec2Reservations map[EC2InstanceID]*ec2.Reservation // Maps a queue URL to a list of messages waiting to be fetched. sqsQueues map[SQSQueueURL][]*sqs.Message @@ -117,11 +117,11 @@ var _ = Describe("Reconciliation", func() { // Names of all nodes currently in cluster. nodeNames []NodeName // Instance IDs for all nodes currently in cluster. - instanceIds []EC2InstanceId + instanceIDs []EC2InstanceID // Change count of nodes in cluster. resizeCluster func(nodeCount uint) // Create an ASG lifecycle action state entry for an EC2 instance ID. - createPendingASGLifecycleAction func(EC2InstanceId) + createPendingASGLifecycleAction func(EC2InstanceID) // Name of default terminator. terminatorNamespaceName types.NamespacedName @@ -169,10 +169,10 @@ var _ = Describe("Reconciliation", func() { "EC2InstanceId": "%s", "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING" } - }`, instanceIds[1])), + }`, instanceIDs[1])), }) - createPendingASGLifecycleAction(instanceIds[1]) + createPendingASGLifecycleAction(instanceIDs[1]) }) It("returns success and requeues the request with the reconciler's configured interval", func() { @@ -185,7 +185,7 @@ var _ = Describe("Reconciliation", func() { }) It("completes the ASG lifecycle action", func() { - Expect(asgLifecycleActions).To(And(HaveKeyWithValue(instanceIds[1], Equal(StateComplete)), HaveLen(1))) + Expect(asgLifecycleActions).To(And(HaveKeyWithValue(instanceIDs[1], Equal(StateComplete)), HaveLen(1))) }) It("deletes the message from the SQS queue", func() { @@ -207,10 +207,10 @@ var _ = Describe("Reconciliation", func() { "EC2InstanceId": "%s", "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING" } - }`, instanceIds[1])), + }`, instanceIDs[1])), }) - createPendingASGLifecycleAction(instanceIds[1]) + createPendingASGLifecycleAction(instanceIDs[1]) }) It("returns success and requeues the request with the reconciler's configured interval", func() { @@ -223,7 +223,7 @@ var _ = Describe("Reconciliation", func() { }) It("completes the ASG lifecycle action", func() { - Expect(asgLifecycleActions).To(And(HaveKeyWithValue(instanceIds[1], Equal(StateComplete)), HaveLen(1))) + Expect(asgLifecycleActions).To(And(HaveKeyWithValue(instanceIDs[1], Equal(StateComplete)), HaveLen(1))) }) It("deletes the message from the SQS queue", func() { @@ -244,7 +244,7 @@ var _ = Describe("Reconciliation", func() { "detail": { "instance-id": "%s" } - }`, instanceIds[1])), + }`, instanceIDs[1])), }) }) @@ -280,7 +280,7 @@ var _ = Describe("Reconciliation", func() { {"entityValue": "%s"} ] } - }`, instanceIds[1], instanceIds[2])), + }`, instanceIDs[1], instanceIDs[2])), }) }) @@ -311,7 +311,7 @@ var _ = Describe("Reconciliation", func() { "detail": { "instance-id": "%s" } - }`, instanceIds[1])), + }`, instanceIDs[1])), }) }) @@ -343,7 +343,7 @@ var _ = Describe("Reconciliation", func() { "instance-id": "%s", "state": "stopping" } - }`, instanceIds[1])), + }`, instanceIDs[1])), }) }) @@ -375,7 +375,7 @@ var _ = Describe("Reconciliation", func() { "instance-id": "%s", "state": "stopped" } - }`, instanceIds[1])), + }`, instanceIDs[1])), }) }) @@ -407,7 +407,7 @@ var _ = Describe("Reconciliation", func() { "instance-id": "%s", "state": "shutting-down" } - }`, instanceIds[1])), + }`, instanceIDs[1])), }) }) @@ -439,7 +439,7 @@ var _ = Describe("Reconciliation", func() { "instance-id": "%s", "state": "terminated" } - }`, instanceIds[1])), + }`, instanceIDs[1])), }) }) @@ -472,7 +472,7 @@ var _ = Describe("Reconciliation", func() { "EC2InstanceId": "%s", "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING" } - }`, instanceIds[1])), + }`, instanceIDs[1])), }, &sqs.Message{ ReceiptHandle: aws.String("msg-2"), @@ -484,7 +484,7 @@ var _ = Describe("Reconciliation", func() { "EC2InstanceId": "%s", "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING" } - }`, instanceIds[2])), + }`, instanceIDs[2])), }, &sqs.Message{ ReceiptHandle: aws.String("msg-3"), @@ -495,7 +495,7 @@ var _ = Describe("Reconciliation", func() { "detail": { "instance-id": "%s" } - }`, instanceIds[3])), + }`, instanceIDs[3])), }, &sqs.Message{ ReceiptHandle: aws.String("msg-4"), @@ -511,7 +511,7 @@ var _ = Describe("Reconciliation", func() { {"entityValue": "%s"} ] } - }`, instanceIds[4], instanceIds[5])), + }`, instanceIDs[4], instanceIDs[5])), }, &sqs.Message{ ReceiptHandle: aws.String("msg-5"), @@ -522,7 +522,7 @@ var _ = Describe("Reconciliation", func() { "detail": { "instance-id": "%s" } - }`, instanceIds[6])), + }`, instanceIDs[6])), }, &sqs.Message{ ReceiptHandle: aws.String("msg-6"), @@ -534,7 +534,7 @@ var _ = Describe("Reconciliation", func() { "instance-id": "%s", "state": "stopping" } - }`, instanceIds[7])), + }`, instanceIDs[7])), }, &sqs.Message{ ReceiptHandle: aws.String("msg-7"), @@ -546,7 +546,7 @@ var _ = Describe("Reconciliation", func() { "instance-id": "%s", "state": "stopped" } - }`, instanceIds[8])), + }`, instanceIDs[8])), }, &sqs.Message{ ReceiptHandle: aws.String("msg-8"), @@ -558,7 +558,7 @@ var _ = Describe("Reconciliation", func() { "instance-id": "%s", "state": "shutting-down" } - }`, instanceIds[9])), + }`, instanceIDs[9])), }, &sqs.Message{ ReceiptHandle: aws.String("msg-9"), @@ -570,7 +570,7 @@ var _ = Describe("Reconciliation", func() { "instance-id": "%s", "state": "terminated" } - }`, instanceIds[10])), + }`, instanceIDs[10])), }, ) }) @@ -735,7 +735,7 @@ var _ = Describe("Reconciliation", func() { "detail": { "instance-id": "%s" } - }`, instanceIds[1])), + }`, instanceIDs[1])), }) describeEC2InstancesFunc = func(_ aws.Context, _ *ec2.DescribeInstancesInput, _ ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { @@ -770,7 +770,7 @@ var _ = Describe("Reconciliation", func() { "detail": { "instance-id": "%s" } - }`, instanceIds[1])), + }`, instanceIDs[1])), }) describeEC2InstancesFunc = func(_ aws.Context, _ *ec2.DescribeInstancesInput, _ ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { @@ -807,7 +807,7 @@ var _ = Describe("Reconciliation", func() { "detail": { "instance-id": "%s" } - }`, instanceIds[1])), + }`, instanceIDs[1])), }) describeEC2InstancesFunc = func(_ aws.Context, _ *ec2.DescribeInstancesInput, _ ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { @@ -846,7 +846,7 @@ var _ = Describe("Reconciliation", func() { "detail": { "instance-id": "%s" } - }`, instanceIds[1])), + }`, instanceIDs[1])), }) describeEC2InstancesFunc = func(_ aws.Context, _ *ec2.DescribeInstancesInput, _ ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { @@ -889,7 +889,7 @@ var _ = Describe("Reconciliation", func() { "detail": { "instance-id": "%s" } - }`, instanceIds[1])), + }`, instanceIDs[1])), }) describeEC2InstancesFunc = func(_ aws.Context, _ *ec2.DescribeInstancesInput, _ ...awsrequest.Option) (*ec2.DescribeInstancesOutput, error) { @@ -932,7 +932,7 @@ var _ = Describe("Reconciliation", func() { "detail": { "instance-id": "%s" } - }`, instanceIds[1])), + }`, instanceIDs[1])), }) defaultKubeGetFunc := kubeGetFunc @@ -973,7 +973,7 @@ var _ = Describe("Reconciliation", func() { "detail": { "instance-id": "%s" } - }`, instanceIds[1])), + }`, instanceIDs[1])), }) cordonFunc = func(_ *kubectl.Helper, _ *v1.Node, _ bool) error { @@ -1008,7 +1008,7 @@ var _ = Describe("Reconciliation", func() { "detail": { "instance-id": "%s" } - }`, instanceIds[1])), + }`, instanceIDs[1])), }) drainFunc = func(_ *kubectl.Helper, _ string) error { @@ -1047,7 +1047,7 @@ var _ = Describe("Reconciliation", func() { "EC2InstanceId": "%s", "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING" } - }`, instanceIds[1])), + }`, instanceIDs[1])), }) completeASGLifecycleActionFunc = func(_ aws.Context, _ *autoscaling.CompleteLifecycleActionInput, _ ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { @@ -1087,7 +1087,7 @@ var _ = Describe("Reconciliation", func() { "EC2InstanceId": "%s", "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING" } - }`, instanceIds[1])), + }`, instanceIDs[1])), }) completeASGLifecycleActionFunc = func(_ aws.Context, _ *autoscaling.CompleteLifecycleActionInput, _ ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { @@ -1127,7 +1127,7 @@ var _ = Describe("Reconciliation", func() { "EC2InstanceId": "%s", "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING" } - }`, instanceIds[1])), + }`, instanceIDs[1])), }) completeASGLifecycleActionFunc = func(_ aws.Context, _ *autoscaling.CompleteLifecycleActionInput, _ ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { @@ -1167,7 +1167,7 @@ var _ = Describe("Reconciliation", func() { "EC2InstanceId": "%s", "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING" } - }`, instanceIds[1])), + }`, instanceIDs[1])), }) completeASGLifecycleActionFunc = func(_ aws.Context, _ *autoscaling.CompleteLifecycleActionInput, _ ...awsrequest.Option) (*autoscaling.CompleteLifecycleActionOutput, error) { @@ -1283,7 +1283,7 @@ var _ = Describe("Reconciliation", func() { "detail": { "instance-id": "%s" } - }`, instanceIds[1])), + }`, instanceIDs[1])), }) }) @@ -1350,7 +1350,7 @@ var _ = Describe("Reconciliation", func() { "detail": { "instance-id": "%s" } - }`, instanceIds[1])), + }`, instanceIDs[1])), }) }) @@ -1403,7 +1403,7 @@ var _ = Describe("Reconciliation", func() { "LifecycleHookName": "%s", "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING" } - }`, autoScalingGroupName, instanceIds[1], lifecycleActionToken, lifecycleHookName)), + }`, autoScalingGroupName, instanceIDs[1], lifecycleActionToken, lifecycleHookName)), }) defaultCompleteASGLifecycleActionFunc := completeASGLifecycleActionFunc @@ -1429,7 +1429,7 @@ var _ = Describe("Reconciliation", func() { Expect(*input.LifecycleActionToken).To(Equal(lifecycleActionToken)) Expect(input.InstanceId).ToNot(BeNil()) - Expect(*input.InstanceId).To(Equal(instanceIds[1])) + Expect(*input.InstanceId).To(Equal(instanceIDs[1])) }) }) @@ -1457,12 +1457,12 @@ var _ = Describe("Reconciliation", func() { }, } nodes = map[types.NamespacedName]*v1.Node{} - ec2Reservations = map[EC2InstanceId]*ec2.Reservation{} + ec2Reservations = map[EC2InstanceID]*ec2.Reservation{} cordonedNodes = map[NodeName]bool{} drainedNodes = map[NodeName]bool{} nodeNames = []NodeName{} - instanceIds = []EC2InstanceId{} + instanceIDs = []EC2InstanceID{} resizeCluster = func(newNodeCount uint) { for currNodeCount := uint(len(nodes)); currNodeCount < newNodeCount; currNodeCount++ { nodeName := fmt.Sprintf("node-%d", currNodeCount) @@ -1471,9 +1471,9 @@ var _ = Describe("Reconciliation", func() { ObjectMeta: metav1.ObjectMeta{Name: nodeName}, } - instanceId := fmt.Sprintf("instanceId-%d", currNodeCount) - instanceIds = append(instanceIds, instanceId) - ec2Reservations[instanceId] = &ec2.Reservation{ + instanceID := fmt.Sprintf("instance-%d", currNodeCount) + instanceIDs = append(instanceIDs, instanceID) + ec2Reservations[instanceID] = &ec2.Reservation{ Instances: []*ec2.Instance{ {PrivateDnsName: aws.String(nodeName)}, }, @@ -1481,13 +1481,13 @@ var _ = Describe("Reconciliation", func() { } nodeNames = nodeNames[:newNodeCount] - instanceIds = instanceIds[:newNodeCount] + instanceIDs = instanceIDs[:newNodeCount] } - asgLifecycleActions = map[EC2InstanceId]State{} - createPendingASGLifecycleAction = func(instanceId EC2InstanceId) { - Expect(asgLifecycleActions).ToNot(HaveKey(instanceId)) - asgLifecycleActions[instanceId] = StatePending + asgLifecycleActions = map[EC2InstanceID]State{} + createPendingASGLifecycleAction = func(instanceID EC2InstanceID) { + Expect(asgLifecycleActions).ToNot(HaveKey(instanceID)) + asgLifecycleActions[instanceID] = StatePending } // 2. Setup stub clients. @@ -1498,11 +1498,11 @@ var _ = Describe("Reconciliation", func() { } output := ec2.DescribeInstancesOutput{} - for _, instanceId := range input.InstanceIds { - if instanceId == nil { + for _, instanceID := range input.InstanceIds { + if instanceID == nil { continue } - if reservation, found := ec2Reservations[*instanceId]; found { + if reservation, found := ec2Reservations[*instanceID]; found { output.Reservations = append(output.Reservations, reservation) } } From 6a7b44b08462cd59f05805461715d19dd27a698f Mon Sep 17 00:00:00 2001 From: Jerad C Date: Thu, 7 Apr 2022 08:11:53 -0500 Subject: [PATCH 10/13] rename event files * "awsevent.go" to "unmarshal.go" * "event.go" to "handler.go" --- src/pkg/event/asgterminate/v1/{event.go => handler.go} | 0 src/pkg/event/asgterminate/v1/{awsevent.go => unmarshal.go} | 0 src/pkg/event/asgterminate/v2/{event.go => handler.go} | 0 src/pkg/event/asgterminate/v2/{awsevent.go => unmarshal.go} | 0 src/pkg/event/rebalancerecommendation/v0/{event.go => handler.go} | 0 .../rebalancerecommendation/v0/{awsevent.go => unmarshal.go} | 0 src/pkg/event/scheduledchange/v1/{event.go => handler.go} | 0 src/pkg/event/scheduledchange/v1/{awsevent.go => unmarshal.go} | 0 src/pkg/event/spotinterruption/v1/{event.go => handler.go} | 0 src/pkg/event/spotinterruption/v1/{awsevent.go => unmarshal.go} | 0 src/pkg/event/statechange/v1/{event.go => handler.go} | 0 src/pkg/event/statechange/v1/{awsevent.go => unmarshal.go} | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename src/pkg/event/asgterminate/v1/{event.go => handler.go} (100%) rename src/pkg/event/asgterminate/v1/{awsevent.go => unmarshal.go} (100%) rename src/pkg/event/asgterminate/v2/{event.go => handler.go} (100%) rename src/pkg/event/asgterminate/v2/{awsevent.go => unmarshal.go} (100%) rename src/pkg/event/rebalancerecommendation/v0/{event.go => handler.go} (100%) rename src/pkg/event/rebalancerecommendation/v0/{awsevent.go => unmarshal.go} (100%) rename src/pkg/event/scheduledchange/v1/{event.go => handler.go} (100%) rename src/pkg/event/scheduledchange/v1/{awsevent.go => unmarshal.go} (100%) rename src/pkg/event/spotinterruption/v1/{event.go => handler.go} (100%) rename src/pkg/event/spotinterruption/v1/{awsevent.go => unmarshal.go} (100%) rename src/pkg/event/statechange/v1/{event.go => handler.go} (100%) rename src/pkg/event/statechange/v1/{awsevent.go => unmarshal.go} (100%) diff --git a/src/pkg/event/asgterminate/v1/event.go b/src/pkg/event/asgterminate/v1/handler.go similarity index 100% rename from src/pkg/event/asgterminate/v1/event.go rename to src/pkg/event/asgterminate/v1/handler.go diff --git a/src/pkg/event/asgterminate/v1/awsevent.go b/src/pkg/event/asgterminate/v1/unmarshal.go similarity index 100% rename from src/pkg/event/asgterminate/v1/awsevent.go rename to src/pkg/event/asgterminate/v1/unmarshal.go diff --git a/src/pkg/event/asgterminate/v2/event.go b/src/pkg/event/asgterminate/v2/handler.go similarity index 100% rename from src/pkg/event/asgterminate/v2/event.go rename to src/pkg/event/asgterminate/v2/handler.go diff --git a/src/pkg/event/asgterminate/v2/awsevent.go b/src/pkg/event/asgterminate/v2/unmarshal.go similarity index 100% rename from src/pkg/event/asgterminate/v2/awsevent.go rename to src/pkg/event/asgterminate/v2/unmarshal.go diff --git a/src/pkg/event/rebalancerecommendation/v0/event.go b/src/pkg/event/rebalancerecommendation/v0/handler.go similarity index 100% rename from src/pkg/event/rebalancerecommendation/v0/event.go rename to src/pkg/event/rebalancerecommendation/v0/handler.go diff --git a/src/pkg/event/rebalancerecommendation/v0/awsevent.go b/src/pkg/event/rebalancerecommendation/v0/unmarshal.go similarity index 100% rename from src/pkg/event/rebalancerecommendation/v0/awsevent.go rename to src/pkg/event/rebalancerecommendation/v0/unmarshal.go diff --git a/src/pkg/event/scheduledchange/v1/event.go b/src/pkg/event/scheduledchange/v1/handler.go similarity index 100% rename from src/pkg/event/scheduledchange/v1/event.go rename to src/pkg/event/scheduledchange/v1/handler.go diff --git a/src/pkg/event/scheduledchange/v1/awsevent.go b/src/pkg/event/scheduledchange/v1/unmarshal.go similarity index 100% rename from src/pkg/event/scheduledchange/v1/awsevent.go rename to src/pkg/event/scheduledchange/v1/unmarshal.go diff --git a/src/pkg/event/spotinterruption/v1/event.go b/src/pkg/event/spotinterruption/v1/handler.go similarity index 100% rename from src/pkg/event/spotinterruption/v1/event.go rename to src/pkg/event/spotinterruption/v1/handler.go diff --git a/src/pkg/event/spotinterruption/v1/awsevent.go b/src/pkg/event/spotinterruption/v1/unmarshal.go similarity index 100% rename from src/pkg/event/spotinterruption/v1/awsevent.go rename to src/pkg/event/spotinterruption/v1/unmarshal.go diff --git a/src/pkg/event/statechange/v1/event.go b/src/pkg/event/statechange/v1/handler.go similarity index 100% rename from src/pkg/event/statechange/v1/event.go rename to src/pkg/event/statechange/v1/handler.go diff --git a/src/pkg/event/statechange/v1/awsevent.go b/src/pkg/event/statechange/v1/unmarshal.go similarity index 100% rename from src/pkg/event/statechange/v1/awsevent.go rename to src/pkg/event/statechange/v1/unmarshal.go From ab10afa0b5225174d0fffa901b9ce0b42caaffcf Mon Sep 17 00:00:00 2001 From: Jerad C Date: Thu, 7 Apr 2022 13:41:16 -0500 Subject: [PATCH 11/13] add more logging for parsing failures --- src/pkg/event/aggregatedparser.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pkg/event/aggregatedparser.go b/src/pkg/event/aggregatedparser.go index 81b6be56..065de052 100644 --- a/src/pkg/event/aggregatedparser.go +++ b/src/pkg/event/aggregatedparser.go @@ -40,6 +40,7 @@ func (p AggregatedParser) Parse(ctx context.Context, str string) terminator.Even ctx = logging.WithLogger(ctx, logging.FromContext(ctx).Named("event.parser")) if str == "" { + logging.FromContext(ctx).Warn("nothing to parse") return noop{} } @@ -49,11 +50,13 @@ func (p AggregatedParser) Parse(ctx context.Context, str string) terminator.Even } } + logging.FromContext(ctx).Error("failed to parse") + md := AWSMetadata{} if err := json.Unmarshal([]byte(str), &md); err != nil { logging.FromContext(ctx). With("error", err). - Error("failed to parse SQS message metadata") + Error("failed to unmarshal message metadata") return noop{} } return noop(md) From 186caec85a0072c85ed39b162dc1dccfc06166b9 Mon Sep 17 00:00:00 2001 From: Jerad C Date: Thu, 7 Apr 2022 15:36:43 -0500 Subject: [PATCH 12/13] dedup lifecycle action completion code --- .../asgterminate/lifecycleaction/complete.go | 55 +++++++++++++++++++ src/pkg/event/asgterminate/v1/handler.go | 23 +++----- src/pkg/event/asgterminate/v2/handler.go | 23 +++----- 3 files changed, 69 insertions(+), 32 deletions(-) create mode 100644 src/pkg/event/asgterminate/lifecycleaction/complete.go diff --git a/src/pkg/event/asgterminate/lifecycleaction/complete.go b/src/pkg/event/asgterminate/lifecycleaction/complete.go new file mode 100644 index 00000000..34cdcba5 --- /dev/null +++ b/src/pkg/event/asgterminate/lifecycleaction/complete.go @@ -0,0 +1,55 @@ +/* +Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package lifecycleaction + +import ( + "context" + "errors" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/service/autoscaling" +) + +type ( + ASGLifecycleActionCompleter interface { + CompleteLifecycleActionWithContext(aws.Context, *autoscaling.CompleteLifecycleActionInput, ...request.Option) (*autoscaling.CompleteLifecycleActionOutput, error) + } + + Input struct { + AutoScalingGroupName string + LifecycleActionToken string + LifecycleHookName string + EC2InstanceID string + } +) + +func Complete(ctx context.Context, completer ASGLifecycleActionCompleter, input Input) (bool, error) { + if _, err := completer.CompleteLifecycleActionWithContext(ctx, &autoscaling.CompleteLifecycleActionInput{ + AutoScalingGroupName: aws.String(input.AutoScalingGroupName), + LifecycleActionResult: aws.String("CONTINUE"), + LifecycleHookName: aws.String(input.LifecycleHookName), + LifecycleActionToken: aws.String(input.LifecycleActionToken), + InstanceId: aws.String(input.EC2InstanceID), + }); err != nil { + var f awserr.RequestFailure + return errors.As(err, &f) && f.StatusCode() != 400, err + } + + return false, nil +} diff --git a/src/pkg/event/asgterminate/v1/handler.go b/src/pkg/event/asgterminate/v1/handler.go index 474e5829..c3c543c9 100644 --- a/src/pkg/event/asgterminate/v1/handler.go +++ b/src/pkg/event/asgterminate/v1/handler.go @@ -18,11 +18,8 @@ package v1 import ( "context" - "errors" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/service/autoscaling" + "github.com/aws/aws-node-termination-handler/pkg/event/asgterminate/lifecycleaction" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -38,18 +35,12 @@ func (e EC2InstanceTerminateLifecycleAction) EC2InstanceIDs() []string { } func (e EC2InstanceTerminateLifecycleAction) Done(ctx context.Context) (bool, error) { - if _, err := e.CompleteLifecycleActionWithContext(ctx, &autoscaling.CompleteLifecycleActionInput{ - AutoScalingGroupName: aws.String(e.Detail.AutoScalingGroupName), - LifecycleActionResult: aws.String("CONTINUE"), - LifecycleHookName: aws.String(e.Detail.LifecycleHookName), - LifecycleActionToken: aws.String(e.Detail.LifecycleActionToken), - InstanceId: aws.String(e.Detail.EC2InstanceID), - }); err != nil { - var f awserr.RequestFailure - return errors.As(err, &f) && f.StatusCode() != 400, err - } - - return false, nil + return lifecycleaction.Complete(ctx, e, lifecycleaction.Input{ + AutoScalingGroupName: e.Detail.AutoScalingGroupName, + LifecycleActionToken: e.Detail.LifecycleActionToken, + LifecycleHookName: e.Detail.LifecycleHookName, + EC2InstanceID: e.Detail.EC2InstanceID, + }) } func (e EC2InstanceTerminateLifecycleAction) MarshalLogObject(enc zapcore.ObjectEncoder) error { diff --git a/src/pkg/event/asgterminate/v2/handler.go b/src/pkg/event/asgterminate/v2/handler.go index 36d7f3f1..26c727a7 100644 --- a/src/pkg/event/asgterminate/v2/handler.go +++ b/src/pkg/event/asgterminate/v2/handler.go @@ -18,11 +18,8 @@ package v2 import ( "context" - "errors" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/service/autoscaling" + "github.com/aws/aws-node-termination-handler/pkg/event/asgterminate/lifecycleaction" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -38,18 +35,12 @@ func (e EC2InstanceTerminateLifecycleAction) EC2InstanceIDs() []string { } func (e EC2InstanceTerminateLifecycleAction) Done(ctx context.Context) (bool, error) { - if _, err := e.CompleteLifecycleActionWithContext(ctx, &autoscaling.CompleteLifecycleActionInput{ - AutoScalingGroupName: aws.String(e.Detail.AutoScalingGroupName), - LifecycleActionResult: aws.String("CONTINUE"), - LifecycleHookName: aws.String(e.Detail.LifecycleHookName), - LifecycleActionToken: aws.String(e.Detail.LifecycleActionToken), - InstanceId: aws.String(e.Detail.EC2InstanceID), - }); err != nil { - var f awserr.RequestFailure - return errors.As(err, &f) && f.StatusCode() != 400, err - } - - return false, nil + return lifecycleaction.Complete(ctx, e, lifecycleaction.Input{ + AutoScalingGroupName: e.Detail.AutoScalingGroupName, + LifecycleActionToken: e.Detail.LifecycleActionToken, + LifecycleHookName: e.Detail.LifecycleHookName, + EC2InstanceID: e.Detail.EC2InstanceID, + }) } func (e EC2InstanceTerminateLifecycleAction) MarshalLogObject(enc zapcore.ObjectEncoder) error { From ef556fcdee84f1db87a9d1bdb5f5f69956ac2695 Mon Sep 17 00:00:00 2001 From: Jerad C Date: Thu, 7 Apr 2022 15:37:48 -0500 Subject: [PATCH 13/13] add more test cases --- src/test/reconciliation_test.go | 623 +++++++++++++++++++++++--------- 1 file changed, 448 insertions(+), 175 deletions(-) diff --git a/src/test/reconciliation_test.go b/src/test/reconciliation_test.go index 1fff76a6..646ee910 100644 --- a/src/test/reconciliation_test.go +++ b/src/test/reconciliation_test.go @@ -20,6 +20,7 @@ import ( "context" "errors" "fmt" + "io" "reflect" "time" @@ -27,6 +28,7 @@ import ( . "github.com/onsi/gomega" "go.uber.org/zap" + "go.uber.org/zap/zapcore" v1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" @@ -156,78 +158,158 @@ var _ = Describe("Reconciliation", func() { }) When("the SQS queue contains an ASG Lifecycle Notification v1", func() { - BeforeEach(func() { - resizeCluster(3) + When("the lifecycle transition is termination", func() { + BeforeEach(func() { + resizeCluster(3) - sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ - ReceiptHandle: aws.String("msg-1"), - Body: aws.String(fmt.Sprintf(`{ - "source": "aws.autoscaling", - "detail-type": "EC2 Instance-terminate Lifecycle Action", - "version": "1", - "detail": { - "EC2InstanceId": "%s", - "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING" - } - }`, instanceIDs[1])), + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.autoscaling", + "detail-type": "EC2 Instance-terminate Lifecycle Action", + "version": "1", + "detail": { + "EC2InstanceId": "%s", + "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING" + } + }`, instanceIDs[1])), + }) + + createPendingASGLifecycleAction(instanceIDs[1]) }) - createPendingASGLifecycleAction(instanceIDs[1]) - }) + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) - It("returns success and requeues the request with the reconciler's configured interval", func() { - Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) - }) + It("cordons and drains only the targeted node", func() { + Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + }) - It("cordons and drains only the targeted node", func() { - Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) - Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) - }) + It("completes the ASG lifecycle action", func() { + Expect(asgLifecycleActions).To(And(HaveKeyWithValue(instanceIDs[1], Equal(StateComplete)), HaveLen(1))) + }) - It("completes the ASG lifecycle action", func() { - Expect(asgLifecycleActions).To(And(HaveKeyWithValue(instanceIDs[1], Equal(StateComplete)), HaveLen(1))) + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueURL]).To(BeEmpty()) + }) }) - It("deletes the message from the SQS queue", func() { - Expect(sqsQueues[queueURL]).To(BeEmpty()) + When("the lifecycle transition is not termination", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.autoscaling", + "detail-type": "EC2 Instance-terminate Lifecycle Action", + "version": "1", + "detail": { + "EC2InstanceId": "%s", + "LifecycleTransition": "test:INVALID" + } + }`, instanceIDs[1])), + }) + + createPendingASGLifecycleAction(instanceIDs[1]) + }) + + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) + + It("does not cordon or drain any nodes", func() { + Expect(cordonedNodes).To(BeEmpty()) + Expect(drainedNodes).To(BeEmpty()) + }) + + It("does not complete the ASG lifecycle action", func() { + Expect(asgLifecycleActions).To(And(HaveKeyWithValue(instanceIDs[1], Equal(StatePending)), HaveLen(1))) + }) + + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueURL]).To(BeEmpty()) + }) }) }) When("the SQS queue contains an ASG Lifecycle Notification v2", func() { - BeforeEach(func() { - resizeCluster(3) + When("the lifecycle transition is termination", func() { + BeforeEach(func() { + resizeCluster(3) - sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ - ReceiptHandle: aws.String("msg-1"), - Body: aws.String(fmt.Sprintf(`{ - "source": "aws.autoscaling", - "detail-type": "EC2 Instance-terminate Lifecycle Action", - "version": "2", - "detail": { - "EC2InstanceId": "%s", - "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING" - } - }`, instanceIDs[1])), + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.autoscaling", + "detail-type": "EC2 Instance-terminate Lifecycle Action", + "version": "2", + "detail": { + "EC2InstanceId": "%s", + "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING" + } + }`, instanceIDs[1])), + }) + + createPendingASGLifecycleAction(instanceIDs[1]) }) - createPendingASGLifecycleAction(instanceIDs[1]) - }) + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) - It("returns success and requeues the request with the reconciler's configured interval", func() { - Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) - }) + It("cordons and drains only the targeted node", func() { + Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + }) - It("cordons and drains only the targeted node", func() { - Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) - Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) - }) + It("completes the ASG lifecycle action", func() { + Expect(asgLifecycleActions).To(And(HaveKeyWithValue(instanceIDs[1], Equal(StateComplete)), HaveLen(1))) + }) - It("completes the ASG lifecycle action", func() { - Expect(asgLifecycleActions).To(And(HaveKeyWithValue(instanceIDs[1], Equal(StateComplete)), HaveLen(1))) + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueURL]).To(BeEmpty()) + }) }) - It("deletes the message from the SQS queue", func() { - Expect(sqsQueues[queueURL]).To(BeEmpty()) + When("the lifecycle transition is not termination", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.autoscaling", + "detail-type": "EC2 Instance-terminate Lifecycle Action", + "version": "2", + "detail": { + "EC2InstanceId": "%s", + "LifecycleTransition": "test:INVALID" + } + }`, instanceIDs[1])), + }) + + createPendingASGLifecycleAction(instanceIDs[1]) + }) + + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) + + It("does not cordon or drain any nodes", func() { + Expect(cordonedNodes).To(BeEmpty()) + Expect(drainedNodes).To(BeEmpty()) + }) + + It("does not complete the ASG lifecycle action", func() { + Expect(asgLifecycleActions).To(And(HaveKeyWithValue(instanceIDs[1], Equal(StatePending)), HaveLen(1))) + }) + + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueURL]).To(BeEmpty()) + }) }) }) @@ -263,38 +345,112 @@ var _ = Describe("Reconciliation", func() { }) When("the SQS queue contains a Scheduled Change Notification", func() { - BeforeEach(func() { - resizeCluster(4) + When("the service is EC2 and the event type category is scheduled change", func() { + BeforeEach(func() { + resizeCluster(4) - sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ - ReceiptHandle: aws.String("msg-1"), - Body: aws.String(fmt.Sprintf(`{ - "source": "aws.health", - "detail-type": "AWS Health Event", - "version": "1", - "detail": { - "service": "EC2", - "eventTypeCategory": "scheduledChange", - "affectedEntities": [ - {"entityValue": "%s"}, - {"entityValue": "%s"} - ] - } - }`, instanceIDs[1], instanceIDs[2])), + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.health", + "detail-type": "AWS Health Event", + "version": "1", + "detail": { + "service": "EC2", + "eventTypeCategory": "scheduledChange", + "affectedEntities": [ + {"entityValue": "%s"}, + {"entityValue": "%s"} + ] + } + }`, instanceIDs[1], instanceIDs[2])), + }) }) - }) - It("returns success and requeues the request with the reconciler's configured interval", func() { - Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) + + It("cordons and drains only the targeted nodes", func() { + Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveKey(nodeNames[2]), HaveLen(2))) + Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveKey(nodeNames[2]), HaveLen(2))) + }) + + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueURL]).To(BeEmpty()) + }) }) - It("cordons and drains only the targeted nodes", func() { - Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveKey(nodeNames[2]), HaveLen(2))) - Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveKey(nodeNames[2]), HaveLen(2))) + When("the service is not EC2", func() { + BeforeEach(func() { + resizeCluster(4) + + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.health", + "detail-type": "AWS Health Event", + "version": "1", + "detail": { + "service": "INVALID", + "eventTypeCategory": "scheduledChange", + "affectedEntities": [ + {"entityValue": "%s"}, + {"entityValue": "%s"} + ] + } + }`, instanceIDs[1], instanceIDs[2])), + }) + }) + + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) + + It("cordons and drains only the targeted nodes", func() { + Expect(cordonedNodes).To(BeEmpty()) + Expect(drainedNodes).To(BeEmpty()) + }) + + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueURL]).To(BeEmpty()) + }) }) - It("deletes the message from the SQS queue", func() { - Expect(sqsQueues[queueURL]).To(BeEmpty()) + When("the event type category is not scheduled change", func() { + BeforeEach(func() { + resizeCluster(4) + + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.health", + "detail-type": "AWS Health Event", + "version": "1", + "detail": { + "service": "EC2", + "eventTypeCategory": "invalid", + "affectedEntities": [ + {"entityValue": "%s"}, + {"entityValue": "%s"} + ] + } + }`, instanceIDs[1], instanceIDs[2])), + }) + }) + + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) + + It("cordons and drains only the targeted nodes", func() { + Expect(cordonedNodes).To(BeEmpty()) + Expect(drainedNodes).To(BeEmpty()) + }) + + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueURL]).To(BeEmpty()) + }) }) }) @@ -330,130 +486,164 @@ var _ = Describe("Reconciliation", func() { }) When("the SQS queue contains a State Change (stopping) Notification", func() { - BeforeEach(func() { - resizeCluster(3) + When("the state is stopping", func() { + BeforeEach(func() { + resizeCluster(3) - sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ - ReceiptHandle: aws.String("msg-1"), - Body: aws.String(fmt.Sprintf(`{ - "source": "aws.ec2", - "detail-type": "EC2 Instance State-change Notification", - "version": "1", - "detail": { - "instance-id": "%s", - "state": "stopping" - } - }`, instanceIDs[1])), + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Instance State-change Notification", + "version": "1", + "detail": { + "instance-id": "%s", + "state": "stopping" + } + }`, instanceIDs[1])), + }) }) - }) - It("returns success and requeues the request with the reconciler's configured interval", func() { - Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) - }) + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) - It("cordons and drains only the targeted nodes", func() { - Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) - Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) - }) + It("cordons and drains only the targeted nodes", func() { + Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + }) - It("deletes the message from the SQS queue", func() { - Expect(sqsQueues[queueURL]).To(BeEmpty()) + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueURL]).To(BeEmpty()) + }) }) - }) - When("the SQS queue contains a State Change (stopped) Notification", func() { - BeforeEach(func() { - resizeCluster(3) + When("the state is stopped", func() { + BeforeEach(func() { + resizeCluster(3) - sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ - ReceiptHandle: aws.String("msg-1"), - Body: aws.String(fmt.Sprintf(`{ - "source": "aws.ec2", - "detail-type": "EC2 Instance State-change Notification", - "version": "1", - "detail": { - "instance-id": "%s", - "state": "stopped" - } - }`, instanceIDs[1])), + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Instance State-change Notification", + "version": "1", + "detail": { + "instance-id": "%s", + "state": "stopped" + } + }`, instanceIDs[1])), + }) }) - }) - It("returns success and requeues the request with the reconciler's configured interval", func() { - Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) - }) + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) - It("cordons and drains only the targeted node", func() { - Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) - Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) - }) + It("cordons and drains only the targeted node", func() { + Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + }) - It("deletes the message from the SQS queue", func() { - Expect(sqsQueues[queueURL]).To(BeEmpty()) + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueURL]).To(BeEmpty()) + }) }) - }) - When("the SQS queue contains a State Change (shutting-down) Notification", func() { - BeforeEach(func() { - resizeCluster(3) + When("the state is shutting-down", func() { + BeforeEach(func() { + resizeCluster(3) - sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ - ReceiptHandle: aws.String("msg-1"), - Body: aws.String(fmt.Sprintf(`{ - "source": "aws.ec2", - "detail-type": "EC2 Instance State-change Notification", - "version": "1", - "detail": { - "instance-id": "%s", - "state": "shutting-down" - } - }`, instanceIDs[1])), + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Instance State-change Notification", + "version": "1", + "detail": { + "instance-id": "%s", + "state": "shutting-down" + } + }`, instanceIDs[1])), + }) }) - }) - It("returns success and requeues the request with the reconciler's configured interval", func() { - Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) - }) + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) - It("cordons and drains only the targeted node", func() { - Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) - Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) - }) + It("cordons and drains only the targeted node", func() { + Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + }) - It("deletes the message from the SQS queue", func() { - Expect(sqsQueues[queueURL]).To(BeEmpty()) + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueURL]).To(BeEmpty()) + }) }) - }) - When("the SQS queue contains a State Change (terminated) Notification", func() { - BeforeEach(func() { - resizeCluster(3) + When("the state is terminated", func() { + BeforeEach(func() { + resizeCluster(3) - sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ - ReceiptHandle: aws.String("msg-1"), - Body: aws.String(fmt.Sprintf(`{ - "source": "aws.ec2", - "detail-type": "EC2 Instance State-change Notification", - "version": "1", - "detail": { - "instance-id": "%s", - "state": "terminated" - } - }`, instanceIDs[1])), + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Instance State-change Notification", + "version": "1", + "detail": { + "instance-id": "%s", + "state": "terminated" + } + }`, instanceIDs[1])), + }) }) - }) - It("returns success and requeues the request with the reconciler's configured interval", func() { - Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) - }) + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) - It("cordons and drains only the targeted node", func() { - Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) - Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + It("cordons and drains only the targeted node", func() { + Expect(cordonedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + Expect(drainedNodes).To(And(HaveKey(nodeNames[1]), HaveLen(1))) + }) + + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueURL]).To(BeEmpty()) + }) }) - It("deletes the message from the SQS queue", func() { - Expect(sqsQueues[queueURL]).To(BeEmpty()) + When("the state is not recognized", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Instance State-change Notification", + "version": "1", + "detail": { + "instance-id": "%s", + "state": "invalid" + } + }`, instanceIDs[1])), + }) + }) + + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) + + It("cordons and drains only the targeted node", func() { + Expect(cordonedNodes).To(BeEmpty()) + Expect(drainedNodes).To(BeEmpty()) + }) + + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueURL]).To(BeEmpty()) + }) }) }) @@ -642,6 +832,53 @@ var _ = Describe("Reconciliation", func() { }) }) + When("the SQS queue contains a message with no body", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + }) + }) + + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) + + It("does not cordon or drain any nodes", func() { + Expect(cordonedNodes).To(BeEmpty()) + Expect(drainedNodes).To(BeEmpty()) + }) + + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueURL]).To(BeEmpty()) + }) + }) + + When("the SQS queue contains an empty message", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(""), + }) + }) + + It("returns success and requeues the request with the reconciler's configured interval", func() { + Expect(result, err).To(HaveField("RequeueAfter", Equal(reconciler.RequeueInterval))) + }) + + It("does not cordon or drain any nodes", func() { + Expect(cordonedNodes).To(BeEmpty()) + Expect(drainedNodes).To(BeEmpty()) + }) + + It("deletes the message from the SQS queue", func() { + Expect(sqsQueues[queueURL]).To(BeEmpty()) + }) + }) + When("the SQS message cannot be parsed", func() { BeforeEach(func() { resizeCluster(3) @@ -722,6 +959,36 @@ var _ = Describe("Reconciliation", func() { }) }) + When("there is an error deleting an SQS message", func() { + BeforeEach(func() { + resizeCluster(3) + + sqsQueues[queueURL] = append(sqsQueues[queueURL], &sqs.Message{ + ReceiptHandle: aws.String("msg-1"), + Body: aws.String(fmt.Sprintf(`{ + "source": "aws.ec2", + "detail-type": "EC2 Spot Instance Interruption Warning", + "version": "1", + "detail": { + "instance-id": "%s" + } + }`, instanceIDs[1])), + }) + + deleteSQSMessageFunc = func(_ context.Context, _ *sqs.DeleteMessageInput, _ ...awsrequest.Option) (*sqs.DeleteMessageOutput, error) { + return nil, errors.New(errMsg) + } + }) + + It("does not requeue the request", func() { + Expect(result).To(BeZero()) + }) + + It("returns an error", func() { + Expect(err).To(MatchError(ContainSubstring(errMsg))) + }) + }) + When("there is an error getting the EC2 reservation for the instance ID", func() { BeforeEach(func() { resizeCluster(3) @@ -1442,7 +1709,13 @@ var _ = Describe("Reconciliation", func() { BeforeEach(func() { // 1. Initialize variables. - ctx = logging.WithLogger(context.Background(), zap.NewNop().Sugar()) + logger := zap.New(zapcore.NewCore( + zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()), + zapcore.AddSync(io.Discard), + zap.NewAtomicLevelAt(zap.DebugLevel), + )) + + ctx = logging.WithLogger(context.Background(), logger.Sugar()) terminatorNamespaceName = types.NamespacedName{Namespace: "test", Name: "foo"} request = reconcile.Request{NamespacedName: terminatorNamespaceName} sqsQueues = map[SQSQueueURL][]*sqs.Message{queueURL: {}}