1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 07:57:07 +00:00
kyverno/pkg/utils/util.go
Vyankatesh Kudtarkar 0cb9b9c248
external.metrics.k8s.io/v1beta1 issue (#4182)
Signed-off-by: Vyankatesh vyankateshkd@gmail.com

Co-authored-by: Prateek Pandey <prateek.pandey@nirmata.com>
2022-07-01 05:05:15 +00:00

359 lines
10 KiB
Go

package utils
import (
"encoding/json"
"fmt"
"regexp"
"strconv"
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/pkg/errors"
"sigs.k8s.io/controller-runtime/pkg/log"
"github.com/go-logr/logr"
wildcard "github.com/kyverno/go-wildcard"
client "github.com/kyverno/kyverno/pkg/dclient"
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
admissionv1 "k8s.io/api/admission/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/discovery"
)
var regexVersion = regexp.MustCompile(`v(\d+).(\d+).(\d+)\.*`)
// CopyMap creates a full copy of the target map
func CopyMap(m map[string]interface{}) map[string]interface{} {
mapCopy := make(map[string]interface{})
for k, v := range m {
mapCopy[k] = v
}
return mapCopy
}
// CopySlice creates a full copy of the target slice
func CopySlice(s []interface{}) []interface{} {
sliceCopy := make([]interface{}, len(s))
copy(sliceCopy, s)
return sliceCopy
}
// CopySliceOfMaps creates a full copy of the target slice
func CopySliceOfMaps(s []map[string]interface{}) []interface{} {
sliceCopy := make([]interface{}, len(s))
for i, v := range s {
sliceCopy[i] = CopyMap(v)
}
return sliceCopy
}
func ToMap(data interface{}) (map[string]interface{}, error) {
if m, ok := data.(map[string]interface{}); ok {
return m, nil
}
b, err := json.Marshal(data)
if err != nil {
return nil, err
}
mapData := make(map[string]interface{})
err = json.Unmarshal(b, &mapData)
if err != nil {
return nil, err
}
return mapData, nil
}
// Contains checks if a string is contained in a list of string
func contains(list []string, element string, fn func(string, string) bool) bool {
for _, e := range list {
if fn(e, element) {
return true
}
}
return false
}
// ContainsNamepace check if namespace satisfies any list of pattern(regex)
func ContainsNamepace(patterns []string, ns string) bool {
return contains(patterns, ns, compareNamespaces)
}
// ContainsString checks if the string is contained in the list
func ContainsString(list []string, element string) bool {
return contains(list, element, compareString)
}
func compareNamespaces(pattern, ns string) bool {
return wildcard.Match(pattern, ns)
}
func compareString(str, name string) bool {
return str == name
}
// CRDsInstalled checks if the Kyverno CRDs are installed or not
func CRDsInstalled(discovery client.IDiscovery) bool {
kyvernoCRDs := []string{"ClusterPolicy", "ClusterPolicyReport", "PolicyReport", "ClusterReportChangeRequest", "ReportChangeRequest"}
for _, crd := range kyvernoCRDs {
if !isCRDInstalled(discovery, crd) {
return false
}
}
return true
}
func isCRDInstalled(discoveryClient client.IDiscovery, kind string) bool {
gvr, err := discoveryClient.GetGVRFromKind(kind)
if gvr.Empty() {
if err == nil {
err = fmt.Errorf("not found")
}
log.Log.Error(err, "failed to retrieve CRD", "kind", kind)
return false
}
log.Log.Info("CRD found", "gvr", gvr.String())
return true
}
// ExtractResources extracts the new and old resource as unstructured
func ExtractResources(newRaw []byte, request *admissionv1.AdmissionRequest) (unstructured.Unstructured, unstructured.Unstructured, error) {
var emptyResource unstructured.Unstructured
var newResource unstructured.Unstructured
var oldResource unstructured.Unstructured
var err error
// New Resource
if newRaw == nil {
newRaw = request.Object.Raw
}
if newRaw != nil {
newResource, err = ConvertResource(newRaw, request.Kind.Group, request.Kind.Version, request.Kind.Kind, request.Namespace)
if err != nil {
return emptyResource, emptyResource, fmt.Errorf("failed to convert new raw to unstructured: %v", err)
}
}
// Old Resource
oldRaw := request.OldObject.Raw
if oldRaw != nil {
oldResource, err = ConvertResource(oldRaw, request.Kind.Group, request.Kind.Version, request.Kind.Kind, request.Namespace)
if err != nil {
return emptyResource, emptyResource, fmt.Errorf("failed to convert old raw to unstructured: %v", err)
}
}
return newResource, oldResource, err
}
// ConvertResource converts raw bytes to an unstructured object
func ConvertResource(raw []byte, group, version, kind, namespace string) (unstructured.Unstructured, error) {
obj, err := engineutils.ConvertToUnstructured(raw)
if err != nil {
return unstructured.Unstructured{}, fmt.Errorf("failed to convert raw to unstructured: %v", err)
}
obj.SetGroupVersionKind(schema.GroupVersionKind{Group: group, Version: version, Kind: kind})
if namespace != "" && kind != "Namespace" {
obj.SetNamespace(namespace)
}
if obj.GetKind() == "Namespace" && obj.GetNamespace() != "" {
obj.SetNamespace("")
}
return *obj, nil
}
func NormalizeSecret(resource *unstructured.Unstructured) (unstructured.Unstructured, error) {
var secret corev1.Secret
data, err := json.Marshal(resource.Object)
if err != nil {
return *resource, err
}
err = json.Unmarshal(data, &secret)
if err != nil {
return *resource, errors.Wrap(err, "object unable to convert to secret")
}
for k, v := range secret.Data {
if len(v) == 0 {
secret.Data[k] = []byte("")
}
}
updateSecret := map[string]interface{}{}
raw, err := json.Marshal(&secret)
if err != nil {
return *resource, nil
}
err = json.Unmarshal(raw, &updateSecret)
if err != nil {
return *resource, nil
}
if err != nil {
return *resource, errors.Wrap(err, "object unable to convert from secret")
}
if secret.Data != nil {
err = unstructured.SetNestedMap(resource.Object, updateSecret["data"].(map[string]interface{}), "data")
if err != nil {
return *resource, errors.Wrap(err, "failed to set secret.data")
}
}
return *resource, nil
}
// HigherThanKubernetesVersion compare Kubernetes client version to user given version
func HigherThanKubernetesVersion(client discovery.ServerVersionInterface, log logr.Logger, major, minor, patch int) bool {
logger := log.WithName("CompareKubernetesVersion")
serverVersion, err := client.ServerVersion()
if err != nil {
logger.Error(err, "Failed to get kubernetes server version")
return false
}
b, err := isVersionHigher(serverVersion.String(), major, minor, patch)
if err != nil {
logger.Error(err, "serverVersion", serverVersion.String())
return false
}
return b
}
func isVersionHigher(version string, major int, minor int, patch int) (bool, error) {
groups := regexVersion.FindStringSubmatch(version)
if len(groups) != 4 {
return false, fmt.Errorf("invalid version %s. Expected {major}.{minor}.{patch}", version)
}
currentMajor, err := strconv.Atoi(groups[1])
if err != nil {
return false, fmt.Errorf("failed to extract major version from %s", version)
}
currentMinor, err := strconv.Atoi(groups[2])
if err != nil {
return false, fmt.Errorf("failed to extract minor version from %s", version)
}
currentPatch, err := strconv.Atoi(groups[3])
if err != nil {
return false, fmt.Errorf("failed to extract minor version from %s", version)
}
if currentMajor < major ||
(currentMajor == major && currentMinor < minor) ||
(currentMajor == major && currentMinor == minor && currentPatch <= patch) {
return false, nil
}
return true, nil
}
// SliceContains checks whether values are contained in slice
func SliceContains(slice []string, values ...string) bool {
var sliceElementsMap = make(map[string]bool, len(slice))
for _, sliceElement := range slice {
sliceElementsMap[sliceElement] = true
}
for _, value := range values {
if sliceElementsMap[value] {
return true
}
}
return false
}
// ApiextensionsJsonToKyvernoConditions takes in user-provided conditions in abstract apiextensions.JSON form
// and converts it into []kyverno.Condition or kyverno.AnyAllConditions according to its content.
// it also helps in validating the condtions as it returns an error when the conditions are provided wrongfully by the user.
func ApiextensionsJsonToKyvernoConditions(original apiextensions.JSON) (interface{}, error) {
path := "preconditions/validate.deny.conditions"
// checks for the existence any other field apart from 'any'/'all' under preconditions/validate.deny.conditions
unknownFieldChecker := func(jsonByteArr []byte, path string) error {
allowedKeys := map[string]bool{
"any": true,
"all": true,
}
var jsonDecoded map[string]interface{}
if err := json.Unmarshal(jsonByteArr, &jsonDecoded); err != nil {
return fmt.Errorf("error occurred while checking for unknown fields under %s: %+v", path, err)
}
for k := range jsonDecoded {
if !allowedKeys[k] {
return fmt.Errorf("unknown field '%s' found under %s", k, path)
}
}
return nil
}
// marshalling the abstract apiextensions.JSON back to JSON form
jsonByte, err := json.Marshal(original)
if err != nil {
return nil, fmt.Errorf("error occurred while marshalling %s: %+v", path, err)
}
var kyvernoOldConditions []kyverno.Condition
if err = json.Unmarshal(jsonByte, &kyvernoOldConditions); err == nil {
var validConditionOperator bool
for _, jsonOp := range kyvernoOldConditions {
for _, validOp := range kyverno.ConditionOperators {
if jsonOp.Operator == validOp {
validConditionOperator = true
}
}
if !validConditionOperator {
return nil, fmt.Errorf("invalid condition operator: %s", jsonOp.Operator)
}
validConditionOperator = false
}
return kyvernoOldConditions, nil
}
var kyvernoAnyAllConditions kyverno.AnyAllConditions
if err = json.Unmarshal(jsonByte, &kyvernoAnyAllConditions); err == nil {
// checking if unknown fields exist or not
err = unknownFieldChecker(jsonByte, path)
if err != nil {
return nil, fmt.Errorf("error occurred while parsing %s: %+v", path, err)
}
return kyvernoAnyAllConditions, nil
}
return nil, fmt.Errorf("error occurred while parsing %s: %+v", path, err)
}
func OverrideRuntimeErrorHandler() {
logger := log.Log.WithName("RuntimeErrorHandler")
if len(runtime.ErrorHandlers) > 0 {
runtime.ErrorHandlers[0] = func(err error) {
logger.V(6).Info("runtime error: %s", err)
}
} else {
runtime.ErrorHandlers = []func(err error){
func(err error) {
logger.V(6).Info("runtime error: %s", err)
},
}
}
}