2020-01-07 10:33:28 -08:00
package generate
import (
2020-11-09 11:26:12 -08:00
"context"
2020-01-07 10:33:28 -08:00
"fmt"
"time"
backoff "github.com/cenkalti/backoff"
2020-12-15 04:22:13 +05:30
"github.com/gardener/controller-manager-library/pkg/logger"
2020-03-17 11:05:20 -07:00
"github.com/go-logr/logr"
2020-10-07 11:12:31 -07:00
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned"
2020-12-15 04:22:13 +05:30
kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1"
kyvernolister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
2020-10-07 11:12:31 -07:00
"github.com/kyverno/kyverno/pkg/config"
2020-08-31 23:55:13 +05:30
"k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2020-12-15 04:22:13 +05:30
"k8s.io/apimachinery/pkg/labels"
2020-01-07 10:33:28 -08:00
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
2020-12-15 04:22:13 +05:30
"k8s.io/client-go/tools/cache"
2020-01-07 10:33:28 -08:00
)
2020-11-17 13:07:30 -08:00
// GenerateRequests provides interface to manage generate requests
2020-01-07 10:33:28 -08:00
type GenerateRequests interface {
2020-07-02 03:20:49 +05:30
Apply ( gr kyverno . GenerateRequestSpec , action v1beta1 . Operation ) error
2020-06-22 18:49:43 -07:00
}
2020-11-17 13:07:30 -08:00
// GeneratorChannel ...
2020-06-22 18:49:43 -07:00
type GeneratorChannel struct {
2020-07-02 03:20:49 +05:30
spec kyverno . GenerateRequestSpec
2020-06-22 18:49:43 -07:00
action v1beta1 . Operation
2020-01-07 10:33:28 -08:00
}
2020-12-14 02:43:16 -08:00
// Generator defines the implementation to mange generate request resource
2020-01-07 10:33:28 -08:00
type Generator struct {
2020-01-24 12:05:53 -08:00
// channel to receive request
2020-06-22 18:49:43 -07:00
ch chan GeneratorChannel
2020-01-07 10:33:28 -08:00
client * kyvernoclient . Clientset
stopCh <- chan struct { }
2020-03-17 11:05:20 -07:00
log logr . Logger
2020-12-15 04:22:13 +05:30
// grLister can list/get generate request from the shared informer's store
grLister kyvernolister . GenerateRequestNamespaceLister
grSynced cache . InformerSynced
2020-01-07 10:33:28 -08:00
}
2020-11-17 13:07:30 -08:00
// NewGenerator returns a new instance of Generate-Request resource generator
2020-12-15 04:22:13 +05:30
func NewGenerator ( client * kyvernoclient . Clientset , grInformer kyvernoinformer . GenerateRequestInformer , stopCh <- chan struct { } , log logr . Logger ) * Generator {
2020-01-07 10:33:28 -08:00
gen := & Generator {
2020-12-15 04:22:13 +05:30
ch : make ( chan GeneratorChannel , 1000 ) ,
client : client ,
stopCh : stopCh ,
log : log ,
grLister : grInformer . Lister ( ) . GenerateRequests ( config . KyvernoNamespace ) ,
grSynced : grInformer . Informer ( ) . HasSynced ,
2020-01-07 10:33:28 -08:00
}
return gen
}
2020-12-14 02:43:16 -08:00
// Apply creates generate request resource (blocking call if channel is full)
2020-07-02 03:20:49 +05:30
func ( g * Generator ) Apply ( gr kyverno . GenerateRequestSpec , action v1beta1 . Operation ) error {
2020-03-17 11:05:20 -07:00
logger := g . log
logger . V ( 4 ) . Info ( "creating Generate Request" , "request" , gr )
2020-12-14 02:43:16 -08:00
2020-11-30 11:22:20 -08:00
// Update to channel
2020-06-22 18:49:43 -07:00
message := GeneratorChannel {
action : action ,
2020-07-02 03:20:49 +05:30
spec : gr ,
2020-06-22 18:49:43 -07:00
}
2020-12-14 02:43:16 -08:00
2020-01-07 10:33:28 -08:00
select {
2020-06-22 18:49:43 -07:00
case g . ch <- message :
2020-01-07 10:33:28 -08:00
return nil
case <- g . stopCh :
2020-03-17 11:05:20 -07:00
logger . Info ( "shutting down channel" )
2020-01-07 10:33:28 -08:00
return fmt . Errorf ( "shutting down gr create channel" )
}
}
// Run starts the generate request spec
2020-12-15 04:22:13 +05:30
func ( g * Generator ) Run ( workers int , stopCh <- chan struct { } ) {
2020-03-17 11:05:20 -07:00
logger := g . log
2020-01-07 10:33:28 -08:00
defer utilruntime . HandleCrash ( )
2020-12-14 02:43:16 -08:00
2020-03-17 11:05:20 -07:00
logger . V ( 4 ) . Info ( "starting" )
2020-01-07 10:33:28 -08:00
defer func ( ) {
2020-03-17 11:05:20 -07:00
logger . V ( 4 ) . Info ( "shutting down" )
2020-01-07 10:33:28 -08:00
} ( )
2020-12-14 02:43:16 -08:00
2020-12-15 04:22:13 +05:30
if ! cache . WaitForCacheSync ( stopCh , g . grSynced ) {
logger . Info ( "failed to sync informer cache" )
return
}
2020-01-07 10:33:28 -08:00
for i := 0 ; i < workers ; i ++ {
2020-12-23 17:48:00 -08:00
go wait . Until ( g . processApply , time . Second , g . stopCh )
2020-01-07 10:33:28 -08:00
}
2020-12-14 02:43:16 -08:00
2020-01-07 10:33:28 -08:00
<- g . stopCh
}
2020-06-22 18:49:43 -07:00
func ( g * Generator ) processApply ( ) {
2020-03-17 11:05:20 -07:00
logger := g . log
2020-01-07 10:33:28 -08:00
for r := range g . ch {
2020-11-17 12:01:01 -08:00
logger . V ( 4 ) . Info ( "received generate request" , "request" , r )
2020-07-02 03:20:49 +05:30
if err := g . generate ( r . spec , r . action ) ; err != nil {
2020-03-17 11:05:20 -07:00
logger . Error ( err , "failed to generate request CR" )
2020-01-07 10:33:28 -08:00
}
}
}
2020-07-02 03:20:49 +05:30
func ( g * Generator ) generate ( grSpec kyverno . GenerateRequestSpec , action v1beta1 . Operation ) error {
2020-06-22 18:49:43 -07:00
// create/update a generate request
2020-09-04 03:04:23 +05:30
2020-12-15 04:22:13 +05:30
if err := retryApplyResource ( g . client , grSpec , g . log , action , g . grLister ) ; err != nil {
2020-01-07 10:33:28 -08:00
return err
}
return nil
}
2020-01-24 12:05:53 -08:00
// -> receiving channel to take requests to create request
2020-01-07 10:33:28 -08:00
// use worker pattern to read and create the CR resource
2020-12-14 16:08:01 -08:00
func retryApplyResource ( client * kyvernoclient . Clientset , grSpec kyverno . GenerateRequestSpec ,
log logr . Logger , action v1beta1 . Operation , grLister kyvernolister . GenerateRequestNamespaceLister ) error {
2020-01-07 10:33:28 -08:00
var i int
var err error
2020-06-22 18:49:43 -07:00
applyResource := func ( ) error {
2020-01-07 10:33:28 -08:00
gr := kyverno . GenerateRequest {
2020-07-02 03:20:49 +05:30
Spec : grSpec ,
2020-01-07 10:33:28 -08:00
}
2020-08-31 23:55:13 +05:30
2020-11-26 16:07:06 -08:00
gr . SetNamespace ( config . KyvernoNamespace )
2020-01-07 10:33:28 -08:00
// Initial state "Pending"
// TODO: status is not updated
// gr.Status.State = kyverno.Pending
// generate requests created in kyverno namespace
2020-08-31 23:55:13 +05:30
isExist := false
if action == v1beta1 . Create || action == v1beta1 . Update {
2020-12-01 12:30:08 -08:00
log . V ( 4 ) . Info ( "querying all generate requests" )
2020-12-15 04:22:13 +05:30
selector := labels . SelectorFromSet ( labels . Set ( map [ string ] string {
2020-12-23 12:20:29 +05:30
"generate.kyverno.io/policy-name" : grSpec . Policy ,
2020-12-23 13:06:48 +05:30
"generate.kyverno.io/resource-name" : grSpec . Resource . Name ,
2020-12-23 12:20:29 +05:30
"generate.kyverno.io/resource-kind" : grSpec . Resource . Kind ,
"generate.kyverno.io/resource-namespace" : grSpec . Resource . Namespace ,
2020-12-15 04:22:13 +05:30
} ) )
grList , err := grLister . List ( selector )
2020-08-31 23:55:13 +05:30
if err != nil {
2020-12-15 04:22:13 +05:30
logger . Error ( err , "failed to get generate request for the resource" , "kind" , grSpec . Resource . Kind , "name" , grSpec . Resource . Name , "namespace" , grSpec . Resource . Namespace )
2020-08-31 23:55:13 +05:30
return err
}
2020-12-01 12:30:08 -08:00
2020-12-15 04:22:13 +05:30
for _ , v := range grList {
2020-12-29 16:36:43 +05:30
grLabels := gr . Labels
if grLabels == nil || len ( grLabels ) == 0 {
grLabels = make ( map [ string ] string )
}
grLabels [ "resources-update" ] = "true"
gr . SetLabels ( grLabels )
2020-12-23 12:20:29 +05:30
v . Spec . Context = gr . Spec . Context
v . Spec . Policy = gr . Spec . Policy
v . Spec . Resource = gr . Spec . Resource
2020-12-23 13:06:48 +05:30
2020-12-23 12:20:29 +05:30
_ , err = client . KyvernoV1 ( ) . GenerateRequests ( config . KyvernoNamespace ) . Update ( context . TODO ( ) , v , metav1 . UpdateOptions { } )
if err != nil {
return err
2020-08-31 23:55:13 +05:30
}
2020-12-23 12:20:29 +05:30
isExist = true
2020-08-31 23:55:13 +05:30
}
2020-12-23 12:20:29 +05:30
2020-08-31 23:55:13 +05:30
if ! isExist {
gr . SetGenerateName ( "gr-" )
2020-12-15 04:22:13 +05:30
gr . SetLabels ( map [ string ] string {
2020-12-23 12:20:29 +05:30
"generate.kyverno.io/policy-name" : grSpec . Policy ,
2020-12-23 13:06:48 +05:30
"generate.kyverno.io/resource-name" : grSpec . Resource . Name ,
2020-12-23 12:20:29 +05:30
"generate.kyverno.io/resource-kind" : grSpec . Resource . Kind ,
"generate.kyverno.io/resource-namespace" : grSpec . Resource . Namespace ,
2020-12-15 04:22:13 +05:30
} )
2020-11-26 16:07:06 -08:00
_ , err = client . KyvernoV1 ( ) . GenerateRequests ( config . KyvernoNamespace ) . Create ( context . TODO ( ) , & gr , metav1 . CreateOptions { } )
2020-08-31 23:55:13 +05:30
if err != nil {
return err
}
}
2020-06-22 18:49:43 -07:00
}
log . V ( 4 ) . Info ( "retrying update generate request CR" , "retryCount" , i , "name" , gr . GetGenerateName ( ) , "namespace" , gr . GetNamespace ( ) )
2020-01-07 10:33:28 -08:00
i ++
return err
}
2020-06-22 18:49:43 -07:00
2020-01-07 10:33:28 -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 ( )
2020-06-22 18:49:43 -07:00
err = backoff . Retry ( applyResource , exbackoff )
2020-01-07 10:33:28 -08:00
if err != nil {
return err
}
return nil
}