mirror of
https://github.com/lukaszraczylo/kubernetes-images-sync-operator.git
synced 2026-06-08 23:09:23 +00:00
Clean up the code and basic improvements.
This commit is contained in:
@@ -6,9 +6,9 @@ CHART_DIR = charts/kube-images-sync-operator
|
||||
# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
|
||||
ENVTEST_K8S_VERSION = 1.31.0
|
||||
|
||||
CURRENT_VERSION = $(shell semver-gen generate -l | awk '/^SEMVER/ {print $$NF}')
|
||||
CURRENT_VERSION ?= $(shell semver-gen generate -l 2>/dev/null | awk '/^SEMVER/ {print $$NF}')
|
||||
ifeq ($(CURRENT_VERSION),)
|
||||
$(error Failed to extract version number)
|
||||
CURRENT_VERSION = 0.5.54
|
||||
endif
|
||||
|
||||
IMAGE_VERSION_TAG ?= $(CURRENT_VERSION)
|
||||
@@ -174,9 +174,9 @@ HELMIFY ?= helmify
|
||||
|
||||
## Tool Versions
|
||||
KUSTOMIZE_VERSION ?= v5.4.3
|
||||
CONTROLLER_TOOLS_VERSION ?= v0.16.1
|
||||
CONTROLLER_TOOLS_VERSION ?= v0.17.1
|
||||
ENVTEST_VERSION ?= release-0.19
|
||||
GOLANGCI_LINT_VERSION ?= v1.59.1
|
||||
GOLANGCI_LINT_VERSION ?= v1.62.2
|
||||
|
||||
.PHONY: print-version
|
||||
print-version:
|
||||
|
||||
@@ -40,7 +40,7 @@ type ClusterImageStorageS3 struct {
|
||||
|
||||
// ClusterImageStorageSpec defines the desired state of ClusterImageStorage
|
||||
type ClusterImageStorageSpec struct {
|
||||
// +kubebuilder:validation:Enum=file;S3
|
||||
// +kubebuilder:validation:Enum=FILE;S3
|
||||
StorageTarget string `json:"target"`
|
||||
S3 ClusterImageStorageS3 `json:"s3,omitempty"`
|
||||
}
|
||||
@@ -67,8 +67,9 @@ type ClusterImageExportSpec struct {
|
||||
JobAnnotations map[string]string `json:"jobAnnotations,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"`
|
||||
// +kubebuilder:validation.Minimum=1
|
||||
// +kubebuilder:validation.Maximum=100
|
||||
// +kubebuilder:validation:Minimum=1
|
||||
// +kubebuilder:validation:Maximum=100
|
||||
// +kubebuilder:default=5
|
||||
MaxConcurrentJobs int `json:"maxConcurrentJobs"`
|
||||
AdditionalImages []string `json:"additionalImages,omitempty"`
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: clusterimages.raczylo.com
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.16.1
|
||||
controller-gen.kubebuilder.io/version: v0.17.1
|
||||
labels:
|
||||
{{- include "chart.labels" . | nindent 4 }}
|
||||
spec:
|
||||
@@ -124,9 +124,3 @@ spec:
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -3,7 +3,7 @@ kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: clusterimageexports.raczylo.com
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.16.1
|
||||
controller-gen.kubebuilder.io/version: v0.17.1
|
||||
labels:
|
||||
{{- include "chart.labels" . | nindent 4 }}
|
||||
spec:
|
||||
@@ -37,7 +37,8 @@ spec:
|
||||
name: v1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: ClusterImageExport is the Schema for the clusterimageexports API
|
||||
description: ClusterImageExport is the Schema for the clusterimageexports
|
||||
API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
@@ -108,6 +109,9 @@ spec:
|
||||
type: string
|
||||
type: object
|
||||
maxConcurrentJobs:
|
||||
default: 5
|
||||
maximum: 100
|
||||
minimum: 1
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
@@ -116,7 +120,8 @@ spec:
|
||||
type: string
|
||||
type: array
|
||||
storage:
|
||||
description: ClusterImageStorageSpec defines the desired state of ClusterImageStorage
|
||||
description: ClusterImageStorageSpec defines the desired state of
|
||||
ClusterImageStorage
|
||||
properties:
|
||||
s3:
|
||||
properties:
|
||||
@@ -134,8 +139,8 @@ spec:
|
||||
region:
|
||||
type: string
|
||||
roleARN:
|
||||
description: RoleARN is the ARN of the role to be used for the
|
||||
deployment
|
||||
description: RoleARN is the ARN of the role to be used for
|
||||
the deployment
|
||||
type: string
|
||||
secretKey:
|
||||
type: string
|
||||
@@ -150,7 +155,7 @@ spec:
|
||||
type: object
|
||||
target:
|
||||
enum:
|
||||
- file
|
||||
- FILE
|
||||
- S3
|
||||
type: string
|
||||
required:
|
||||
@@ -179,9 +184,3 @@ spec:
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.16.1
|
||||
controller-gen.kubebuilder.io/version: v0.17.1
|
||||
name: clusterimageexports.raczylo.com
|
||||
spec:
|
||||
group: raczylo.com
|
||||
@@ -108,6 +108,9 @@ spec:
|
||||
type: string
|
||||
type: object
|
||||
maxConcurrentJobs:
|
||||
default: 5
|
||||
maximum: 100
|
||||
minimum: 1
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
@@ -151,7 +154,7 @@ spec:
|
||||
type: object
|
||||
target:
|
||||
enum:
|
||||
- file
|
||||
- FILE
|
||||
- S3
|
||||
type: string
|
||||
required:
|
||||
|
||||
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.16.1
|
||||
controller-gen.kubebuilder.io/version: v0.17.1
|
||||
name: clusterimages.raczylo.com
|
||||
spec:
|
||||
group: raczylo.com
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
# This patch adds the args to allow exposing the metrics endpoint using HTTPS
|
||||
# This patch adds the args to allow exposing the metrics endpoint securely using HTTPS
|
||||
- op: add
|
||||
path: /spec/template/spec/containers/0/args/0
|
||||
value: --metrics-bind-address=:8443
|
||||
- op: add
|
||||
path: /spec/template/spec/containers/0/args/1
|
||||
value: --metrics-secure
|
||||
|
||||
+67
-68
@@ -4,71 +4,70 @@ kind: ClusterRole
|
||||
metadata:
|
||||
name: impex-mgr
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- deletecollection
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- daemonsets
|
||||
- deployments
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- batch
|
||||
resources:
|
||||
- cronjobs
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- batch
|
||||
resources:
|
||||
- jobs
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- "*"
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- "*/finalizers"
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- "*/status"
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- daemonsets
|
||||
- deployments
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- batch
|
||||
resources:
|
||||
- cronjobs
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- batch
|
||||
resources:
|
||||
- jobs
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- '*'
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- '*/finalizers'
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- '*/status'
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
@@ -13,8 +14,9 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/utils/pointer"
|
||||
"k8s.io/utils/ptr"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
@@ -28,6 +30,7 @@ type ClusterImageReconciler struct {
|
||||
Scheme *runtime.Scheme
|
||||
MaxParallelJobs int
|
||||
ActiveJobs int
|
||||
activeJobsMu sync.Mutex // protects ActiveJobs counter
|
||||
KubeClient *kubernetes.Clientset
|
||||
}
|
||||
|
||||
@@ -66,7 +69,7 @@ func (r *ClusterImageReconciler) Reconcile(ctx context.Context, req ctrl.Request
|
||||
if err := r.Get(ctx, req.NamespacedName, latest); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
|
||||
latest.Status.Progress = shared.STATUS_PENDING
|
||||
if err := r.Status().Update(ctx, latest); err != nil {
|
||||
if errors.IsConflict(err) {
|
||||
@@ -80,7 +83,10 @@ func (r *ClusterImageReconciler) Reconcile(ctx context.Context, req ctrl.Request
|
||||
}
|
||||
|
||||
// If we've reached the maximum number of parallel jobs, requeue
|
||||
if r.ActiveJobs >= r.MaxParallelJobs && clusterImage.Status.Progress == shared.STATUS_PENDING {
|
||||
r.activeJobsMu.Lock()
|
||||
activeJobs := r.ActiveJobs
|
||||
r.activeJobsMu.Unlock()
|
||||
if activeJobs >= r.MaxParallelJobs && clusterImage.Status.Progress == shared.STATUS_PENDING {
|
||||
return ctrl.Result{RequeueAfter: time.Second * 30}, nil
|
||||
}
|
||||
|
||||
@@ -117,7 +123,7 @@ func (r *ClusterImageReconciler) handlePendingClusterImage(ctx context.Context,
|
||||
if err := r.Get(ctx, types.NamespacedName{Name: clusterImage.Name, Namespace: clusterImage.Namespace}, latest); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
|
||||
latest.Status.Progress = shared.STATUS_PRESENT
|
||||
if err := r.Status().Update(ctx, latest); err != nil {
|
||||
if errors.IsConflict(err) {
|
||||
@@ -151,7 +157,9 @@ func (r *ClusterImageReconciler) handlePendingClusterImage(ctx context.Context,
|
||||
}
|
||||
|
||||
// Increment the active jobs count
|
||||
r.activeJobsMu.Lock()
|
||||
r.ActiveJobs++
|
||||
r.activeJobsMu.Unlock()
|
||||
|
||||
return ctrl.Result{Requeue: true}, nil
|
||||
}
|
||||
@@ -217,7 +225,9 @@ func (r *ClusterImageReconciler) handleRunningClusterImage(ctx context.Context,
|
||||
}
|
||||
|
||||
latest.Status.Progress = shared.STATUS_SUCCESS
|
||||
r.activeJobsMu.Lock()
|
||||
r.ActiveJobs--
|
||||
r.activeJobsMu.Unlock()
|
||||
// Update the status before cleaning up the job
|
||||
if err := r.Status().Update(ctx, latest); err != nil {
|
||||
if errors.IsConflict(err) {
|
||||
@@ -232,7 +242,9 @@ func (r *ClusterImageReconciler) handleRunningClusterImage(ctx context.Context,
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
} else if existingJob.Status.Failed > 0 {
|
||||
r.activeJobsMu.Lock()
|
||||
r.ActiveJobs--
|
||||
r.activeJobsMu.Unlock()
|
||||
if clusterImage.Status.RetryCount < 3 {
|
||||
// Cleanup the failed job before retrying
|
||||
if err := r.cleanupJobAndPods(ctx, existingJob); err != nil {
|
||||
@@ -300,8 +312,22 @@ func (r *ClusterImageReconciler) handleRunningClusterImage(ctx context.Context,
|
||||
return r.updateClusterImageExportStatus(ctx, clusterImage)
|
||||
}
|
||||
func (r *ClusterImageReconciler) cleanupJobAndPods(ctx context.Context, job *v1batch.Job) error {
|
||||
// Add a short delay to allow status updates to propagate
|
||||
time.Sleep(2 * time.Second)
|
||||
// Wait for job status to propagate before deletion
|
||||
jobKey := types.NamespacedName{Name: job.Name, Namespace: job.Namespace}
|
||||
err := wait.PollUntilContextTimeout(ctx, 100*time.Millisecond, 5*time.Second, true, func(ctx context.Context) (done bool, err error) {
|
||||
currentJob := &v1batch.Job{}
|
||||
if err := r.Get(ctx, jobKey, currentJob); err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return true, nil // Job already deleted
|
||||
}
|
||||
return false, nil // Retry on transient errors
|
||||
}
|
||||
// Job status has been updated, proceed with deletion
|
||||
return currentJob.Status.Active == 0, nil
|
||||
})
|
||||
if err != nil && !errors.IsNotFound(err) && err != context.DeadlineExceeded {
|
||||
return fmt.Errorf("failed to wait for job status: %w", err)
|
||||
}
|
||||
|
||||
// Delete the job
|
||||
if err := r.Delete(ctx, job, client.PropagationPolicy(metav1.DeletePropagationBackground)); err != nil && !errors.IsNotFound(err) {
|
||||
@@ -374,8 +400,8 @@ func (r *ClusterImageReconciler) createBackupJob(ctx context.Context, clusterIma
|
||||
Kind: clusterImage.Kind,
|
||||
Name: clusterImage.Name,
|
||||
UID: clusterImage.UID,
|
||||
BlockOwnerDeletion: pointer.Bool(true),
|
||||
Controller: pointer.Bool(true),
|
||||
BlockOwnerDeletion: ptr.To(true),
|
||||
Controller: ptr.To(true),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package raczylocom
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"crypto/md5" // #nosec G501 - MD5 used for non-cryptographic unique identifiers only
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"k8s.io/utils/pointer"
|
||||
"k8s.io/utils/ptr"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
// ClusterImageExportReconciler reconciles a ClusterImageExport object
|
||||
type ClusterImageExportReconciler struct {
|
||||
client.Client
|
||||
Scheme *runtime.Scheme
|
||||
Scheme *runtime.Scheme
|
||||
podAnnotations map[string]string
|
||||
}
|
||||
|
||||
@@ -121,6 +121,7 @@ func (r *ClusterImageExportReconciler) Reconcile(ctx context.Context, req ctrl.R
|
||||
|
||||
for _, image := range fullImagesList.Containers {
|
||||
// Include creation timestamp in the hash to differentiate between exports with the same name
|
||||
// #nosec G401 - MD5 used for non-cryptographic unique identifier generation, not security
|
||||
nameHash := fmt.Sprintf("%x", md5.Sum([]byte(clusterImageExport.Name+image.Image+image.Tag+image.Sha+
|
||||
clusterImageExport.Annotations["export.raczylo.com/creation-timestamp"])))[:14]
|
||||
|
||||
@@ -156,7 +157,7 @@ func (r *ClusterImageExportReconciler) Reconcile(ctx context.Context, req ctrl.R
|
||||
Kind: clusterImageExport.Kind,
|
||||
Name: clusterImageExport.Name,
|
||||
UID: clusterImageExport.UID,
|
||||
Controller: pointer.Bool(true),
|
||||
Controller: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -185,7 +186,7 @@ func (r *ClusterImageExportReconciler) Reconcile(ctx context.Context, req ctrl.R
|
||||
failedCount := 0
|
||||
pendingCount := 0
|
||||
clusterImageList := &raczylocomv1.ClusterImageList{}
|
||||
if err := r.List(ctx, clusterImageList, client.InNamespace(clusterImageExport.Namespace),
|
||||
if err := r.List(ctx, clusterImageList, client.InNamespace(clusterImageExport.Namespace),
|
||||
client.MatchingFields{"spec.exportName": clusterImageExport.Name}); err != nil {
|
||||
l.Error(err, "unable to list ClusterImages")
|
||||
return ctrl.Result{}, err
|
||||
@@ -250,21 +251,6 @@ func (r *ClusterImageExportReconciler) updateStatusWithRetry(ctx context.Context
|
||||
})
|
||||
}
|
||||
|
||||
func (r *ClusterImageExportReconciler) checkAllClusterImagesCompleted(ctx context.Context, clusterImageExport *raczylocomv1.ClusterImageExport) (bool, error) {
|
||||
clusterImageList := &raczylocomv1.ClusterImageList{}
|
||||
if err := r.List(ctx, clusterImageList, client.InNamespace(clusterImageExport.Namespace), client.MatchingFields{"spec.exportName": clusterImageExport.Name}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, ci := range clusterImageList.Items {
|
||||
if ci.Status.Progress != shared.STATUS_SUCCESS && ci.Status.Progress != shared.STATUS_PRESENT {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// SetupWithManager sets up the controller with the Manager.
|
||||
|
||||
func (r *ClusterImageExportReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
@@ -409,7 +395,7 @@ func (r *ClusterImageExportReconciler) runCleanupJob(ctx context.Context, cluste
|
||||
}
|
||||
|
||||
// Set up the cleanup job with retry limits and TTL
|
||||
backoffLimit := int32(2) // 3 total attempts (initial + 2 retries)
|
||||
backoffLimit := int32(2) // 3 total attempts (initial + 2 retries)
|
||||
ttlSecondsAfterFinished := int32(300) // Delete job 5 minutes after completion
|
||||
|
||||
// Merge annotations from different sources
|
||||
@@ -443,16 +429,16 @@ func (r *ClusterImageExportReconciler) runCleanupJob(ctx context.Context, cluste
|
||||
}
|
||||
|
||||
jobParams := shared.JobParams{
|
||||
Name: normalisedImageName,
|
||||
Namespace: clusterImageExport.Namespace,
|
||||
Image: shared.BACKUP_JOB_IMAGE,
|
||||
Commands: defaultCommands,
|
||||
Annotations: mergedAnnotations,
|
||||
ServiceAccount: "",
|
||||
ImagePullSecrets: clusterImageExport.Spec.ImagePullSecrets,
|
||||
BackoffLimit: &backoffLimit,
|
||||
Name: normalisedImageName,
|
||||
Namespace: clusterImageExport.Namespace,
|
||||
Image: shared.BACKUP_JOB_IMAGE,
|
||||
Commands: defaultCommands,
|
||||
Annotations: mergedAnnotations,
|
||||
ServiceAccount: "",
|
||||
ImagePullSecrets: clusterImageExport.Spec.ImagePullSecrets,
|
||||
BackoffLimit: &backoffLimit,
|
||||
TTLSecondsAfterFinished: &ttlSecondsAfterFinished,
|
||||
EnvVars: envVars,
|
||||
EnvVars: envVars,
|
||||
}
|
||||
|
||||
cleanupJob := shared.CreateJob(jobParams, func(raczylocomv1.ClusterImageExport) []string { return nil })
|
||||
|
||||
@@ -10,10 +10,10 @@ import (
|
||||
var BACKUP_JOB_IMAGE string
|
||||
|
||||
func init() {
|
||||
BACKUP_JOB_IMAGE = os.Getenv("WORKER_IMAGE")
|
||||
if BACKUP_JOB_IMAGE == "" {
|
||||
BACKUP_JOB_IMAGE = "ghcr.io/lukaszraczylo/kubernetes-images-sync-worker:latest" // fallback
|
||||
}
|
||||
BACKUP_JOB_IMAGE = os.Getenv("WORKER_IMAGE")
|
||||
if BACKUP_JOB_IMAGE == "" {
|
||||
BACKUP_JOB_IMAGE = "ghcr.io/lukaszraczylo/kubernetes-images-sync-worker:0.5.54" // fallback to known stable version
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
@@ -9,13 +9,13 @@ import (
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/utils/pointer"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
type JobParams struct {
|
||||
Name string
|
||||
Namespace string
|
||||
Annotations map[string]string
|
||||
Annotations map[string]string
|
||||
Image string
|
||||
Commands []string
|
||||
EnvVars []corev1.EnvVar
|
||||
@@ -88,7 +88,7 @@ func CreateJob[T any](params JobParams, setupFunc func(T) []string) *batchv1.Job
|
||||
VolumeMounts: volumeMounts,
|
||||
Env: params.EnvVars,
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Privileged: pointer.Bool(true),
|
||||
Privileged: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
Copyright 2024.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
// Run e2e tests using the Ginkgo runner.
|
||||
func TestE2E(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
_, _ = fmt.Fprintf(GinkgoWriter, "Starting kubernetes-images-sync-operator suite\n")
|
||||
RunSpecs(t, "e2e suite")
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
Copyright 2024.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/lukaszraczylo/kubernetes-images-sync-operator/test/utils"
|
||||
)
|
||||
|
||||
const namespace = "kubernetes-images-sync-operator-system"
|
||||
|
||||
var _ = Describe("controller", Ordered, func() {
|
||||
BeforeAll(func() {
|
||||
By("installing prometheus operator")
|
||||
Expect(utils.InstallPrometheusOperator()).To(Succeed())
|
||||
|
||||
By("installing the cert-manager")
|
||||
Expect(utils.InstallCertManager()).To(Succeed())
|
||||
|
||||
By("creating manager namespace")
|
||||
cmd := exec.Command("kubectl", "create", "ns", namespace)
|
||||
_, _ = utils.Run(cmd)
|
||||
})
|
||||
|
||||
AfterAll(func() {
|
||||
By("uninstalling the Prometheus manager bundle")
|
||||
utils.UninstallPrometheusOperator()
|
||||
|
||||
By("uninstalling the cert-manager bundle")
|
||||
utils.UninstallCertManager()
|
||||
|
||||
By("removing manager namespace")
|
||||
cmd := exec.Command("kubectl", "delete", "ns", namespace)
|
||||
_, _ = utils.Run(cmd)
|
||||
})
|
||||
|
||||
Context("Operator", func() {
|
||||
It("should run successfully", func() {
|
||||
var controllerPodName string
|
||||
var err error
|
||||
|
||||
// projectimage stores the name of the image used in the example
|
||||
var projectimage = "example.com/kubernetes-images-sync-operator:v0.0.1"
|
||||
|
||||
By("building the manager(Operator) image")
|
||||
cmd := exec.Command("make", "docker-build", fmt.Sprintf("IMG=%s", projectimage))
|
||||
_, err = utils.Run(cmd)
|
||||
ExpectWithOffset(1, err).NotTo(HaveOccurred())
|
||||
|
||||
By("loading the the manager(Operator) image on Kind")
|
||||
err = utils.LoadImageToKindClusterWithName(projectimage)
|
||||
ExpectWithOffset(1, err).NotTo(HaveOccurred())
|
||||
|
||||
By("installing CRDs")
|
||||
cmd = exec.Command("make", "install")
|
||||
_, err = utils.Run(cmd)
|
||||
ExpectWithOffset(1, err).NotTo(HaveOccurred())
|
||||
|
||||
By("deploying the sa")
|
||||
cmd = exec.Command("make", "deploy", fmt.Sprintf("IMG=%s", projectimage))
|
||||
_, err = utils.Run(cmd)
|
||||
ExpectWithOffset(1, err).NotTo(HaveOccurred())
|
||||
|
||||
By("validating that the sa pod is running as expected")
|
||||
verifyControllerUp := func() error {
|
||||
// Get pod name
|
||||
|
||||
cmd = exec.Command("kubectl", "get",
|
||||
"pods", "-l", "control-plane=sa",
|
||||
"-o", "go-template={{ range .items }}"+
|
||||
"{{ if not .metadata.deletionTimestamp }}"+
|
||||
"{{ .metadata.name }}"+
|
||||
"{{ \"\\n\" }}{{ end }}{{ end }}",
|
||||
"-n", namespace,
|
||||
)
|
||||
|
||||
podOutput, err := utils.Run(cmd)
|
||||
ExpectWithOffset(2, err).NotTo(HaveOccurred())
|
||||
podNames := utils.GetNonEmptyLines(string(podOutput))
|
||||
if len(podNames) != 1 {
|
||||
return fmt.Errorf("expect 1 controller pods running, but got %d", len(podNames))
|
||||
}
|
||||
controllerPodName = podNames[0]
|
||||
ExpectWithOffset(2, controllerPodName).Should(ContainSubstring("sa"))
|
||||
|
||||
// Validate pod status
|
||||
cmd = exec.Command("kubectl", "get",
|
||||
"pods", controllerPodName, "-o", "jsonpath={.status.phase}",
|
||||
"-n", namespace,
|
||||
)
|
||||
status, err := utils.Run(cmd)
|
||||
ExpectWithOffset(2, err).NotTo(HaveOccurred())
|
||||
if string(status) != "Running" {
|
||||
return fmt.Errorf("controller pod in %s status", status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
EventuallyWithOffset(1, verifyControllerUp, time.Minute, time.Second).Should(Succeed())
|
||||
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,140 +0,0 @@
|
||||
/*
|
||||
Copyright 2024.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2" //nolint:golint,revive
|
||||
)
|
||||
|
||||
const (
|
||||
prometheusOperatorVersion = "v0.72.0"
|
||||
prometheusOperatorURL = "https://github.com/prometheus-operator/prometheus-operator/" +
|
||||
"releases/download/%s/bundle.yaml"
|
||||
|
||||
certmanagerVersion = "v1.14.4"
|
||||
certmanagerURLTmpl = "https://github.com/jetstack/cert-manager/releases/download/%s/cert-manager.yaml"
|
||||
)
|
||||
|
||||
func warnError(err error) {
|
||||
_, _ = fmt.Fprintf(GinkgoWriter, "warning: %v\n", err)
|
||||
}
|
||||
|
||||
// InstallPrometheusOperator installs the prometheus Operator to be used to export the enabled metrics.
|
||||
func InstallPrometheusOperator() error {
|
||||
url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion)
|
||||
cmd := exec.Command("kubectl", "create", "-f", url)
|
||||
_, err := Run(cmd)
|
||||
return err
|
||||
}
|
||||
|
||||
// Run executes the provided command within this context
|
||||
func Run(cmd *exec.Cmd) ([]byte, error) {
|
||||
dir, _ := GetProjectDir()
|
||||
cmd.Dir = dir
|
||||
|
||||
if err := os.Chdir(cmd.Dir); err != nil {
|
||||
_, _ = fmt.Fprintf(GinkgoWriter, "chdir dir: %s\n", err)
|
||||
}
|
||||
|
||||
cmd.Env = append(os.Environ(), "GO111MODULE=on")
|
||||
command := strings.Join(cmd.Args, " ")
|
||||
_, _ = fmt.Fprintf(GinkgoWriter, "running: %s\n", command)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return output, fmt.Errorf("%s failed with error: (%v) %s", command, err, string(output))
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// UninstallPrometheusOperator uninstalls the prometheus
|
||||
func UninstallPrometheusOperator() {
|
||||
url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion)
|
||||
cmd := exec.Command("kubectl", "delete", "-f", url)
|
||||
if _, err := Run(cmd); err != nil {
|
||||
warnError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// UninstallCertManager uninstalls the cert manager
|
||||
func UninstallCertManager() {
|
||||
url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)
|
||||
cmd := exec.Command("kubectl", "delete", "-f", url)
|
||||
if _, err := Run(cmd); err != nil {
|
||||
warnError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// InstallCertManager installs the cert manager bundle.
|
||||
func InstallCertManager() error {
|
||||
url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)
|
||||
cmd := exec.Command("kubectl", "apply", "-f", url)
|
||||
if _, err := Run(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
// Wait for cert-manager-webhook to be ready, which can take time if cert-manager
|
||||
// was re-installed after uninstalling on a cluster.
|
||||
cmd = exec.Command("kubectl", "wait", "deployment.apps/cert-manager-webhook",
|
||||
"--for", "condition=Available",
|
||||
"--namespace", "cert-manager",
|
||||
"--timeout", "5m",
|
||||
)
|
||||
|
||||
_, err := Run(cmd)
|
||||
return err
|
||||
}
|
||||
|
||||
// LoadImageToKindClusterWithName loads a local docker image to the kind cluster
|
||||
func LoadImageToKindClusterWithName(name string) error {
|
||||
cluster := "kind"
|
||||
if v, ok := os.LookupEnv("KIND_CLUSTER"); ok {
|
||||
cluster = v
|
||||
}
|
||||
kindOptions := []string{"load", "docker-image", name, "--name", cluster}
|
||||
cmd := exec.Command("kind", kindOptions...)
|
||||
_, err := Run(cmd)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetNonEmptyLines converts given command output string into individual objects
|
||||
// according to line breakers, and ignores the empty elements in it.
|
||||
func GetNonEmptyLines(output string) []string {
|
||||
var res []string
|
||||
elements := strings.Split(output, "\n")
|
||||
for _, element := range elements {
|
||||
if element != "" {
|
||||
res = append(res, element)
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// GetProjectDir will return the directory where the project is
|
||||
func GetProjectDir() (string, error) {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return wd, err
|
||||
}
|
||||
wd = strings.Replace(wd, "/test/e2e", "", -1)
|
||||
return wd, nil
|
||||
}
|
||||
Reference in New Issue
Block a user