2020-01-07 10:33:28 -08:00
package generate
import (
2020-02-04 12:13:41 -08:00
"encoding/json"
2020-01-07 10:33:28 -08:00
"fmt"
2020-06-22 18:49:43 -07:00
"reflect"
2020-03-04 13:11:48 +05:30
"time"
2020-01-07 10:33:28 -08:00
2020-03-17 11:05:20 -07:00
"github.com/go-logr/logr"
2020-01-07 10:33:28 -08:00
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
dclient "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/engine/context"
"github.com/nirmata/kyverno/pkg/engine/validate"
"github.com/nirmata/kyverno/pkg/engine/variables"
2020-01-09 17:53:27 -08:00
apierrors "k8s.io/apimachinery/pkg/api/errors"
2020-01-07 10:33:28 -08:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func ( c * Controller ) processGR ( gr * kyverno . GenerateRequest ) error {
2020-03-17 11:05:20 -07:00
logger := c . log . WithValues ( "name" , gr . Name , "policy" , gr . Spec . Policy , "kind" , gr . Spec . Resource . Kind , "namespace" , gr . Spec . Resource . Namespace , "name" , gr . Spec . Resource . Name )
2020-01-07 15:13:57 -08:00
var err error
var resource * unstructured . Unstructured
var genResources [ ] kyverno . ResourceSpec
2020-01-07 10:33:28 -08:00
// 1 - Check if the resource exists
2020-01-07 15:13:57 -08:00
resource , err = getResource ( c . client , gr . Spec . Resource )
2020-01-07 10:33:28 -08:00
if err != nil {
// Dont update status
2020-03-17 11:05:20 -07:00
logger . Error ( err , "resource does not exist or is yet to be created, requeueing" )
2020-01-07 10:33:28 -08:00
return err
}
// 2 - Apply the generate policy on the resource
2020-01-07 15:13:57 -08:00
genResources , err = c . applyGenerate ( * resource , * gr )
2020-01-07 10:33:28 -08:00
// 3 - Report Events
2020-03-17 11:05:20 -07:00
reportEvents ( logger , err , c . eventGen , * gr , * resource )
2020-01-07 10:33:28 -08:00
// 4 - Update Status
2020-01-07 15:13:57 -08:00
return updateStatus ( c . statusControl , * gr , err , genResources )
2020-01-07 10:33:28 -08:00
}
2020-01-07 15:13:57 -08:00
func ( c * Controller ) applyGenerate ( resource unstructured . Unstructured , gr kyverno . GenerateRequest ) ( [ ] kyverno . ResourceSpec , error ) {
2020-03-17 11:05:20 -07:00
logger := c . log . WithValues ( "name" , gr . Name , "policy" , gr . Spec . Policy , "kind" , gr . Spec . Resource . Kind , "namespace" , gr . Spec . Resource . Namespace , "name" , gr . Spec . Resource . Name )
2020-01-07 10:33:28 -08:00
// Get the list of rules to be applied
// get policy
policy , err := c . pLister . Get ( gr . Spec . Policy )
if err != nil {
2020-03-17 11:05:20 -07:00
logger . Error ( err , "policy not found" )
2020-01-07 15:13:57 -08:00
return nil , nil
2020-01-07 10:33:28 -08:00
}
// build context
ctx := context . NewContext ( )
resourceRaw , err := resource . MarshalJSON ( )
if err != nil {
2020-03-17 11:05:20 -07:00
logger . Error ( err , "failed to marshal resource" )
2020-01-07 15:13:57 -08:00
return nil , err
2020-01-07 10:33:28 -08:00
}
2020-01-24 09:37:12 -08:00
err = ctx . AddResource ( resourceRaw )
if err != nil {
2020-03-17 11:05:20 -07:00
logger . Error ( err , "failed to load resource in context" )
2020-01-24 09:37:12 -08:00
return nil , err
}
err = ctx . AddUserInfo ( gr . Spec . Context . UserRequestInfo )
if err != nil {
2020-03-17 11:05:20 -07:00
logger . Error ( err , "failed to load SA in context" )
2020-01-24 09:37:12 -08:00
return nil , err
}
err = ctx . AddSA ( gr . Spec . Context . UserRequestInfo . AdmissionUserInfo . Username )
if err != nil {
2020-03-17 11:05:20 -07:00
logger . Error ( err , "failed to load UserInfo in context" )
2020-01-24 09:37:12 -08:00
return nil , err
}
2020-01-07 10:33:28 -08:00
policyContext := engine . PolicyContext {
NewResource : resource ,
Policy : * policy ,
Context : ctx ,
AdmissionInfo : gr . Spec . Context . UserRequestInfo ,
}
// check if the policy still applies to the resource
2020-01-24 12:05:53 -08:00
engineResponse := engine . Generate ( policyContext )
2020-01-07 10:33:28 -08:00
if len ( engineResponse . PolicyResponse . Rules ) == 0 {
2020-03-17 11:05:20 -07:00
logger . V ( 4 ) . Info ( "policy does not apply to resource" )
2020-01-07 15:13:57 -08:00
return nil , fmt . Errorf ( "policy %s, dont not apply to resource %v" , gr . Spec . Policy , gr . Spec . Resource )
2020-01-07 10:33:28 -08:00
}
2020-01-07 15:13:57 -08:00
2020-01-07 10:33:28 -08:00
// Apply the generate rule on resource
2020-03-17 17:23:18 -07:00
return c . applyGeneratePolicy ( logger , policyContext , gr )
2020-01-07 10:33:28 -08:00
}
2020-01-07 15:13:57 -08:00
func updateStatus ( statusControl StatusControlInterface , gr kyverno . GenerateRequest , err error , genResources [ ] kyverno . ResourceSpec ) error {
2020-01-07 10:33:28 -08:00
if err != nil {
2020-01-07 15:13:57 -08:00
return statusControl . Failed ( gr , err . Error ( ) , genResources )
2020-01-07 10:33:28 -08:00
}
// Generate request successfully processed
2020-01-07 15:13:57 -08:00
return statusControl . Success ( gr , genResources )
2020-01-07 10:33:28 -08:00
}
2020-03-17 17:23:18 -07:00
func ( c * Controller ) applyGeneratePolicy ( log logr . Logger , policyContext engine . PolicyContext , gr kyverno . GenerateRequest ) ( [ ] kyverno . ResourceSpec , error ) {
2020-01-07 15:13:57 -08:00
// List of generatedResources
var genResources [ ] kyverno . ResourceSpec
2020-01-07 10:33:28 -08:00
// Get the response as the actions to be performed on the resource
// - - substitute values
policy := policyContext . Policy
resource := policyContext . NewResource
ctx := policyContext . Context
// To manage existing resources, we compare the creation time for the default resiruce to be generated and policy creation time
processExisting := func ( ) bool {
rcreationTime := resource . GetCreationTimestamp ( )
pcreationTime := policy . GetCreationTimestamp ( )
return rcreationTime . Before ( & pcreationTime )
} ( )
2020-03-04 13:11:48 +05:30
ruleNameToProcessingTime := make ( map [ string ] time . Duration )
2020-01-07 10:33:28 -08:00
for _ , rule := range policy . Spec . Rules {
if ! rule . HasGenerate ( ) {
continue
}
2020-03-04 13:11:48 +05:30
startTime := time . Now ( )
2020-03-17 17:23:18 -07:00
genResource , err := applyRule ( log , c . client , rule , resource , ctx , processExisting )
2020-01-07 15:13:57 -08:00
if err != nil {
return nil , err
2020-01-07 10:33:28 -08:00
}
2020-03-04 13:11:48 +05:30
ruleNameToProcessingTime [ rule . Name ] = time . Since ( startTime )
2020-01-07 15:13:57 -08:00
genResources = append ( genResources , genResource )
2020-01-07 10:33:28 -08:00
}
2020-03-04 13:11:48 +05:30
if gr . Status . State == "" {
2020-03-07 12:53:37 +05:30
c . policyStatusListener . Send ( generateSyncStats {
2020-03-04 13:35:49 +05:30
policyName : policy . Name ,
ruleNameToProcessingTime : ruleNameToProcessingTime ,
2020-03-07 12:53:37 +05:30
} )
2020-03-04 13:11:48 +05:30
}
2020-01-07 15:13:57 -08:00
return genResources , nil
2020-01-07 10:33:28 -08:00
}
2020-03-04 13:11:48 +05:30
type generateSyncStats struct {
policyName string
ruleNameToProcessingTime map [ string ] time . Duration
}
2020-03-04 15:45:20 +05:30
func ( vc generateSyncStats ) PolicyName ( ) string {
return vc . policyName
}
func ( vc generateSyncStats ) UpdateStatus ( status kyverno . PolicyStatus ) kyverno . PolicyStatus {
2020-03-04 13:11:48 +05:30
for i := range status . Rules {
if executionTime , exist := vc . ruleNameToProcessingTime [ status . Rules [ i ] . Name ] ; exist {
status . ResourcesGeneratedCount += 1
status . Rules [ i ] . ResourcesGeneratedCount += 1
averageOver := int64 ( status . Rules [ i ] . AppliedCount + status . Rules [ i ] . FailedCount )
status . Rules [ i ] . ExecutionTime = updateGenerateExecutionTime (
executionTime ,
status . Rules [ i ] . ExecutionTime ,
averageOver ,
) . String ( )
}
}
2020-03-04 15:45:20 +05:30
return status
2020-03-04 13:11:48 +05:30
}
func updateGenerateExecutionTime ( newTime time . Duration , oldAverageTimeString string , averageOver int64 ) time . Duration {
if averageOver == 0 {
return newTime
}
oldAverageExecutionTime , _ := time . ParseDuration ( oldAverageTimeString )
numerator := ( oldAverageExecutionTime . Nanoseconds ( ) * averageOver ) + newTime . Nanoseconds ( )
denominator := averageOver
newAverageTimeInNanoSeconds := numerator / denominator
return time . Duration ( newAverageTimeInNanoSeconds ) * time . Nanosecond
}
2020-03-17 11:05:20 -07:00
func applyRule ( log logr . Logger , client * dclient . Client , rule kyverno . Rule , resource unstructured . Unstructured , ctx context . EvalInterface , processExisting bool ) ( kyverno . ResourceSpec , error ) {
2020-01-07 10:33:28 -08:00
var rdata map [ string ] interface { }
var err error
2020-02-04 12:13:41 -08:00
var mode ResourceMode
2020-01-07 15:13:57 -08:00
var noGenResource kyverno . ResourceSpec
2020-02-13 13:57:48 -08:00
// convert to unstructured Resource
genUnst , err := getUnstrRule ( rule . Generation . DeepCopy ( ) )
if err != nil {
return noGenResource , err
}
// Variable substitutions
// format : {{<variable_name}}
// - if there is variables that are not defined the context -> results in error and rule is not applied
// - valid variables are replaced with the values
2020-04-09 22:21:59 +05:30
object , err := variables . SubstituteVars ( log , ctx , genUnst . Object )
if err != nil {
2020-02-13 13:57:48 -08:00
return noGenResource , err
}
2020-04-09 22:21:59 +05:30
genUnst . Object , _ = object . ( map [ string ] interface { } )
2020-02-13 13:57:48 -08:00
genKind , _ , err := unstructured . NestedString ( genUnst . Object , "kind" )
if err != nil {
return noGenResource , err
}
genName , _ , err := unstructured . NestedString ( genUnst . Object , "name" )
if err != nil {
return noGenResource , err
}
genNamespace , _ , err := unstructured . NestedString ( genUnst . Object , "namespace" )
if err != nil {
return noGenResource , err
2020-01-10 11:59:05 -08:00
}
2020-01-07 15:13:57 -08:00
// Resource to be generated
newGenResource := kyverno . ResourceSpec {
2020-02-13 13:57:48 -08:00
Kind : genKind ,
Namespace : genNamespace ,
Name : genName ,
2020-01-07 10:33:28 -08:00
}
2020-02-13 13:57:48 -08:00
genData , _ , err := unstructured . NestedMap ( genUnst . Object , "data" )
if err != nil {
return noGenResource , err
}
genCopy , _ , err := unstructured . NestedMap ( genUnst . Object , "clone" )
if err != nil {
return noGenResource , err
}
if genData != nil {
2020-03-17 11:05:20 -07:00
rdata , mode , err = manageData ( log , genKind , genNamespace , genName , genData , client , resource )
2020-02-13 13:57:48 -08:00
} else {
2020-03-17 11:05:20 -07:00
rdata , mode , err = manageClone ( log , genKind , genNamespace , genName , genCopy , client , resource )
2020-02-13 13:57:48 -08:00
}
2020-02-14 11:59:28 -08:00
if err != nil {
return noGenResource , err
}
2020-02-13 13:57:48 -08:00
if rdata == nil {
// existing resource contains the configuration
return newGenResource , nil
2020-01-07 10:33:28 -08:00
}
if processExisting {
// handle existing resources
// policy was generated after the resource
// we do not create new resource
2020-01-07 15:13:57 -08:00
return noGenResource , err
2020-06-22 18:49:43 -07:00
2020-01-07 10:33:28 -08:00
}
2020-02-04 12:13:41 -08:00
// build the resource template
2020-01-07 10:33:28 -08:00
newResource := & unstructured . Unstructured { }
newResource . SetUnstructuredContent ( rdata )
2020-02-13 13:57:48 -08:00
newResource . SetName ( genName )
newResource . SetNamespace ( genNamespace )
2020-05-16 21:29:23 -07:00
if newResource . GetKind ( ) == "" {
newResource . SetKind ( genKind )
}
2020-01-07 10:33:28 -08:00
2020-02-10 12:44:20 -08:00
// manage labels
// - app.kubernetes.io/managed-by: kyverno
// - kyverno.io/generated-by: kind/namespace/name (trigger resource)
manageLabels ( newResource , resource )
2020-03-17 11:05:20 -07:00
logger := log . WithValues ( "genKind" , genKind , "genNamespace" , genNamespace , "genName" , genName )
2020-02-04 12:13:41 -08:00
if mode == Create {
// Reset resource version
newResource . SetResourceVersion ( "" )
// Create the resource
2020-03-17 11:05:20 -07:00
logger . V ( 4 ) . Info ( "creating new resource" )
2020-02-13 13:57:48 -08:00
_ , err = client . CreateResource ( genKind , genNamespace , newResource , false )
2020-02-04 12:13:41 -08:00
if err != nil {
// Failed to create resource
return noGenResource , err
}
2020-03-17 11:05:20 -07:00
logger . V ( 4 ) . Info ( "created new resource" )
2020-02-04 12:13:41 -08:00
} else if mode == Update {
2020-06-22 18:49:43 -07:00
if rule . Generation . Synchronize {
logger . V ( 4 ) . Info ( "updating existing resource" )
// Update the resource
_ , err := client . UpdateResource ( genKind , genNamespace , newResource , false )
if err != nil {
// Failed to update resource
return noGenResource , err
}
logger . V ( 4 ) . Info ( "updated new resource" )
} else {
logger . V ( 4 ) . Info ( "Synchronize resource is disabled" )
2020-02-04 12:13:41 -08:00
}
2020-01-07 10:33:28 -08:00
}
2020-02-04 12:13:41 -08:00
2020-01-07 15:13:57 -08:00
return newGenResource , nil
2020-01-07 10:33:28 -08:00
}
2020-03-17 11:05:20 -07:00
func manageData ( log logr . Logger , kind , namespace , name string , data map [ string ] interface { } , client * dclient . Client , resource unstructured . Unstructured ) ( map [ string ] interface { } , ResourceMode , error ) {
2020-02-13 13:57:48 -08:00
// check if resource to be generated exists
obj , err := client . GetResource ( kind , namespace , name )
2020-01-09 17:53:27 -08:00
if apierrors . IsNotFound ( err ) {
2020-03-17 11:05:20 -07:00
log . Error ( err , "resource does not exist, will try to create" , "genKind" , kind , "genNamespace" , namespace , "genName" , name )
2020-02-13 13:57:48 -08:00
return data , Create , nil
2020-01-07 10:33:28 -08:00
}
if err != nil {
//something wrong while fetching resource
2020-02-13 13:57:48 -08:00
// client-errors
2020-02-04 12:13:41 -08:00
return nil , Skip , err
2020-01-07 10:33:28 -08:00
}
// Resource exists; verfiy the content of the resource
2020-03-17 11:05:20 -07:00
err = checkResource ( log , data , obj )
2020-02-13 13:57:48 -08:00
if err == nil {
// Existing resource does contain the mentioned configuration in spec, skip processing the resource as it is already in expected state
return nil , Skip , nil
2020-01-07 10:33:28 -08:00
}
2020-03-17 11:05:20 -07:00
log . Info ( "to be generated resoruce already exists, but is missing the specifeid configurations, will try to update" , "genKind" , kind , "genNamespace" , namespace , "genName" , name )
2020-02-13 13:57:48 -08:00
return data , Update , nil
2020-02-04 12:13:41 -08:00
2020-01-07 10:33:28 -08:00
}
2020-03-17 11:05:20 -07:00
func manageClone ( log logr . Logger , kind , namespace , name string , clone map [ string ] interface { } , client * dclient . Client , resource unstructured . Unstructured ) ( map [ string ] interface { } , ResourceMode , error ) {
2020-02-13 13:57:48 -08:00
newRNs , _ , err := unstructured . NestedString ( clone , "namespace" )
if err != nil {
return nil , Skip , err
2020-01-07 10:33:28 -08:00
}
2020-02-13 13:57:48 -08:00
newRName , _ , err := unstructured . NestedString ( clone , "name" )
if err != nil {
return nil , Skip , err
}
// Short-circuit if the resource to be generated and the clone is the same
if newRNs == namespace && newRName == name {
// attempting to clone it self, this will fail -> short-ciruit it
return nil , Skip , nil
}
2020-06-22 18:49:43 -07:00
2020-02-13 13:57:48 -08:00
// check if the resource as reference in clone exists?
obj , err := client . GetResource ( kind , newRNs , newRName )
2020-01-07 10:33:28 -08:00
if err != nil {
2020-02-14 11:59:28 -08:00
return nil , Skip , fmt . Errorf ( "reference clone resource %s/%s/%s not found. %v" , kind , newRNs , newRName , err )
2020-01-07 10:33:28 -08:00
}
2020-06-22 18:49:43 -07:00
// check if resource to be generated exists
newResource , err := client . GetResource ( kind , namespace , name )
if err == nil {
obj . SetUID ( newResource . GetUID ( ) )
obj . SetSelfLink ( newResource . GetSelfLink ( ) )
obj . SetCreationTimestamp ( newResource . GetCreationTimestamp ( ) )
obj . SetManagedFields ( newResource . GetManagedFields ( ) )
obj . SetResourceVersion ( newResource . GetResourceVersion ( ) )
if reflect . DeepEqual ( obj , newResource ) {
return nil , Skip , nil
}
return obj . UnstructuredContent ( ) , Update , nil
}
//TODO: check this
if ! apierrors . IsNotFound ( err ) {
log . Error ( err , "reference/clone resource is not found" , "genKind" , kind , "genNamespace" , namespace , "genName" , name )
//something wrong while fetching resource
return nil , Skip , err
}
2020-02-04 12:13:41 -08:00
// create the resource based on the reference clone
return obj . UnstructuredContent ( ) , Create , nil
2020-02-13 13:57:48 -08:00
2020-01-07 10:33:28 -08:00
}
2020-02-13 13:57:48 -08:00
// ResourceMode defines the mode for generated resource
type ResourceMode string
const (
//Skip : failed to process rule, will not update the resource
Skip ResourceMode = "SKIP"
//Create : create a new resource
Create = "CREATE"
//Update : update/overwrite the new resource
Update = "UPDATE"
)
2020-03-17 11:05:20 -07:00
func checkResource ( log logr . Logger , newResourceSpec interface { } , resource * unstructured . Unstructured ) error {
2020-01-07 10:33:28 -08:00
// check if the resource spec if a subset of the resource
2020-03-17 11:05:20 -07:00
if path , err := validate . ValidateResourceWithPattern ( log , resource . Object , newResourceSpec ) ; err != nil {
log . Error ( err , "Failed to match the resource " , "path" , path )
2020-02-13 13:57:48 -08:00
return err
2020-01-07 10:33:28 -08:00
}
2020-02-13 13:57:48 -08:00
return nil
2020-01-07 10:33:28 -08:00
}
2020-02-13 13:57:48 -08:00
func getUnstrRule ( rule * kyverno . Generation ) ( * unstructured . Unstructured , error ) {
ruleData , err := json . Marshal ( rule )
if err != nil {
return nil , err
}
return ConvertToUnstructured ( ruleData )
}
2020-01-07 10:33:28 -08:00
2020-02-13 13:57:48 -08:00
//ConvertToUnstructured converts the resource to unstructured format
func ConvertToUnstructured ( data [ ] byte ) ( * unstructured . Unstructured , error ) {
resource := & unstructured . Unstructured { }
err := resource . UnmarshalJSON ( data )
if err != nil {
return nil , err
2020-01-07 10:33:28 -08:00
}
2020-02-13 13:57:48 -08:00
return resource , nil
2020-01-07 10:33:28 -08:00
}