Skip to content

Commit

Permalink
At a point with scan
Browse files Browse the repository at this point in the history
  • Loading branch information
Blaize Kaye committed Jun 23, 2024
1 parent 6c7de67 commit d835097
Show file tree
Hide file tree
Showing 2 changed files with 263 additions and 60 deletions.
227 changes: 167 additions & 60 deletions controllers/build_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,30 @@ package controllers

import (
"context"
"errors"
"fmt"
v1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
v12 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"strings"
)

// BuildReconciler reconciles a Build Pod object
type BuildReconciler struct {
client.Client
Scheme *runtime.Scheme
InsightsJWTSecret string
//ScanImageName string // TODO: make this something that's passed as an argument
}

const insightsScannedLabel = "insights.lagoon.sh/scanned"
const scanImageName = "imagecache.amazeeio.cloud/bomoko/insights-scan:latest"

//+kubebuilder:rbac:groups=core,resources=namespaces,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=core,resources=namespaces/status,verbs=get;update;patch
Expand All @@ -54,18 +60,47 @@ func (r *BuildReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
log := log.FromContext(ctx)

var buildPod corev1.Pod

if err := r.Get(ctx, req.NamespacedName, &buildPod); err != nil {
log.Error(err, "Unable to load Pod")
return ctrl.Result{}, client.IgnoreNotFound(err)
}

// we check the build pod itself to see if its status is good
if buildPod.Status.Phase == corev1.PodSucceeded {

imageList, err := r.scanDeployments(ctx, req, buildPod.Namespace)
if err != nil {
log.Error(err, "Unable to scan deployments")
return ctrl.Result{}, client.IgnoreNotFound(err)
}

// now we generate the pod spec we'd like to deploy
podspec, err := generateScanPodSpec(imageList, buildPod.Name, buildPod.Namespace)
if err != nil {
log.Error(err, "Unable to generate podspec")
return ctrl.Result{}, client.IgnoreNotFound(err)
}

// Remove any existing pods
err = r.killExistingScans(ctx, scannerNameFromBuildname(buildPod.Name), buildPod.Namespace)
if err != nil {
log.Error(err, "Unable to remove existing scan pods")
return ctrl.Result{}, client.IgnoreNotFound(err)
}

// Deploy scan pod
err = r.Client.Create(ctx, podspec)
if err != nil {
log.Error(err, "Couldn't create pod")
return ctrl.Result{}, client.IgnoreNotFound(err)
}

labels := buildPod.GetLabels()
// Let's label the pod as having been seen
labels[insightsScannedLabel] = "true"
buildPod.SetLabels(labels)
err := r.Update(ctx, &buildPod)
err = r.Update(ctx, &buildPod)
if err != nil {
log.Error(err, fmt.Sprintf("Unable to update pod labels: %v", req.NamespacedName.String()))
return ctrl.Result{}, client.IgnoreNotFound(err)
Expand All @@ -75,6 +110,123 @@ func (r *BuildReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
return ctrl.Result{}, nil
}

// scanDeployments will look at all the deployments in a namespace and
// if they're labeled correctly, bundle their images into a single scan
func (r *BuildReconciler) scanDeployments(ctx context.Context, req ctrl.Request, namespace string) ([]string, error) {

deploymentList := &v1.DeploymentList{}
err := r.List(ctx, deploymentList, client.InNamespace(namespace))
if err != nil {
return []string{}, err
}
var imageList []string
for _, d := range deploymentList.Items {
log.Log.Info(fmt.Sprintf("Found deployment '%v' in namespace '%v'\n", d.Name, namespace))

// TODO so we want to filter deployments based on the appropriate labels

// Let's get a list of all the images involved
for _, i := range d.Spec.Template.Spec.Containers {
imageList = append(imageList, i.Image)
log.Log.Info(fmt.Sprintf(" Found image: %v\n", i.Image))
}
}

return imageList, nil
}

func generateScanPodSpec(images []string, buildName string, namespace string) (*corev1.Pod, error) {

if len(images) == 0 {
return nil, errors.New("No images to scan")
}

insightScanImages := strings.Join(images, ",")

// Define PodSpec

podSpec := &corev1.Pod{
ObjectMeta: v12.ObjectMeta{
Namespace: namespace,
Name: scannerNameFromBuildname(buildName),
Labels: imageScanPodLabels(),
},
Spec: corev1.PodSpec{
ServiceAccountName: "lagoon-deployer",
Containers: []corev1.Container{
{
Name: "scanner",
Image: scanImageName,
Env: []corev1.EnvVar{
{
Name: "INSIGHT_SCAN_IMAGES",
Value: insightScanImages,
},
{
Name: "NAMESPACE",
Value: namespace,
},
},
ImagePullPolicy: "Always",
VolumeMounts: []corev1.VolumeMount{
{
Name: "lagoon-internal-registry-secret",
MountPath: "/home/.docker/",
ReadOnly: true,
},
},
},
},
RestartPolicy: "Never",
Volumes: []corev1.Volume{ // Here we have to mount the
{
Name: "lagoon-internal-registry-secret",
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: "lagoon-internal-registry-secret",
Items: []corev1.KeyToPath{
{Key: ".dockerconfigjson", Path: "config.json"},
},
},
},
},
},
},
}
return podSpec, nil
}

func (r *BuildReconciler) killExistingScans(ctx context.Context, newScannerName string, namespace string) error {
// Find pods in namespace with the image scan pod labels

podlist := &corev1.PodList{}
//ls := client.MatchingLabels{"insights.lagoon.sh/imagescanner": "scanning"}
ls := client.MatchingLabels(imageScanPodLabels())
ns := client.InNamespace(namespace)
err := r.Client.List(ctx, podlist, ns, ls)
if err != nil {
return err
}
for _, i := range podlist.Items {
if i.Name != newScannerName { // Then we have a rogue pod
//r.Client.Delete(ctx, &i)
log.Log.Info(fmt.Sprintf("Going to delete pod: %v", i.Name))
}
}
return nil
}

func imageScanPodLabels() map[string]string {
return map[string]string{
"insights.lagoon.sh/imagescanner": "scanning",
"lagoon.sh/buildName": "notabuild",
}
}

func scannerNameFromBuildname(buildName string) string {
return fmt.Sprintf("insights-scanner-%v", buildName)
}

func successfulBuildPodsPredicate() predicate.Predicate {
return predicate.Funcs{
CreateFunc: func(event event.CreateEvent) bool { return false },
Expand All @@ -84,14 +236,27 @@ func successfulBuildPodsPredicate() predicate.Predicate {
//TODO: need the logic here to find the appropriate types
// that is, successful and build pods

// TODO: remove when happy with process - and we want it to run across everything
if event.ObjectNew.GetNamespace() != "test6-drupal-example-simple-test1copy" {
return false
}

labels := event.ObjectNew.GetLabels()
_, err := getValueFromMap(labels, "lagoon.sh/buildName")
if err != nil {
log.Log.Info(fmt.Sprintf("Not a build pod, skipping : %v", event.ObjectNew.GetName()))
return false //this isn't a build pod
}

_, err = getValueFromMap(labels, "insights.lagoon.sh/imagescanner")
if err == nil {
log.Log.Info(fmt.Sprintf("Found a scanner pod, skipping : %v", event.ObjectNew.GetName()))
return false //this isn't a build pod
}

val, err := getValueFromMap(labels, insightsScannedLabel)
if err == nil && val == "false" {
if err == nil {
log.Log.Info(fmt.Sprintf("Build pod already scanned, skipping : %v - value: %v", event.ObjectNew.GetName(), val))
return false
}
return true
Expand All @@ -103,64 +268,6 @@ func successfulBuildPodsPredicate() predicate.Predicate {
}
}

//
//func activeNamespacePredicate(tokenTargetLabel string) predicate.Predicate {
// return predicate.Funcs{
// CreateFunc: func(event event.CreateEvent) bool {
// labels := event.Object.GetLabels()
// _, err := getValueFromMap(labels, "lagoon.sh/environmentId")
// if err != nil {
// return false
// }
// _, err = getValueFromMap(labels, "lagoon.sh/project")
// if err != nil {
// return false
// }
// _, err = getValueFromMap(labels, "lagoon.sh/environment")
// if err != nil {
// return false
// }
//
// if tokenTargetLabel != "" {
// _, err = getValueFromMap(labels, tokenTargetLabel)
// if err != nil {
// return false
// }
// }
//
// return true
// },
// DeleteFunc: func(deleteEvent event.DeleteEvent) bool {
// return false
// },
// UpdateFunc: func(updateEvent event.UpdateEvent) bool {
// labels := updateEvent.ObjectNew.GetLabels()
// _, err := getValueFromMap(labels, "lagoon.sh/environmentId")
// if err != nil {
// return false
// }
// _, err = getValueFromMap(labels, "lagoon.sh/project")
// if err != nil {
// return false
// }
// _, err = getValueFromMap(labels, "lagoon.sh/environment")
// if err != nil {
// return false
// }
// if tokenTargetLabel != "" {
// _, err = getValueFromMap(labels, tokenTargetLabel)
// if err != nil {
// return false
// }
// }
// return true
// },
// GenericFunc: func(genericEvent event.GenericEvent) bool {
// return false
// },
// }
//}

// SetupWithManager sets up the controller with the Manager.
func (r *BuildReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
Expand Down
96 changes: 96 additions & 0 deletions controllers/build_controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package controllers

import (
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"reflect"
"testing"
)

func Test_generateScanPodSpec(t *testing.T) {
type args struct {
images []string
buildName string
namespace string
}
tests := []struct {
name string
args args
want *corev1.Pod
wantErr bool
}{
{
name: "No images",
args: args{images: nil, namespace: "testns", buildName: "buildnamehere"},
wantErr: true,
},
{
name: "FoundImages",
args: args{
images: []string{"image1", "image2"},
namespace: "testns",
buildName: "buildnamehere",
},
want: &corev1.Pod{
ObjectMeta: v1.ObjectMeta{
Namespace: "testns",
Name: scannerNameFromBuildname("buildnamehere"),
Labels: imageScanPodLabels(),
},
Spec: corev1.PodSpec{
ServiceAccountName: "lagoon-deployer",
Containers: []corev1.Container{
{
Name: "scanner",
Image: scanImageName,
Env: []corev1.EnvVar{
{
Name: "INSIGHT_SCAN_IMAGES",
Value: "image1,image2",
},
{
Name: "NAMESPACE",
Value: "testns",
},
},
ImagePullPolicy: "Always",
VolumeMounts: []corev1.VolumeMount{
{
Name: "lagoon-internal-registry-secret",
MountPath: "/home/.docker/",
ReadOnly: true,
},
},
},
},
Volumes: []corev1.Volume{
{
Name: "lagoon-internal-registry-secret",
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: "lagoon-internal-registry-secret",
Items: []corev1.KeyToPath{
{Key: ".dockerconfigjson", Path: "config.json"},
},
},
},
},
},
RestartPolicy: "Never",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := generateScanPodSpec(tt.args.images, tt.args.buildName, tt.args.namespace)
if (err != nil) != tt.wantErr {
t.Errorf("generateScanPodSpec() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("generateScanPodSpec() got = %v, want %v", got, tt.want)
}
})
}
}

0 comments on commit d835097

Please sign in to comment.