diff --git a/api/v1alpha1/application.go b/api/v1alpha1/application.go index f07d3740..6e005b2a 100644 --- a/api/v1alpha1/application.go +++ b/api/v1alpha1/application.go @@ -44,7 +44,7 @@ type ApplicationSpec struct { //+kubebuilder:validation:Optional Resources ResourceRequirements `json:"resources,omitempty"` //+kubebuilder:validation:Optional - Replicas Replicas `json:"replicas,omitempty"` + Replicas *Replicas `json:"replicas,omitempty"` //+kubebuilder:validation:Optional Strategy Strategy `json:"strategy,omitempty"` @@ -284,8 +284,16 @@ const ( ) func (a *Application) FillDefaultsSpec() { - a.Spec.Replicas.Min = max(1, a.Spec.Replicas.Min) - a.Spec.Replicas.Max = max(a.Spec.Replicas.Min, a.Spec.Replicas.Max) + if a.Spec.Replicas == nil { + a.Spec.Replicas = &Replicas{ + Min: 2, + Max: 5, + } + } else if a.Spec.Replicas.Min == 0 && a.Spec.Replicas.Max == 0 { + } else { + a.Spec.Replicas.Min = max(1, a.Spec.Replicas.Min) + a.Spec.Replicas.Max = max(a.Spec.Replicas.Min, a.Spec.Replicas.Max) + } if a.Spec.Replicas.TargetCpuUtilization == 0 { a.Spec.Replicas.TargetCpuUtilization = 80 diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index d8ff2c71..b7d5354d 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -95,7 +95,11 @@ func (in *ApplicationSpec) DeepCopyInto(out *ApplicationSpec) { copy(*out, *in) } in.Resources.DeepCopyInto(&out.Resources) - out.Replicas = in.Replicas + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + *out = new(Replicas) + **out = **in + } out.Strategy = in.Strategy if in.Env != nil { in, out := &in.Env, &out.Env diff --git a/controllers/application/deployment.go b/controllers/application/deployment.go index 83ec5ced..f98c53c5 100644 --- a/controllers/application/deployment.go +++ b/controllers/application/deployment.go @@ -3,14 +3,15 @@ package applicationcontroller import ( "context" "fmt" - skiperatorv1alpha1 "github.com/kartverket/skiperator/api/v1alpha1" "github.com/kartverket/skiperator/pkg/util" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/controller-runtime/pkg/client" ctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) @@ -20,10 +21,7 @@ const ( AnnotationKeyLinkPrefix = "link.argocd.argoproj.io/external-link" ) -func (r *ApplicationReconciler) reconcileDeployment(ctx context.Context, application *skiperatorv1alpha1.Application) (reconcile.Result, error) { - controllerName := "Deployment" - r.SetControllerProgressing(ctx, application, controllerName) - +func (r *ApplicationReconciler) defineDeployment(ctx context.Context, application *skiperatorv1alpha1.Application) (appsv1.Deployment, error) { deployment := appsv1.Deployment{ TypeMeta: metav1.TypeMeta{ Kind: "Deployment", @@ -35,96 +33,163 @@ func (r *ApplicationReconciler) reconcileDeployment(ctx context.Context, applica }, } - _, err := ctrlutil.CreateOrPatch(ctx, r.GetClient(), &deployment, func() error { - // Set application as owner of the deployment - err := ctrlutil.SetControllerReference(application, &deployment, r.GetScheme()) - if err != nil { - r.SetControllerError(ctx, application, controllerName, err) - return err - } + skiperatorContainer := corev1.Container{ + Name: application.Name, + Image: application.Spec.Image, + ImagePullPolicy: corev1.PullAlways, + Command: application.Spec.Command, + SecurityContext: &corev1.SecurityContext{ + Privileged: util.PointTo(false), + AllowPrivilegeEscalation: util.PointTo(false), + ReadOnlyRootFilesystem: util.PointTo(true), + RunAsUser: util.PointTo(util.SkiperatorUser), + RunAsGroup: util.PointTo(util.SkiperatorUser), + }, + Ports: getContainerPorts(application), + EnvFrom: getEnvFrom(application.Spec.EnvFrom), + Resources: corev1.ResourceRequirements{ + Limits: application.Spec.Resources.Limits, + Requests: application.Spec.Resources.Requests, + }, + Env: application.Spec.Env, + ReadinessProbe: getProbe(application.Spec.Readiness), + LivenessProbe: getProbe(application.Spec.Liveness), + StartupProbe: getProbe(application.Spec.Startup), + TerminationMessagePath: corev1.TerminationMessagePathDefault, + TerminationMessagePolicy: corev1.TerminationMessagePolicy("File"), + } - r.SetLabelsFromApplication(ctx, &deployment, *application) - util.SetCommonAnnotations(&deployment) - - skiperatorContainer := corev1.Container{ - Name: application.Name, - Image: application.Spec.Image, - ImagePullPolicy: corev1.PullAlways, - Command: application.Spec.Command, - SecurityContext: &corev1.SecurityContext{ - Privileged: util.PointTo(false), - AllowPrivilegeEscalation: util.PointTo(false), - ReadOnlyRootFilesystem: util.PointTo(true), - RunAsUser: util.PointTo(util.SkiperatorUser), - RunAsGroup: util.PointTo(util.SkiperatorUser), - }, - Ports: getContainerPorts(application), - EnvFrom: getEnvFrom(application.Spec.EnvFrom), - Resources: corev1.ResourceRequirements{ - Limits: application.Spec.Resources.Limits, - Requests: application.Spec.Resources.Requests, - }, - Env: application.Spec.Env, - ReadinessProbe: getProbe(application.Spec.Readiness), - LivenessProbe: getProbe(application.Spec.Liveness), - StartupProbe: getProbe(application.Spec.Startup), - } + var err error - podVolumes, containerVolumeMounts := getContainerVolumeMountsAndPodVolumes(application) - podVolumes, containerVolumeMounts, err = r.appendGCPVolumeMount(application, ctx, &skiperatorContainer, containerVolumeMounts, podVolumes) - if err != nil { - r.SetControllerError(ctx, application, controllerName, err) - return err - } - skiperatorContainer.VolumeMounts = containerVolumeMounts + podVolumes, containerVolumeMounts := getContainerVolumeMountsAndPodVolumes(application) + podVolumes, containerVolumeMounts, err = r.appendGCPVolumeMount(application, ctx, &skiperatorContainer, containerVolumeMounts, podVolumes) + if err != nil { + r.SetControllerError(ctx, application, controllerName, err) + return deployment, err + } + skiperatorContainer.VolumeMounts = containerVolumeMounts - labels := util.GetApplicationSelector(application.Name) + labels := util.GetApplicationSelector(application.Name) - deployment.Spec = appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{MatchLabels: labels}, - Replicas: getReplicasFromAppSpec(application.Spec.Replicas.Min), - Strategy: appsv1.DeploymentStrategy{ - Type: appsv1.DeploymentStrategyType(application.Spec.Strategy.Type), - RollingUpdate: getRollingUpdateStrategy(application.Spec.Strategy.Type), + deployment.Spec = appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{MatchLabels: labels}, + Strategy: appsv1.DeploymentStrategy{ + Type: appsv1.DeploymentStrategyType(application.Spec.Strategy.Type), + RollingUpdate: getRollingUpdateStrategy(application.Spec.Strategy.Type), + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + Annotations: map[string]string{ + "argocd.argoproj.io/sync-options": "Prune=false", + "prometheus.io/scrape": "true", + }, }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: labels, - Annotations: map[string]string{ - "argocd.argoproj.io/sync-options": "Prune=false", - "prometheus.io/scrape": "true", - }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + skiperatorContainer, }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - skiperatorContainer, - }, - // TODO: Make this as part of operator in a safe way - ImagePullSecrets: []corev1.LocalObjectReference{{Name: "github-auth"}}, - SecurityContext: &corev1.PodSecurityContext{ - SupplementalGroups: []int64{util.SkiperatorUser}, - FSGroup: util.PointTo(util.SkiperatorUser), - SeccompProfile: &corev1.SeccompProfile{ - Type: corev1.SeccompProfileTypeRuntimeDefault, - }, + // TODO: Make this as part of operator in a safe way + ImagePullSecrets: []corev1.LocalObjectReference{{Name: "github-auth"}}, + SecurityContext: &corev1.PodSecurityContext{ + SupplementalGroups: []int64{util.SkiperatorUser}, + FSGroup: util.PointTo(util.SkiperatorUser), + SeccompProfile: &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, }, - ServiceAccountName: application.Name, - Volumes: podVolumes, - PriorityClassName: fmt.Sprintf("skip-%s", application.Spec.Priority), }, + ServiceAccountName: application.Name, + // The resulting kubernetes object includes the ServiceAccount field, and thus it's required in order + // to not create a diff for the hash of existing and wanted spec + DeprecatedServiceAccount: application.Name, + Volumes: podVolumes, + PriorityClassName: fmt.Sprintf("skip-%s", application.Spec.Priority), + RestartPolicy: corev1.RestartPolicyAlways, + TerminationGracePeriodSeconds: util.PointTo(int64(corev1.DefaultTerminationGracePeriodSeconds)), + DNSPolicy: corev1.DNSClusterFirst, + SchedulerName: corev1.DefaultSchedulerName, }, - RevisionHistoryLimit: util.PointTo(int32(2)), - } + }, + RevisionHistoryLimit: util.PointTo(int32(2)), + ProgressDeadlineSeconds: util.PointTo(int32(600)), + } + + // Setting replicas to 0 when skiperator manifest specifies min/max to 0 + if shouldScaleToZero(application.Spec.Replicas.Min, application.Spec.Replicas.Max) { + deployment.Spec.Replicas = util.PointTo(int32(0)) + } - // add an external link to argocd - ingresses := application.Spec.Ingresses - if len(ingresses) > 0 { - deployment.ObjectMeta.Annotations[AnnotationKeyLinkPrefix] = fmt.Sprintf("https://%s", ingresses[0]) + r.SetLabelsFromApplication(ctx, &deployment, *application) + util.SetCommonAnnotations(&deployment) + + // add an external link to argocd + ingresses := application.Spec.Ingresses + if len(ingresses) > 0 { + deployment.ObjectMeta.Annotations[AnnotationKeyLinkPrefix] = fmt.Sprintf("https://%s", ingresses[0]) + } + + // Set application as owner of the deployment + err = ctrlutil.SetControllerReference(application, &deployment, r.GetScheme()) + if err != nil { + r.SetControllerError(ctx, application, controllerName, err) + return deployment, err + } + + return deployment, nil +} + +func (r *ApplicationReconciler) reconcileDeployment(ctx context.Context, application *skiperatorv1alpha1.Application) (reconcile.Result, error) { + controllerName := "Deployment" + r.SetControllerProgressing(ctx, application, controllerName) + + deployment := appsv1.Deployment{} + deploymentDefinition, err := r.defineDeployment(ctx, application) + + err = r.GetClient().Get(ctx, types.NamespacedName{Name: application.Name, Namespace: application.Namespace}, &deployment) + if err != nil { + if errors.IsNotFound(err) { + r.GetRecorder().Eventf( + application, + corev1.EventTypeNormal, "NotFound", + "Deployment resource for application %s not found. Creating deployment", + application.Name, + ) + err = r.GetClient().Create(ctx, &deploymentDefinition) + if err != nil { + r.SetControllerError(ctx, application, controllerName, err) + return reconcile.Result{}, err + } + } else { + r.SetControllerError(ctx, application, controllerName, err) + return reconcile.Result{}, err + } + } else { + if !shouldScaleToZero(application.Spec.Replicas.Min, application.Spec.Replicas.Max) { + // Ignore replicas set by HPA when checking diff + if int32(*deployment.Spec.Replicas) > 0 { + deployment.Spec.Replicas = nil + } } - return nil - }) + deploymentHash := util.GetHashForStructs([]interface{}{ + &deployment.Spec, + &deployment.Labels, + }) + deploymentDefinitionHash := util.GetHashForStructs([]interface{}{ + &deploymentDefinition.Spec, + &deploymentDefinition.Labels, + }) + + if deploymentHash != deploymentDefinitionHash { + patch := client.MergeFrom(deployment.DeepCopy()) + err = r.GetClient().Patch(ctx, &deploymentDefinition, patch) + if err != nil { + r.SetControllerError(ctx, application, controllerName, err) + return reconcile.Result{}, err + } + } + } r.SetControllerFinishedOutcome(ctx, application, controllerName, err) @@ -321,6 +386,7 @@ func getContainerPorts(application *skiperatorv1alpha1.Application) []corev1.Con { Name: "main", ContainerPort: int32(application.Spec.Port), + Protocol: corev1.ProtocolTCP, }, } @@ -340,15 +406,16 @@ func getRollingUpdateStrategy(updateStrategy string) *appsv1.RollingUpdateDeploy return nil } - return &appsv1.RollingUpdateDeployment{} + return &appsv1.RollingUpdateDeployment{ + // Fill with defaults + MaxUnavailable: &intstr.IntOrString{Type: intstr.String, StrVal: "25%"}, + MaxSurge: &intstr.IntOrString{Type: intstr.String, StrVal: "25%"}, + } } -func getReplicasFromAppSpec(appReplicas uint) *int32 { - var replicas = int32(appReplicas) - if replicas == 0 { - minReplicas := int32(1) - return &minReplicas +func shouldScaleToZero(minReplicas uint, maxReplicas uint) bool { + if minReplicas == 0 && maxReplicas == 0 { + return true } - - return &replicas + return false } diff --git a/controllers/application/horizontal_pod_autoscaler.go b/controllers/application/horizontal_pod_autoscaler.go index d73129e8..ec6f6646 100644 --- a/controllers/application/horizontal_pod_autoscaler.go +++ b/controllers/application/horizontal_pod_autoscaler.go @@ -2,11 +2,11 @@ package applicationcontroller import ( "context" - skiperatorv1alpha1 "github.com/kartverket/skiperator/api/v1alpha1" "github.com/kartverket/skiperator/pkg/util" autoscalingv2 "k8s.io/api/autoscaling/v2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" ctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) @@ -16,6 +16,17 @@ func (r *ApplicationReconciler) reconcileHorizontalPodAutoscaler(ctx context.Con r.SetControllerProgressing(ctx, application, controllerName) horizontalPodAutoscaler := autoscalingv2.HorizontalPodAutoscaler{ObjectMeta: metav1.ObjectMeta{Namespace: application.Namespace, Name: application.Name}} + if shouldScaleToZero(application.Spec.Replicas.Min, application.Spec.Replicas.Max) { + err := r.GetClient().Delete(ctx, &horizontalPodAutoscaler) + err = client.IgnoreNotFound(err) + if err != nil { + r.SetControllerError(ctx, application, controllerName, err) + return reconcile.Result{}, err + } + r.SetControllerFinishedOutcome(ctx, application, controllerName, nil) + return reconcile.Result{}, nil + } + _, err := ctrlutil.CreateOrPatch(ctx, r.GetClient(), &horizontalPodAutoscaler, func() error { // Set application as owner of the horizontal pod autoscaler err := ctrlutil.SetControllerReference(application, &horizontalPodAutoscaler, r.GetScheme()) diff --git a/controllers/application/service.go b/controllers/application/service.go index 71ff3d30..46f51b19 100644 --- a/controllers/application/service.go +++ b/controllers/application/service.go @@ -30,6 +30,9 @@ func (r *ApplicationReconciler) reconcileService(ctx context.Context, applicatio // ServiceMonitor requires labels to be set on service to select it labels := service.GetLabels() + if len(labels) == 0 { + labels = make(map[string]string) + } labels["app"] = application.Name service.SetLabels(labels) diff --git a/generate.go b/generate.go index 9e41a248..26ee7f16 100644 --- a/generate.go +++ b/generate.go @@ -1,4 +1,5 @@ package root -//go:generate controller-gen crd paths=./api/... +//go:generate controller-gen crd paths=./... +//go:generate controller-gen object paths=./... //go:generate controller-gen rbac:roleName=skiperator paths=./... diff --git a/go.mod b/go.mod index 7d823408..fd2efa41 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.65.2 go.etcd.io/etcd/server/v3 v3.5.9 go.uber.org/zap v1.24.0 + golang.org/x/crypto v0.6.0 golang.org/x/exp v0.0.0-20220706164943-b4a6d9510983 istio.io/api v0.0.0-20230518153929-d0aebaa77ab8 istio.io/client-go v1.16.5 @@ -76,6 +77,7 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -112,7 +114,6 @@ require ( go.opentelemetry.io/proto/otlp v0.19.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.6.0 // indirect - golang.org/x/crypto v0.6.0 // indirect golang.org/x/mod v0.10.0 // indirect golang.org/x/net v0.10.0 // indirect golang.org/x/oauth2 v0.5.0 // indirect diff --git a/go.sum b/go.sum index 886ad298..14cec0fd 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,10 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= @@ -48,6 +52,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -81,6 +87,7 @@ github.com/coreos/go-systemd/v22 v22.4.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -121,6 +128,7 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= @@ -217,8 +225,10 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= @@ -229,6 +239,7 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QG github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= @@ -240,10 +251,12 @@ github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9q github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -268,6 +281,10 @@ github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPn github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= +github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= @@ -277,6 +294,8 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= @@ -289,6 +308,7 @@ github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6 github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -703,6 +723,7 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/pkg/util/helperfunctions.go b/pkg/util/helperfunctions.go index cb47d07f..fcbadcac 100644 --- a/pkg/util/helperfunctions.go +++ b/pkg/util/helperfunctions.go @@ -3,6 +3,7 @@ package util import ( "context" "fmt" + "github.com/mitchellh/hashstructure/v2" "hash/fnv" "regexp" "unicode" @@ -22,6 +23,14 @@ func IsInternal(hostname string) bool { return internalPattern.MatchString(hostname) } +func GetHashForStructs(obj []interface{}) string { + hash, err := hashstructure.Hash(obj, hashstructure.FormatV2, nil) + if err != nil { + panic(err) + } + return fmt.Sprintf("%d", hash) +} + func GenerateHashFromName(name string) uint64 { hash := fnv.New64() _, _ = hash.Write([]byte(name)) diff --git a/pkg/util/reconciler.go b/pkg/util/reconciler.go index 76beec38..4e1e2894 100644 --- a/pkg/util/reconciler.go +++ b/pkg/util/reconciler.go @@ -140,6 +140,9 @@ func (r *ReconcilerBase) setResourceLabelsIfApplies(context context.Context, obj if present { if strings.EqualFold(objectGroupVersionKind.Group, resourceLabelGroupKind.Group) && strings.EqualFold(objectGroupVersionKind.Kind, resourceLabelGroupKind.Kind) { objectLabels := obj.GetLabels() + if len(objectLabels) == 0 { + objectLabels = make(map[string]string) + } maps.Copy(objectLabels, resourceLabels) obj.SetLabels(objectLabels) } @@ -150,7 +153,6 @@ func (r *ReconcilerBase) setResourceLabelsIfApplies(context context.Context, obj "Could not find according Kind for Resource "+controllerResource+". Make sure your resource is spelled correctly", ) } - } } @@ -159,8 +161,10 @@ func (r *ReconcilerBase) SetLabelsFromApplication(context context.Context, objec if len(labels) == 0 { labels = make(map[string]string) } - maps.Copy(labels, app.Spec.Labels) - object.SetLabels(labels) + if app.Spec.Labels != nil { + maps.Copy(labels, app.Spec.Labels) + object.SetLabels(labels) + } r.setResourceLabelsIfApplies(context, object, app) } diff --git a/tests/copy/00-assert.yaml b/tests/copy/00-assert.yaml index d36a8184..e9de7a03 100644 --- a/tests/copy/00-assert.yaml +++ b/tests/copy/00-assert.yaml @@ -3,7 +3,6 @@ kind: Deployment metadata: name: copy spec: - replicas: 3 strategy: type: Recreate template: diff --git a/tests/hpa/00-application.yaml b/tests/hpa/00-application.yaml new file mode 100644 index 00000000..a392722b --- /dev/null +++ b/tests/hpa/00-application.yaml @@ -0,0 +1,7 @@ +apiVersion: skiperator.kartverket.no/v1alpha1 +kind: Application +metadata: + name: test-deployment-hpa +spec: + image: image + port: 8080 diff --git a/tests/hpa/00-assert.yaml b/tests/hpa/00-assert.yaml new file mode 100644 index 00000000..e5d801f4 --- /dev/null +++ b/tests/hpa/00-assert.yaml @@ -0,0 +1,28 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-deployment-hpa +spec: + selector: + matchLabels: + app: test-deployment-hpa + replicas: 1 +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: test-deployment-hpa +spec: + minReplicas: 2 + maxReplicas: 5 + metrics: + - resource: + name: cpu + target: + averageUtilization: 80 + type: Utilization + type: Resource + scaleTargetRef: + kind: Deployment + apiVersion: apps/v1 + name: test-deployment-hpa diff --git a/tests/hpa/01-assert.yaml b/tests/hpa/01-assert.yaml new file mode 100644 index 00000000..14a4089d --- /dev/null +++ b/tests/hpa/01-assert.yaml @@ -0,0 +1,25 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-deployment-hpa +spec: + replicas: 1 +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: test-deployment-hpa +spec: + minReplicas: 10 + maxReplicas: 10 + metrics: + - resource: + name: cpu + target: + averageUtilization: 80 + type: Utilization + type: Resource + scaleTargetRef: + kind: Deployment + apiVersion: apps/v1 + name: test-deployment-hpa diff --git a/tests/hpa/01-patch-app.yaml b/tests/hpa/01-patch-app.yaml new file mode 100644 index 00000000..61aa63c9 --- /dev/null +++ b/tests/hpa/01-patch-app.yaml @@ -0,0 +1,9 @@ +apiVersion: skiperator.kartverket.no/v1alpha1 +kind: Application +metadata: + name: test-deployment-hpa +spec: + image: image + port: 8080 + replicas: + min: 10 diff --git a/tests/hpa/02-assert.yaml b/tests/hpa/02-assert.yaml new file mode 100644 index 00000000..409c8dc3 --- /dev/null +++ b/tests/hpa/02-assert.yaml @@ -0,0 +1,25 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-deployment-hpa +spec: + replicas: 1 +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: test-deployment-hpa +spec: + minReplicas: 3 + maxReplicas: 6 + metrics: + - resource: + name: cpu + target: + averageUtilization: 60 + type: Utilization + type: Resource + scaleTargetRef: + kind: Deployment + apiVersion: apps/v1 + name: test-deployment-hpa diff --git a/tests/hpa/02-patch-app.yaml b/tests/hpa/02-patch-app.yaml new file mode 100644 index 00000000..d0f4b87d --- /dev/null +++ b/tests/hpa/02-patch-app.yaml @@ -0,0 +1,11 @@ +apiVersion: skiperator.kartverket.no/v1alpha1 +kind: Application +metadata: + name: test-deployment-hpa +spec: + image: image + port: 8080 + replicas: + min: 3 + max: 6 + targetCpuUtilization: 60 diff --git a/tests/hpa/03-assert.yaml b/tests/hpa/03-assert.yaml new file mode 100644 index 00000000..fd7083c4 --- /dev/null +++ b/tests/hpa/03-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-deployment-hpa +spec: + replicas: 0 diff --git a/tests/hpa/03-errors.yaml b/tests/hpa/03-errors.yaml new file mode 100644 index 00000000..72500eb0 --- /dev/null +++ b/tests/hpa/03-errors.yaml @@ -0,0 +1,4 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: test-deployment-hpa diff --git a/tests/hpa/03-patch-app.yaml b/tests/hpa/03-patch-app.yaml new file mode 100644 index 00000000..01d6de49 --- /dev/null +++ b/tests/hpa/03-patch-app.yaml @@ -0,0 +1,10 @@ +apiVersion: skiperator.kartverket.no/v1alpha1 +kind: Application +metadata: + name: test-deployment-hpa +spec: + image: image + port: 8080 + replicas: + min: 0 + max: 0 diff --git a/tests/hpa/04-delete-application.yaml b/tests/hpa/04-delete-application.yaml new file mode 100644 index 00000000..e2de2cfa --- /dev/null +++ b/tests/hpa/04-delete-application.yaml @@ -0,0 +1,6 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: skiperator.kartverket.no/v1alpha1 + kind: Application + name: test-deployment-hpa diff --git a/tests/minimal/00-assert.yaml b/tests/minimal/00-assert.yaml index cf73b00e..e20efec4 100644 --- a/tests/minimal/00-assert.yaml +++ b/tests/minimal/00-assert.yaml @@ -55,8 +55,8 @@ kind: HorizontalPodAutoscaler metadata: name: minimal spec: - minReplicas: 1 - maxReplicas: 1 + minReplicas: 2 + maxReplicas: 5 metrics: - type: Resource resource: diff --git a/tests/pdb/00-application.yaml b/tests/pdb/00-application.yaml index f719587e..6ba87974 100644 --- a/tests/pdb/00-application.yaml +++ b/tests/pdb/00-application.yaml @@ -27,3 +27,14 @@ spec: port: 8080 replicas: min: 1 +--- +apiVersion: skiperator.kartverket.no/v1alpha1 +kind: Application +metadata: + name: yes-disruption-2 +spec: + image: image + port: 8080 + replicas: + min: 0 + max: 0 diff --git a/tests/pdb/00-assert.yaml b/tests/pdb/00-assert.yaml index ba2273dc..08a539af 100644 --- a/tests/pdb/00-assert.yaml +++ b/tests/pdb/00-assert.yaml @@ -27,3 +27,16 @@ spec: selector: matchLabels: app: yes-disruption +--- +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: yes-disruption-2 +spec: + minAvailable: 0 + selector: + matchLabels: + app: yes-disruption-2 +status: + desiredHealthy: 0 + expectedPods: 0