2020-03-04 18:56:59 +05:30
package openapi
import (
2020-03-05 22:50:32 +05:30
"encoding/json"
2020-03-27 19:06:06 +05:30
"fmt"
2020-03-04 18:56:59 +05:30
"time"
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-03-20 11:43:21 -07:00
log "sigs.k8s.io/controller-runtime/pkg/log"
2020-03-05 22:50:32 +05:30
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-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-03-20 11:43:21 -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-03-20 11:43:21 -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-03-05 22:50:32 +05:30
go wait . Until ( c . sync , time . Second * 10 , stopCh )
2020-03-04 18:56:59 +05:30
}
}
2020-03-05 22:50:32 +05:30
func ( c * crdSync ) sync ( ) {
crds , err := c . client . ListResource ( "CustomResourceDefinition" , "" , nil )
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 ( ) {
for _ , crd := range o . crdList {
delete ( o . kindToDefinitionName , crd )
delete ( o . definitions , crd )
2020-03-06 01:09:38 +05:30
}
2020-03-27 19:06:06 +05:30
o . crdList = [ ] string { }
2020-03-06 01:09:38 +05:30
}
2020-03-27 19:06:06 +05:30
func ( o * Controller ) parseCRD ( crd unstructured . Unstructured ) {
var crdDefinition struct {
Spec struct {
Names struct {
Kind string ` json:"kind" `
} ` json:"names" `
Versions [ ] struct {
Schema struct {
OpenAPIV3Schema interface { } ` json:"openAPIV3Schema" `
} ` json:"schema" `
} ` json:"versions" `
} ` json:"spec" `
}
2020-03-06 01:09:38 +05:30
crdRaw , _ := json . Marshal ( crd . Object )
_ = json . Unmarshal ( crdRaw , & crdDefinition )
crdName := crdDefinition . Spec . Names . Kind
if len ( crdDefinition . Spec . Versions ) < 1 {
2020-03-20 11:43:21 -07:00
log . Log . V ( 4 ) . Info ( "could not parse crd schema, no versions present" )
2020-03-06 01:09:38 +05:30
return
2020-03-04 18:56:59 +05:30
}
2020-03-06 01:09:38 +05:30
var schema yaml . MapSlice
schemaRaw , _ := json . Marshal ( crdDefinition . Spec . Versions [ 0 ] . Schema . OpenAPIV3Schema )
2020-03-24 19:08:46 +05:30
schemaRaw = addingDefaultFieldsToSchema ( schemaRaw )
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-03-20 11:43:21 -07:00
log . Log . Error ( err , "could not parse crd schema:" )
2020-03-06 01:09:38 +05:30
return
}
2020-03-27 19:06:06 +05:30
o . crdList = append ( o . crdList , crdName )
2020-03-06 01:09:38 +05:30
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
func addingDefaultFieldsToSchema ( schemaRaw [ ] byte ) [ ] byte {
var schema struct {
Properties map [ string ] interface { } ` json:"properties" `
}
_ = json . Unmarshal ( schemaRaw , & schema )
if schema . Properties [ "apiVersion" ] == nil {
2020-04-01 12:41:37 -07:00
schema . Properties = make ( map [ string ] interface { } )
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 )
return schemaWithDefaultFields
}