Merge pull request #4811 from JoshVanL/controllers-server-side-apply-certificates-shim

Server Side Apply: Adds support for certificate-shim controllers to use SSA with Feature Gate
This commit is contained in:
jetstack-bot 2022-03-28 14:33:31 +01:00 committed by GitHub
commit bfcc204c2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 115 additions and 5 deletions

View File

@ -29,6 +29,26 @@ import (
cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned"
)
// Apply will make a Apply API call with the given client to the certificates
// resource endpoint. All data in the given Certificate's status field is
// dropped.
// The given fieldManager is will be used as the FieldManager in the Apply
// call.
// Always sets Force Apply to true.
func Apply(ctx context.Context, cl cmclient.Interface, fieldManager string, crt *cmapi.Certificate) error {
crtData, err := serializeApply(crt)
if err != nil {
return err
}
_, err = cl.CertmanagerV1().Certificates(crt.Namespace).Patch(
ctx, crt.Name, apitypes.ApplyPatchType, crtData,
metav1.PatchOptions{Force: pointer.Bool(true), FieldManager: fieldManager},
)
return err
}
// ApplyStatus will make a Patch API call with the given client to the
// certificates status sub-resource endpoint. All data in the given Certificate
// object is dropped; expect for the name, namespace, and status object. The
@ -48,6 +68,24 @@ func ApplyStatus(ctx context.Context, cl cmclient.Interface, fieldManager string
return err
}
// serializeApply converts the given Certificate object in JSON.
// The status field will be set empty before serializing.
// TypeMeta will be populated with the Kind "Certificate" and API Version
// "cert-manager.io/v1" respectively.
func serializeApply(crt *cmapi.Certificate) ([]byte, error) {
crt = &cmapi.Certificate{
TypeMeta: metav1.TypeMeta{Kind: cmapi.CertificateKind, APIVersion: cmapi.SchemeGroupVersion.Identifier()},
ObjectMeta: *crt.ObjectMeta.DeepCopy(),
Spec: *crt.Spec.DeepCopy(),
Status: cmapi.CertificateStatus{},
}
crtData, err := json.Marshal(crt)
if err != nil {
return nil, fmt.Errorf("failed to marshal certificate object: %w", err)
}
return crtData, nil
}
// serializeApplyStatus converts the given Certificate object in JSON. Only the
// name, namespace, and status field values will be copied and encoded into the
// serialized slice. All other fields will be left at their zero value.

View File

@ -17,6 +17,7 @@ limitations under the License.
package certificates
import (
"encoding/json"
"strconv"
"sync"
"testing"
@ -27,6 +28,46 @@ import (
cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
)
func Test_serializeApply(t *testing.T) {
const (
expReg = `^{"kind":"Certificate","apiVersion":"cert-manager.io/v1","metadata":{.*},"spec":{.*},"status":{}}$`
numJobs = 10000
)
var wg sync.WaitGroup
jobs := make(chan int)
wg.Add(numJobs)
for i := 0; i < 3; i++ {
go func() {
for j := range jobs {
t.Run("fuzz_"+strconv.Itoa(j), func(t *testing.T) {
var crt cmapi.Certificate
fuzz.New().NilChance(0.5).Fuzz(&crt)
crt.ManagedFields = nil
crtData, err := serializeApply(&crt)
assert.NoError(t, err)
assert.Regexp(t, expReg, string(crtData))
// Test round trip serializing Certificate preserved the spec.
var rtCrt cmapi.Certificate
assert.NoError(t, json.Unmarshal(crtData, &rtCrt))
assert.Equal(t, rtCrt.Spec, crt.Spec)
wg.Done()
})
}
}()
}
for i := 0; i < numJobs; i++ {
jobs <- i
}
close(jobs)
wg.Wait()
}
// This test ensures that when a Certificate object is serialized in
// preparation for a Certificate status Apply call. Only the required
// metadata/type fields are present, and only empty spec fields are set. We
@ -66,6 +107,11 @@ func Test_serializeApplyStatus(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, expEmpty, string(crtData))
// Test round trip serializing Certificate preserved the status.
var rtCrt cmapi.Certificate
assert.NoError(t, json.Unmarshal(crtData, &rtCrt))
assert.Equal(t, rtCrt.Status, crt.Status)
wg.Done()
})
}

View File

