Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ACM-10801] Added Validating Webhook for Discovery #231

Merged
36 changes: 36 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ docker-build: ## Build docker image with the manager.
docker-push: ## Push docker image with the manager.
docker push "${URL}"

podman-build: ## Build podman image with the manager.
podman build -t "${URL}" .

podman-push: ## Push podman image with the manager.
podman push "${URL}"

##@ Deployment

install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.
Expand Down Expand Up @@ -153,6 +159,14 @@ bundle-build: ## Build the bundle image.
bundle-push: ## Push the bundle image.
$(MAKE) docker-push IMG=$(BUNDLE_IMG)

.PHONY: podman-bundle-build
podman-bundle-build: ## Build the bundle image.
podman build -f bundle.Dockerfile -t $(BUNDLE_IMG) .

.PHONY: podman-bundle-push
podman-bundle-push: ## Push the bundle image.
$(MAKE) podman-push IMG=$(BUNDLE_IMG)

.PHONY: opm
OPM = ./bin/opm
opm: ## Download opm locally if necessary.
Expand Down Expand Up @@ -194,6 +208,18 @@ catalog-build: opm ## Build a catalog image.
catalog-push: ## Push a catalog image.
$(MAKE) docker-push IMG=$(CATALOG_IMG)

# Build a catalog image by adding bundle images to an empty catalog using the operator package manager tool, 'opm'.
# This recipe invokes 'opm' in 'semver' bundle add mode. For more information on add modes, see:
# https://github.com/operator-framework/community-operators/blob/7f1438c/docs/packaging-operator.md#updating-your-existing-operator
.PHONY: podman-catalog-build
podman-catalog-build: opm ## Build a catalog image.
$(OPM) index add --container-tool podman --mode semver --tag $(CATALOG_IMG) --bundles $(BUNDLE_IMGS) $(FROM_INDEX_OPT)

# Push the catalog image.
.PHONY: podman-catalog-push
podman-catalog-push: ## Push a catalog image.
$(MAKE) podman-push IMG=$(CATALOG_IMG)

##@ Testing

.PHONY: test
Expand Down Expand Up @@ -226,6 +252,16 @@ docker-run-tests: ## Run the containerized functional tests
--volume $(shell pwd)/test/e2e/results:/results \
$(REGISTRY)/$(IMG)-tests:$(VERSION)

podman-build-tests: ## Build the functional test image
@echo "Building $(REGISTRY)/$(IMG)-tests:$(VERSION)"
podman build . -f test/e2e/build/Dockerfile -t $(REGISTRY)/$(IMG)-tests:$(VERSION)

podman-run-tests: ## Run the containerized functional tests
podman run --network host \
--volume ~/.kube/config:/opt/.kube/config \
--volume $(shell pwd)/test/e2e/results:/results \
$(REGISTRY)/$(IMG)-tests:$(VERSION)

scale-test: ## Run scalability test
go run test/scale/scale.go

