2022-04-25 20:20:40 +08:00
package updaterequest
import (
"context"
"time"
backoff "github.com/cenkalti/backoff"
2022-05-17 13:12:43 +02:00
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
2022-05-24 09:41:12 +02:00
"github.com/kyverno/kyverno/pkg/background/common"
2022-04-25 20:20:40 +08:00
kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned"
2022-05-18 06:02:31 +02:00
kyvernov1beta1informers "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1beta1"
kyvernov1beta1listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1beta1"
2022-04-25 20:20:40 +08:00
"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"
"k8s.io/client-go/tools/cache"
)
2022-05-23 16:39:12 +02:00
// Generator provides interface to manage update requests
type Generator 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
}
2022-05-23 16:39:12 +02:00
// generator defines the implementation to manage update request resource
type generator struct {
// clients
2022-05-02 22:30:07 +02:00
client kyvernoclient . Interface
2022-04-25 20:20:40 +08:00
2022-05-23 16:39:12 +02:00
// listers
2022-05-18 06:02:31 +02:00
urLister kyvernov1beta1listers . UpdateRequestNamespaceLister
2022-04-25 20:20:40 +08:00
}
// NewGenerator returns a new instance of UpdateRequest resource generator
2022-05-23 16:39:12 +02:00
func NewGenerator ( client kyvernoclient . Interface , urInformer kyvernov1beta1informers . UpdateRequestInformer ) Generator {
return & generator {
2022-04-25 20:20:40 +08:00
client : client ,
2022-05-11 08:14:30 +02:00
urLister : urInformer . Lister ( ) . UpdateRequests ( config . KyvernoNamespace ( ) ) ,
2022-04-25 20:20:40 +08:00
}
}
// Apply creates update request resource
2022-05-23 16:39:12 +02:00
func ( g * generator ) Apply ( ur kyvernov1beta1 . UpdateRequestSpec , action admissionv1 . Operation ) error {
2022-04-29 19:05:49 +08:00
logger . V ( 4 ) . Info ( "reconcile Update Request" , "request" , ur )
2022-05-23 16:39:12 +02:00
if action == admissionv1 . Delete && ur . Type == kyvernov1beta1 . Generate {
return nil
}
_ , policyName , err := cache . SplitMetaNamespaceKey ( ur . Policy )
if err != nil {
return err
2022-04-25 20:20:40 +08:00
}
2022-05-23 16:39:12 +02:00
go g . applyResource ( policyName , ur )
2022-04-25 20:20:40 +08:00
return nil
}
2022-05-23 16:39:12 +02:00
func ( g * generator ) applyResource ( policyName string , urSpec kyvernov1beta1 . UpdateRequestSpec ) {
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 ( )
if err := backoff . Retry ( func ( ) error { return g . tryApplyResource ( policyName , urSpec ) } , exbackoff ) ; err != nil {
2022-04-25 20:20:40 +08:00
logger . Error ( err , "failed to update request CR" )
}
}
2022-05-23 16:39:12 +02:00
func ( g * generator ) tryApplyResource ( policyName string , urSpec kyvernov1beta1 . UpdateRequestSpec ) error {
2022-05-24 18:58:45 +02:00
l := logger . WithValues ( "ruleType" , urSpec . Type , "kind" , urSpec . Resource . Kind , "name" , urSpec . Resource . Name , "namespace" , urSpec . Resource . Namespace )
2022-05-24 09:41:12 +02:00
var queryLabels labels . Set
2022-05-24 18:58:45 +02:00
if urSpec . Type == kyvernov1beta1 . Mutate {
queryLabels = common . MutateLabelsSet ( urSpec . Policy , urSpec . Resource )
} else if urSpec . Type == kyvernov1beta1 . Generate {
queryLabels = common . GenerateLabelsSet ( urSpec . Policy , urSpec . Resource )
2022-04-25 20:20:40 +08:00
}
2022-05-23 16:39:12 +02:00
urList , err := g . urLister . List ( labels . SelectorFromSet ( queryLabels ) )
2022-04-25 20:20:40 +08:00
if err != nil {
2022-05-24 18:58:45 +02:00
l . 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
}
2022-05-23 16:39:12 +02:00
for _ , v := range urList {
2022-05-24 18:58:45 +02:00
l := l . WithValues ( "name" , v . GetName ( ) )
l . V ( 4 ) . Info ( "updating existing update request" )
if _ , err := common . Update ( g . client , g . urLister , v . GetName ( ) , func ( ur * kyvernov1beta1 . UpdateRequest ) {
v . Spec = urSpec
} ) ; err != nil {
l . V ( 4 ) . Error ( err , "failed to update UpdateRequest" )
2022-04-25 20:20:40 +08:00
return err
2022-05-23 16:39:12 +02:00
} else {
2022-05-24 18:58:45 +02:00
l . V ( 4 ) . Info ( "successfully updated UpdateRequest" )
2022-04-25 20:20:40 +08:00
}
2022-05-24 18:58:45 +02:00
if _ , err := common . UpdateStatus ( g . client , g . urLister , v . GetName ( ) , kyvernov1beta1 . Pending , "" , nil ) ; err != nil {
l . V ( 4 ) . Error ( err , "failed to update UpdateRequest status" )
2022-05-23 16:39:12 +02:00
return err
2022-04-25 20:20:40 +08:00
}
}
2022-05-24 18:58:45 +02:00
if len ( urList ) == 0 {
l . V ( 4 ) . Info ( "creating new UpdateRequest" )
ur := kyvernov1beta1 . UpdateRequest {
ObjectMeta : metav1 . ObjectMeta {
Namespace : config . KyvernoNamespace ( ) ,
GenerateName : "ur-" ,
Labels : queryLabels ,
} ,
Spec : urSpec ,
}
2022-05-24 18:31:56 +02:00
if new , err := g . client . KyvernoV1beta1 ( ) . UpdateRequests ( config . KyvernoNamespace ( ) ) . Create ( context . TODO ( ) , & ur , metav1 . CreateOptions { } ) ; err != nil {
2022-05-24 18:58:45 +02:00
l . V ( 4 ) . Error ( err , "failed to create UpdateRequest, retrying" , "name" , ur . GetGenerateName ( ) , "namespace" , ur . GetNamespace ( ) )
2022-05-23 16:39:12 +02:00
return err
} else {
2022-05-24 18:58:45 +02:00
l . V ( 4 ) . Info ( "successfully created UpdateRequest" , "name" , new . GetName ( ) , "namespace" , ur . GetNamespace ( ) )
2022-05-23 16:39:12 +02:00
}
2022-04-25 20:20:40 +08:00
}
return nil
}