@ -9,6 +9,8 @@ go_library(
importpath = "github.com/cert-manager/cert-manager/pkg/controller/certificate-shim",
visibility = ["//visibility:public"],
deps = [
"//internal/controller/certificates:go_default_library",
"//internal/controller/feature:go_default_library",
"//internal/ingress:go_default_library",
"//pkg/api/util:go_default_library",
"//pkg/apis/acme/v1:go_default_library",
@ -18,6 +20,7 @@ go_library(
"//pkg/client/listers/certmanager/v1:go_default_library",
"//pkg/controller:go_default_library",
"//pkg/logs:go_default_library",
"//pkg/util/feature:go_default_library",
"@com_github_go_logr_logr//:go_default_library",
"@io_k8s_api//core/v1:go_default_library",
"@io_k8s_api//networking/v1:go_default_library",

View File

@ -55,7 +55,7 @@ type controller struct {
func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) {
c.gatewayLister = ctx.GWShared.Gateway().V1alpha2().Gateways().Lister()
log := logf.FromContext(ctx.RootContext, ControllerName)
c.sync = shimhelper.SyncFnFor(ctx.Recorder, log, ctx.CMClient, ctx.SharedInformerFactory.Certmanager().V1().Certificates().Lister(), ctx.IngressShimOptions)
c.sync = shimhelper.SyncFnFor(ctx.Recorder, log, ctx.CMClient, ctx.SharedInformerFactory.Certmanager().V1().Certificates().Lister(), ctx.IngressShimOptions, ctx.FieldManager)
// We don't need to requeue Gateways on "Deleted" events, since our Sync
// function does nothing when the Gateway lister returns "not found". But we

View File

@ -53,7 +53,7 @@ func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitin
c.ingressLister = internalIngressLister
log := logf.FromContext(ctx.RootContext, ControllerName)
c.sync = shimhelper.SyncFnFor(ctx.Recorder, log, ctx.CMClient, cmShared.Certmanager().V1().Certificates().Lister(), ctx.IngressShimOptions)
c.sync = shimhelper.SyncFnFor(ctx.Recorder, log, ctx.CMClient, cmShared.Certmanager().V1().Certificates().Lister(), ctx.IngressShimOptions, ctx.FieldManager)
queue := workqueue.NewNamedRateLimitingQueue(controllerpkg.DefaultItemBasedRateLimiter(), ControllerName)

View File

@ -37,6 +37,8 @@ import (
"k8s.io/client-go/tools/record"
gwapi "sigs.k8s.io/gateway-api/apis/v1alpha2"
internalcertificates "github.com/cert-manager/cert-manager/internal/controller/certificates"
"github.com/cert-manager/cert-manager/internal/controller/feature"
ingress "github.com/cert-manager/cert-manager/internal/ingress"
cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1"
cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
@ -45,6 +47,7 @@ import (
cmlisters "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1"
"github.com/cert-manager/cert-manager/pkg/controller"
logf "github.com/cert-manager/cert-manager/pkg/logs"
utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature"
)
const (
@ -75,6 +78,7 @@ func SyncFnFor(
cmClient clientset.Interface,
cmLister cmlisters.CertificateLister,
defaults controller.IngressShimOptions,
fieldManager string,
) SyncFn {
return func(ctx context.Context, ingLike metav1.Object) error {
log := logf.WithResource(log, ingLike)
@ -121,7 +125,7 @@ func SyncFnFor(
}
for _, crt := range newCrts {
_, err := cmClient.CertmanagerV1().Certificates(crt.Namespace).Create(ctx, crt, metav1.CreateOptions{})
_, err := cmClient.CertmanagerV1().Certificates(crt.Namespace).Create(ctx, crt, metav1.CreateOptions{FieldManager: fieldManager})
if err != nil {
return err
}
@ -129,10 +133,29 @@ func SyncFnFor(
}
for _, crt := range updateCrts {
_, err := cmClient.CertmanagerV1().Certificates(crt.Namespace).Update(ctx, crt, metav1.UpdateOptions{})
if utilfeature.DefaultFeatureGate.Enabled(feature.ServerSideApply) {
err = internalcertificates.Apply(ctx, cmClient, fieldManager, &cmapi.Certificate{
ObjectMeta: metav1.ObjectMeta{
Name: crt.Name,
Namespace: crt.Namespace,
Labels: crt.Labels,
OwnerReferences: crt.OwnerReferences,
},
Spec: cmapi.CertificateSpec{
DNSNames: crt.Spec.DNSNames,
SecretName: crt.Spec.SecretName,
IssuerRef: crt.Spec.IssuerRef,
Usages: crt.Spec.Usages,
},
})
} else {
_, err = cmClient.CertmanagerV1().Certificates(crt.Namespace).Update(ctx, crt, metav1.UpdateOptions{})
}
if err != nil {
return err
}
rec.Eventf(ingLikeObj, corev1.EventTypeNormal, reasonUpdateCertificate, "Successfully updated Certificate %q", crt.Name)
}

View File

@ -2486,7 +2486,7 @@ func TestSync(t *testing.T) {
DefaultIssuerKind: test.DefaultIssuerKind,
DefaultIssuerGroup: test.DefaultIssuerGroup,
DefaultAutoCertificateAnnotations: []string{"kubernetes.io/tls-acme"},
})
}, "cert-manager-test")
b.Start()
err := sync(context.Background(), test.IngressLike)