mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-31 03:45:17 +00:00
generation: check existing resources
This commit is contained in:
parent
63c1874016
commit
3fbcb992a1
5 changed files with 257 additions and 38 deletions
14
main.go
14
main.go
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/nirmata/kyverno/pkg/config"
|
||||
|
@ -36,19 +35,6 @@ func main() {
|
|||
if err != nil {
|
||||
glog.Fatalf("Error creating client: %v\n", err)
|
||||
}
|
||||
// test Code
|
||||
rGVR := client.DiscoveryClient.GetGVRFromKind("ConfigMap")
|
||||
obj, err := client.GetResource(rGVR.Resource, "ns2", "default-config")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
data, err := obj.MarshalJSON()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Println(string(data))
|
||||
|
||||
// test Code
|
||||
|
||||
policyInformerFactory, err := sharedinformer.NewSharedInformerFactory(clientConfig)
|
||||
if err != nil {
|
||||
|
|
|
@ -1,26 +1,29 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/golang/glog"
|
||||
v1alpha1 "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
|
||||
client "github.com/nirmata/kyverno/pkg/dclient"
|
||||
"github.com/nirmata/kyverno/pkg/info"
|
||||
"github.com/nirmata/kyverno/pkg/utils"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
//GenerateNew apply generation rules on a resource
|
||||
func GenerateNew(client *client.Client, policy *v1alpha1.Policy, ns *corev1.Namespace, processExisting bool) []*info.RuleInfo {
|
||||
func GenerateNew(client *client.Client, policy *v1alpha1.Policy, ns *corev1.Namespace) []*info.RuleInfo {
|
||||
ris := []*info.RuleInfo{}
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
if rule.Generation == nil {
|
||||
continue
|
||||
}
|
||||
ri := info.NewRuleInfo(rule.Name, info.Generation)
|
||||
err := applyRuleGeneratorNew(client, ns, rule.Generation, processExisting)
|
||||
err := applyRuleGeneratorNew(client, ns, rule.Generation)
|
||||
if err != nil {
|
||||
ri.Fail()
|
||||
ri.Addf("Rule %s: Failed to apply rule generator, err %v.", rule.Name, err)
|
||||
|
@ -33,39 +36,45 @@ func GenerateNew(client *client.Client, policy *v1alpha1.Policy, ns *corev1.Name
|
|||
return ris
|
||||
}
|
||||
|
||||
func applyRuleGeneratorNew(client *client.Client, ns *corev1.Namespace, gen *v1alpha1.Generation, processExisting bool) error {
|
||||
func applyRuleGeneratorNew(client *client.Client, ns *corev1.Namespace, gen *v1alpha1.Generation) error {
|
||||
var err error
|
||||
resource := &unstructured.Unstructured{}
|
||||
var rdata map[string]interface{}
|
||||
// get resource from kind
|
||||
rGVR := client.DiscoveryClient.GetGVRFromKind(gen.Kind)
|
||||
if rGVR.Resource == "" {
|
||||
return fmt.Errorf("Kind to Resource Name conversion failed for %s", gen.Kind)
|
||||
}
|
||||
// If processing Existing resource, we only check if the resource
|
||||
// already exists
|
||||
if processExisting {
|
||||
obj, err := client.GetResource(rGVR.Resource, ns.Name, gen.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := []byte{}
|
||||
if err := obj.UnmarshalJSON(data); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Println(string(data))
|
||||
}
|
||||
|
||||
var rdata map[string]interface{}
|
||||
// data -> create new resource
|
||||
if gen.Data != nil {
|
||||
// 1> Check if resource exists
|
||||
obj, err := client.GetResource(rGVR.Resource, ns.Name, gen.Name)
|
||||
if err == nil {
|
||||
// 2> If already exsists, then verify the content is contained
|
||||
// found the resource
|
||||
// check if the rule is create, if yes, then verify if the specified configuration is present in the resource
|
||||
ok, err := checkResource(gen.Data, obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return errors.New("rule configuration not present in resource")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
rdata, err = runtime.DefaultUnstructuredConverter.ToUnstructured(&gen.Data)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
// clone -> copy from existing resource
|
||||
if gen.Clone != nil {
|
||||
// 1> Check if resource exists
|
||||
_, err := client.GetResource(rGVR.Resource, ns.Name, gen.Name)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
// 2> If already exists return
|
||||
resource, err = client.GetResource(rGVR.Resource, gen.Clone.Namespace, gen.Clone.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -84,3 +93,41 @@ func applyRuleGeneratorNew(client *client.Client, ns *corev1.Namespace, gen *v1a
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkResource(config interface{}, resource *unstructured.Unstructured) (bool, error) {
|
||||
var err error
|
||||
|
||||
objByte, err := resource.MarshalJSON()
|
||||
if err != nil {
|
||||
// unable to parse the json
|
||||
return false, err
|
||||
}
|
||||
err = resource.UnmarshalJSON(objByte)
|
||||
if err != nil {
|
||||
// unable to parse the json
|
||||
return false, err
|
||||
}
|
||||
// marshall and unmarshall json to verify if its right format
|
||||
configByte, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
// unable to marshall the config
|
||||
return false, err
|
||||
}
|
||||
var configData interface{}
|
||||
err = json.Unmarshal(configByte, &configData)
|
||||
if err != nil {
|
||||
// unable to unmarshall
|
||||
return false, err
|
||||
|
||||
}
|
||||
|
||||
var objData interface{}
|
||||
err = json.Unmarshal(objByte, &objData)
|
||||
if err != nil {
|
||||
// unable to unmarshall
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Check if the config is a subset of resource
|
||||
return utils.JSONsubsetValue(configData, objData), nil
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package gencontroller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/golang/glog"
|
||||
v1alpha1 "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
|
||||
"github.com/nirmata/kyverno/pkg/engine"
|
||||
|
@ -13,7 +11,6 @@ import (
|
|||
|
||||
func (c *Controller) processNamespace(ns *corev1.Namespace) error {
|
||||
//Get all policies and then verify if the namespace matches any of the defined selectors
|
||||
fmt.Println(ns.Name)
|
||||
policies, err := c.listPolicies(ns)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -37,7 +34,6 @@ func (c *Controller) listPolicies(ns *corev1.Namespace) ([]*v1alpha1.Policy, err
|
|||
// Check if the policy contains a generatoin rule
|
||||
for _, r := range p.Spec.Rules {
|
||||
if r.Generation != nil {
|
||||
fmt.Println(p.Name)
|
||||
// Check if the resource meets the description
|
||||
if namespaceMeetsRuleDescription(ns, r.ResourceDescription) {
|
||||
fpolicies = append(fpolicies, p)
|
||||
|
@ -56,7 +52,7 @@ func (c *Controller) processPolicy(ns *corev1.Namespace, p *v1alpha1.Policy) err
|
|||
ns.Name,
|
||||
"") // Namespace has no namespace..WOW
|
||||
|
||||
ruleInfos := engine.GenerateNew(c.client, p, ns, false)
|
||||
ruleInfos := engine.GenerateNew(c.client, p, ns)
|
||||
policyInfo.AddRuleInfos(ruleInfos)
|
||||
if !policyInfo.IsSuccessful() {
|
||||
glog.Infof("Failed to apply policy %s on resource %s %s", p.Name, ns.Kind, ns.Name)
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
//NewKubeInformerFactory returns a kubeinformer
|
||||
func NewKubeInformerFactory(cfg *rest.Config) kubeinformers.SharedInformerFactory {
|
||||
// kubernetes client
|
||||
kubeClient, err := kubernetes.NewForConfig(cfg)
|
||||
|
|
189
pkg/utils/json.go
Normal file
189
pkg/utils/json.go
Normal file
|
@ -0,0 +1,189 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
//JSONsubsetValue checks if JSON a is contained in JSON b
|
||||
func JSONsubsetValue(a interface{}, b interface{}) bool {
|
||||
switch typed := a.(type) {
|
||||
case bool:
|
||||
bv, ok := b.(bool)
|
||||
if !ok {
|
||||
glog.Errorf("expected bool found %T", b)
|
||||
return false
|
||||
}
|
||||
av, _ := a.(bool)
|
||||
if av == bv {
|
||||
return true
|
||||
}
|
||||
case int:
|
||||
bv, ok := b.(int)
|
||||
if !ok {
|
||||
glog.Errorf("expected int found %T", b)
|
||||
return false
|
||||
}
|
||||
av, _ := a.(int)
|
||||
if av == bv {
|
||||
return true
|
||||
}
|
||||
case float64:
|
||||
bv, ok := b.(float64)
|
||||
if !ok {
|
||||
glog.Errorf("expected float64 found %T", b)
|
||||
return false
|
||||
}
|
||||
av, _ := a.(float64)
|
||||
if av == bv {
|
||||
return true
|
||||
}
|
||||
|
||||
case string:
|
||||
bv, ok := b.(string)
|
||||
if !ok {
|
||||
glog.Errorf("expected string found %T", b)
|
||||
return false
|
||||
}
|
||||
av, _ := a.(string)
|
||||
if av == bv {
|
||||
return true
|
||||
}
|
||||
|
||||
case map[string]interface{}:
|
||||
bv, ok := b.(map[string]interface{})
|
||||
if !ok {
|
||||
glog.Errorf("expected map[string]interface{} found %T", b)
|
||||
return false
|
||||
}
|
||||
av, _ := a.(map[string]interface{})
|
||||
return subsetMap(av, bv)
|
||||
case []interface{}:
|
||||
// TODO: verify the logic
|
||||
bv, ok := b.([]interface{})
|
||||
if !ok {
|
||||
glog.Errorf("expected []interface{} found %T", b)
|
||||
return false
|
||||
}
|
||||
av, _ := a.([]interface{})
|
||||
return subsetSlice(av, bv)
|
||||
default:
|
||||
glog.Errorf("Unspported type %s", typed)
|
||||
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func subsetMap(a, b map[string]interface{}) bool {
|
||||
// check if keys are present
|
||||
for k := range a {
|
||||
if _, ok := b[k]; !ok {
|
||||
glog.Errorf("key %s, not present in resource", k)
|
||||
return false
|
||||
}
|
||||
}
|
||||
// check if values for the keys match
|
||||
for ak, av := range a {
|
||||
bv := b[ak]
|
||||
if !JSONsubsetValue(av, bv) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func contains(a interface{}, b []interface{}) bool {
|
||||
switch typed := a.(type) {
|
||||
case bool:
|
||||
for _, bv := range b {
|
||||
bv, ok := bv.(bool)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
av, _ := a.(bool)
|
||||
|
||||
if bv == av {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case int:
|
||||
for _, bv := range b {
|
||||
bv, ok := bv.(int)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
av, _ := a.(int)
|
||||
|
||||
if bv == av {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case float64:
|
||||
for _, bv := range b {
|
||||
bv, ok := bv.(float64)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
av, _ := a.(float64)
|
||||
|
||||
if bv == av {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case string:
|
||||
for _, bv := range b {
|
||||
bv, ok := bv.(string)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
av, _ := a.(string)
|
||||
|
||||
if bv == av {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case map[string]interface{}:
|
||||
for _, bv := range b {
|
||||
bv, ok := bv.(map[string]interface{})
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
av, _ := a.(map[string]interface{})
|
||||
if subsetMap(av, bv) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case []interface{}:
|
||||
for _, bv := range b {
|
||||
bv, ok := bv.([]interface{})
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
av, _ := a.([]interface{})
|
||||
if JSONsubsetValue(av, bv) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
default:
|
||||
glog.Errorf("Unspported type %s", typed)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func subsetSlice(a, b []interface{}) bool {
|
||||
// if empty
|
||||
if len(a) == 0 {
|
||||
return true
|
||||
}
|
||||
// check if len is not greater
|
||||
if len(a) > len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, av := range a {
|
||||
if !contains(av, b) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
Loading…
Add table
Reference in a new issue