mirror of
https://github.com/lukaszraczylo/kubernetes-images-sync-operator.git
synced 2026-06-05 22:53:39 +00:00
Add ability to include/exclude namespaces.
This commit is contained in:
+1
-1
@@ -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
|
||||
# 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.
|
||||
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
|
||||
# Refer to https://github.com/GoogleContainerTools/distroless for more details
|
||||
|
||||
@@ -10,6 +10,8 @@ ifeq ($(CURRENT_VERSION),)
|
||||
$(error Failed to extract version number)
|
||||
endif
|
||||
|
||||
IMAGE_VERSION_TAG ?= $(CURRENT_VERSION)
|
||||
|
||||
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
|
||||
ifeq (,$(shell go env GOBIN))
|
||||
GOBIN=$(shell go env GOPATH)/bin
|
||||
@@ -87,11 +89,11 @@ lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes
|
||||
|
||||
.PHONY: build
|
||||
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
|
||||
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.
|
||||
# (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;
|
||||
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}; 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
|
||||
|
||||
# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist
|
||||
|
||||
@@ -33,9 +33,20 @@ spec:
|
||||
# Excludes will remove all images with listed wording from the backup list
|
||||
# excludes:
|
||||
# - nginx
|
||||
|
||||
# Includes will add ONLY images with listed wording to the backup list
|
||||
includes:
|
||||
- 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
|
||||
storage:
|
||||
target: S3 # file backup is not ready yet
|
||||
|
||||
@@ -28,13 +28,14 @@ import (
|
||||
// +kubebuilder:printcolumn:name="Path",type="string",JSONPath=".spec.exportPath"
|
||||
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
|
||||
type ClusterImageSpec struct {
|
||||
Image string `json:"image,omitempty"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
Sha string `json:"sha,omitempty"`
|
||||
FullName string `json:"fullName,omitempty"` // Because I'm lazy and it's easier to pull that way
|
||||
Storage string `json:"storage,omitempty"`
|
||||
ExportName string `json:"exportName"`
|
||||
ExportPath string `json:"exportPath,omitempty"`
|
||||
Image string `json:"image,omitempty"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
Sha string `json:"sha,omitempty"`
|
||||
FullName string `json:"fullName,omitempty"` // Because I'm lazy and it's easier to pull that way
|
||||
Storage string `json:"storage,omitempty"`
|
||||
ExportName string `json:"exportName"`
|
||||
ExportPath string `json:"exportPath,omitempty"`
|
||||
ImageNamespace string `json:"imageNamespace,omitempty"`
|
||||
}
|
||||
|
||||
// ClusterImageStatus defines the observed state of ClusterImage
|
||||
|
||||
@@ -54,7 +54,9 @@ type ClusterImageExportSpec struct {
|
||||
// Exclude images which contain these strings
|
||||
Excludes []string `json:"excludes,omitempty"`
|
||||
// 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
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:MaxLength=255
|
||||
|
||||
@@ -124,6 +124,16 @@ func (in *ClusterImageExportSpec) DeepCopyInto(out *ClusterImageExportSpec) {
|
||||
*out = make([]string, len(*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
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -10,9 +10,9 @@ description: |
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -76,6 +76,8 @@ spec:
|
||||
type: string
|
||||
image:
|
||||
type: string
|
||||
imageNamespace:
|
||||
type: string
|
||||
sha:
|
||||
type: string
|
||||
storage:
|
||||
|
||||
@@ -61,6 +61,10 @@ spec:
|
||||
createdAt:
|
||||
format: date-time
|
||||
type: string
|
||||
excludedNamespaces:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
excludes:
|
||||
description: Exclude images which contain these strings
|
||||
items:
|
||||
@@ -75,6 +79,10 @@ spec:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
namespaces:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
storage:
|
||||
description: ClusterImageStorageSpec defines the desired state of ClusterImageStorage
|
||||
properties:
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ cmRaczyloCom:
|
||||
- ALL
|
||||
image:
|
||||
repository: ghcr.io/lukaszraczylo/kubernetes-images-sync-operator
|
||||
tag: 0.1.5
|
||||
tag: 0.1.7
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
|
||||
@@ -61,6 +61,10 @@ spec:
|
||||
createdAt:
|
||||
format: date-time
|
||||
type: string
|
||||
excludedNamespaces:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
excludes:
|
||||
description: Exclude images which contain these strings
|
||||
items:
|
||||
@@ -75,6 +79,10 @@ spec:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
namespaces:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
storage:
|
||||
description: ClusterImageStorageSpec defines the desired state of
|
||||
ClusterImageStorage
|
||||
|
||||
@@ -75,6 +75,8 @@ spec:
|
||||
type: string
|
||||
image:
|
||||
type: string
|
||||
imageNamespace:
|
||||
type: string
|
||||
sha:
|
||||
type: string
|
||||
storage:
|
||||
|
||||
@@ -130,13 +130,14 @@ func (r *ClusterImageExportReconciler) Reconcile(ctx context.Context, req ctrl.R
|
||||
},
|
||||
},
|
||||
Spec: raczylocomv1.ClusterImageSpec{
|
||||
Image: image.Image,
|
||||
Tag: image.Tag,
|
||||
Sha: image.Sha,
|
||||
FullName: image.FullName,
|
||||
Storage: clusterImageExport.Spec.Storage.StorageTarget,
|
||||
ExportName: clusterImageExport.Name,
|
||||
ExportPath: clusterImageExport.Spec.BasePath,
|
||||
Image: image.Image,
|
||||
Tag: image.Tag,
|
||||
Sha: image.Sha,
|
||||
FullName: image.FullName,
|
||||
ImageNamespace: image.ImageNamespace,
|
||||
Storage: clusterImageExport.Spec.Storage.StorageTarget,
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
l.Info("List of containers in the cluster", "containers", containersList)
|
||||
|
||||
|
||||
@@ -5,10 +5,9 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// JOB IMAGES
|
||||
BACKUP_JOB_IMAGE = "ghcr.io/lukaszraczylo/kubernetes-images-sync-worker:1.0.2"
|
||||
var BACKUP_JOB_IMAGE = "ghcr.io/lukaszraczylo/kubernetes-images-sync-worker:1.0.2"
|
||||
|
||||
const (
|
||||
// AVAILABLE STATUSES
|
||||
STATUS_PENDING = "PENDING"
|
||||
STATUS_STARTING = "STARTING"
|
||||
@@ -24,10 +23,11 @@ const (
|
||||
)
|
||||
|
||||
type Container struct {
|
||||
Image string `json:"image"`
|
||||
Tag string `json:"tag"`
|
||||
Sha string `json:"sha"`
|
||||
FullName string `json:"fullName"`
|
||||
Image string `json:"image"`
|
||||
Tag string `json:"tag"`
|
||||
Sha string `json:"sha"`
|
||||
FullName string `json:"fullName"`
|
||||
ImageNamespace string `json:"imageNamespace"`
|
||||
}
|
||||
|
||||
type ContainersList struct {
|
||||
@@ -96,3 +96,34 @@ func NormalizeImageName(name string) string {
|
||||
// Trim leading and trailing hyphens
|
||||
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
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ func processContainerName(containerName string) (Container, error) {
|
||||
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()
|
||||
if podSpec == nil {
|
||||
return fmt.Errorf("nil PodSpec")
|
||||
@@ -72,13 +72,13 @@ func processContainers[T K8sResource](resource T, containersList *ContainersList
|
||||
|
||||
allContainers := append(podSpec.Containers, podSpec.InitContainers...)
|
||||
for _, container := range allContainers {
|
||||
if err := processContainer(container.Image, containersList); err != nil {
|
||||
if err := processContainer(container.Image, namespace, containersList); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -87,11 +87,12 @@ func processContainers[T K8sResource](resource T, containersList *ContainersList
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to process container name: %s - %w", image, err)
|
||||
}
|
||||
cnt.ImageNamespace = containerNamespace
|
||||
containersList.Containers = append(containersList.Containers, cnt)
|
||||
return nil
|
||||
}
|
||||
@@ -105,25 +106,25 @@ func ListAndProcessResources[T K8sResource, L client.ObjectList](ctx context.Con
|
||||
switch typedList := any(list).(type) {
|
||||
case *appsv1.DeploymentList:
|
||||
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
|
||||
}
|
||||
}
|
||||
case *batchv1.JobList:
|
||||
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
|
||||
}
|
||||
}
|
||||
case *appsv1.DaemonSetList:
|
||||
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
|
||||
}
|
||||
}
|
||||
case *batchv1.CronJobList:
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user