1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-31 03:45:17 +00:00

Merge pull request #130 from nirmata/21_enhancement

support generate on any resource type
This commit is contained in:
Shivkumar Dudhani 2019-06-03 10:40:51 -07:00 committed by GitHub
commit 53a395f013
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 121 additions and 122 deletions

View file

@ -102,12 +102,15 @@ spec:
required:
- kind
- name
- namespace
properties:
kind:
type: string
name:
type: string
copyFrom:
namespace:
type: string
clone:
type: object
required:
- namespace
@ -118,13 +121,7 @@ spec:
name:
type: string
data:
type: object
additionalProperties:
type: string
labels:
type: object
additionalProperties:
type: string
AnyValue: {}
---
kind: Namespace
apiVersion: v1

View file

@ -83,4 +83,4 @@ func init() {
flag.StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
config.LogDefaultFlags()
flag.Parse()
}
}

View file

@ -61,16 +61,16 @@ type Validation struct {
// Generation describes which resources will be created when other resource is created
type Generation struct {
Kind string `json:"kind"`
Name string `json:"name"`
CopyFrom *CopyFrom `json:"copyFrom"`
Data map[string]string `json:"data"`
Labels map[string]string `json:"labels"`
Kind string `json:"kind"`
Name string `json:"name"`
Namespace string `json:"namespace"`
Data interface{} `json:"data"`
Clone *CloneFrom `json:"clone"`
}
// CopyFrom - location of a Secret or a ConfigMap
// CloneFrom - location of a Secret or a ConfigMap
// which will be used as source when applying 'generate'
type CopyFrom struct {
type CloneFrom struct {
Namespace string `json:"namespace"`
Name string `json:"name"`
}

View file

@ -64,8 +64,11 @@ func (pp *Patch) Validate() error {
// Validate returns error if generator is configured incompletely
func (pcg *Generation) Validate() error {
if len(pcg.Data) == 0 && pcg.CopyFrom == nil {
return fmt.Errorf("Neither Data nor CopyFrom (source) of %s/%s is specified", pcg.Kind, pcg.Name)
if pcg.Data == nil && pcg.Clone == nil {
return fmt.Errorf("Neither data nor clone (source) of %s is specified", pcg.Kind)
}
if pcg.Data != nil && pcg.Clone != nil {
return fmt.Errorf("Both data nor clone (source) of %s are specified", pcg.Kind)
}
return nil
}
@ -93,3 +96,11 @@ func (in *Validation) DeepCopyInto(out *Validation) {
*out = *in
}
}
// DeepCopyInto is declared because k8s:deepcopy-gen is
// not able to generate this method for interface{} member
func (in *Generation) DeepCopyInto(out *Generation) {
if out != nil {
*out = *in
}
}

View file

@ -174,4 +174,4 @@ func (pc *PolicyController) syncHandler(obj interface{}) error {
//TODO: processPolicy
glog.Infof("process policy %s on existing resources", policy.GetName())
return nil
}
}

View file

@ -1,7 +1,9 @@
package client
import (
"errors"
"fmt"
"strings"
"time"
"github.com/golang/glog"
@ -164,94 +166,86 @@ func ConvertToRuntimeObject(obj *unstructured.Unstructured) (*runtime.Object, er
return &runtimeObj, nil
}
//TODO: make this generic for all resource type
//GenerateSecret to generate secrets
func (c *Client) GenerateSecret(generator types.Generation, namespace string) error {
glog.Infof("Preparing to create secret %s/%s", namespace, generator.Name)
secret := v1.Secret{}
if generator.CopyFrom != nil {
glog.Infof("Copying data from secret %s/%s", generator.CopyFrom.Namespace, generator.CopyFrom.Name)
// Get configMap resource
unstrSecret, err := c.GetResource(Secrets, generator.CopyFrom.Namespace, generator.CopyFrom.Name)
if err != nil {
return err
// only support 2 levels of keys
// To-Do support multiple levels of key
func keysExist(data map[string]interface{}, keys ...string) bool {
var v interface{}
var t map[string]interface{}
var ok bool
for _, key := range keys {
ks := strings.Split(key, ".")
if len(ks) > 2 {
glog.Error("Only support 2 levels of keys from root. Support to be extendend in future")
return false
}
// typed object
secret, err = convertToSecret(unstrSecret)
if err != nil {
return err
if v, ok = data[ks[0]]; !ok {
glog.Infof("key %s does not exist", key)
return false
}
if len(ks) == 2 {
if t, ok = v.(map[string]interface{}); !ok {
glog.Error("expecting type map[string]interface{}")
}
return keyExist(t, ks[1])
}
}
return true
}
secret.ObjectMeta = meta.ObjectMeta{
Name: generator.Name,
Namespace: namespace,
func keyExist(data map[string]interface{}, key string) (ok bool) {
if _, ok = data[key]; !ok {
glog.Infof("key %s does not exist", key)
}
return ok
}
// Copy data from generator to the new secret
// support mode 'data' -> create resource
// To-Do: support 'from' -> copy/clone the resource
func (c *Client) GenerateResource(generator types.Generation, namespace string) error {
var err error
rGVR := c.getGVRFromKind(generator.Kind)
resource := &unstructured.Unstructured{}
var rdata map[string]interface{}
// data -> create new resource
if generator.Data != nil {
if secret.Data == nil {
secret.Data = make(map[string][]byte)
rdata, err = runtime.DefaultUnstructuredConverter.ToUnstructured(&generator.Data)
if err != nil {
utilruntime.HandleError(err)
return err
}
for k, v := range generator.Data {
secret.Data[k] = []byte(v)
// verify if mandatory attributes have been defined
if !keysExist(rdata, "kind", "apiVersion", "metadata.name", "metadata.namespace") {
return errors.New("mandatory keys not defined")
}
}
// clone -> copy from existing resource
if generator.Clone != nil {
resource, err = c.GetResource(rGVR.Resource, generator.Clone.Namespace, generator.Clone.Name)
if err != nil {
return err
}
rdata = resource.UnstructuredContent()
}
go c.createSecretAfterNamespaceIsCreated(secret, namespace)
resource.SetUnstructuredContent(rdata)
resource.SetName(generator.Name)
resource.SetNamespace(generator.Namespace)
resource.SetResourceVersion("")
err = c.waitUntilNamespaceIsCreated(namespace)
if err != nil {
glog.Errorf("Can't create a resource %s: %v", generator.Name, err)
return nil
}
_, err = c.CreateResource(rGVR.Resource, generator.Namespace, resource)
if err != nil {
return err
}
return nil
}
//TODO: make this generic for all resource type
//GenerateConfigMap to generate configMap
func (c *Client) GenerateConfigMap(generator types.Generation, namespace string) error {
glog.Infof("Preparing to create configmap %s/%s", namespace, generator.Name)
configMap := v1.ConfigMap{}
if generator.CopyFrom != nil {
glog.Infof("Copying data from configmap %s/%s", generator.CopyFrom.Namespace, generator.CopyFrom.Name)
// Get configMap resource
unstrConfigMap, err := c.GetResource(ConfigMaps, generator.CopyFrom.Namespace, generator.CopyFrom.Name)
if err != nil {
return err
}
// typed object
configMap, err = convertToConfigMap(unstrConfigMap)
if err != nil {
return err
}
}
configMap.ObjectMeta = meta.ObjectMeta{
Name: generator.Name,
Namespace: namespace,
}
// Copy data from generator to the new configmap
if generator.Data != nil {
if configMap.Data == nil {
configMap.Data = make(map[string]string)
}
for k, v := range generator.Data {
configMap.Data[k] = v
}
}
go c.createConfigMapAfterNamespaceIsCreated(configMap, namespace)
return nil
}
func convertToConfigMap(obj *unstructured.Unstructured) (v1.ConfigMap, error) {
configMap := v1.ConfigMap{}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), &configMap); err != nil {
return configMap, err
}
return configMap, nil
}
//To-Do remove this to use unstructured type
func convertToSecret(obj *unstructured.Unstructured) (v1.Secret, error) {
secret := v1.Secret{}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), &secret); err != nil {
@ -260,6 +254,7 @@ func convertToSecret(obj *unstructured.Unstructured) (v1.Secret, error) {
return secret, nil
}
//To-Do remove this to use unstructured type
func convertToCSR(obj *unstructured.Unstructured) (*certificates.CertificateSigningRequest, error) {
csr := certificates.CertificateSigningRequest{}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), &csr); err != nil {
@ -268,26 +263,6 @@ func convertToCSR(obj *unstructured.Unstructured) (*certificates.CertificateSign
return &csr, nil
}
func (c *Client) createConfigMapAfterNamespaceIsCreated(configMap v1.ConfigMap, namespace string) {
err := c.waitUntilNamespaceIsCreated(namespace)
if err == nil {
_, err = c.CreateResource(ConfigMaps, namespace, configMap)
}
if err != nil {
glog.Errorf("Can't create a configmap: %s", err)
}
}
func (c *Client) createSecretAfterNamespaceIsCreated(secret v1.Secret, namespace string) {
err := c.waitUntilNamespaceIsCreated(namespace)
if err == nil {
_, err = c.CreateResource(Secrets, namespace, secret)
}
if err != nil {
glog.Errorf("Can't create a secret: %s", err)
}
}
// Waits until namespace is created with maximum duration maxWaitTimeForNamespaceCreation
func (c *Client) waitUntilNamespaceIsCreated(name string) error {
timeStart := time.Now()
@ -324,3 +299,27 @@ func (c *Client) getGVR(resource string) schema.GroupVersionResource {
}
return emptyGVR
}
//To-do: measure performance
//To-do: evaluate DefaultRESTMapper to fetch kind->resource mapping
func (c *Client) getGVRFromKind(kind string) schema.GroupVersionResource {
emptyGVR := schema.GroupVersionResource{}
serverresources, err := c.cachedClient.ServerPreferredResources()
if err != nil {
utilruntime.HandleError(err)
return emptyGVR
}
for _, serverresource := range serverresources {
for _, resource := range serverresource.APIResources {
if resource.Kind == kind && !strings.Contains(resource.Name, "/") {
gv, err := schema.ParseGroupVersion(serverresource.GroupVersion)
if err != nil {
utilruntime.HandleError(err)
return emptyGVR
}
return gv.WithResource(resource.Name)
}
}
}
return emptyGVR
}

View file

@ -42,15 +42,7 @@ func applyRuleGenerator(client *client.Client, rawResource []byte, generator *ku
var err error
namespace := ParseNameFromObject(rawResource)
switch generator.Kind {
case "ConfigMap":
err = client.GenerateConfigMap(*generator, namespace)
case "Secret":
err = client.GenerateSecret(*generator, namespace)
default:
err = fmt.Errorf("Unsupported config Kind '%s'", generator.Kind)
}
err = client.GenerateResource(*generator, namespace)
if err != nil {
return fmt.Errorf("Unable to apply generator for %s '%s/%s' : %v", generator.Kind, namespace, generator.Name, err)
}