2022-04-25 20:20:40 +08:00
package updaterequest
import (
"context"
"time"
backoff "github.com/cenkalti/backoff"
"github.com/gardener/controller-manager-library/pkg/logger"
"github.com/go-logr/logr"
2022-05-17 13:12:43 +02:00
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
2022-04-25 20:20:40 +08:00
kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned"
urkyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1beta1"
urkyvernolister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1beta1"
"github.com/kyverno/kyverno/pkg/config"
admissionv1 "k8s.io/api/admission/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/tools/cache"
)
2022-04-29 19:05:49 +08:00
// UpdateRequest provides interface to manage update requests
2022-04-25 20:20:40 +08:00
type Interface interface {
2022-05-17 13:12:43 +02:00
Apply ( gr kyvernov1beta1 . UpdateRequestSpec , action admissionv1 . Operation ) error
2022-04-25 20:20:40 +08:00
}
// info object stores message data to create update request
type info struct {
2022-05-17 13:12:43 +02:00
spec kyvernov1beta1 . UpdateRequestSpec
2022-04-25 20:20:40 +08:00
action admissionv1 . Operation
}
// Generator defines the implementation to mange update request resource
type Generator struct {
2022-05-02 22:30:07 +02:00
client kyvernoclient . Interface
2022-04-25 20:20:40 +08:00
stopCh <- chan struct { }
log logr . Logger
urLister urkyvernolister . UpdateRequestNamespaceLister
}
// NewGenerator returns a new instance of UpdateRequest resource generator
2022-05-02 22:30:07 +02:00
func NewGenerator ( client kyvernoclient . Interface , urInformer urkyvernoinformer . UpdateRequestInformer , stopCh <- chan struct { } , log logr . Logger ) * Generator {
2022-04-25 20:20:40 +08:00
gen := & Generator {
client : client ,
stopCh : stopCh ,
log : log ,
2022-05-11 08:14:30 +02:00
urLister : urInformer . Lister ( ) . UpdateRequests ( config . KyvernoNamespace ( ) ) ,
2022-04-25 20:20:40 +08:00
}
return gen
}
// Apply creates update request resource
2022-05-17 13:12:43 +02:00
func ( g * Generator ) Apply ( ur kyvernov1beta1 . UpdateRequestSpec , action admissionv1 . Operation ) error {
2022-04-25 20:20:40 +08:00
logger := g . log
2022-04-29 19:05:49 +08:00
logger . V ( 4 ) . Info ( "reconcile Update Request" , "request" , ur )
2022-04-25 20:20:40 +08:00
message := info {
action : action ,
2022-04-29 19:05:49 +08:00
spec : ur ,
2022-04-25 20:20:40 +08:00
}
go g . processApply ( message )
return nil
}
// Run starts the update request spec
func ( g * Generator ) Run ( workers int , stopCh <- chan struct { } ) {
logger := g . log
defer utilruntime . HandleCrash ( )
logger . V ( 4 ) . Info ( "starting" )
defer func ( ) {
logger . V ( 4 ) . Info ( "shutting down" )
} ( )
<- g . stopCh
}
func ( g * Generator ) processApply ( i info ) {
if err := g . generate ( i ) ; err != nil {
logger . Error ( err , "failed to update request CR" )
}
}
func ( g * Generator ) generate ( i info ) error {
if err := retryApplyResource ( g . client , i . spec , g . log , i . action , g . urLister ) ; err != nil {
return err
}
return nil
}
2022-05-10 19:01:29 +02:00
func retryApplyResource (
client kyvernoclient . Interface ,
2022-05-17 13:12:43 +02:00
urSpec kyvernov1beta1 . UpdateRequestSpec ,
2022-05-10 19:01:29 +02:00
log logr . Logger ,
action admissionv1 . Operation ,
urLister urkyvernolister . UpdateRequestNamespaceLister ,
) error {
2022-05-17 13:12:43 +02:00
if action == admissionv1 . Delete && urSpec . Type == kyvernov1beta1 . Generate {
2022-04-25 20:20:40 +08:00
return nil
}
var i int
var err error
_ , policyName , err := cache . SplitMetaNamespaceKey ( urSpec . Policy )
if err != nil {
return err
}
applyResource := func ( ) error {
2022-05-17 13:12:43 +02:00
ur := kyvernov1beta1 . UpdateRequest {
2022-04-25 20:20:40 +08:00
Spec : urSpec ,
2022-05-17 13:12:43 +02:00
Status : kyvernov1beta1 . UpdateRequestStatus {
State : kyvernov1beta1 . Pending ,
2022-04-25 20:20:40 +08:00
} ,
}
queryLabels := make ( map [ string ] string )
2022-05-17 13:12:43 +02:00
if ur . Spec . Type == kyvernov1beta1 . Mutate {
2022-04-25 20:20:40 +08:00
queryLabels := map [ string ] string {
2022-05-17 13:12:43 +02:00
kyvernov1beta1 . URMutatePolicyLabel : ur . Spec . Policy ,
2022-04-25 20:20:40 +08:00
"mutate.updaterequest.kyverno.io/trigger-name" : ur . Spec . Resource . Name ,
"mutate.updaterequest.kyverno.io/trigger-namespace" : ur . Spec . Resource . Namespace ,
"mutate.updaterequest.kyverno.io/trigger-kind" : ur . Spec . Resource . Kind ,
}
if ur . Spec . Resource . APIVersion != "" {
queryLabels [ "mutate.updaterequest.kyverno.io/trigger-apiversion" ] = ur . Spec . Resource . APIVersion
}
2022-05-17 13:12:43 +02:00
} else if ur . Spec . Type == kyvernov1beta1 . Generate {
2022-04-25 20:20:40 +08:00
queryLabels = labels . Set ( map [ string ] string {
2022-05-17 13:12:43 +02:00
kyvernov1beta1 . URGeneratePolicyLabel : policyName ,
2022-04-25 20:20:40 +08:00
"generate.kyverno.io/resource-name" : urSpec . Resource . Name ,
"generate.kyverno.io/resource-kind" : urSpec . Resource . Kind ,
"generate.kyverno.io/resource-namespace" : urSpec . Resource . Namespace ,
} )
}
2022-05-11 08:14:30 +02:00
ur . SetNamespace ( config . KyvernoNamespace ( ) )
2022-04-25 20:20:40 +08:00
isExist := false
log . V ( 4 ) . Info ( "apply UpdateRequest" , "ruleType" , ur . Spec . Type )
urList , err := urLister . List ( labels . SelectorFromSet ( queryLabels ) )
if err != nil {
2022-05-05 18:56:27 +08:00
log . Error ( err , "failed to get update request for the resource" , "kind" , urSpec . Resource . Kind , "name" , urSpec . Resource . Name , "namespace" , urSpec . Resource . Namespace )
2022-04-25 20:20:40 +08:00
return err
}
for _ , v := range urList {
log . V ( 4 ) . Info ( "updating existing update request" , "name" , v . GetName ( ) )
v . Spec . Context = ur . Spec . Context
v . Spec . Policy = ur . Spec . Policy
v . Spec . Resource = ur . Spec . Resource
v . Status . Message = ""
2022-05-11 08:14:30 +02:00
new , err := client . KyvernoV1beta1 ( ) . UpdateRequests ( config . KyvernoNamespace ( ) ) . Update ( context . TODO ( ) , v , metav1 . UpdateOptions { } )
2022-04-25 20:20:40 +08:00
if err != nil {
2022-04-27 03:18:24 +08:00
log . V ( 4 ) . Info ( "failed to update UpdateRequest, retrying" , "retryCount" , i , "name" , ur . GetName ( ) , "namespace" , ur . GetNamespace ( ) , "err" , err . Error ( ) )
2022-04-25 20:20:40 +08:00
i ++
2022-04-27 03:18:24 +08:00
return err
2022-04-25 20:20:40 +08:00
} else {
log . V ( 4 ) . Info ( "successfully updated UpdateRequest" , "retryCount" , i , "name" , ur . GetName ( ) , "namespace" , ur . GetNamespace ( ) )
}
2022-05-17 13:12:43 +02:00
new . Status . State = kyvernov1beta1 . Pending
2022-05-11 08:14:30 +02:00
if _ , err := client . KyvernoV1beta1 ( ) . UpdateRequests ( config . KyvernoNamespace ( ) ) . UpdateStatus ( context . TODO ( ) , new , metav1 . UpdateOptions { } ) ; err != nil {
2022-04-25 20:20:40 +08:00
log . Error ( err , "failed to set UpdateRequest state to Pending" )
2022-04-27 03:18:24 +08:00
return err
2022-04-25 20:20:40 +08:00
}
isExist = true
}
if ! isExist {
log . V ( 4 ) . Info ( "creating new UpdateRequest" , "type" , ur . Spec . Type )
ur . SetGenerateName ( "ur-" )
ur . SetLabels ( queryLabels )
2022-05-11 08:14:30 +02:00
new , err := client . KyvernoV1beta1 ( ) . UpdateRequests ( config . KyvernoNamespace ( ) ) . Create ( context . TODO ( ) , & ur , metav1 . CreateOptions { } )
2022-04-25 20:20:40 +08:00
if err != nil {
2022-04-27 03:18:24 +08:00
log . V ( 4 ) . Info ( "failed to create UpdateRequest, retrying" , "retryCount" , i , "name" , ur . GetGenerateName ( ) , "namespace" , ur . GetNamespace ( ) , "err" , err . Error ( ) )
2022-04-25 20:20:40 +08:00
i ++
2022-04-27 03:18:24 +08:00
return err
2022-04-25 20:20:40 +08:00
} else {
log . V ( 4 ) . Info ( "successfully created UpdateRequest" , "retryCount" , i , "name" , new . GetName ( ) , "namespace" , ur . GetNamespace ( ) )
}
2022-05-17 13:12:43 +02:00
new . Status . State = kyvernov1beta1 . Pending
2022-05-11 08:14:30 +02:00
if _ , err := client . KyvernoV1beta1 ( ) . UpdateRequests ( config . KyvernoNamespace ( ) ) . UpdateStatus ( context . TODO ( ) , new , metav1 . UpdateOptions { } ) ; err != nil {
2022-04-25 20:20:40 +08:00
log . Error ( err , "failed to set UpdateRequest state to Pending" )
2022-04-27 03:18:24 +08:00
return err
2022-04-25 20:20:40 +08:00
}
}
2022-04-27 03:18:24 +08:00
return nil
2022-04-25 20:20:40 +08:00
}
exbackoff := & backoff . ExponentialBackOff {
InitialInterval : 500 * time . Millisecond ,
RandomizationFactor : 0.5 ,
Multiplier : 1.5 ,
MaxInterval : time . Second ,
MaxElapsedTime : 3 * time . Second ,
Clock : backoff . SystemClock ,
}
exbackoff . Reset ( )
err = backoff . Retry ( applyResource , exbackoff )
if err != nil {
return err
}
return nil
}