Skip to content

Commit

Permalink
Merge pull request #353 from hashicorp/crd-controller-base
Browse files Browse the repository at this point in the history
CRD Controller
  • Loading branch information
Ashwin Venkatesh authored Oct 8, 2020
2 parents 2379a37 + b7f17e1 commit 01f62a3
Show file tree
Hide file tree
Showing 134 changed files with 15,240 additions and 295 deletions.
68 changes: 57 additions & 11 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,54 @@
version: 2.1

# reusable 'executor' object for jobs
executors:
go:
docker:
- image: circleci/golang:1.14
environment:
- TEST_RESULTS: /tmp/test-results # path to where test results are saved
- CONSUL_VERSION: 1.8.2 # Consul's OSS version to use in tests
- CONSUL_ENT_VERSION: 1.8.2+ent # Consul's enterprise version to use in tests
- CONSUL_VERSION: 71ba8300a3d25b335aff0de9f9dd2a82fc2e58f1 # Consul's OSS version to use in tests
- CONSUL_ENT_VERSION: 9fb10787b1ec44d6a7c42c3a5f326953943fc39e # Consul's enterprise version to use in tests

commands:
get-aws-cli:
steps:
- run:
name: download and install AWS CLI
command: |
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
echo -e "${AWS_CLI_GPG_KEY}" | gpg --import
curl -o awscliv2.sig https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip.sig
gpg --verify awscliv2.sig awscliv2.zip
unzip awscliv2.zip
sudo ./aws/install
aws-assume-role:
steps:
- run:
name: assume-role aws creds
command: |
# assume role has duration of 15 min (the minimum allowed)
CREDENTIALS="$(aws sts assume-role --duration-seconds 900 --role-arn ${ROLE_ARN} --role-session-name build-${CIRCLE_SHA1} | jq '.Credentials')"
echo "export AWS_ACCESS_KEY_ID=$(echo $CREDENTIALS | jq -r '.AccessKeyId')" >> $BASH_ENV
echo "export AWS_SECRET_ACCESS_KEY=$(echo $CREDENTIALS | jq -r '.SecretAccessKey')" >> $BASH_ENV
echo "export AWS_SESSION_TOKEN=$(echo $CREDENTIALS | jq -r '.SessionToken')" >> $BASH_ENV
install-dev-consul:
description: "Pull a dev version of consul/consul-enterprise for testing"
parameters:
bucket:
type: env_var_name
default: CONSUL_DEV_ARTIFACT_BUCKET # we use the same bucket for oss/ent dev builds (at different paths)
path:
type: env_var_name
sha:
type: env_var_name
steps:
- run:
name: download and install dev consul/consul-enterprise
command: |
aws s3 cp s3://${<< parameters.bucket >>}/${<< parameters.path >>}/${<< parameters.sha >>}.tar.gz /tmp
sudo tar -xzvf /tmp/${<< parameters.sha >>}.tar.gz -C /usr/local/bin
consul version
jobs:
go-fmt-and-vet:
Expand Down Expand Up @@ -61,12 +102,15 @@ jobs:
keys:
- consul-k8s-modcache-v1-{{ checksum "go.mod" }}

# pull a dev build of consul oss from s3
- get-aws-cli
- aws-assume-role
- install-dev-consul:
path: CONSUL_DEV_ARTIFACT_PATH
sha: CONSUL_VERSION

# run go tests with gotestsum
- run: |
# download and install the consul binary
wget https://releases.hashicorp.com/consul/"${CONSUL_VERSION}"/consul_"${CONSUL_VERSION}"_linux_amd64.zip && \
unzip consul_"${CONSUL_VERSION}"_linux_amd64.zip -d /home/circleci/bin &&
rm consul_"${CONSUL_VERSION}"_linux_amd64.zip
PACKAGE_NAMES=$(go list ./...)
gotestsum --junitfile $TEST_RESULTS/gotestsum-report.xml -- -p 4 $PACKAGE_NAMES
Expand All @@ -89,12 +133,14 @@ jobs:
keys:
- consul-k8s-modcache-v1-{{ checksum "go.mod" }}

# pull a dev build of consul ent from s3
- get-aws-cli
- aws-assume-role
- install-dev-consul:
path: CONSUL_ENT_DEV_ARTIFACT_PATH
sha: CONSUL_ENT_VERSION

# run go tests with gotestsum
- run: |
# download and install the consul binary
wget https://releases.hashicorp.com/consul/"${CONSUL_ENT_VERSION}"/consul_"${CONSUL_ENT_VERSION}"_linux_amd64.zip && \
unzip consul_"${CONSUL_ENT_VERSION}"_linux_amd64.zip -d /home/circleci/bin &&
rm consul_"${CONSUL_ENT_VERSION}"_linux_amd64.zip
- run: |
PACKAGE_NAMES=$(go list ./...)
gotestsum --junitfile $TEST_RESULTS/gotestsum-report.xml -- -tags=enterprise -p 4 $PACKAGE_NAMES
Expand Down
59 changes: 57 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export GIT_DESCRIBE
export GOLDFLAGS
export GOTAGS


