diff --git a/docs/customization/custom-checks.md b/docs/customization/custom-checks.md index 33693bbb..e1dba81c 100644 --- a/docs/customization/custom-checks.md +++ b/docs/customization/custom-checks.md @@ -134,6 +134,8 @@ schema: const: "{{ .metadata.name }}" ``` +Note that the object available via the template is the full object, and not the object implied by the `target`. A check that specifies `target: PodSpec` can directly access the pod specification via the built-in template variable `.Polaris.PodSpec`. + You can also use the full [Go template syntax](https://golang.org/pkg/text/template/), though you may need to specify your schema as a string in order to use concepts like `range`. E.g. this check ensures that at least one of the object's labels is present in `matchLabels`: diff --git a/pkg/kube/resources.go b/pkg/kube/resources.go index 344b8850..6e390e82 100644 --- a/pkg/kube/resources.go +++ b/pkg/kube/resources.go @@ -17,6 +17,7 @@ package kube import ( "bytes" "context" + "encoding/json" "fmt" "io" "io/ioutil" @@ -475,3 +476,17 @@ func (resources *ResourceProvider) addResourceFromString(contents string) error } return err } + +// SerializePod converts a typed PodSpec into a map[string]interface{} +func SerializePod(pod *corev1.PodSpec) (map[string]interface{}, error) { + podJSON, err := json.Marshal(pod) + if err != nil { + return nil, err + } + podMap := make(map[string]interface{}) + err = json.Unmarshal(podJSON, &podMap) + if err != nil { + return nil, err + } + return podMap, nil +} diff --git a/pkg/validator/schema.go b/pkg/validator/schema.go index fc3b1f95..111da610 100644 --- a/pkg/validator/schema.go +++ b/pkg/validator/schema.go @@ -27,6 +27,7 @@ import ( "gomodules.xyz/jsonpatch/v2" corev1 "k8s.io/api/core/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "github.com/fairwindsops/polaris/pkg/config" "github.com/fairwindsops/polaris/pkg/kube" @@ -64,13 +65,38 @@ func resolveCheck(conf *config.Configuration, checkID string, test schemaTestCas if !check.IsActionable(test.Target, test.Resource.Kind, test.IsInitContianer) { return nil, nil } - checkPtr, err := check.TemplateForResource(test.Resource.Resource.Object) + templateInput, err := getTemplateInput(test) + if err != nil { + return nil, err + } + checkPtr, err := check.TemplateForResource(templateInput) if err != nil { return nil, err } return checkPtr, nil } +// getTemplateInput augments a schemaTestCase.Resource.Resource.Object with +// Polaris built-in variables. The result can be used as input for +// CheckSchema.TemplateForResource(). +func getTemplateInput(test schemaTestCase) (map[string]interface{}, error) { + templateInput := test.Resource.Resource.Object + if templateInput == nil { + return nil, nil + } + if test.Target == config.TargetPodSpec { + podSpecMap, err := kube.SerializePod(test.Resource.PodSpec) + if err != nil { + return nil, err + } + err = unstructured.SetNestedMap(templateInput, podSpecMap, "Polaris", "PodSpec") + if err != nil { + return nil, err + } + } + return templateInput, nil +} + func makeResult(conf *config.Configuration, check *config.SchemaCheck, passes bool, issues []jsonschema.ValError) ResultMessage { details := []string{} for _, issue := range issues {