Compare commits

..
Author SHA1 Message Date
jdesouza f295dd0fb5 Using controller-utils to get workloads (#1012)
* Using controller-utils to get workloads

* Code cleanup
2023-11-01 15:59:46 -03:00
Robert Brennan 97687761a7 Fix numerical resource ranges (#991)
* fix numerical resource numbers

* add resource range tests
2023-09-05 11:15:45 -06:00
Robert BrennanandAndrew Suderman c856874564 Update topologySpreadConstraint.yaml (#971)
* Update topologySpreadConstraint.yaml

* Update topologySpreadConstraint.yaml

* fix test

* add passing test

---------

Co-authored-by: Andrew Suderman <andy@fairwinds.com>
2023-08-28 08:49:08 -06:00
Takumi Sue 09525d01a1 Add helm-skip-tests flag (#986)
* Add helm-skip-tests flag

close #985

* Update cli doc
2023-08-25 08:39:27 -06:00
14 changed files with 104 additions and 106 deletions
-25
View File
@@ -1,25 +0,0 @@
successMessage: CPU limits are within the required range
failureMessage: CPU limits should be within the required range
category: Efficiency
target: Container
containers:
exclude:
- initContainer
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required:
- resources
properties:
resources:
type: object
required:
- limits
properties:
limits:
required:
- cpu
properties:
cpu:
resourceMinimum: 200m
resourceMaximum: "2"
+5 -9
View File
@@ -2,6 +2,10 @@ successMessage: Pod has a valid topology spread constraint
failureMessage: Pod should be configured with a valid topology spread constraint
category: Reliability
target: PodSpec
controllers:
exclude:
- Job
- CronJob
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
@@ -10,12 +14,4 @@ schema:
properties:
topologySpreadConstraints:
type: array
items:
type: object
properties:
topologyKey:
anyOf:
- type: string
const: "kubernetes.io/hostname"
- type: string
const: "topology.kubernetes.io/zone"
minItems: 1
+8 -2
View File
@@ -50,6 +50,7 @@ var (
useColor bool
helmChart string
helmValues []string
helmSkipTests bool
checks []string
auditNamespace string
severityLevel string
@@ -72,6 +73,7 @@ func init() {
auditCmd.PersistentFlags().StringVar(&resourceToAudit, "resource", "", "Audit a specific resource, in the format namespace/kind/version/name, e.g. nginx-ingress/Deployment.apps/v1/default-backend.")
auditCmd.PersistentFlags().StringVar(&helmChart, "helm-chart", "", "Will fill out Helm template")
auditCmd.PersistentFlags().StringSliceVar(&helmValues, "helm-values", []string{}, "Optional flag to add helm values")
auditCmd.PersistentFlags().BoolVar(&helmSkipTests, "helm-skip-tests", false, "Corresponds to --skip-tests of helm template")
auditCmd.PersistentFlags().StringSliceVar(&checks, "checks", []string{}, "Optional flag to specify specific checks to check")
auditCmd.PersistentFlags().StringVar(&auditNamespace, "namespace", "", "Namespace to audit. Only applies to in-cluster audits")
auditCmd.PersistentFlags().StringVar(&severityLevel, "severity", "", "Severity level used to filter results. Behaves like log levels. 'danger' is the least verbose (warning, danger)")
@@ -110,7 +112,7 @@ var auditCmd = &cobra.Command{
}
if helmChart != "" {
var err error
auditPath, err = ProcessHelmTemplates(helmChart, helmValues)
auditPath, err = ProcessHelmTemplates(helmChart, helmValues, helmSkipTests)
if err != nil {
logrus.Errorf("Couldn't process helm chart: %v", err)
os.Exit(1)
@@ -196,7 +198,7 @@ var auditCmd = &cobra.Command{
}
// ProcessHelmTemplates turns helm into yaml to be processed by Polaris or the other tools.
func ProcessHelmTemplates(helmChart string, helmValues []string) (string, error) {
func ProcessHelmTemplates(helmChart string, helmValues []string, helmSkipTests bool) (string, error) {
cmd := exec.Command("helm", "dependency", "update", helmChart)
output, err := cmd.CombinedOutput()
if err != nil {
@@ -218,6 +220,10 @@ func ProcessHelmTemplates(helmChart string, helmValues []string) (string, error)
params = append(params, "--values", v)
}
if helmSkipTests {
params = append(params, "--skip-tests")
}
cmd = exec.Command("helm", params...)
output, err = cmd.CombinedOutput()
+1 -1
View File
@@ -50,6 +50,7 @@ webhook
-f, --format string Output format for results - json, yaml, pretty, or score. (default "json")
--helm-chart string Will fill out Helm template
--helm-values string Optional flag to add helm values
--helm-skip-tests bool Corresponds to --skip-tests of helm template
-h, --help help for audit
--namespace string Namespace to audit. Only applies to in-cluster audits
--only-show-failed-tests If specified, audit output will only show failed tests.
@@ -80,4 +81,3 @@ webhook
status View authentication status.
token Print the auth token gh is configured to use.
```
-1
View File
@@ -38,7 +38,6 @@ var (
"memoryRequestsMissing",
"cpuLimitsMissing",
"cpuRequestsMissing",
"cpuLimitsRange",
"readinessProbeMissing",
"livenessProbeMissing",
"pullPolicyNotAlways",
+3
View File
@@ -143,6 +143,9 @@ func (max resourceMaximum) Validate(path string, data interface{}, errs *[]jsons
}
func parseQuantity(i interface{}) (resource.Quantity, *[]jsonschema.ValError) {
if resNum, ok := i.(float64); ok {
i = fmt.Sprintf("%f", resNum)
}
resStr, ok := i.(string)
if !ok {
return resource.Quantity{}, &[]jsonschema.ValError{
+16 -59
View File
@@ -26,6 +26,7 @@ import (
"strings"
"time"
"github.com/fairwindsops/controller-utils/pkg/controller"
conf "github.com/fairwindsops/polaris/pkg/config"
"github.com/sirupsen/logrus"
@@ -33,7 +34,6 @@ import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
k8sYaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/dynamic"
@@ -297,13 +297,6 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
}
namespaces = nsList
}
logrus.Info("Loading pods")
pods, err := kube.CoreV1().Pods(c.Namespace).List(ctx, listOpts)
if err != nil {
logrus.Errorf("Error fetching Pods: %v", err)
return nil, err
}
logrus.Info("Setting up restmapper")
resources, err := restmapper.GetAPIGroupResources(kube.Discovery())
if err != nil {
@@ -358,17 +351,24 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
kubernetesResources = append(kubernetesResources, res)
}
}
objectCache := map[string]unstructured.Unstructured{}
logrus.Info("Loading controllers")
controllers, err := LoadControllers(ctx, pods.Items, dynamic, restMapper, objectCache)
client := controller.Client{
Context: ctx,
Dynamic: dynamic,
RESTMapper: restMapper,
}
topControllers, err := client.GetAllTopControllersSummary("")
if err != nil {
logrus.Errorf("Error loading controllers from pods: %v", err)
return nil, err
return nil, fmt.Errorf("error while getting all TopControllers: %v", err)
}
for _, workload := range topControllers {
topController := workload.TopController
workloadObj, err := NewGenericResourceFromUnstructured(topController, nil)
if err != nil {
return nil, fmt.Errorf("could not parse workload %v: %w", workload, err)
}
kubernetesResources = append(kubernetesResources, workloadObj)
}
// resources loaded from custom checks can also contain controllers and thus would be added twice to the provider
kubernetesResources = deduplicateControllers(append(kubernetesResources, controllers...))
provider.Nodes = nodes.Items
provider.Namespaces = namespaces.Items
@@ -377,49 +377,6 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
return &provider, nil
}
// LoadControllers loads a list of controllers from the kubeResources Pods
func LoadControllers(ctx context.Context, pods []corev1.Pod, dynamicClient dynamic.Interface, restMapperPointer meta.RESTMapper, objectCache map[string]unstructured.Unstructured) ([]GenericResource, error) {
interfaces := []GenericResource{}
deduped := map[string]*corev1.Pod{}
for idx, pod := range pods {
owners := pod.ObjectMeta.OwnerReferences
if len(owners) == 0 {
deduped[pod.ObjectMeta.Namespace+"/Pod/"+pod.ObjectMeta.Name] = &pods[idx]
continue
}
deduped[pod.ObjectMeta.Namespace+"/"+owners[0].Kind+"/"+owners[0].Name] = &pods[idx]
}
for key, pod := range deduped {
logrus.Debugf("Resolving controller from pod %s", key)
workload, err := ResolveControllerFromPod(ctx, *pod, dynamicClient, restMapperPointer, objectCache)
if err != nil {
return nil, err
}
interfaces = append(interfaces, workload)
}
return interfaces, nil
}
// Because the controllers with an Owner take on the name of the Owner, this eliminates any duplicates.
// In cases like CronJobs older children can hang around, so this takes the most recent.
func deduplicateControllers(inputResources []GenericResource) []GenericResource {
controllerMap := make(map[string]GenericResource)
for _, controller := range inputResources {
key := controller.ObjectMeta.GetNamespace() + "/" + controller.Kind + "/" + controller.ObjectMeta.GetName()
oldController, ok := controllerMap[key]
if !ok || controller.ObjectMeta.GetCreationTimestamp().Time.After(oldController.ObjectMeta.GetCreationTimestamp().Time) {
controllerMap[key] = controller
}
}
results := make([]GenericResource, len(controllerMap))
idx := 0
for _, controller := range controllerMap {
results[idx] = controller
idx++
}
return results
}
func (resources *ResourceProvider) addResourcesFromReader(reader io.Reader) error {
contents, err := io.ReadAll(reader)
if err != nil {
+32
View File
@@ -0,0 +1,32 @@
containers:
exclude:
- initContainer
successMessage: Resource limits are within the required range
failureMessage: Resource limits should be within the required range
category: Resources
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required:
- resources
properties:
resources:
type: object
required:
- limits
properties:
limits:
type: object
required:
- memory
- cpu
properties:
memory:
type: string
resourceMinimum: 100Mi # 104857600 bytes
resourceMaximum: 6G
cpu:
type: string
resourceMinimum: 100m
resourceMaximum: "2"
@@ -0,0 +1,14 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: nginx
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
memory: 250Mi
cpu: 0.05
@@ -10,5 +10,5 @@ spec:
image: nginx
resources:
limits:
memory: 128Mi
memory: 104857599
cpu: 1
@@ -10,5 +10,5 @@ spec:
image: nginx
resources:
limits:
memory: 128Mi
cpu: 5
memory: 250Mi
cpu: 1.5
@@ -0,0 +1,14 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: nginx
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
memory: 104857600
cpu: 1
@@ -19,10 +19,7 @@ spec:
app.kubernetes.io/name: basic-demo
app.kubernetes.io/instance: demo
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: farglebargle
whenUnsatisfiable: ScheduleAnyway
topologySpreadConstraints: []
containers:
- name: basic-demo
image: "quay.io/fairwinds/docker-demo:latest"
@@ -21,8 +21,13 @@ spec:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: "topology.kubernetes.io/zone"
whenUnsatisfiable: ScheduleAnyway
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: foo
matchLabelKeys:
- pod-template-hash
containers:
- name: basic-demo
image: "quay.io/fairwinds/docker-demo:latest"