CRD_OPTIONS ?= "crd:trivialVersions=true,allowDangerousTypes=true,crdVersions=v1beta1"

################
# CI Variables #
Expand Down Expand Up @@ -79,7 +79,7 @@ else
DEV_PUSH_ARG=--no-push
endif

all: bin
all: bin ctrl-generate

bin:
@$(SHELL) $(CURDIR)/build-support/scripts/build-local.sh
Expand Down Expand Up @@ -128,6 +128,57 @@ clean:
$(CURDIR)/bin \
$(CURDIR)/pkg

# Run controller tests
ENVTEST_ASSETS_DIR=$(shell pwd)/testbin
ctrl-test: ctrl-generate ctrl-manifests
mkdir -p ${ENVTEST_ASSETS_DIR}
test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/master/hack/setup-envtest.sh
source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./...

# Deploy controller in the configured Kubernetes cluster in ~/.kube/config
ctrl-deploy: ctrl-manifests kustomize
cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
$(KUSTOMIZE) build config/default | kubectl apply -f -

# Generate manifests e.g. CRD, RBAC etc.
ctrl-manifests: controller-gen
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases

# Generate code
ctrl-generate: controller-gen
$(CONTROLLER_GEN) object:headerFile="build-support/controller/boilerplate.go.txt" paths="./..."

# find or download controller-gen
# download controller-gen if necessary
controller-gen:
ifeq (, $(shell which controller-gen))
@{ \
set -e ;\
CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\
cd $$CONTROLLER_GEN_TMP_DIR ;\
go mod init tmp ;\
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.4.0 ;\
rm -rf $$CONTROLLER_GEN_TMP_DIR ;\
}
CONTROLLER_GEN=$(GOBIN)/controller-gen
else
CONTROLLER_GEN=$(shell which controller-gen)
endif

kustomize:
ifeq (, $(shell which kustomize))
@{ \
set -e ;\
KUSTOMIZE_GEN_TMP_DIR=$$(mktemp -d) ;\
cd $$KUSTOMIZE_GEN_TMP_DIR ;\
go mod init tmp ;\
go get sigs.k8s.io/kustomize/kustomize/v3@v3.5.4 ;\
rm -rf $$KUSTOMIZE_GEN_TMP_DIR ;\
}
KUSTOMIZE=$(GOBIN)/kustomize
else
KUSTOMIZE=$(shell which kustomize)
endif

# In CircleCI, the linux binary will be attached from a previous step at pkg/bin/linux_amd64/. This make target
# should only run in CI and not locally.
Expand All @@ -148,5 +199,9 @@ ifeq ($(CIRCLE_BRANCH), master)
@docker tag $(CI_DEV_DOCKER_NAMESPACE)/$(CI_DEV_DOCKER_IMAGE_NAME):$(GIT_COMMIT) $(CI_DEV_DOCKER_NAMESPACE)/$(CI_DEV_DOCKER_IMAGE_NAME):latest
@docker push $(CI_DEV_DOCKER_NAMESPACE)/$(CI_DEV_DOCKER_IMAGE_NAME):latest
endif
ifeq ($(CIRCLE_BRANCH), crd-controller-base)
@docker tag $(CI_DEV_DOCKER_NAMESPACE)/$(CI_DEV_DOCKER_IMAGE_NAME):$(GIT_COMMIT) $(CI_DEV_DOCKER_NAMESPACE)/$(CI_DEV_DOCKER_IMAGE_NAME):crd-controller-base-latest
@docker push $(CI_DEV_DOCKER_NAMESPACE)/$(CI_DEV_DOCKER_IMAGE_NAME):crd-controller-base-latest
endif

.PHONY: all bin clean dev dist docker-images go-build-image test tools ci.dev-docker
7 changes: 7 additions & 0 deletions PROJECT
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# this is a generated file used for operator sdk during code generation of CRDs, Controllers and webhooks
domain: hashicorp.com
layout: go.kubebuilder.io/v2
repo: github.com/hashicorp/consul-k8s
version: 3-alpha
plugins:
go.operator-sdk.io/v2-alpha: {}
19 changes: 19 additions & 0 deletions api/common/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Package common holds code that isn't tied to a particular CRD version or type.
package common

