Skip to content

Commit

Permalink
moved needsUpdate
Browse files Browse the repository at this point in the history
Signed-off-by: Erik Godding Boye <[email protected]>
  • Loading branch information
erikgb committed Jul 7, 2024
1 parent c485e71 commit 8251d2a
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 136 deletions.
2 changes: 1 addition & 1 deletion pkg/bundle/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ func (b *bundle) bundleTargetNamespaceSelector(bundleObj *trustapi.Bundle) (labe
// created by the Update operation.
func (b *bundle) migrateBundleStatusToApply(ctx context.Context, obj client.Object) (bool, error) {
fieldManager := string(operator.FieldManager)
patch, err := csaupgrade.UpgradeManagedFieldsPatch(obj, sets.New(fieldManager, crRegressionFieldManager), fieldManager, csaupgrade.Subresource("status"))
patch, err := csaupgrade.UpgradeManagedFieldsPatch(obj, sets.New(fieldManager, ssa_client.RegressionFieldManager), fieldManager, csaupgrade.Subresource("status"))
if err != nil {
return false, err
}
Expand Down
8 changes: 8 additions & 0 deletions pkg/bundle/internal/ssa_client/patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
// RegressionFieldManager is the field manager that was introduced by a regression in controller-runtime
// version 0.15.0; fixed in 15.1 and 0.16.0: https://github.com/kubernetes-sigs/controller-runtime/pull/2435
// trust-manager 0.6.0 was released with this regression in controller-runtime, which means that we have to
// take extra care when migrating from CSA to SSA.
RegressionFieldManager = "Go-http-client"
)

type applyPatch struct {
patch []byte
}
Expand Down
122 changes: 122 additions & 0 deletions pkg/bundle/internal/target/target.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
package target

import (
"bytes"
"context"
"fmt"
trustapi "github.com/cert-manager/trust-manager/pkg/apis/trust/v1alpha1"
"github.com/go-logr/logr"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
metav1applyconfig "k8s.io/client-go/applyconfigurations/meta/v1"
"k8s.io/client-go/util/csaupgrade"
"k8s.io/utils/ptr"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
"strings"

corev1 "k8s.io/api/core/v1"
coreapplyconfig "k8s.io/client-go/applyconfigurations/core/v1"
Expand Down Expand Up @@ -87,3 +95,117 @@ func NewSecretPatch(name types.NamespacedName, bundle trustapi.Bundle) *coreappl
WithController(true),
)
}

type Kind string

const (
KindConfigMap Kind = "ConfigMap"
KindSecret Kind = "Secret"
)

func (b *Reconciler) NeedsUpdate(ctx context.Context, kind Kind, log logr.Logger, obj *metav1.PartialObjectMetadata, bundle *trustapi.Bundle, dataHash string) (bool, error) {
needsUpdate := false
if !metav1.IsControlledBy(obj, bundle) {
needsUpdate = true
}

if obj.GetLabels()[trustapi.BundleLabelKey] != bundle.Name {
needsUpdate = true
}

if obj.GetAnnotations()[trustapi.BundleHashAnnotationKey] != dataHash {
needsUpdate = true
}

{
var key string
var targetFieldNames []string
switch kind {
case KindConfigMap:
key = bundle.Spec.Target.ConfigMap.Key
targetFieldNames = []string{"data", "binaryData"}
case KindSecret:
key = bundle.Spec.Target.Secret.Key
targetFieldNames = []string{"data"}
default:
return false, fmt.Errorf("unknown targetType: %s", kind)
}

properties, err := listManagedProperties(obj, string(operator.FieldManager), targetFieldNames...)
if err != nil {
return false, fmt.Errorf("failed to list managed properties: %w", err)
}
expectedProperties := sets.New[string](key)
if bundle.Spec.Target.AdditionalFormats != nil && bundle.Spec.Target.AdditionalFormats.JKS != nil {
expectedProperties.Insert(bundle.Spec.Target.AdditionalFormats.JKS.Key)
}
if bundle.Spec.Target.AdditionalFormats != nil && bundle.Spec.Target.AdditionalFormats.PKCS12 != nil {
expectedProperties.Insert(bundle.Spec.Target.AdditionalFormats.PKCS12.Key)
}
if !properties.Equal(expectedProperties) {
needsUpdate = true
}

if kind == KindConfigMap {
if bundle.Spec.Target.ConfigMap != nil {
// Check if we need to migrate the ConfigMap managed fields to the Apply field operation
if didMigrate, err := b.migrateConfigMapToApply(ctx, obj); err != nil {
return false, fmt.Errorf("failed to migrate ConfigMap %s/%s to Apply: %w", obj.Namespace, obj.Name, err)
} else if didMigrate {
log.V(2).Info("migrated configmap from CSA to SSA")
needsUpdate = true
}
}
}
}
return needsUpdate, nil
}

