Skip to content

Commit

Permalink
PoC rework ensure
Browse files Browse the repository at this point in the history
  • Loading branch information
bouskaJ committed Nov 13, 2024
1 parent 34b74c6 commit c3847c5
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 121 deletions.
24 changes: 24 additions & 0 deletions internal/controller/common/action/base_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ import (
"strings"
"time"

"github.com/securesign/operator/internal/apis"
"github.com/securesign/operator/internal/controller/annotations"
"github.com/securesign/operator/internal/controller/constants"
"k8s.io/apimachinery/pkg/api/equality"
apiErrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

Expand Down Expand Up @@ -62,6 +65,7 @@ func (action *BaseAction) StatusUpdate(ctx context.Context, obj client2.Object)
return &Result{Result: reconcile.Result{Requeue: false}}
}

// Deprecated: Use Error function
func (action *BaseAction) Failed(err error) *Result {
action.Logger.Error(err, "error during action execution")
return &Result{
Expand All @@ -70,6 +74,25 @@ func (action *BaseAction) Failed(err error) *Result {
}
}

func (action *BaseAction) Error(ctx context.Context, err error, instance apis.ConditionsAwareObject) *Result {
if errors.Is(err, reconcile.TerminalError(err)) {
instance.SetCondition(metav1.Condition{
Type: constants.Ready,
Status: metav1.ConditionFalse,
Reason: constants.Failure,
Message: err.Error(),
})
if updateErr := action.Client.Status().Update(ctx, instance); updateErr != nil {
err = errors.Join(err, updateErr)
}
}
action.Logger.Error(err, "error during action execution")
return &Result{
Err: err,
}
}

// Deprecated: Use Error function with TerminalError passed as an argument
func (action *BaseAction) FailedWithStatusUpdate(ctx context.Context, err error, instance client2.Object) *Result {
if e := action.Client.Status().Update(ctx, instance); e != nil {
if strings.Contains(err.Error(), OptimisticLockErrorMsg) {
Expand All @@ -96,6 +119,7 @@ func (action *BaseAction) Requeue() *Result {
}
}

// Deprecated: Use kubernetes.CreateOrUpdate function
func (action *BaseAction) Ensure(ctx context.Context, obj client2.Object, opts ...EnsureOption) (bool, error) {
var (
expected client2.Object
Expand Down
24 changes: 24 additions & 0 deletions internal/controller/common/utils/ensure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package utils

import (
"golang.org/x/exp/maps"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func EnsureLabels(obj client.Object, labels map[string]string) {
if obj.GetLabels() == nil {
obj.SetLabels(labels)
return
}

maps.Copy(obj.GetLabels(), labels)
}

func EnsureAnnotations(obj client.Object, annotations map[string]string) {
if obj.GetAnnotations() == nil {
obj.GetAnnotations()
return
}

maps.Copy(obj.GetAnnotations(), annotations)
}
24 changes: 24 additions & 0 deletions internal/controller/common/utils/kubernetes/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import (
"strconv"
"strings"

"github.com/securesign/operator/internal/controller/annotations"
apiErrors "k8s.io/apimachinery/pkg/api/errors"
k8sLabels "k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

v13 "github.com/openshift/api/operator/v1"
"github.com/securesign/operator/internal/controller/common/utils"
Expand Down Expand Up @@ -133,3 +137,23 @@ func FindByLabelSelector(ctx context.Context, c client.Client, list client.Objec

return c.List(ctx, list, client.InNamespace(namespace), listOptions)
}

func CreateOrUpdate[T client.Object](ctx context.Context, cli client.Client, obj T, fn func(object T) error) (result controllerutil.OperationResult, err error) {
err = retry.OnError(retry.DefaultRetry, func(err error) bool {
return apiErrors.IsConflict(err) || apiErrors.IsAlreadyExists(err)
}, func() error {
var createUpdateError error
result, createUpdateError = controllerutil.CreateOrUpdate(ctx, cli, obj, func() error {
annoStr, find := obj.GetAnnotations()[annotations.PausedReconciliation]
if find {
annoBool, _ := strconv.ParseBool(annoStr)
if annoBool {
return nil
}
}
return fn(obj)
})
return createUpdateError
})
return
}
21 changes: 20 additions & 1 deletion internal/controller/common/utils/set_proxy.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
package utils

import (
"reflect"
"slices"

"github.com/operator-framework/operator-lib/proxy"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
)

// SetProxyEnvs set the standard environment variables for proxys "HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY"
func SetProxyEnvs(dep *appsv1.Deployment) {
proxyEnvs := proxy.ReadProxyVarsFromEnv()
for i, container := range dep.Spec.Template.Spec.Containers {
dep.Spec.Template.Spec.Containers[i].Env = append(container.Env, proxy.ReadProxyVarsFromEnv()...)
for _, e := range proxyEnvs {
if index := slices.IndexFunc(container.Env,
func(envVar v1.EnvVar) bool { return e.Name == envVar.Name },
); index > -1 {
if reflect.DeepEqual(e, container.Env[index]) {
// variable already present
continue
} else {
// overwrite
dep.Spec.Template.Spec.Containers[i].Env[index] = e
}
} else {
dep.Spec.Template.Spec.Containers[i].Env = append(dep.Spec.Template.Spec.Containers[i].Env, e)
}
}
}
}
19 changes: 15 additions & 4 deletions internal/controller/common/utils/set_proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"testing"

. "github.com/onsi/gomega"

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
)
Expand All @@ -28,6 +27,10 @@ func TestSetProxyEnvs(t *testing.T) {
Name: "answer",
Value: "42",
},
{
Name: "no_proxy",
Value: "toBeOverwritten",
},
}

// Define a mock deployment
Expand All @@ -49,7 +52,7 @@ func TestSetProxyEnvs(t *testing.T) {
SetProxyEnvs(dep)

g.Expect(dep.Spec.Template.Spec.Containers).ShouldNot(BeNil())
g.Expect(dep.Spec.Template.Spec.Containers[0].Env).Should(HaveLen(1))
g.Expect(dep.Spec.Template.Spec.Containers[0].Env).Should(HaveLen(2))
g.Expect(dep.Spec.Template.Spec.Containers[0].Env).Should(BeEquivalentTo(defaultEnv))

for _, e := range mockReadProxyVarsFromEnv() {
Expand All @@ -58,9 +61,17 @@ func TestSetProxyEnvs(t *testing.T) {

SetProxyEnvs(dep)

expectedEnvVars := append(defaultEnv, mockReadProxyVarsFromEnv()...)
expectedEnvVars := append(mockReadProxyVarsFromEnv(), corev1.EnvVar{
Name: "answer",
Value: "42",
})

g.Expect(dep.Spec.Template.Spec.Containers).ShouldNot(BeNil())
g.Expect(dep.Spec.Template.Spec.Containers[0].Env).Should(HaveLen(7))
g.Expect(dep.Spec.Template.Spec.Containers[0].Env).Should(BeEquivalentTo(expectedEnvVars))
g.Expect(dep.Spec.Template.Spec.Containers[0].Env).Should(ConsistOf(expectedEnvVars))

// ensure no duplicates
SetProxyEnvs(dep)
g.Expect(dep.Spec.Template.Spec.Containers).ShouldNot(BeNil())
g.Expect(dep.Spec.Template.Spec.Containers[0].Env).Should(HaveLen(7))
}
117 changes: 96 additions & 21 deletions internal/controller/tuf/actions/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ import (

rhtasv1alpha1 "github.com/securesign/operator/api/v1alpha1"
"github.com/securesign/operator/internal/controller/common/action"
"github.com/securesign/operator/internal/controller/common/utils"
"github.com/securesign/operator/internal/controller/common/utils/kubernetes"
"github.com/securesign/operator/internal/controller/constants"
"github.com/securesign/operator/internal/controller/labels"
tufutils "github.com/securesign/operator/internal/controller/tuf/utils"
v1 "k8s.io/api/apps/v1"
core "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

Expand All @@ -32,34 +36,105 @@ func (i deployAction) CanHandle(_ context.Context, tuf *rhtasv1alpha1.Tuf) bool
}

func (i deployAction) Handle(ctx context.Context, instance *rhtasv1alpha1.Tuf) *action.Result {
var (
updated bool
err error
)

labels := labels.For(ComponentName, DeploymentName, instance.Name)

dp := tufutils.CreateTufDeployment(instance, DeploymentName, RBACName, labels)

if err = controllerutil.SetControllerReference(instance, dp, i.Client.Scheme()); err != nil {
return i.Failed(fmt.Errorf("could not set controller reference for Deployment: %w", err))
}

if updated, err = i.Ensure(ctx, dp); err != nil {
meta.SetStatusCondition(&instance.Status.Conditions, metav1.Condition{
Type: constants.Ready,
Status: metav1.ConditionFalse,
Reason: constants.Failure,
Message: err.Error(),
})
return i.FailedWithStatusUpdate(ctx, fmt.Errorf("could not create TUF: %w", err), instance)
var (
result controllerutil.OperationResult
err error
)
if result, err = kubernetes.CreateOrUpdate(ctx, i.Client,
&v1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: DeploymentName,
Namespace: instance.Namespace,
},
}, i.createTufDeployment(instance, RBACName, labels)); err != nil {
return i.Error(ctx, fmt.Errorf("could not create TUF: %w", err), instance)
}

if updated {
if result != controllerutil.OperationResultNone {
meta.SetStatusCondition(&instance.Status.Conditions, metav1.Condition{Type: constants.Ready,
Status: metav1.ConditionFalse, Reason: constants.Creating, Message: "Deployment created"})
return i.StatusUpdate(ctx, instance)
} else {
return i.Continue()
}
}

func (i deployAction) createTufDeployment(instance *rhtasv1alpha1.Tuf, sa string, labels map[string]string) func(*v1.Deployment) error {
return func(dp *v1.Deployment) error {
if err := controllerutil.SetControllerReference(instance, dp, i.Client.Scheme()); err != nil {
return fmt.Errorf("could not set controller reference for Deployment: %w", err)
}
utils.EnsureLabels(dp, labels)

spec := &dp.Spec
spec.Replicas = utils.Pointer[int32](1)
spec.Selector = &metav1.LabelSelector{
MatchLabels: labels,
}
spec.Strategy = v1.DeploymentStrategy{
Type: "Recreate",
}

template := &spec.Template
template.Labels = labels
template.Spec.ServiceAccountName = sa

if template.Spec.Volumes == nil {
template.Spec.Volumes = make([]core.Volume, 1)
}
volume := &template.Spec.Volumes[0]
volume.Name = "repository"
volume.VolumeSource = core.VolumeSource{
PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{
ClaimName: instance.Status.PvcName,
},
}

if template.Spec.Containers == nil {
template.Spec.Containers = make([]core.Container, 1)
}
container := &template.Spec.Containers[0]
container.Name = "tuf-server"
container.Image = constants.HttpServerImage
container.Ports = []core.ContainerPort{
{
Protocol: core.ProtocolTCP,
ContainerPort: 8080,
},
}

container.VolumeMounts = []core.VolumeMount{
{
Name: "repository",
MountPath: "/var/www/html",
// let user upload manual update using `oc rsync` command
ReadOnly: false,
}}

if container.LivenessProbe == nil {
container.LivenessProbe = &core.Probe{}
}
// server is running returning any status code (including 403 - noindex.html)
container.LivenessProbe.Exec = &core.ExecAction{Command: []string{"curl", "localhost:8080"}}
container.LivenessProbe.InitialDelaySeconds = 30
container.LivenessProbe.PeriodSeconds = 10

if container.ReadinessProbe == nil {
container.ReadinessProbe = &core.Probe{}
}
container.ReadinessProbe.HTTPGet = &core.HTTPGetAction{
Path: "/root.json",
Port: intstr.FromInt32(8080),
Scheme: "HTTP",
}
container.ReadinessProbe.InitialDelaySeconds = 10
container.ReadinessProbe.PeriodSeconds = 10
container.ReadinessProbe.FailureThreshold = 10

utils.SetProxyEnvs(dp)

return nil
}
}
Loading

0 comments on commit c3847c5

Please sign in to comment.