2020-03-04 18:56:59 +05:30
package openapi
import (
2020-03-05 22:50:32 +05:30
"encoding/json"
2020-04-03 18:29:21 +05:30
"errors"
2020-03-27 19:06:06 +05:30
"fmt"
2020-03-04 18:56:59 +05:30
2020-04-02 12:19:32 +05:30
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtimeSchema "k8s.io/apimachinery/pkg/runtime/schema"
2020-03-06 01:09:38 +05:30
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2020-03-05 22:50:32 +05:30
"gopkg.in/yaml.v2"
"github.com/googleapis/gnostic/compiler"
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
2020-05-27 19:51:34 -07:00
"sigs.k8s.io/controller-runtime/pkg/log"
2020-03-05 22:50:32 +05:30
2020-05-16 22:15:09 -07:00
"github.com/nirmata/kyverno/pkg/constant"
2020-03-04 18:56:59 +05:30
client "github.com/nirmata/kyverno/pkg/dclient"
"k8s.io/apimachinery/pkg/util/wait"
)
type crdSync struct {
2020-03-27 19:06:06 +05:30
client * client . Client
controller * Controller
2020-03-04 18:56:59 +05:30
}
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-03-27 19:06:06 +05:30
func NewCRDSync ( client * client . Client , controller * Controller ) * crdSync {
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 { } ) {
2020-03-05 22:50:32 +05:30
newDoc , err := c . client . DiscoveryClient . OpenAPISchema ( )
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-03-27 19:06:06 +05:30
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 ++ {
2020-05-16 22:15:09 -07:00
go wait . Until ( c . sync , constant . CRDControllerResync , stopCh )
2020-03-04 18:56:59 +05:30
}
}
2020-03-05 22:50:32 +05:30
func ( c * crdSync ) sync ( ) {
2020-04-02 12:19:32 +05:30
crds , err := c . client . GetDynamicInterface ( ) . Resource ( runtimeSchema . GroupVersionResource {
Group : "apiextensions.k8s.io" ,
Version : "v1beta1" ,
Resource : "customresourcedefinitions" ,
} ) . List ( v1 . 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-29 09:09:26 +05:30
c . controller . mutex . Lock ( )
defer c . controller . mutex . Unlock ( )
2020-03-25 02:00:30 +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-03-27 19:06:06 +05:30
c . controller . parseCRD ( crd )
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 {
delete ( o . kindToDefinitionName , crd )
delete ( o . definitions , 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-03-27 19:06:06 +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 {
log . Log . V ( 3 ) . Info ( "skip adding schema, CRD has no properties" , "name" , crdName )
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 {
2020-05-15 16:57:26 -07:00
log . Log . V ( 3 ) . Info ( "could not parse crd schema" , "name" , crdName )
2020-04-02 09:09:49 +05:30
return
}
2020-04-03 18:29:21 +05:30
schemaRaw , err = addingDefaultFieldsToSchema ( schemaRaw )
if err != nil {
2020-05-15 16:57:26 -07:00
log . Log . Error ( err , "could not parse crd schema" , "name" , crdName )
2020-04-03 18:29:21 +05:30
return
}
2020-05-15 16:57:26 -07:00
var schema yaml . MapSlice
2020-03-06 01:09:38 +05:30
_ = yaml . Unmarshal ( schemaRaw , & schema )
parsedSchema , err := openapi_v2 . NewSchema ( schema , compiler . NewContext ( "schema" , nil ) )
if err != nil {
2020-05-15 16:57:26 -07:00
log . Log . Error ( err , "could not parse crd schema" , "name" , crdName )
2020-03-06 01:09:38 +05:30
return
}
2020-06-01 19:36:01 -07:00
o . crdList = append ( o . crdList , crdName )
2020-03-27 19:06:06 +05:30
o . kindToDefinitionName [ crdName ] = crdName
o . definitions [ crdName ] = parsedSchema
2020-03-04 18:56:59 +05:30
}
2020-03-24 19:08:46 +05:30
// addingDefaultFieldsToSchema will add any default missing fields like apiVersion, metadata
2020-04-03 18:29:21 +05:30
func addingDefaultFieldsToSchema ( 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 {
return nil , errors . New ( "crd schema has no properties" )
}
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
}