const (
ServiceDefaults string = "servicedefaults"
ProxyDefaults string = "proxydefaults"
ServiceResolver string = "serviceresolver"
ServiceRouter string = "servicerouter"
ServiceSplitter string = "servicesplitter"
ServiceIntentions string = "serviceintentions"

Global string = "global"
DefaultConsulNamespace string = "default"
WildcardNamespace string = "*"

SourceKey string = "external-source"
DatacenterKey string = "consul.hashicorp.com/source-datacenter"
SourceValue string = "kubernetes"
)
60 changes: 60 additions & 0 deletions api/common/configentry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package common

import (
"github.com/hashicorp/consul/api"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)

// ConfigEntryResource is a generic config entry custom resource. It is implemented
// by each config entry type so that they can be acted upon generically.
// It is not tied to a specific CRD version.
type ConfigEntryResource interface {
// GetObjectMeta returns object meta.
GetObjectMeta() metav1.ObjectMeta
// AddFinalizer adds a finalizer to the list of finalizers.
AddFinalizer(name string)
// RemoveFinalizer removes this finalizer from the list.
RemoveFinalizer(name string)
// Finalizers returns the list of finalizers for this object.
Finalizers() []string
// ConsulKind returns the Consul config entry kind, i.e. service-defaults, not
// servicedefaults.
ConsulKind() string
// ConsulGlobalResource returns if the resource exists in the default
// Consul namespace only.
ConsulGlobalResource() bool
// ConsulMirroringNS returns the Consul namespace that the config entry should
// be created in if namespaces and mirroring are enabled.
ConsulMirroringNS() string
// KubeKind returns the Kube config entry kind, i.e. servicedefaults, not
// service-defaults.
KubeKind() string
// ConsulName returns the name of the config entry as saved in Consul.
// This may be different than KubernetesName() in the case of a ServiceIntentions
// config entry.
ConsulName() string
// KubernetesName returns the name of the Kubernetes resource.
KubernetesName() string
// SetSyncedCondition updates the synced condition.
SetSyncedCondition(status corev1.ConditionStatus, reason, message string)
// SyncedCondition gets the synced condition.
SyncedCondition() (status corev1.ConditionStatus, reason, message string)
// SyncedConditionStatus returns the status of the synced condition.
SyncedConditionStatus() corev1.ConditionStatus
// ToConsul converts the resource to the corresponding Consul API definition.
// Its return type is the generic ConfigEntry but a specific config entry
// type should be constructed e.g. ServiceConfigEntry.
ToConsul(datacenter string) api.ConfigEntry
// MatchesConsul returns true if the resource has the same fields as the Consul
// config entry.
MatchesConsul(candidate api.ConfigEntry) bool
// GetObjectKind should be implemented by the generated code.
GetObjectKind() schema.ObjectKind
// DeepCopyObject should be implemented by the generated code.
DeepCopyObject() runtime.Object
// Validate returns an error if the resource is invalid.
Validate() error
}
60 changes: 60 additions & 0 deletions api/common/configentry_webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package common

import (
"context"
"fmt"
"net/http"

"github.com/go-logr/logr"
"k8s.io/api/admission/v1beta1"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

// ConfigEntryLister is implemented by CRD-specific webhooks.
type ConfigEntryLister interface {
// List returns all resources of this type across all namespaces in a
// Kubernetes cluster.
List(ctx context.Context) ([]ConfigEntryResource, error)
}

// ValidateConfigEntry validates cfgEntry. It is a generic method that
// can be used by all CRD-specific validators.
// Callers should pass themselves as validator and kind should be the custom
// resource name, e.g. "ServiceDefaults".
func ValidateConfigEntry(
ctx context.Context,
req admission.Request,
logger logr.Logger,
configEntryLister ConfigEntryLister,
cfgEntry ConfigEntryResource,
enableConsulNamespaces bool,
nsMirroring bool) admission.Response {

// On create we need to validate that there isn't already a resource with
// the same name in a different namespace if we're need to mapping all Kube
// resources to a single Consul namespace. The only case where we're not
// mapping all kube resources to a single Consul namespace is when we
// are running Consul enterprise with namespace mirroring.
singleConsulDestNS := !(enableConsulNamespaces && nsMirroring)
if req.Operation == v1beta1.Create && singleConsulDestNS {
logger.Info("validate create", "name", cfgEntry.KubernetesName())

list, err := configEntryLister.List(ctx)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}
for _, item := range list {
if item.KubernetesName() == cfgEntry.KubernetesName() {
return admission.Errored(http.StatusBadRequest,
fmt.Errorf("%s resource with name %q is already defined – all %s resources must have unique names across namespaces",
cfgEntry.KubeKind(),
cfgEntry.KubernetesName(),
cfgEntry.KubeKind()))
}
}
}
if err := cfgEntry.Validate(); err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
return admission.Allowed(fmt.Sprintf("valid %s request", cfgEntry.KubeKind()))
}
Loading

0 comments on commit 01f62a3

Please sign in to comment.