Skip to content

Commit

Permalink
refactor: Merge resources package with kube package (aquasecurity#430)
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel Pacak <pacak.daniel@gmail.com>
  • Loading branch information
danielpacak authored Mar 13, 2021
1 parent d066379 commit 30c164c
Show file tree
Hide file tree
Showing 11 changed files with 348 additions and 46 deletions.
3 changes: 0 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,7 @@ itests-starboard: check-env get-ginkgo
-coverprofile=coverage.txt \
-coverpkg=github.com/aquasecurity/starboard/pkg/cmd,\
github.com/aquasecurity/starboard/pkg/plugin,\
github.com/aquasecurity/starboard/pkg/resources,\
github.com/aquasecurity/starboard/pkg/kube,\
github.com/aquasecurity/starboard/pkg/kube/pod,\
github.com/aquasecurity/starboard/pkg/kubebench,\
github.com/aquasecurity/starboard/pkg/kubehunter,\
github.com/aquasecurity/starboard/pkg/plugin/trivy,\
Expand All @@ -89,7 +87,6 @@ itests-starboard-operator: check-env get-ginkgo
github.com/aquasecurity/starboard/pkg/operator/predicate,\
github.com/aquasecurity/starboard/pkg/operator/controller,\
github.com/aquasecurity/starboard/pkg/plugin,\
github.com/aquasecurity/starboard/pkg/resources,\
github.com/aquasecurity/starboard/pkg/plugin/trivy,\
github.com/aquasecurity/starboard/pkg/plugin/polaris,\
github.com/aquasecurity/starboard/pkg/configauditreport,\
Expand Down
2 changes: 2 additions & 0 deletions pkg/kube/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// The kube package provides primitives for working with Kubernetes objects.
package kube
7 changes: 6 additions & 1 deletion pkg/kube/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ func GetPartialObjectFromKindAndNamespacedName(kind Kind, name types.NamespacedN
}
}

// GetPodSpec returns v1.PodSpec from the specified Kubernetes
// client.Object. Returns error if the given client.Object
// is not a Kubernetes workload.
func GetPodSpec(obj client.Object) (corev1.PodSpec, error) {
switch t := obj.(type) {
case *corev1.Pod:
Expand All @@ -132,8 +135,10 @@ func GetPodSpec(obj client.Object) (corev1.PodSpec, error) {
return (obj.(*appsv1.DaemonSet)).Spec.Template.Spec, nil
case *batchv1beta1.CronJob:
return (obj.(*batchv1beta1.CronJob)).Spec.JobTemplate.Spec.Template.Spec, nil
case *batchv1.Job:
return (obj.(*batchv1.Job)).Spec.Template.Spec, nil
default:
return corev1.PodSpec{}, fmt.Errorf("unsupported workload %T", t)
return corev1.PodSpec{}, fmt.Errorf("unsupported workload: %T", t)
}
}

Expand Down
246 changes: 246 additions & 0 deletions pkg/kube/object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ import (
"github.com/aquasecurity/starboard/pkg/kube"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
batchv1beta1 "k8s.io/api/batch/v1beta1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func TestObjectFromLabelsSet(t *testing.T) {
Expand Down Expand Up @@ -86,3 +92,243 @@ func TestContainerImages_AsJSON_And_FromJSON(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, containerImages, newContainerImages)
}

func TestGetPartialObjectFromKindAndNamespacedName(t *testing.T) {
partial := kube.GetPartialObjectFromKindAndNamespacedName(kube.KindReplicaSet, types.NamespacedName{
Namespace: "prod",
Name: "wordpress",
})
assert.Equal(t, kube.Object{
Kind: kube.KindReplicaSet,
Name: "wordpress",
Namespace: "prod",
}, partial)
}

func TestGetPodSpec(t *testing.T) {
testCases := []struct {
name string
object client.Object
expectedPodSpec corev1.PodSpec
expectedError string
}{
{
name: "Should return PodSpec for Pod",
object: &corev1.Pod{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "pod",
Image: "pod:1.3",
},
},
},
},
expectedPodSpec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "pod",
Image: "pod:1.3",
},
},
},
},
{
name: "Should return PodSpec for Deployment",
object: &appsv1.Deployment{
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "deployment",
Image: "deployment:2.4",
},
},
},
},
},
},
expectedPodSpec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "deployment",
Image: "deployment:2.4",
},
},
},
},
{
name: "Should return PodSpec for ReplicaSet",
object: &appsv1.ReplicaSet{
Spec: appsv1.ReplicaSetSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "replicaset",
Image: "replicaset:3.17",
},
},
},
},
},
},
expectedPodSpec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "replicaset",
Image: "replicaset:3.17",
},
},
},
},
{
name: "Should return PodSpec for ReplicationController",
object: &corev1.ReplicationController{
Spec: corev1.ReplicationControllerSpec{
Template: &corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "replicationcontroller",
Image: "replicationcontroller:latest",
},
},
},
},
},
},
expectedPodSpec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "replicationcontroller",
Image: "replicationcontroller:latest",
},
},
},
},
{
name: "Should return PodSpec for StatefulSet",
object: &appsv1.StatefulSet{
Spec: appsv1.StatefulSetSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "statefulset",
Image: "statefulset:8",
},
},
},
},
},
},
expectedPodSpec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "statefulset",
Image: "statefulset:8",
},
},
},
},
{
name: "Should return PodSpec for DaemonSet",
object: &appsv1.DaemonSet{
Spec: appsv1.DaemonSetSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "daemonset",
Image: "daemonset:1.1",
},
},
},
},
},
},
expectedPodSpec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "daemonset",
Image: "daemonset:1.1",
},
},
},
},
{
name: "Should return PodSpec for CronJob",
object: &batchv1beta1.CronJob{
Spec: batchv1beta1.CronJobSpec{
JobTemplate: batchv1beta1.JobTemplateSpec{
Spec: batchv1.JobSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "cronjob",
Image: "cronjob:5",
},
},
},
},
},
},
},
},
expectedPodSpec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "cronjob",
Image: "cronjob:5",
},
},
},
},
{
name: "Should return PodSpec for Job",
object: &batchv1.Job{
Spec: batchv1.JobSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "job",
Image: "job:2.8.2",
},
},
},
},
},
},
expectedPodSpec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "job",
Image: "job:2.8.2",
},
},
},
},
{
name: "Should return error for Service",
object: &corev1.Service{},
expectedError: "unsupported workload: *v1.Service",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
podSpec, err := kube.GetPodSpec(tc.object)
if tc.expectedError != "" {
require.EqualError(t, err, tc.expectedError)
} else {
require.NoError(t, err)
assert.Equal(t, tc.expectedPodSpec, podSpec)
}

})
}
}
37 changes: 21 additions & 16 deletions pkg/resources/resources.go → pkg/kube/resources.go
Original file line number Diff line number Diff line change
@@ -1,37 +1,42 @@
package resources
package kube

