mirror of
https://github.com/lukaszraczylo/kubernetes-images-sync-operator.git
synced 2026-10-12 00:45:27 +00:00
Migrate the lint config to the v2 format. Replace the deprecated scheme.Builder with runtime.NewSchemeBuilder. Handle Close errors in the worker and replace repeated strings with constants. Remove unused parameters and use switch statements where the linter asks.
224 lines
5.8 KiB
Go
224 lines
5.8 KiB
Go
package shared
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
raczylocomv1 "github.com/lukaszraczylo/kubernetes-images-sync-operator/api/raczylo.com/v1"
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
batchv1 "k8s.io/api/batch/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/util/wait"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/manager"
|
|
)
|
|
|
|
type K8sResource interface {
|
|
GetPodSpec() *corev1.PodSpec
|
|
}
|
|
|
|
// Wrapper types
|
|
type (
|
|
DeploymentWrapper appsv1.Deployment
|
|
JobWrapper batchv1.Job
|
|
DaemonSetWrapper appsv1.DaemonSet
|
|
CronJobWrapper batchv1.CronJob
|
|
)
|
|
|
|
// Implement the K8sResource interface for wrapper types
|
|
func (d *DeploymentWrapper) GetPodSpec() *corev1.PodSpec { return &d.Spec.Template.Spec }
|
|
func (j *JobWrapper) GetPodSpec() *corev1.PodSpec { return &j.Spec.Template.Spec }
|
|
func (ds *DaemonSetWrapper) GetPodSpec() *corev1.PodSpec { return &ds.Spec.Template.Spec }
|
|
func (cj *CronJobWrapper) GetPodSpec() *corev1.PodSpec {
|
|
return &cj.Spec.JobTemplate.Spec.Template.Spec
|
|
}
|
|
|
|
type ContainerCache struct {
|
|
sync.RWMutex
|
|
cache map[string]Container
|
|
}
|
|
|
|
var containerCache = &ContainerCache{
|
|
cache: make(map[string]Container),
|
|
}
|
|
|
|
func (cc *ContainerCache) Get(key string) (Container, bool) {
|
|
cc.RLock()
|
|
defer cc.RUnlock()
|
|
c, ok := cc.cache[key]
|
|
return c, ok
|
|
}
|
|
|
|
func (cc *ContainerCache) Set(key string, value Container) {
|
|
cc.Lock()
|
|
defer cc.Unlock()
|
|
cc.cache[key] = value
|
|
}
|
|
|
|
func ProcessContainerName(containerName string) (Container, error) {
|
|
if cnt, ok := containerCache.Get(containerName); ok {
|
|
return cnt, nil
|
|
}
|
|
|
|
cnt := Container{FullName: containerName}
|
|
parts := strings.Split(containerName, "@")
|
|
if len(parts) > 2 {
|
|
return cnt, fmt.Errorf("invalid container name format: %s", containerName)
|
|
}
|
|
|
|
imageAndTag := strings.SplitN(parts[0], ":", 2)
|
|
cnt.Image = imageAndTag[0]
|
|
if len(imageAndTag) == 2 {
|
|
cnt.Tag = imageAndTag[1]
|
|
}
|
|
|
|
if len(parts) == 2 {
|
|
shaParts := strings.SplitN(parts[1], ":", 2)
|
|
if len(shaParts) != 2 || (shaParts[0] != "sha" && shaParts[0] != "sha256") {
|
|
return cnt, fmt.Errorf("invalid SHA format: %s", parts[1])
|
|
}
|
|
cnt.Sha = parts[1]
|
|
}
|
|
|
|
if cnt.Sha == "" && cnt.Tag == "" {
|
|
cnt.Tag = DEFAULT_IMAGE_TAG
|
|
}
|
|
|
|
if cnt.Image == "" {
|
|
return cnt, fmt.Errorf("image name is required")
|
|
}
|
|
|
|
containerCache.Set(containerName, cnt)
|
|
return cnt, nil
|
|
}
|
|
|
|
func processContainers(ctx context.Context, resource K8sResource, namespace string, containersList *ContainersList) error {
|
|
podSpec := resource.GetPodSpec()
|
|
if podSpec == nil {
|
|
return fmt.Errorf("nil PodSpec")
|
|
}
|
|
|
|
allContainers := append(podSpec.Containers, podSpec.InitContainers...)
|
|
for _, container := range allContainers {
|
|
if err := processContainer(ctx, container.Image, namespace, containersList); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for _, container := range podSpec.EphemeralContainers {
|
|
if err := processContainer(ctx, container.Image, namespace, containersList); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func processContainer(ctx context.Context, image string, containerNamespace string, containersList *ContainersList) error {
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
default:
|
|
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
|
|
}
|
|
}
|
|
|
|
func ListAndProcessResources[T K8sResource, L client.ObjectList](ctx context.Context, r client.Client, list L, containersList *ContainersList) error {
|
|
if err := r.List(ctx, list, &client.ListOptions{}); err != nil {
|
|
return fmt.Errorf("failed to list resources: %w", err)
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
errChan := make(chan error, 1)
|
|
semaphore := make(chan struct{}, 10) // Limit concurrent goroutines
|
|
var mu sync.Mutex // Mutex to protect containersList
|
|
|
|
processItem := func(item K8sResource, namespace string) {
|
|
defer wg.Done()
|
|
semaphore <- struct{}{}
|
|
defer func() { <-semaphore }()
|
|
|
|
localContainersList := &ContainersList{}
|
|
if err := processContainers(ctx, item, namespace, localContainersList); err != nil {
|
|
select {
|
|
case errChan <- err:
|
|
default:
|
|
}
|
|
return
|
|
}
|
|
|
|
// Safely append the local results to the main containersList
|
|
mu.Lock()
|
|
containersList.Containers = append(containersList.Containers, localContainersList.Containers...)
|
|
mu.Unlock()
|
|
}
|
|
|
|
switch typedList := any(list).(type) {
|
|
case *appsv1.DeploymentList:
|
|
for i := range typedList.Items {
|
|
wg.Add(1)
|
|
go processItem((*DeploymentWrapper)(&typedList.Items[i]), typedList.Items[i].Namespace)
|
|
}
|
|
case *batchv1.JobList:
|
|
for i := range typedList.Items {
|
|
wg.Add(1)
|
|
go processItem((*JobWrapper)(&typedList.Items[i]), typedList.Items[i].Namespace)
|
|
}
|
|
case *appsv1.DaemonSetList:
|
|
for i := range typedList.Items {
|
|
wg.Add(1)
|
|
go processItem((*DaemonSetWrapper)(&typedList.Items[i]), typedList.Items[i].Namespace)
|
|
}
|
|
case *batchv1.CronJobList:
|
|
for i := range typedList.Items {
|
|
wg.Add(1)
|
|
go processItem((*CronJobWrapper)(&typedList.Items[i]), typedList.Items[i].Namespace)
|
|
}
|
|
default:
|
|
return fmt.Errorf("unsupported list type: %T", list)
|
|
}
|
|
|
|
go func() {
|
|
wg.Wait()
|
|
close(errChan)
|
|
}()
|
|
|
|
select {
|
|
case err := <-errChan:
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func SetupIndexers(mgr manager.Manager) error {
|
|
return wait.ExponentialBackoff(wait.Backoff{
|
|
Duration: 1 * time.Second,
|
|
Factor: 2,
|
|
Jitter: 0.1,
|
|
Steps: 5,
|
|
}, func() (bool, error) {
|
|
err := mgr.GetFieldIndexer().IndexField(context.Background(), &raczylocomv1.ClusterImage{}, FIELD_EXPORT_NAME, func(rawObj client.Object) []string {
|
|
clusterImage := rawObj.(*raczylocomv1.ClusterImage)
|
|
return []string{clusterImage.Spec.ExportName}
|
|
})
|
|
if err != nil {
|
|
return false, nil
|
|
}
|
|
return true, nil
|
|
})
|
|
}
|