Adds CertificateRequest approver controller. This controller will
currently _always_ set the Approved condition to true on CertificateRequests Signed-off-by: joshvanl <vleeuwenjoshua@gmail.com>
This commit is contained in:
parent
0ef25daeb3
commit
2db7582586
56
pkg/controller/certificaterequests/approver/BUILD.bazel
Normal file
56
pkg/controller/certificaterequests/approver/BUILD.bazel
Normal file
@ -0,0 +1,56 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"approver.go",
|
||||
"sync.go",
|
||||
],
|
||||
importpath = "github.com/jetstack/cert-manager/pkg/controller/certificaterequests/approver",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/api/util:go_default_library",
|
||||
"//pkg/apis/certmanager/v1:go_default_library",
|
||||
"//pkg/apis/meta/v1:go_default_library",
|
||||
"//pkg/client/clientset/versioned:go_default_library",
|
||||
"//pkg/client/listers/certmanager/v1:go_default_library",
|
||||
"//pkg/controller:go_default_library",
|
||||
"//pkg/logs:go_default_library",
|
||||
"@com_github_go_logr_logr//:go_default_library",
|
||||
"@io_k8s_api//core/v1:go_default_library",
|
||||
"@io_k8s_apimachinery//pkg/api/errors:go_default_library",
|
||||
"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library",
|
||||
"@io_k8s_client_go//tools/cache:go_default_library",
|
||||
"@io_k8s_client_go//tools/record:go_default_library",
|
||||
"@io_k8s_client_go//util/workqueue:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["approver_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/certmanager/v1:go_default_library",
|
||||
"//pkg/apis/meta/v1:go_default_library",
|
||||
"//pkg/controller:go_default_library",
|
||||
"//pkg/controller/test:go_default_library",
|
||||
"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library",
|
||||
"@io_k8s_client_go//testing:go_default_library",
|
||||
"@io_k8s_utils//clock/testing:go_default_library",
|
||||
],
|
||||
)
|
||||
103
pkg/controller/certificaterequests/approver/approver.go
Normal file
103
pkg/controller/certificaterequests/approver/approver.go
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
Copyright 2021 The cert-manager Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package approver
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
|
||||
cmclient "github.com/jetstack/cert-manager/pkg/client/clientset/versioned"
|
||||
cmlisters "github.com/jetstack/cert-manager/pkg/client/listers/certmanager/v1"
|
||||
controllerpkg "github.com/jetstack/cert-manager/pkg/controller"
|
||||
logf "github.com/jetstack/cert-manager/pkg/logs"
|
||||
)
|
||||
|
||||
const (
|
||||
ControllerName = "certificaterequests-approver"
|
||||
)
|
||||
|
||||
// Controller is a CertificateRequest controller which manages the "Approved"
|
||||
// condition. In the absence of any automated policy engine, this controller
|
||||
// will _always_ set the "Approved" condition to True. All CertificateRequest
|
||||
// signing controllers should wait until the "Approved" condition is set to
|
||||
// True before processing.
|
||||
type Controller struct {
|
||||
// logger to be used by this controller
|
||||
log logr.Logger
|
||||
|
||||
certificateRequestLister cmlisters.CertificateRequestLister
|
||||
cmClient cmclient.Interface
|
||||
|
||||
recorder record.EventRecorder
|
||||
|
||||
queue workqueue.RateLimitingInterface
|
||||
}
|
||||
|
||||
func init() {
|
||||
// create certificate request approver controller
|
||||
controllerpkg.Register(ControllerName, func(ctx *controllerpkg.Context) (controllerpkg.Interface, error) {
|
||||
return controllerpkg.NewBuilder(ctx, ControllerName).
|
||||
For(new(Controller)).Complete()
|
||||
})
|
||||
}
|
||||
|
||||
// Register registers and constructs the controller using the provided context.
|
||||
// It returns the workqueue to be used to enqueue items, a list of
|
||||
// InformerSynced functions that must be synced, or an error.
|
||||
func (c *Controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) {
|
||||
c.log = logf.FromContext(ctx.RootContext, ControllerName)
|
||||
c.queue = workqueue.NewNamedRateLimitingQueue(controllerpkg.DefaultItemBasedRateLimiter(), ControllerName)
|
||||
|
||||
certificateRequestInformer := ctx.SharedInformerFactory.Certmanager().V1().CertificateRequests()
|
||||
mustSync := append([]cache.InformerSynced{certificateRequestInformer.Informer().HasSynced})
|
||||
certificateRequestInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: c.queue})
|
||||
|
||||
c.certificateRequestLister = certificateRequestInformer.Lister()
|
||||
c.cmClient = ctx.CMClient
|
||||
c.recorder = ctx.Recorder
|
||||
|
||||
c.log.V(logf.DebugLevel).Info("certificate request approver controller registered")
|
||||
|
||||
return c.queue, mustSync, nil
|
||||
}
|
||||
|
||||
func (c *Controller) ProcessItem(ctx context.Context, key string) error {
|
||||
log := logf.FromContext(ctx)
|
||||
namespace, name, err := cache.SplitMetaNamespaceKey(key)
|
||||
if err != nil {
|
||||
log.Error(err, "invalid resource key")
|
||||
return nil
|
||||
}
|
||||
|
||||
cr, err := c.certificateRequestLister.CertificateRequests(namespace).Get(name)
|
||||
if apierrors.IsNotFound(err) {
|
||||
log.Error(err, "certificate request in work queue no longer exists")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx = logf.NewContext(ctx, logf.WithResource(log, cr))
|
||||
return c.Sync(ctx, cr)
|
||||
}
|
||||
240
pkg/controller/certificaterequests/approver/approver_test.go
Normal file
240
pkg/controller/certificaterequests/approver/approver_test.go
Normal file
@ -0,0 +1,240 @@
|
||||
/*
|
||||
Copyright 2021 The cert-manager Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package approver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
coretesting "k8s.io/client-go/testing"
|
||||
fakeclock "k8s.io/utils/clock/testing"
|
||||
|
||||
cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
|
||||
cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
|
||||
controllerpkg "github.com/jetstack/cert-manager/pkg/controller"
|
||||
testpkg "github.com/jetstack/cert-manager/pkg/controller/test"
|
||||
)
|
||||
|
||||
func TestProcessItem(t *testing.T) {
|
||||
// now time is the current time at the start of the test (the clock is fixed)
|
||||
now := time.Now()
|
||||
metaNow := metav1.NewTime(now)
|
||||
tests := map[string]struct {
|
||||
// key that should be passed to ProcessItem.
|
||||
// if not set, the 'namespace/name' of the 'CertificateRequest' field will be used.
|
||||
// if neither is set, the key will be ""
|
||||
key string
|
||||
|
||||
// CertificateRequest to be synced for the test.
|
||||
// if not set, the 'key' will be passed to ProcessItem instead.
|
||||
request *cmapi.CertificateRequest
|
||||
|
||||
// expectedEvent, if set, is an 'event string' that is expected to be fired.
|
||||
expectedEvent string
|
||||
|
||||
// expectedConditions is the expected set of conditions on the
|
||||
// CertificateRequest resource if an Update is made.
|
||||
// If nil, no update is expected.
|
||||
// If empty, an update to the empty set/nil is expected.
|
||||
expectedConditions []cmapi.CertificateRequestCondition
|
||||
|
||||
// err is the expected error text returned by the controller, if any.
|
||||
err string
|
||||
}{
|
||||
"do nothing if an empty 'key' is used": {},
|
||||
"do nothing if an invalid 'key' is used": {
|
||||
key: "abc/def/ghi",
|
||||
},
|
||||
"do nothing if a key references a Certificate that does not exist": {
|
||||
key: "namespace/name",
|
||||
},
|
||||
"do nothing if CertificateRequest already has 'Approved' True condition": {
|
||||
request: &cmapi.CertificateRequest{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: "testns", Name: "test"},
|
||||
Status: cmapi.CertificateRequestStatus{
|
||||
Conditions: []cmapi.CertificateRequestCondition{
|
||||
{
|
||||
Type: cmapi.CertificateRequestConditionApproved,
|
||||
Status: cmmeta.ConditionTrue,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"do nothing if CertificateRequest already has 'Denied' True condition": {
|
||||
request: &cmapi.CertificateRequest{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: "testns", Name: "test"},
|
||||
Status: cmapi.CertificateRequestStatus{
|
||||
Conditions: []cmapi.CertificateRequestCondition{
|
||||
{
|
||||
Type: cmapi.CertificateRequestConditionApproved,
|
||||
Status: cmmeta.ConditionFalse,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"do nothing if CertificateRequest already has 'Ready' Failed condition": {
|
||||
request: &cmapi.CertificateRequest{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: "testns", Name: "test"},
|
||||
Status: cmapi.CertificateRequestStatus{
|
||||
Conditions: []cmapi.CertificateRequestCondition{
|
||||
{
|
||||
Type: cmapi.CertificateRequestConditionReady,
|
||||
Status: cmmeta.ConditionFalse,
|
||||
Reason: cmapi.CertificateRequestReasonFailed,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"do nothing if CertificateRequest already has 'Ready' Issued condition": {
|
||||
request: &cmapi.CertificateRequest{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: "testns", Name: "test"},
|
||||
Status: cmapi.CertificateRequestStatus{
|
||||
Conditions: []cmapi.CertificateRequestCondition{
|
||||
{
|
||||
Type: cmapi.CertificateRequestConditionReady,
|
||||
Status: cmmeta.ConditionTrue,
|
||||
Reason: cmapi.CertificateRequestReasonIssued,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"approve CertificateRequest if no condition": {
|
||||
request: &cmapi.CertificateRequest{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: "testns", Name: "test"},
|
||||
Status: cmapi.CertificateRequestStatus{
|
||||
Conditions: []cmapi.CertificateRequestCondition{},
|
||||
},
|
||||
},
|
||||
expectedConditions: []cmapi.CertificateRequestCondition{
|
||||
{
|
||||
Type: cmapi.CertificateRequestConditionApproved,
|
||||
Status: cmmeta.ConditionTrue,
|
||||
Reason: cmapi.CertificateRequestReasonApproved,
|
||||
Message: ApprovedMessage,
|
||||
LastTransitionTime: &metaNow,
|
||||
},
|
||||
},
|
||||
expectedEvent: "Normal Approved Certificate request has been approved by cert-manager.io",
|
||||
},
|
||||
"approve CertificateRequest has 'Ready' Pending condition": {
|
||||
request: &cmapi.CertificateRequest{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: "testns", Name: "test"},
|
||||
Status: cmapi.CertificateRequestStatus{
|
||||
Conditions: []cmapi.CertificateRequestCondition{
|
||||
{
|
||||
Type: cmapi.CertificateRequestConditionReady,
|
||||
Status: cmmeta.ConditionFalse,
|
||||
Reason: cmapi.CertificateRequestReasonPending,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedConditions: []cmapi.CertificateRequestCondition{
|
||||
{
|
||||
Type: cmapi.CertificateRequestConditionReady,
|
||||
Status: cmmeta.ConditionFalse,
|
||||
Reason: cmapi.CertificateRequestReasonPending,
|
||||
},
|
||||
{
|
||||
Type: cmapi.CertificateRequestConditionApproved,
|
||||
Status: cmmeta.ConditionTrue,
|
||||
Reason: cmapi.CertificateRequestReasonApproved,
|
||||
Message: ApprovedMessage,
|
||||
LastTransitionTime: &metaNow,
|
||||
},
|
||||
},
|
||||
expectedEvent: "Normal Approved Certificate request has been approved by cert-manager.io",
|
||||
},
|
||||
}
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// Create and initialise a new unit test builder
|
||||
builder := &testpkg.Builder{
|
||||
T: t,
|
||||
Clock: fakeclock.NewFakeClock(now),
|
||||
}
|
||||
if test.request != nil {
|
||||
builder.CertManagerObjects = append(builder.CertManagerObjects, test.request)
|
||||
}
|
||||
builder.Init()
|
||||
|
||||
c := new(Controller)
|
||||
_, _, err := c.Register(builder.Context)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if test.expectedConditions != nil {
|
||||
if test.request == nil {
|
||||
t.Fatal("cannot expect an Update operation if test.request is nil")
|
||||
}
|
||||
expectedRequest := test.request.DeepCopy()
|
||||
expectedRequest.Status.Conditions = test.expectedConditions
|
||||
builder.ExpectedActions = append(builder.ExpectedActions,
|
||||
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
|
||||
cmapi.SchemeGroupVersion.WithResource("certificaterequests"),
|
||||
"status",
|
||||
test.request.Namespace,
|
||||
expectedRequest,
|
||||
)),
|
||||
)
|
||||
}
|
||||
if test.expectedEvent != "" {
|
||||
builder.ExpectedEvents = []string{test.expectedEvent}
|
||||
}
|
||||
// Start the informers and begin processing updates
|
||||
builder.Start()
|
||||
defer builder.Stop()
|
||||
|
||||
key := test.key
|
||||
if key == "" && test.request != nil {
|
||||
key, err = controllerpkg.KeyFunc(test.request)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Call ProcessItem
|
||||
err = c.ProcessItem(context.Background(), key)
|
||||
switch {
|
||||
case err != nil:
|
||||
if test.err != err.Error() {
|
||||
t.Errorf("error text did not match, got=%s, exp=%s", err.Error(), test.err)
|
||||
}
|
||||
default:
|
||||
if test.err != "" {
|
||||
t.Errorf("got no error but expected: %s", test.err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := builder.AllEventsCalled(); err != nil {
|
||||
builder.T.Error(err)
|
||||
}
|
||||
if err := builder.AllActionsExecuted(); err != nil {
|
||||
builder.T.Error(err)
|
||||
}
|
||||
if err := builder.AllReactorsCalled(); err != nil {
|
||||
builder.T.Error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
73
pkg/controller/certificaterequests/approver/sync.go
Normal file
73
pkg/controller/certificaterequests/approver/sync.go
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
Copyright 2021 The cert-manager Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package approver
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
apiutil "github.com/jetstack/cert-manager/pkg/api/util"
|
||||
cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
|
||||
cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
|
||||
logf "github.com/jetstack/cert-manager/pkg/logs"
|
||||
)
|
||||
|
||||
const (
|
||||
ApprovedMessage = "Certificate request has been approved by cert-manager.io"
|
||||
)
|
||||
|
||||
// Sync will set the "Approved" condition to True on synced
|
||||
// CertificateRequests. If the "Denied", "Approved" or "Ready" condition alrady
|
||||
// exists, exit early.
|
||||
func (c *Controller) Sync(ctx context.Context, cr *cmapi.CertificateRequest) (err error) {
|
||||
log := logf.FromContext(ctx, "approver")
|
||||
|
||||
switch {
|
||||
case
|
||||
// If the CertificateRequest has already been approved, exit early.
|
||||
apiutil.CertificateRequestHasApproved(cr),
|
||||
|
||||
// If the CertificateRequest has already been denied, exit early.
|
||||
apiutil.CertificateRequestHasDenied(cr),
|
||||
|
||||
// If the CertificateRequest is "Issued" or "Failed", exit early.
|
||||
apiutil.CertificateRequestReadyReason(cr) == cmapi.CertificateRequestReasonFailed,
|
||||
apiutil.CertificateRequestReadyReason(cr) == cmapi.CertificateRequestReasonIssued:
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update the CertificateRequest approved condition to true.
|
||||
apiutil.SetCertificateRequestCondition(cr,
|
||||
cmapi.CertificateRequestConditionApproved,
|
||||
cmmeta.ConditionTrue,
|
||||
cmapi.CertificateRequestReasonApproved,
|
||||
ApprovedMessage,
|
||||
)
|
||||
|
||||
// Update CertificateRequest with
|
||||
_, err = c.cmClient.CertmanagerV1().CertificateRequests(cr.Namespace).UpdateStatus(ctx, cr, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.recorder.Event(cr, corev1.EventTypeNormal, cmapi.CertificateRequestReasonApproved, ApprovedMessage)
|
||||
|
||||
log.V(logf.DebugLevel).Info("approved certificate request")
|
||||
|
||||
return nil
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user