Add ability to include/exclude namespaces.

This commit is contained in:
2024-09-05 08:15:05 +01:00
parent 0e34ab7a27
commit 9b1135cb7b
15 changed files with 124 additions and 37 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ COPY internal/ internal/
# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO # was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO
# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore, # the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,
# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. # by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -ldflags "-X shared.BACKUP_JOB_IMAGE=ghcr.io/lukaszraczylo/kubernetes-images-sync-worker:v${IMAGE_VERSION_TAG}" -a -o manager cmd/main.go RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -ldflags "-X github.com/lukaszraczylo/kubernetes-images-sync-operator/internal/shared.BACKUP_JOB_IMAGE=ghcr.io/lukaszraczylo/kubernetes-images-sync-worker:${IMAGE_VERSION_TAG}" -a -o manager cmd/main.go
# Use distroless as minimal base image to package the manager binary # Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details # Refer to https://github.com/GoogleContainerTools/distroless for more details
+5 -3
View File
@@ -10,6 +10,8 @@ ifeq ($(CURRENT_VERSION),)
$(error Failed to extract version number) $(error Failed to extract version number)
endif endif
IMAGE_VERSION_TAG ?= $(CURRENT_VERSION)
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN)) ifeq (,$(shell go env GOBIN))
GOBIN=$(shell go env GOPATH)/bin GOBIN=$(shell go env GOPATH)/bin
@@ -87,11 +89,11 @@ lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes
.PHONY: build .PHONY: build
build: manifests generate fmt vet ## Build manager binary. build: manifests generate fmt vet ## Build manager binary.
go build -o bin/manager cmd/main.go go build -ldflags "-X github.com/lukaszraczylo/kubernetes-images-sync-operator/internal/shared.BACKUP_JOB_IMAGE=ghcr.io/lukaszraczylo/kubernetes-images-sync-worker:$(IMAGE_VERSION_TAG)" -o bin/manager cmd/main.go
.PHONY: run .PHONY: run
run: manifests generate fmt vet ## Run a controller from your host. run: manifests generate fmt vet ## Run a controller from your host.
go run ./cmd/main.go go run -ldflags "-X github.com/lukaszraczylo/kubernetes-images-sync-operator/internal/shared.BACKUP_JOB_IMAGE=ghcr.io/lukaszraczylo/kubernetes-images-sync-worker:$(IMAGE_VERSION_TAG)" ./cmd/main.go
# If you wish to build the manager image targeting other platforms you can use the --platform flag. # If you wish to build the manager image targeting other platforms you can use the --platform flag.
# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it. # (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it.
@@ -231,7 +233,7 @@ release-chart:
cr package --config ../../chart-releaser.yaml; cr package --config ../../chart-releaser.yaml;
cd ../helm-charts/; git add -A charts/packages; git fix; git push; cd ../helm-charts/; git add -A charts/packages; git fix; git push;
cd ../helm-charts/charts/${CHART_NAME}; cr upload --config ../../chart-releaser.yaml --skip-existing; cd ../helm-charts/charts/${CHART_NAME}; cr upload --config ../../chart-releaser.yaml --skip-existing;
cd ../helm-charts/charts/${CHART_NAME}; rm -fr .cr-index; mkdir .cr-index; cr index --config ../../chart-releaser.yaml; cp .cr-index/index.yaml ../../index.yaml; || true cd ../helm-charts/charts/${CHART_NAME}; rm -fr .cr-index; mkdir .cr-index; cr index --config ../../chart-releaser.yaml; cp .cr-index/index.yaml ../../index.yaml;
git fix; git push git fix; git push
# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist # go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist
+11
View File
@@ -33,9 +33,20 @@ spec:
# Excludes will remove all images with listed wording from the backup list # Excludes will remove all images with listed wording from the backup list
# excludes: # excludes:
# - nginx # - nginx
# Includes will add ONLY images with listed wording to the backup list # Includes will add ONLY images with listed wording to the backup list
includes: includes:
- busybox - busybox
# Works only with images within specified namespaces
# namespaces:
# - default
# - longhorn
# Works with all images EXCEPT of the ones within namespaces specified
# excludedNamespaces:
# - my-awesome-namespace
basePath: /images # base path in the target directory basePath: /images # base path in the target directory
storage: storage:
target: S3 # file backup is not ready yet target: S3 # file backup is not ready yet
+8 -7
View File
@@ -28,13 +28,14 @@ import (
// +kubebuilder:printcolumn:name="Path",type="string",JSONPath=".spec.exportPath" // +kubebuilder:printcolumn:name="Path",type="string",JSONPath=".spec.exportPath"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
type ClusterImageSpec struct { type ClusterImageSpec struct {
Image string `json:"image,omitempty"` Image string `json:"image,omitempty"`
Tag string `json:"tag,omitempty"` Tag string `json:"tag,omitempty"`
Sha string `json:"sha,omitempty"` Sha string `json:"sha,omitempty"`
FullName string `json:"fullName,omitempty"` // Because I'm lazy and it's easier to pull that way FullName string `json:"fullName,omitempty"` // Because I'm lazy and it's easier to pull that way
Storage string `json:"storage,omitempty"` Storage string `json:"storage,omitempty"`
ExportName string `json:"exportName"` ExportName string `json:"exportName"`
ExportPath string `json:"exportPath,omitempty"` ExportPath string `json:"exportPath,omitempty"`
ImageNamespace string `json:"imageNamespace,omitempty"`
} }
// ClusterImageStatus defines the observed state of ClusterImage // ClusterImageStatus defines the observed state of ClusterImage
@@ -54,7 +54,9 @@ type ClusterImageExportSpec struct {
// Exclude images which contain these strings // Exclude images which contain these strings
Excludes []string `json:"excludes,omitempty"` Excludes []string `json:"excludes,omitempty"`
// Include only images which contain these strings // Include only images which contain these strings
Includes []string `json:"includes,omitempty"` Includes []string `json:"includes,omitempty"`
Namespaces []string `json:"namespaces,omitempty"`
ExcludedNamespaces []string `json:"excludedNamespaces,omitempty"`
// Base path for the export - both file and S3 // Base path for the export - both file and S3
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=255 // +kubebuilder:validation:MaxLength=255
@@ -124,6 +124,16 @@ func (in *ClusterImageExportSpec) DeepCopyInto(out *ClusterImageExportSpec) {
*out = make([]string, len(*in)) *out = make([]string, len(*in))
copy(*out, *in) copy(*out, *in)
} }
if in.Namespaces != nil {
in, out := &in.Namespaces, &out.Namespaces
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ExcludedNamespaces != nil {
in, out := &in.ExcludedNamespaces, &out.ExcludedNamespaces
*out = make([]string, len(*in))
copy(*out, *in)
}
out.Storage = in.Storage out.Storage = in.Storage
} }
+2 -2
View File
@@ -10,9 +10,9 @@ description: |
type: application type: application
version: 0.1.5 version: 0.1.7
appVersion: "0.1.5" appVersion: "0.1.7"
home: https://github.com/lukaszraczylo/kubernetes-images-sync-operator home: https://github.com/lukaszraczylo/kubernetes-images-sync-operator
+2
View File
@@ -76,6 +76,8 @@ spec:
type: string type: string
image: image:
type: string type: string
imageNamespace:
type: string
sha: sha:
type: string type: string
storage: storage:
@@ -61,6 +61,10 @@ spec:
createdAt: createdAt:
format: date-time format: date-time
type: string type: string
excludedNamespaces:
items:
type: string
type: array
excludes: excludes:
description: Exclude images which contain these strings description: Exclude images which contain these strings
items: items:
@@ -75,6 +79,10 @@ spec:
type: integer type: integer
name: name:
type: string type: string
namespaces:
items:
type: string
type: array
storage: storage:
description: ClusterImageStorageSpec defines the desired state of ClusterImageStorage description: ClusterImageStorageSpec defines the desired state of ClusterImageStorage
properties: properties:
+1 -1
View File
@@ -11,7 +11,7 @@ cmRaczyloCom:
- ALL - ALL
image: image:
repository: ghcr.io/lukaszraczylo/kubernetes-images-sync-operator repository: ghcr.io/lukaszraczylo/kubernetes-images-sync-operator
tag: 0.1.5 tag: 0.1.7
resources: resources:
limits: limits:
cpu: 500m cpu: 500m
@@ -61,6 +61,10 @@ spec:
createdAt: createdAt:
format: date-time format: date-time
type: string type: string
excludedNamespaces:
items:
type: string
type: array
excludes: excludes:
description: Exclude images which contain these strings description: Exclude images which contain these strings
items: items:
@@ -75,6 +79,10 @@ spec:
type: integer type: integer
name: name:
type: string type: string
namespaces:
items:
type: string
type: array
storage: storage:
description: ClusterImageStorageSpec defines the desired state of description: ClusterImageStorageSpec defines the desired state of
ClusterImageStorage ClusterImageStorage
@@ -75,6 +75,8 @@ spec:
type: string type: string
image: image:
type: string type: string
imageNamespace:
type: string
sha: sha:
type: string type: string
storage: storage:
@@ -130,13 +130,14 @@ func (r *ClusterImageExportReconciler) Reconcile(ctx context.Context, req ctrl.R
}, },
}, },
Spec: raczylocomv1.ClusterImageSpec{ Spec: raczylocomv1.ClusterImageSpec{
Image: image.Image, Image: image.Image,
Tag: image.Tag, Tag: image.Tag,
Sha: image.Sha, Sha: image.Sha,
FullName: image.FullName, FullName: image.FullName,
Storage: clusterImageExport.Spec.Storage.StorageTarget, ImageNamespace: image.ImageNamespace,
ExportName: clusterImageExport.Name, Storage: clusterImageExport.Spec.Storage.StorageTarget,
ExportPath: clusterImageExport.Spec.BasePath, ExportName: clusterImageExport.Name,
ExportPath: clusterImageExport.Spec.BasePath,
}, },
} }
@@ -211,6 +212,14 @@ func (r *ClusterImageExportReconciler) listImagesInCluster(ctx context.Context,
containersList = shared.RemoveExcludedImages(containersList, clusterImageExport.Spec.Excludes) containersList = shared.RemoveExcludedImages(containersList, clusterImageExport.Spec.Excludes)
} }
if len(clusterImageExport.Spec.Namespaces) > 0 {
containersList = shared.FilterOnlyFromNamespaces(containersList, clusterImageExport.Spec.Namespaces)
}
if len(clusterImageExport.Spec.ExcludedNamespaces) > 0 {
containersList = shared.FilterOutWholeNamespaces(containersList, clusterImageExport.Spec.ExcludedNamespaces)
}
containersList = shared.RemoveDuplicates(containersList) containersList = shared.RemoveDuplicates(containersList)
l.Info("List of containers in the cluster", "containers", containersList) l.Info("List of containers in the cluster", "containers", containersList)
+38 -7
View File
@@ -5,10 +5,9 @@ import (
"strings" "strings"
) )
const ( var BACKUP_JOB_IMAGE = "ghcr.io/lukaszraczylo/kubernetes-images-sync-worker:1.0.2"
// JOB IMAGES
BACKUP_JOB_IMAGE = "ghcr.io/lukaszraczylo/kubernetes-images-sync-worker:1.0.2"
const (
// AVAILABLE STATUSES // AVAILABLE STATUSES
STATUS_PENDING = "PENDING" STATUS_PENDING = "PENDING"
STATUS_STARTING = "STARTING" STATUS_STARTING = "STARTING"
@@ -24,10 +23,11 @@ const (
) )
type Container struct { type Container struct {
Image string `json:"image"` Image string `json:"image"`
Tag string `json:"tag"` Tag string `json:"tag"`
Sha string `json:"sha"` Sha string `json:"sha"`
FullName string `json:"fullName"` FullName string `json:"fullName"`
ImageNamespace string `json:"imageNamespace"`
} }
type ContainersList struct { type ContainersList struct {
@@ -96,3 +96,34 @@ func NormalizeImageName(name string) string {
// Trim leading and trailing hyphens // Trim leading and trailing hyphens
return strings.Trim(normalized, "-") return strings.Trim(normalized, "-")
} }
// filterOnlyFromNamespaces filters out containers from namespaces that are not in the list
func FilterOnlyFromNamespaces(containers ContainersList, namespaces []string) ContainersList {
result := ContainersList{}
for _, container := range containers.Containers {
for _, namespace := range namespaces {
if container.ImageNamespace == namespace {
result.Containers = append(result.Containers, container)
}
}
}
return result
}
// filterOutWholeNamespaces filters out containers from namespaces that are in the list
func FilterOutWholeNamespaces(containers ContainersList, namespaces []string) ContainersList {
result := ContainersList{}
for _, container := range containers.Containers {
excluded := false
for _, namespace := range namespaces {
if container.ImageNamespace == namespace {
excluded = true
break
}
}
if !excluded {
result.Containers = append(result.Containers, container)
}
}
return result
}
+9 -8
View File
@@ -64,7 +64,7 @@ func processContainerName(containerName string) (Container, error) {
return cnt, nil return cnt, nil
} }
func processContainers[T K8sResource](resource T, containersList *ContainersList) error { func processContainers[T K8sResource](resource T, namespace string, containersList *ContainersList) error {
podSpec := resource.GetPodSpec() podSpec := resource.GetPodSpec()
if podSpec == nil { if podSpec == nil {
return fmt.Errorf("nil PodSpec") return fmt.Errorf("nil PodSpec")
@@ -72,13 +72,13 @@ func processContainers[T K8sResource](resource T, containersList *ContainersList
allContainers := append(podSpec.Containers, podSpec.InitContainers...) allContainers := append(podSpec.Containers, podSpec.InitContainers...)
for _, container := range allContainers { for _, container := range allContainers {
if err := processContainer(container.Image, containersList); err != nil { if err := processContainer(container.Image, namespace, containersList); err != nil {
return err return err
} }
} }
for _, container := range podSpec.EphemeralContainers { for _, container := range podSpec.EphemeralContainers {
if err := processContainer(container.EphemeralContainerCommon.Image, containersList); err != nil { if err := processContainer(container.EphemeralContainerCommon.Image, namespace, containersList); err != nil {
return err return err
} }
} }
@@ -87,11 +87,12 @@ func processContainers[T K8sResource](resource T, containersList *ContainersList
} }
// processContainer handles the processing of a single container image // processContainer handles the processing of a single container image
func processContainer(image string, containersList *ContainersList) error { func processContainer(image string, containerNamespace string, containersList *ContainersList) error {
cnt, err := processContainerName(image) cnt, err := processContainerName(image)
if err != nil { if err != nil {
return fmt.Errorf("failed to process container name: %s - %w", image, err) return fmt.Errorf("failed to process container name: %s - %w", image, err)
} }
cnt.ImageNamespace = containerNamespace
containersList.Containers = append(containersList.Containers, cnt) containersList.Containers = append(containersList.Containers, cnt)
return nil return nil
} }
@@ -105,25 +106,25 @@ func ListAndProcessResources[T K8sResource, L client.ObjectList](ctx context.Con
switch typedList := any(list).(type) { switch typedList := any(list).(type) {
case *appsv1.DeploymentList: case *appsv1.DeploymentList:
for i := range typedList.Items { for i := range typedList.Items {
if err := processContainers((*DeploymentWrapper)(&typedList.Items[i]), containersList); err != nil { if err := processContainers((*DeploymentWrapper)(&typedList.Items[i]), typedList.Items[i].Namespace, containersList); err != nil {
return err return err
} }
} }
case *batchv1.JobList: case *batchv1.JobList:
for i := range typedList.Items { for i := range typedList.Items {
if err := processContainers((*JobWrapper)(&typedList.Items[i]), containersList); err != nil { if err := processContainers((*JobWrapper)(&typedList.Items[i]), typedList.Items[i].Namespace, containersList); err != nil {
return err return err
} }
} }
case *appsv1.DaemonSetList: case *appsv1.DaemonSetList:
for i := range typedList.Items { for i := range typedList.Items {
if err := processContainers((*DaemonSetWrapper)(&typedList.Items[i]), containersList); err != nil { if err := processContainers((*DaemonSetWrapper)(&typedList.Items[i]), typedList.Items[i].Namespace, containersList); err != nil {
return err return err
} }
} }
case *batchv1.CronJobList: case *batchv1.CronJobList:
for i := range typedList.Items { for i := range typedList.Items {
if err := processContainers((*CronJobWrapper)(&typedList.Items[i]), containersList); err != nil { if err := processContainers((*CronJobWrapper)(&typedList.Items[i]), typedList.Items[i].Namespace, containersList); err != nil {
return err return err
} }
} }