Expand Down
148 changes: 148 additions & 0 deletions api/v1/discovery_webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// Copyright Contributors to the Open Cluster Management project
/*
Copyright 2021.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES 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 (
"errors"
"fmt"

admissionregistration "k8s.io/api/admissionregistration/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
cl "sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

// log is for logging in this package.
var (
discoveredclusterLog = logf.Log.WithName("discoveredcluster-resource")
Client cl.Client

ErrInvalidImportStrategy = errors.New("invalid import-strategy")
)

// ValidatingWebhook returns the ValidatingWebhookConfiguration used for the discoveredcluster
// linked to a service in the provided namespace
func ValidatingWebhook(namespace string) *admissionregistration.ValidatingWebhookConfiguration {
fail := admissionregistration.Fail
none := admissionregistration.SideEffectClassNone
path := "/validate-discovery-open-cluster-management-io-v1-discoveredcluster"
return &admissionregistration.ValidatingWebhookConfiguration{
TypeMeta: metav1.TypeMeta{
APIVersion: "admissionregistration.k8s.io/v1",
Kind: "ValidatingWebhookConfiguration",
},
ObjectMeta: metav1.ObjectMeta{
Name: "discovery.open-cluster-management.io",
Annotations: map[string]string{"service.beta.openshift.io/inject-cabundle": "true"},
},
Webhooks: []admissionregistration.ValidatingWebhook{
{
AdmissionReviewVersions: []string{
"v1",
"v1beta1",
},
Name: "discovery.open-cluster-management.io",
ClientConfig: admissionregistration.WebhookClientConfig{
Service: &admissionregistration.ServiceReference{
Name: "discovery-operator-webhook-service",
Namespace: namespace,
Path: &path,
},
},
FailurePolicy: &fail,
Rules: []admissionregistration.RuleWithOperations{
{
Rule: admissionregistration.Rule{
APIGroups: []string{GroupVersion.Group},
APIVersions: []string{GroupVersion.Version},
Resources: []string{"discoveredclusters"},
},
Operations: []admissionregistration.OperationType{
admissionregistration.Create,
admissionregistration.Update,
admissionregistration.Delete,
},
},
},
SideEffects: &none,
},
},
}
}

func (r *DiscoveredCluster) SetupWebhookWithManager(mgr ctrl.Manager) error {
Client = mgr.GetClient()
return ctrl.NewWebhookManagedBy(mgr).
For(r).
Complete()
}

var _ webhook.Defaulter = &DiscoveredCluster{}

// Default implements webhook.Defaulter so a webhook will be registered for the type
func (r *DiscoveredCluster) Default() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this doing anything?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just for setting the default setting of the webhook. No harm in just having this log for the moment, since this func can be enhanced at a later time.

discoveredclusterLog.Info("default", "Name", r.Name)
}

var _ webhook.Validator = &DiscoveredCluster{}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there a global variable that's not defined?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following the kubebuilder docs, they were adding that line when they were building the webhook: https://book.kubebuilder.io/multiversion-tutorial/webhooks.html#and-maingo. I checked out a few other repos that are configuring a webhook and they are using the same logic, so it might be best for us to remain consistent with the other projects/components.


// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *DiscoveredCluster) ValidateCreate() (admission.Warnings, error) {
discoveredclusterLog.Info("validate create", "Name", r.Name)

// Validate resource
if r.Spec.Type != "ROSA" && r.Spec.EnableAutoImport {
return nil, fmt.Errorf(
"cannot create DiscoveredCluster '%s': enableAutoImport is not allowed for clusters of type '%s'. "+
"Only ROSA type clusters support auto import",
r.Spec.Type, r.Name,
)
}

return nil, nil
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (r *DiscoveredCluster) ValidateUpdate(old runtime.Object) (admission.Warnings, error) {
discoveredclusterLog.Info("validate update", "Name", r.Name)

if r.Annotations == nil {
r.Annotations = make(map[string]string)
}

oldDiscoveredCluster := old.(*DiscoveredCluster)
if oldDiscoveredCluster.Spec.Type != "ROSA" && r.Spec.EnableAutoImport {
return nil, fmt.Errorf(
"cannot update DiscoveredCluster '%s': enableAutoImport is not allowed for clusters of type '%s'."+
"Only ROSA type clusters support auto import",
r.Spec.Type, r.Name,
)
}

return nil, nil
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (r *DiscoveredCluster) ValidateDelete() (admission.Warnings, error) {
discoveredclusterLog.Info("validate delete", "Name", r.Name)
return nil, nil
}
2 changes: 1 addition & 1 deletion api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion bundle.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/
LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/
LABEL operators.operatorframework.io.bundle.package.v1=discovery
LABEL operators.operatorframework.io.bundle.channels.v1=alpha
LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.22.2
LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.34.1
LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1
LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v3

Expand Down
15 changes: 15 additions & 0 deletions bundle/manifests/discovery-operator-webhook_v1_service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
annotations:
service.beta.openshift.io/serving-cert-secret-name: discovery-operator-webhook-service
creationTimestamp: null
name: discovery-operator-webhook-service
spec:
ports:
- port: 443
targetPort: 9443
selector:
app: discovery-operator
status:
loadBalancer: {}
Loading
Loading