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:
commit
53a395f013
7 changed files with 121 additions and 122 deletions
|
@ -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
|
||||
|
|
2
main.go
2
main.go
|
@ -83,4 +83,4 @@ func init() {
|
|||
flag.StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
|
||||
config.LogDefaultFlags()
|
||||
flag.Parse()
|
||||
}
|
||||
}
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue