2020-03-04 18:56:59 +05:30
package openapi
import (
2020-10-15 17:54:58 -07:00
"context"
2020-03-05 22:50:32 +05:30
"encoding/json"
2020-03-27 19:06:06 +05:30
"fmt"
2020-09-02 16:33:55 +05:30
"strings"
2020-12-23 17:48:00 -08:00
"time"
2020-03-04 18:56:59 +05:30
2020-10-15 17:54:58 -07:00
"github.com/googleapis/gnostic/compiler"
2021-02-07 20:26:56 -08:00
openapiv2 "github.com/googleapis/gnostic/openapiv2"
2022-05-17 16:40:51 +02:00
"github.com/kyverno/kyverno/pkg/dclient"
2022-07-01 08:30:05 +05:30
util "github.com/kyverno/kyverno/pkg/utils"
2022-05-17 07:56:48 +02:00
"github.com/pkg/errors"
2021-02-07 20:26:56 -08:00
"gopkg.in/yaml.v3"
2022-05-17 16:14:31 +02:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2020-10-15 17:54:58 -07:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
runtimeSchema "k8s.io/apimachinery/pkg/runtime/schema"
2020-03-04 18:56:59 +05:30
"k8s.io/apimachinery/pkg/util/wait"
2022-07-01 08:30:05 +05:30
"k8s.io/client-go/discovery"
2020-10-15 17:54:58 -07:00
"sigs.k8s.io/controller-runtime/pkg/log"
2020-03-04 18:56:59 +05:30
)
type crdSync struct {
2022-05-17 16:40:51 +02:00
client dclient . Interface
2020-03-27 19:06:06 +05:30
controller * Controller
2020-03-04 18:56:59 +05:30
}
2022-07-01 08:30:05 +05:30
const (
skipErrorMsg = "Got empty response for"
)
2020-05-27 19:51:34 -07:00
// crdDefinitionPrior represents CRDs version prior to 1.16
2020-05-15 16:57:26 -07:00
var crdDefinitionPrior struct {
Spec struct {
Names struct {
Kind string ` json:"kind" `
} ` json:"names" `
Validation struct {
OpenAPIV3Schema interface { } ` json:"openAPIV3Schema" `
} ` json:"validation" `
} ` json:"spec" `
}
2020-05-27 19:51:34 -07:00
// crdDefinitionNew represents CRDs version 1.16+
2020-05-15 16:57:26 -07:00
var crdDefinitionNew struct {
Spec struct {
Names struct {
Kind string ` json:"kind" `
} ` json:"names" `
Versions [ ] struct {
Schema struct {
OpenAPIV3Schema interface { } ` json:"openAPIV3Schema" `
} ` json:"schema" `
Storage bool ` json:"storage" `
} ` json:"versions" `
} ` json:"spec" `
}
2020-11-17 13:07:30 -08:00
// NewCRDSync ...
2022-05-17 16:40:51 +02:00
func NewCRDSync ( client dclient . Interface , controller * Controller ) * crdSync {
2020-03-27 19:06:06 +05:30
if controller == nil {
panic ( fmt . Errorf ( "nil controller sent into crd sync" ) )
}
2020-03-04 18:56:59 +05:30
return & crdSync {
2020-03-27 19:06:06 +05:30
controller : controller ,
client : client ,
2020-03-04 18:56:59 +05:30
}
}
func ( c * crdSync ) Run ( workers int , stopCh <- chan struct { } ) {
2021-05-13 12:03:13 -07:00
if err := c . updateInClusterKindToAPIVersions ( ) ; err != nil {
log . Log . Error ( err , "failed to update in-cluster api versions" )
}
2022-07-01 08:30:05 +05:30
newDoc , err := c . client . Discovery ( ) . OpenAPISchema ( )
2020-03-05 22:50:32 +05:30
if err != nil {
2020-05-27 19:51:34 -07:00
log . Log . Error ( err , "cannot get OpenAPI schema" )
2020-03-05 22:50:32 +05:30
}
2020-11-17 13:07:30 -08:00
err = c . controller . useOpenAPIDocument ( newDoc )
2020-03-05 22:50:32 +05:30
if err != nil {
2020-05-27 19:51:34 -07:00
log . Log . Error ( err , "Could not set custom OpenAPI document" )
2020-03-05 22:50:32 +05:30
}
2020-03-25 02:20:04 +05:30
// Sync CRD before kyverno starts
c . sync ( )
2020-03-04 18:56:59 +05:30
for i := 0 ; i < workers ; i ++ {
2021-11-22 19:22:45 +05:30
go wait . Until ( c . sync , 15 * time . Second , stopCh )
2020-03-04 18:56:59 +05:30
}
}
2020-03-05 22:50:32 +05:30
func ( c * crdSync ) sync ( ) {
2022-05-03 07:30:04 +02:00
c . client . Discovery ( ) . DiscoveryCache ( ) . Invalidate ( )
2020-04-02 12:19:32 +05:30
crds , err := c . client . GetDynamicInterface ( ) . Resource ( runtimeSchema . GroupVersionResource {
Group : "apiextensions.k8s.io" ,
2021-05-13 12:03:13 -07:00
Version : "v1" ,
2020-04-02 12:19:32 +05:30
Resource : "customresourcedefinitions" ,
2022-05-17 16:14:31 +02:00
} ) . List ( context . TODO ( ) , metav1 . ListOptions { } )
2020-03-04 18:56:59 +05:30
if err != nil {
2020-03-20 11:43:21 -07:00
log . Log . Error ( err , "could not fetch crd's from server" )
2020-03-05 22:50:32 +05:30
return
2020-03-04 18:56:59 +05:30
}
2020-03-27 19:06:06 +05:30
c . controller . deleteCRDFromPreviousSync ( )
2020-03-06 01:09:38 +05:30
2020-03-05 22:50:32 +05:30
for _ , crd := range crds . Items {
2020-08-30 20:43:05 +05:30
c . controller . ParseCRD ( crd )
2020-03-06 01:09:38 +05:30
}
2021-05-13 12:03:13 -07:00
if err := c . updateInClusterKindToAPIVersions ( ) ; err != nil {
log . Log . Error ( err , "sync failed, unable to update in-cluster api versions" )
}
2021-11-22 19:22:45 +05:30
2022-07-01 08:30:05 +05:30
newDoc , err := c . client . Discovery ( ) . OpenAPISchema ( )
2021-11-22 19:22:45 +05:30
if err != nil {
log . Log . Error ( err , "cannot get OpenAPI schema" )
}
err = c . controller . useOpenAPIDocument ( newDoc )
if err != nil {
log . Log . Error ( err , "Could not set custom OpenAPI document" )
}
2021-05-13 12:03:13 -07:00
}
func ( c * crdSync ) updateInClusterKindToAPIVersions ( ) error {
2022-07-01 08:30:05 +05:30
util . OverrideRuntimeErrorHandler ( )
_ , apiResourceLists , err := discovery . ServerGroupsAndResources ( c . client . Discovery ( ) . DiscoveryInterface ( ) )
if err != nil && ! strings . Contains ( err . Error ( ) , skipErrorMsg ) {
2021-11-22 18:27:51 +05:30
return errors . Wrapf ( err , "fetching API server groups and resources" )
2021-05-13 12:03:13 -07:00
}
2022-07-01 08:30:05 +05:30
preferredAPIResourcesLists , err := discovery . ServerPreferredResources ( c . client . Discovery ( ) . DiscoveryInterface ( ) )
if err != nil && ! strings . Contains ( err . Error ( ) , skipErrorMsg ) {
2021-11-22 18:27:51 +05:30
return errors . Wrapf ( err , "fetching API server preferreds resources" )
2021-05-13 12:03:13 -07:00
}
c . controller . updateKindToAPIVersions ( apiResourceLists , preferredAPIResourcesLists )
return nil
2020-03-06 01:09:38 +05:30
}
2020-03-27 19:06:06 +05:30
func ( o * Controller ) deleteCRDFromPreviousSync ( ) {
2020-06-01 19:36:01 -07:00
for _ , crd := range o . crdList {
2021-05-13 12:03:13 -07:00
o . gvkToDefinitionName . Remove ( crd )
2021-01-04 23:17:17 -08:00
o . definitions . Remove ( crd )
2020-05-27 19:51:34 -07:00
}
2020-06-01 19:36:01 -07:00
o . crdList = make ( [ ] string , 0 )
2020-03-06 01:09:38 +05:30
}
2020-11-17 13:07:30 -08:00
// ParseCRD loads CRD to the cache
2020-08-30 20:43:05 +05:30
func ( o * Controller ) ParseCRD ( crd unstructured . Unstructured ) {
2020-04-03 18:29:21 +05:30
var err error
2020-03-27 19:06:06 +05:30
2020-03-06 01:09:38 +05:30
crdRaw , _ := json . Marshal ( crd . Object )
2020-05-15 16:57:26 -07:00
_ = json . Unmarshal ( crdRaw , & crdDefinitionPrior )
openV3schema := crdDefinitionPrior . Spec . Validation . OpenAPIV3Schema
crdName := crdDefinitionPrior . Spec . Names . Kind
if openV3schema == nil {
_ = json . Unmarshal ( crdRaw , & crdDefinitionNew )
for _ , crdVersion := range crdDefinitionNew . Spec . Versions {
if crdVersion . Storage {
openV3schema = crdVersion . Schema . OpenAPIV3Schema
crdName = crdDefinitionNew . Spec . Names . Kind
break
}
}
}
2020-03-06 01:09:38 +05:30
2020-05-18 21:02:21 -07:00
if openV3schema == nil {
2020-11-05 23:14:58 +05:30
log . Log . V ( 4 ) . Info ( "skip adding schema, CRD has no properties" , "name" , crdName )
2020-05-18 21:02:21 -07:00
return
}
2020-05-15 16:57:26 -07:00
schemaRaw , _ := json . Marshal ( openV3schema )
2020-04-02 09:09:49 +05:30
if len ( schemaRaw ) < 1 {
2022-01-14 12:08:04 -08:00
log . Log . V ( 4 ) . Info ( "failed to parse crd schema" , "name" , crdName )
2020-04-02 09:09:49 +05:30
return
}
2022-01-14 12:08:04 -08:00
schemaRaw , err = addingDefaultFieldsToSchema ( crdName , schemaRaw )
2020-04-03 18:29:21 +05:30
if err != nil {
2022-01-14 12:08:04 -08:00
log . Log . Error ( err , "failed to parse crd schema" , "name" , crdName )
2020-04-03 18:29:21 +05:30
return
}
2020-05-15 16:57:26 -07:00
2021-02-07 20:26:56 -08:00
var schema yaml . Node
2020-03-06 01:09:38 +05:30
_ = yaml . Unmarshal ( schemaRaw , & schema )
2021-02-07 20:26:56 -08:00
parsedSchema , err := openapiv2 . NewSchema ( & schema , compiler . NewContext ( "schema" , & schema , nil ) )
2020-03-06 01:09:38 +05:30
if err != nil {
2020-09-03 09:47:43 +05:30
v3valueFound := isOpenV3Error ( err )
2022-05-10 11:24:27 +02:00
if ! v3valueFound {
2022-01-14 12:08:04 -08:00
log . Log . Error ( err , "failed to parse crd schema" , "name" , crdName )
2020-09-03 09:47:43 +05:30
}
2020-09-04 19:42:29 +05:30
return
2020-03-06 01:09:38 +05:30
}
2020-06-01 19:36:01 -07:00
o . crdList = append ( o . crdList , crdName )
2021-05-13 12:03:13 -07:00
o . gvkToDefinitionName . Set ( crdName , crdName )
2021-01-04 23:17:17 -08:00
o . definitions . Set ( crdName , parsedSchema )
2020-03-04 18:56:59 +05:30
}
2020-03-24 19:08:46 +05:30
2020-09-03 09:47:43 +05:30
func isOpenV3Error ( err error ) bool {
2020-09-02 16:33:55 +05:30
unsupportedValues := [ ] string { "anyOf" , "allOf" , "not" }
v3valueFound := false
for _ , value := range unsupportedValues {
if ! strings . Contains ( err . Error ( ) , fmt . Sprintf ( "has invalid property: %s" , value ) ) {
v3valueFound = true
break
}
}
2020-09-03 09:47:43 +05:30
return v3valueFound
2020-09-02 16:33:55 +05:30
}
2020-03-24 19:08:46 +05:30
// addingDefaultFieldsToSchema will add any default missing fields like apiVersion, metadata
2022-01-14 12:08:04 -08:00
func addingDefaultFieldsToSchema ( crdName string , schemaRaw [ ] byte ) ( [ ] byte , error ) {
2020-03-24 19:08:46 +05:30
var schema struct {
Properties map [ string ] interface { } ` json:"properties" `
}
_ = json . Unmarshal ( schemaRaw , & schema )
2020-04-03 18:29:21 +05:30
if len ( schema . Properties ) < 1 {
2022-04-28 17:51:06 +08:00
log . Log . V ( 6 ) . Info ( "crd schema has no properties" , "name" , crdName )
2022-01-14 12:08:04 -08:00
return schemaRaw , nil
2020-04-03 18:29:21 +05:30
}
2020-04-02 09:09:49 +05:30
if schema . Properties [ "apiVersion" ] == nil {
2020-03-24 19:08:46 +05:30
apiVersionDefRaw := ` { "description":"APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources","type":"string"} `
apiVersionDef := make ( map [ string ] interface { } )
_ = json . Unmarshal ( [ ] byte ( apiVersionDefRaw ) , & apiVersionDef )
schema . Properties [ "apiVersion" ] = apiVersionDef
}
if schema . Properties [ "metadata" ] == nil {
metadataDefRaw := ` { "$ref":"#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta","description":"Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata"} `
metadataDef := make ( map [ string ] interface { } )
_ = json . Unmarshal ( [ ] byte ( metadataDefRaw ) , & metadataDef )
schema . Properties [ "metadata" ] = metadataDef
}
schemaWithDefaultFields , _ := json . Marshal ( schema )
2020-04-03 18:29:21 +05:30
return schemaWithDefaultFields , nil
2020-03-24 19:08:46 +05:30
}