import (
"fmt"
"hash"
"hash/fnv"

"github.com/aquasecurity/starboard/pkg/kube"
"github.com/davecgh/go-spew/spew"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/rand"
)

func GetContainerImagesFromPodSpec(spec corev1.PodSpec) kube.ContainerImages {
images := kube.ContainerImages{}
// GetContainerImagesFromPodSpec returns a map of container names
// to container images from the specified v1.PodSpec.
func GetContainerImagesFromPodSpec(spec corev1.PodSpec) ContainerImages {
images := ContainerImages{}
for _, container := range spec.Containers {
images[container.Name] = container.Image
}
return images
}

func GetContainerImagesFromJob(job *batchv1.Job) (kube.ContainerImages, error) {
// GetContainerImagesFromJob returns a map of container names
// to container images from the specified v1.Job.
// The mapping is encoded as JSON value of the AnnotationContainerImages
// annotation.
func GetContainerImagesFromJob(job *batchv1.Job) (ContainerImages, error) {
var containerImagesAsJSON string
var ok bool

if containerImagesAsJSON, ok = job.Annotations[kube.AnnotationContainerImages]; !ok {
return nil, fmt.Errorf("job does not have required annotation: %s", kube.AnnotationContainerImages)
if containerImagesAsJSON, ok = job.Annotations[AnnotationContainerImages]; !ok {
return nil, fmt.Errorf("required annotation not set: %s", AnnotationContainerImages)
}
containerImages := kube.ContainerImages{}
containerImages := ContainerImages{}
err := containerImages.FromJSON(containerImagesAsJSON)
if err != nil {
return nil, fmt.Errorf("parsing job annotation: %s: %w", kube.AnnotationContainerImages, err)
return nil, fmt.Errorf("parsing annotation: %s: %w", AnnotationContainerImages, err)
}
return containerImages, nil
}
Expand All @@ -51,30 +56,30 @@ func GetContainerImagesFromJob(job *batchv1.Job) (kube.ContainerImages, error) {
// Pods created and controlled by third party frameworks, such as Argo workflow
// engine, are considered as unmanaged. Otherwise we'd need to maintain and
// extend the list of RBAC permissions over time.
// TODO Merge this method with OwnerResolver, which accepts kube.Object and resolves client.Object.
func GetImmediateOwnerReference(pod *corev1.Pod) kube.Object {
// TODO Merge this method with ObjectResolver, which accepts kube.Object and resolves client.Object.
func GetImmediateOwnerReference(pod *corev1.Pod) Object {
ownerRef := metav1.GetControllerOf(pod)

if ownerRef != nil {
switch ownerRef.Kind {
case "Pod", "ReplicaSet", "ReplicationController", "Deployment", "StatefulSet", "DaemonSet", "CronJob", "Job":
return kube.Object{
return Object{
Namespace: pod.Namespace,
Kind: kube.Kind(ownerRef.Kind),
Kind: Kind(ownerRef.Kind),
Name: ownerRef.Name,
}
}
}

// Pod owned by anything else is treated the same as an unmanaged pod
return kube.Object{
Kind: kube.KindPod,
return Object{
Kind: KindPod,
Namespace: pod.Namespace,
Name: pod.Name,
}
}

// ComputeHash returns a hash value calculated from pod spec.
// ComputeHash returns a hash value calculated from a given object.
// The hash will be safe encoded to avoid bad words.
func ComputeHash(obj interface{}) string {
podSpecHasher := fnv.New32a()
Expand Down
Loading

0 comments on commit 30c164c

Please sign in to comment.