mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
fb9c66f455
* feat(perf): add new linter prealloc to enforce slice declarations best practice Signed-off-by: ShutingZhao <shuting@nirmata.com> * fix(linter): prealloac slices Signed-off-by: ShutingZhao <shuting@nirmata.com> --------- Signed-off-by: ShutingZhao <shuting@nirmata.com>
197 lines
5.7 KiB
Go
197 lines
5.7 KiB
Go
package api
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
|
"github.com/kyverno/kyverno/pkg/config"
|
|
"github.com/kyverno/kyverno/pkg/engine/jmespath"
|
|
"github.com/kyverno/kyverno/pkg/logging"
|
|
imageutils "github.com/kyverno/kyverno/pkg/utils/image"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
)
|
|
|
|
type ImageInfo struct {
|
|
imageutils.ImageInfo
|
|
// Pointer is the path to the image object in the resource
|
|
Pointer string `json:"jsonPointer"`
|
|
}
|
|
|
|
var (
|
|
podExtractors = BuildStandardExtractors("spec")
|
|
podControllerExtractors = BuildStandardExtractors("spec", "template", "spec")
|
|
cronjobControllerExtractors = BuildStandardExtractors("spec", "jobTemplate", "spec", "template", "spec")
|
|
registeredExtractors = map[string][]imageExtractor{
|
|
"Pod": podExtractors,
|
|
"DaemonSet": podControllerExtractors,
|
|
"Deployment": podControllerExtractors,
|
|
"ReplicaSet": podControllerExtractors,
|
|
"ReplicationController": podControllerExtractors,
|
|
"StatefulSet": podControllerExtractors,
|
|
"CronJob": cronjobControllerExtractors,
|
|
"Job": podControllerExtractors,
|
|
}
|
|
)
|
|
|
|
type imageExtractor struct {
|
|
Fields []string
|
|
Key string
|
|
Value string
|
|
Name string
|
|
JMESPath string
|
|
}
|
|
|
|
func (i *imageExtractor) ExtractFromResource(resource interface{}, cfg config.Configuration) (map[string]ImageInfo, error) {
|
|
imageInfo := map[string]ImageInfo{}
|
|
if err := extract(resource, []string{}, i.Key, i.Value, i.Fields, i.JMESPath, &imageInfo, cfg); err != nil {
|
|
return nil, err
|
|
}
|
|
return imageInfo, nil
|
|
}
|
|
|
|
func extract(
|
|
obj interface{},
|
|
path []string,
|
|
keyPath string,
|
|
valuePath string,
|
|
fields []string,
|
|
jmesPath string,
|
|
imageInfos *map[string]ImageInfo,
|
|
cfg config.Configuration,
|
|
) error {
|
|
if obj == nil {
|
|
return nil
|
|
}
|
|
if len(fields) > 0 && fields[0] == "*" {
|
|
switch typedObj := obj.(type) {
|
|
case []interface{}:
|
|
for i, v := range typedObj {
|
|
if err := extract(v, append(path, strconv.Itoa(i)), keyPath, valuePath, fields[1:], jmesPath, imageInfos, cfg); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
case map[string]interface{}:
|
|
for i, v := range typedObj {
|
|
if err := extract(v, append(path, i), keyPath, valuePath, fields[1:], jmesPath, imageInfos, cfg); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
case interface{}:
|
|
return fmt.Errorf("invalid type")
|
|
}
|
|
return nil
|
|
}
|
|
output, ok := obj.(map[string]interface{})
|
|
if !ok {
|
|
return fmt.Errorf("invalid image config")
|
|
}
|
|
if len(fields) == 0 {
|
|
pointer := fmt.Sprintf("/%s/%s", strings.Join(path, "/"), valuePath)
|
|
key := pointer
|
|
if keyPath != "" {
|
|
key, ok = output[keyPath].(string)
|
|
if !ok {
|
|
return fmt.Errorf("invalid key")
|
|
}
|
|
}
|
|
value, ok := output[valuePath].(string)
|
|
if !ok || strings.TrimSpace(value) == "" {
|
|
// the image may not be present
|
|
logging.V(4).Info("image information is not present", "pointer", pointer)
|
|
return nil
|
|
}
|
|
if jmesPath != "" {
|
|
// TODO: should be injected
|
|
jp := jmespath.New(cfg)
|
|
q, err := jp.Query(jmesPath)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid jmespath %s: %v", jmesPath, err)
|
|
}
|
|
result, err := q.Search(value)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to apply jmespath %s: %v", jmesPath, err)
|
|
}
|
|
resultStr, ok := result.(string)
|
|
if !ok {
|
|
return fmt.Errorf("jmespath %s must produce a string, but produced %v", jmesPath, result)
|
|
}
|
|
value = resultStr
|
|
}
|
|
if imageInfo, err := imageutils.GetImageInfo(value, cfg); err != nil {
|
|
return fmt.Errorf("invalid image '%s' (%s)", value, err.Error())
|
|
} else {
|
|
(*imageInfos)[key] = ImageInfo{*imageInfo, pointer}
|
|
}
|
|
return nil
|
|
}
|
|
currentPath := fields[0]
|
|
return extract(output[currentPath], append(path, currentPath), keyPath, valuePath, fields[1:], jmesPath, imageInfos, cfg)
|
|
}
|
|
|
|
func BuildStandardExtractors(tags ...string) []imageExtractor {
|
|
extractors := make([]imageExtractor, 0, 3)
|
|
for _, tag := range []string{"initContainers", "containers", "ephemeralContainers"} {
|
|
var t []string
|
|
t = append(t, tags...)
|
|
t = append(t, tag)
|
|
t = append(t, "*")
|
|
extractors = append(extractors, imageExtractor{Fields: t, Key: "name", Value: "image", Name: tag})
|
|
}
|
|
return extractors
|
|
}
|
|
|
|
func lookupImageExtractor(kind string, configs kyvernov1.ImageExtractorConfigs) []imageExtractor {
|
|
if configs != nil {
|
|
if extractorConfigs, ok := configs[kind]; ok {
|
|
extractors := []imageExtractor{}
|
|
for _, c := range extractorConfigs {
|
|
fields := func(input []string) []string {
|
|
output := []string{}
|
|
for _, i := range input {
|
|
o := strings.Trim(i, " ")
|
|
if o != "" {
|
|
output = append(output, o)
|
|
}
|
|
}
|
|
return output
|
|
}(strings.Split(c.Path, "/"))
|
|
name := c.Name
|
|
if name == "" {
|
|
name = "custom"
|
|
}
|
|
value := c.Value
|
|
if value == "" {
|
|
value = fields[len(fields)-1]
|
|
fields = fields[:len(fields)-1]
|
|
}
|
|
extractors = append(extractors, imageExtractor{
|
|
Fields: fields,
|
|
Key: c.Key,
|
|
Name: name,
|
|
Value: value,
|
|
JMESPath: c.JMESPath,
|
|
})
|
|
}
|
|
return extractors
|
|
}
|
|
}
|
|
return registeredExtractors[kind]
|
|
}
|
|
|
|
func ExtractImagesFromResource(resource unstructured.Unstructured, configs kyvernov1.ImageExtractorConfigs, cfg config.Configuration) (map[string]map[string]ImageInfo, error) {
|
|
infos := map[string]map[string]ImageInfo{}
|
|
extractors := lookupImageExtractor(resource.GetKind(), configs)
|
|
if extractors != nil && len(extractors) == 0 {
|
|
return nil, fmt.Errorf("no extractors found for %s", resource.GetKind())
|
|
}
|
|
for _, extractor := range extractors {
|
|
if infoMap, err := extractor.ExtractFromResource(resource.Object, cfg); err != nil {
|
|
return nil, err
|
|
} else if len(infoMap) > 0 {
|
|
infos[extractor.Name] = infoMap
|
|
}
|
|
}
|
|
return infos, nil
|
|
}
|