func listManagedProperties(configmap *metav1.PartialObjectMetadata, fieldManager string, fieldNames ...string) (sets.Set[string], error) {
properties := sets.New[string]()

for _, managedField := range configmap.ManagedFields {
// If the managed field isn't owned by the cert-manager controller, ignore.
if managedField.Manager != fieldManager || managedField.FieldsV1 == nil {
continue
}

// Decode the managed field.
var fieldset fieldpath.Set
if err := fieldset.FromJSON(bytes.NewReader(managedField.FieldsV1.Raw)); err != nil {
return nil, err
}

for _, fieldName := range fieldNames {
// Extract the labels and annotations of the managed fields.
configmapData := fieldset.Children.Descend(fieldpath.PathElement{
FieldName: ptr.To(fieldName),
})

// Gather the properties on the managed fields. Remove the '.'
// prefix which appears on managed field keys.
configmapData.Iterate(func(path fieldpath.Path) {
properties.Insert(strings.TrimPrefix(path.String(), "."))
})
}
}

return properties, nil
}

// MIGRATION: This is a migration function that migrates the ownership of
// fields from the Update operation to the Apply operation. This is required
// to ensure that the apply operations will also remove fields that were
// created by the Update operation.
func (b *Reconciler) migrateConfigMapToApply(ctx context.Context, obj client.Object) (bool, error) {
fieldManager := string(operator.FieldManager)
patch, err := csaupgrade.UpgradeManagedFieldsPatch(obj, sets.New(fieldManager, ssa_client.RegressionFieldManager), fieldManager)
if err != nil {
return false, err
}
if patch != nil {
return true, b.Client.Patch(ctx, obj, client.RawPatch(types.JSONPatchType, patch))
}
// No work to be done - already upgraded
return false, nil
}
138 changes: 3 additions & 135 deletions pkg/bundle/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,16 @@ limitations under the License.
package bundle

import (
"bytes"
"context"
"crypto/sha256"
"errors"
"fmt"
trustapi "github.com/cert-manager/trust-manager/pkg/apis/trust/v1alpha1"
"github.com/cert-manager/trust-manager/pkg/bundle/internal/target"
"strings"

"github.com/go-logr/logr"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/util/csaupgrade"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/structured-merge-diff/fieldpath"

trustapi "github.com/cert-manager/trust-manager/pkg/apis/trust/v1alpha1"
"github.com/cert-manager/trust-manager/pkg/operator"
)

const (
// crRegressionFieldManager is the field manager that was introduced by a regression in controller-runtime
// version 0.15.0; fixed in 15.1 and 0.16.0: https://github.com/kubernetes-sigs/controller-runtime/pull/2435
// trust-manager 0.6.0 was released with this regression in controller-runtime, which means that we have to
// take extra care when migrating from CSA to SSA.
crRegressionFieldManager = "Go-http-client"
)

