зеркало из https://github.com/Azure/ARO-RP.git
240 строки
7.9 KiB
Go
240 строки
7.9 KiB
Go
package dynamichelper
|
|
|
|
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the Apache License 2.0.
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
kruntime "k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/client-go/kubernetes/scheme"
|
|
|
|
"github.com/Azure/ARO-RP/pkg/util/cmp"
|
|
_ "github.com/Azure/ARO-RP/pkg/util/scheme"
|
|
)
|
|
|
|
// the UnstructuredObj related stuff is specifically for the Guardrails
|
|
// to handle the gatekeeper Constraint as it does not have a scheme that can be imported
|
|
func (dh *dynamicHelper) ensureUnstructuredObj(ctx context.Context, uns *unstructured.Unstructured) error {
|
|
gvr, err := dh.Resolve(uns.GroupVersionKind().GroupKind().String(), uns.GroupVersionKind().Version)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
create := false
|
|
obj, err := dh.dynamicClient.Resource(*gvr).Namespace(uns.GetNamespace()).Get(ctx, uns.GetName(), metav1.GetOptions{})
|
|
if err != nil {
|
|
if !notFound(err) {
|
|
return err
|
|
}
|
|
create = true
|
|
}
|
|
if create {
|
|
dh.log.Infof("Create %s", keyFunc(uns.GroupVersionKind().GroupKind(), uns.GetNamespace(), uns.GetName()))
|
|
if _, err = dh.dynamicClient.Resource(*gvr).Namespace(uns.GetNamespace()).Create(ctx, uns, metav1.CreateOptions{}); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
enNew, err := GetEnforcementAction(uns)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
enOld, err := GetEnforcementAction(obj)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
if strings.EqualFold(enOld, enNew) {
|
|
// currently EnforcementAction is the only part that may change in an update
|
|
return nil
|
|
}
|
|
dh.log.Infof("Update %s: enforcementAction: %s->%s", keyFunc(uns.GroupVersionKind().GroupKind(), uns.GetNamespace(), uns.GetName()), enOld, enNew)
|
|
uns.SetResourceVersion(obj.GetResourceVersion())
|
|
|
|
if _, err = dh.dynamicClient.Resource(*gvr).Namespace(uns.GetNamespace()).Update(ctx, uns, metav1.UpdateOptions{}); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func GetEnforcementAction(obj *unstructured.Unstructured) (string, error) {
|
|
name := obj.GetName()
|
|
ns := obj.GetNamespace()
|
|
field, ok := obj.Object["spec"]
|
|
if !ok {
|
|
return "", fmt.Errorf("%s/%s: get spec failed", ns, name)
|
|
}
|
|
spec, ok := field.(map[string]interface{})
|
|
if !ok {
|
|
return "", fmt.Errorf("%s/%s: spec: %T is not map", ns, name, field)
|
|
}
|
|
|
|
field, ok = spec["enforcementAction"]
|
|
if !ok {
|
|
return "", fmt.Errorf("%s/%s: get enforcementAction failed", ns, name)
|
|
}
|
|
enforce, ok := field.(string)
|
|
if !ok {
|
|
return "", fmt.Errorf("%s/%s: enforcementAction: %T is not string", ns, name, field)
|
|
}
|
|
|
|
return enforce, nil
|
|
}
|
|
|
|
func (dh *dynamicHelper) deleteUnstructuredObj(ctx context.Context, groupKind, namespace, name string) error {
|
|
gvr, err := dh.Resolve(groupKind, "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
uns, err := dh.dynamicClient.Resource(*gvr).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
|
|
if kerrors.IsNotFound(err) {
|
|
return nil
|
|
}
|
|
if err != nil || uns == nil {
|
|
return err
|
|
}
|
|
if err = dh.dynamicClient.Resource(*gvr).Namespace(namespace).Delete(ctx, name, metav1.DeleteOptions{}); !(err == nil || notFound(err)) {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func notFound(err error) bool {
|
|
if err == nil || kerrors.IsNotFound(err) || strings.Contains(strings.ToLower(err.Error()), "notfound") {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// mergeGK takes the existing (old) and desired (new) objects. It checks the
|
|
// the interested fields in the *new* object to see if an update is necessary,
|
|
// fixes up the *old* object if needed, and returns the difference for
|
|
// debugging purposes. The reason for using *old* as basis is that the *old*
|
|
// object are changed by gatekeeper binaries and the changes must be kept.
|
|
func mergeGK(old, new kruntime.Object) (kruntime.Object, bool, string, error) {
|
|
if reflect.TypeOf(old) != reflect.TypeOf(new) {
|
|
return nil, false, "", fmt.Errorf("types differ: %T %T", old, new)
|
|
}
|
|
expected := old.DeepCopyObject()
|
|
|
|
// 1. Set defaults on old. This gets rid of many false positive diffs.
|
|
scheme.Scheme.Default(expected)
|
|
|
|
// 2. Do fix-ups on a per-Kind basis.
|
|
hasChanged := false
|
|
switch new.(type) {
|
|
case *appsv1.Deployment:
|
|
hasChanged = handleDeployment(new, expected)
|
|
case *admissionregistrationv1.ValidatingWebhookConfiguration:
|
|
hasChanged = handleValidatingWebhook(new, expected)
|
|
case *admissionregistrationv1.MutatingWebhookConfiguration:
|
|
hasChanged = handleMutatingWebhook(new, expected)
|
|
}
|
|
var diff string
|
|
if _, ok := expected.(*corev1.Secret); !ok { // Don't show a diff if kind is Secret
|
|
diff = cmp.Diff(old, expected)
|
|
}
|
|
return expected, hasChanged, diff, nil
|
|
}
|
|
|
|
func handleDeployment(new, expected kruntime.Object) bool {
|
|
hasChanged := false
|
|
newDeployment, expectedDeployment := new.(*appsv1.Deployment), expected.(*appsv1.Deployment)
|
|
for i := range expectedDeployment.Spec.Template.Spec.Containers {
|
|
ec := &expectedDeployment.Spec.Template.Spec.Containers[i]
|
|
nc := &newDeployment.Spec.Template.Spec.Containers[i]
|
|
if ec.Image != nc.Image {
|
|
ec.Image = nc.Image
|
|
hasChanged = true
|
|
}
|
|
if cmpAndCopy(&nc.Resources.Limits, &ec.Resources.Limits) {
|
|
hasChanged = true
|
|
}
|
|
if cmpAndCopy(&nc.Resources.Requests, &ec.Resources.Requests) {
|
|
hasChanged = true
|
|
}
|
|
}
|
|
return hasChanged
|
|
}
|
|
|
|
func handleValidatingWebhook(new, expected kruntime.Object) bool {
|
|
hasChanged := false
|
|
newWebhook := new.(*admissionregistrationv1.ValidatingWebhookConfiguration)
|
|
expectedWebhook := expected.(*admissionregistrationv1.ValidatingWebhookConfiguration)
|
|
for i := range expectedWebhook.Webhooks {
|
|
if expectedWebhook.Webhooks[i].FailurePolicy != nil &&
|
|
newWebhook.Webhooks[i].FailurePolicy != nil &&
|
|
*expectedWebhook.Webhooks[i].FailurePolicy != *newWebhook.Webhooks[i].FailurePolicy {
|
|
*expectedWebhook.Webhooks[i].FailurePolicy = *newWebhook.Webhooks[i].FailurePolicy
|
|
hasChanged = true
|
|
}
|
|
if expectedWebhook.Webhooks[i].TimeoutSeconds != nil &&
|
|
newWebhook.Webhooks[i].TimeoutSeconds != nil &&
|
|
*expectedWebhook.Webhooks[i].TimeoutSeconds != *newWebhook.Webhooks[i].TimeoutSeconds {
|
|
*expectedWebhook.Webhooks[i].TimeoutSeconds = *newWebhook.Webhooks[i].TimeoutSeconds
|
|
hasChanged = true
|
|
}
|
|
}
|
|
return hasChanged
|
|
}
|
|
|
|
func handleMutatingWebhook(new, expected kruntime.Object) bool {
|
|
hasChanged := false
|
|
newWebhook := new.(*admissionregistrationv1.MutatingWebhookConfiguration)
|
|
expectedWebhook := expected.(*admissionregistrationv1.MutatingWebhookConfiguration)
|
|
for i := range expectedWebhook.Webhooks {
|
|
if expectedWebhook.Webhooks[i].FailurePolicy != nil &&
|
|
newWebhook.Webhooks[i].FailurePolicy != nil &&
|
|
*expectedWebhook.Webhooks[i].FailurePolicy != *newWebhook.Webhooks[i].FailurePolicy {
|
|
*expectedWebhook.Webhooks[i].FailurePolicy = *newWebhook.Webhooks[i].FailurePolicy
|
|
hasChanged = true
|
|
}
|
|
if expectedWebhook.Webhooks[i].TimeoutSeconds != nil &&
|
|
newWebhook.Webhooks[i].TimeoutSeconds != nil &&
|
|
*expectedWebhook.Webhooks[i].TimeoutSeconds != *newWebhook.Webhooks[i].TimeoutSeconds {
|
|
*expectedWebhook.Webhooks[i].TimeoutSeconds = *newWebhook.Webhooks[i].TimeoutSeconds
|
|
hasChanged = true
|
|
}
|
|
}
|
|
return hasChanged
|
|
}
|
|
|
|
func cmpAndCopy(srcPtr, dstPtr *corev1.ResourceList) bool {
|
|
src, dst := *srcPtr, *dstPtr
|
|
hasChanged := false
|
|
for key, val := range dst {
|
|
if !val.Equal(src[key]) {
|
|
dst[key] = src[key].DeepCopy()
|
|
hasChanged = true
|
|
}
|
|
}
|
|
return hasChanged
|
|
}
|
|
|
|
func (dh *dynamicHelper) IsConstraintTemplateReady(ctx context.Context, name string) (bool, error) {
|
|
gvr := schema.GroupVersionResource{Group: "templates.gatekeeper.sh", Version: "v1beta1", Resource: "constrainttemplates"}
|
|
ct, err := dh.dynamicClient.Resource(gvr).Get(ctx, name, metav1.GetOptions{})
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
status, ok := ct.Object["status"].(map[string]interface{})
|
|
if !ok {
|
|
return false, nil
|
|
}
|
|
created, ok := status["created"].(bool)
|
|
if !ok {
|
|
return false, nil
|
|
}
|
|
return created, nil
|
|
}
|