// syncConfigMapTarget syncs the given data to the target ConfigMap in the given namespace.
Expand Down Expand Up @@ -105,7 +87,7 @@ func (b *bundle) syncConfigMapTarget(
// If the ConfigMap doesn't exist, create it.
if !apierrors.IsNotFound(err) {
// Exit early if no update is needed
if exit, err := b.needsUpdate(ctx, targetKindConfigMap, log, targetObj, bundle, dataHash); err != nil {
if exit, err := b.targetReconciler.NeedsUpdate(ctx, target.KindConfigMap, log, targetObj, bundle, dataHash); err != nil {
return false, err
} else if !exit {
return false, nil
Expand Down Expand Up @@ -189,7 +171,7 @@ func (b *bundle) syncSecretTarget(
// If the Secret doesn't exist, create it.
if !apierrors.IsNotFound(err) {
// Exit early if no update is needed
if exit, err := b.needsUpdate(ctx, targetKindSecret, log, targetObj, bundle, dataHash); err != nil {
if exit, err := b.targetReconciler.NeedsUpdate(ctx, target.KindSecret, log, targetObj, bundle, dataHash); err != nil {
return false, err
} else if !exit {
return false, nil
Expand All @@ -210,117 +192,3 @@ func (b *bundle) syncSecretTarget(

return true, nil
}

type targetKind string

const (
targetKindConfigMap targetKind = "ConfigMap"
targetKindSecret targetKind = "Secret"
)

func (b *bundle) needsUpdate(ctx context.Context, kind targetKind, log logr.Logger, obj *metav1.PartialObjectMetadata, bundle *trustapi.Bundle, dataHash string) (bool, error) {
needsUpdate := false
if !metav1.IsControlledBy(obj, bundle) {
needsUpdate = true
}

if obj.GetLabels()[trustapi.BundleLabelKey] != bundle.Name {
needsUpdate = true
}

if obj.GetAnnotations()[trustapi.BundleHashAnnotationKey] != dataHash {
needsUpdate = true
}

{
var key string
var targetFieldNames []string
switch kind {
case targetKindConfigMap:
key = bundle.Spec.Target.ConfigMap.Key
targetFieldNames = []string{"data", "binaryData"}
case targetKindSecret:
key = bundle.Spec.Target.Secret.Key
targetFieldNames = []string{"data"}
default:
return false, fmt.Errorf("unknown targetType: %s", kind)
}

properties, err := listManagedProperties(obj, string(operator.FieldManager), targetFieldNames...)
if err != nil {
return false, fmt.Errorf("failed to list managed properties: %w", err)
}
expectedProperties := sets.New[string](key)
if bundle.Spec.Target.AdditionalFormats != nil && bundle.Spec.Target.AdditionalFormats.JKS != nil {
expectedProperties.Insert(bundle.Spec.Target.AdditionalFormats.JKS.Key)
}
if bundle.Spec.Target.AdditionalFormats != nil && bundle.Spec.Target.AdditionalFormats.PKCS12 != nil {
expectedProperties.Insert(bundle.Spec.Target.AdditionalFormats.PKCS12.Key)
}
if !properties.Equal(expectedProperties) {
needsUpdate = true
}

if kind == targetKindConfigMap {
if bundle.Spec.Target.ConfigMap != nil {
// Check if we need to migrate the ConfigMap managed fields to the Apply field operation
if didMigrate, err := b.migrateConfigMapToApply(ctx, obj); err != nil {
return false, fmt.Errorf("failed to migrate ConfigMap %s/%s to Apply: %w", obj.Namespace, obj.Name, err)
} else if didMigrate {
log.V(2).Info("migrated configmap from CSA to SSA")
needsUpdate = true
}
}
}
}
return needsUpdate, nil
}

func listManagedProperties(configmap *metav1.PartialObjectMetadata, fieldManager string, fieldNames ...string) (sets.Set[string], error) {
properties := sets.New[string]()

for _, managedField := range configmap.ManagedFields {
// If the managed field isn't owned by the cert-manager controller, ignore.
if managedField.Manager != fieldManager || managedField.FieldsV1 == nil {
continue
}

// Decode the managed field.
var fieldset fieldpath.Set
if err := fieldset.FromJSON(bytes.NewReader(managedField.FieldsV1.Raw)); err != nil {
return nil, err
}

for _, fieldName := range fieldNames {
// Extract the labels and annotations of the managed fields.
configmapData := fieldset.Children.Descend(fieldpath.PathElement{
FieldName: ptr.To(fieldName),
})

// Gather the properties on the managed fields. Remove the '.'
// prefix which appears on managed field keys.
configmapData.Iterate(func(path fieldpath.Path) {
properties.Insert(strings.TrimPrefix(path.String(), "."))
})
}
}

return properties, nil
}

// MIGRATION: This is a migration function that migrates the ownership of
// fields from the Update operation to the Apply operation. This is required
// to ensure that the apply operations will also remove fields that were
// created by the Update operation.
func (b *bundle) migrateConfigMapToApply(ctx context.Context, obj client.Object) (bool, error) {
fieldManager := string(operator.FieldManager)
patch, err := csaupgrade.UpgradeManagedFieldsPatch(obj, sets.New(fieldManager, crRegressionFieldManager), fieldManager)
if err != nil {
return false, err
}
if patch != nil {
return true, b.client.Patch(ctx, obj, client.RawPatch(types.JSONPatchType, patch))
}
// No work to be done - already upgraded
return false, nil
}

0 comments on commit 8251d2a

Please sign in to comment.