Skip to content

Commit

Permalink
Merge pull request #5 from tomp21/refactor/reconcilers
Browse files Browse the repository at this point in the history
Refactor/reconcilers
  • Loading branch information
tomp21 authored Dec 26, 2024
2 parents 4aad98a + b6fb88a commit db4101c
Show file tree
Hide file tree
Showing 14 changed files with 621 additions and 587 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.22.0
require (
github.com/onsi/ginkgo/v2 v2.17.1
github.com/onsi/gomega v1.32.0
k8s.io/api v0.30.1
k8s.io/apimachinery v0.30.1
k8s.io/client-go v0.30.1
sigs.k8s.io/controller-runtime v0.18.4
Expand All @@ -18,6 +19,7 @@ require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
Expand Down Expand Up @@ -81,7 +83,6 @@ require (
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.30.1 // indirect
k8s.io/apiextensions-apiserver v0.30.1 // indirect
k8s.io/apiserver v0.30.1 // indirect
k8s.io/component-base v0.30.1 // indirect
Expand Down
5 changes: 0 additions & 5 deletions internal/controller/configmaps/conf/master.conf

This file was deleted.

6 changes: 0 additions & 6 deletions internal/controller/configmaps/conf/redis.conf

This file was deleted.

Empty file.
15 changes: 0 additions & 15 deletions internal/controller/configmaps/scripts/start-master.sh

This file was deleted.

50 changes: 0 additions & 50 deletions internal/controller/configmaps/scripts/start-replica.sh

This file was deleted.

91 changes: 91 additions & 0 deletions internal/controller/reconcilers/secret_reconciler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package reconcilers

import (
"bytes"
"context"
"math/rand/v2"

corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

cachev1alpha1 "github.com/tomp21/yazio-challenge/api/v1alpha1"
"github.com/tomp21/yazio-challenge/internal/util"
)

const (
passwordSpecialChars = "!@#$%^&*()_+-=[]{};':,./?~"
passwordLetters = "abcdefghijklmnopqrstuvwxyz"
passwordNumbers = "0123456789"
passwordLength = 12
)

var PasswordSecretKey = "redis-password"

type SecretReconciler struct {
client.Client
scheme *runtime.Scheme
}

func NewSecretReconciler(client client.Client, scheme *runtime.Scheme) *SecretReconciler {
return &SecretReconciler{
Client: client,
scheme: scheme,
}
}

// Known issue: if the secret was edited and the redis-secret field no longer exist, but the secret itself is there, we are not fixing it
func (r *SecretReconciler) Reconcile(ctx context.Context, redis *cachev1alpha1.Redis) error {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: redis.Name,
Namespace: redis.Namespace,
Labels: util.GetLabels(redis, nil),
},
}
namespacedName := types.NamespacedName{
Name: redis.Name,
Namespace: redis.Namespace,
}
password := generateSecureRedisPassword()
err := r.Get(ctx, namespacedName, secret)
if err != nil {
if apierrors.IsNotFound(err) {
secret.StringData = map[string]string{PasswordSecretKey: password}
controllerutil.SetControllerReference(redis, secret, r.scheme)
return r.Create(ctx, secret)
}
return err
}
return nil
}

// This code is quite ugly, but does the job
func generateSecureRedisPassword() string {
var password []byte
//We get an amount of letters that will be uppercase, which at max will be passwordLength/2, then we iterate pulling random letters until we get that amount
// Using IntN()+1 we ensure the amount is not 0
upperCaseChars := rand.IntN(passwordLength/2) + 1
for len(password) < upperCaseChars {
password = append(password, passwordLetters[rand.IntN(len(passwordLetters)-1)])
}
// We set this letters to uppercase and continue pulling at least 1 char of each string of symbols
password = bytes.ToUpper(password)
password = append(password, passwordSpecialChars[rand.IntN(len(passwordSpecialChars)-2)]+1)
password = append(password, passwordLetters[rand.IntN(len(passwordLetters)-2)]+1)
password = append(password, passwordNumbers[rand.IntN(len(passwordNumbers)-2)]+1)
allchars := passwordSpecialChars + passwordLetters + passwordNumbers
// In the end, we keep pulling random characters from a string containing all symbols in order to get the length defined
for len(password) < passwordLength {
password = append(password, allchars[rand.IntN(len(allchars)-1)])
}
// shuffle them around
rand.Shuffle(len(password), func(i, j int) {
password[i], password[j] = password[j], password[i]
})
return string(password)
}
34 changes: 34 additions & 0 deletions internal/controller/reconcilers/secret_reconciler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package reconcilers

import (
"strings"
"testing"
)

func Test_generateSecret(t *testing.T) {
password := generateSecureRedisPassword()

if len(password) != passwordLength {
t.Fatalf("Password length is not the expected one")
}
var succeed, specialChar, lowerCaseLetter, upperCaseLetter, number bool
for _, rune := range password {
char := string(rune)
if strings.Contains(passwordSpecialChars, char) {
specialChar = true
}
if strings.Contains(passwordLetters, char) {
lowerCaseLetter = true
}
if strings.Contains(passwordNumbers, char) {
number = true
}
if strings.Contains(passwordLetters, strings.ToLower(char)) {
upperCaseLetter = true
}
succeed = specialChar && lowerCaseLetter && number && upperCaseLetter
}
if !succeed {
t.Fatalf("Password %s did not contain all required types of characters", password)
}
}
86 changes: 86 additions & 0 deletions internal/controller/reconcilers/service_reconciler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package reconcilers

import (
"context"
"fmt"
cachev1alpha1 "github.com/tomp21/yazio-challenge/api/v1alpha1"
"github.com/tomp21/yazio-challenge/internal/util"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

const (
RedisPort = 6379
RedisPortName = "redis"
)

type ServiceReconciler struct {
client *client.Client
scheme *runtime.Scheme
}

func NewServiceReconciler(client *client.Client, scheme *runtime.Scheme) *ServiceReconciler {
return &ServiceReconciler{
client: client,
scheme: scheme,
}
}

func (r *ServiceReconciler) Reconcile(ctx context.Context, redis *cachev1alpha1.Redis) error {
//master svc
masterSvcName := fmt.Sprintf("%s-master", redis.Name)
err := r.createOrUpdateService(ctx, masterSvcName, redis)
if err != nil {
return err
}
//replicas svc
replicaSvcName := fmt.Sprintf("%s-replica", redis.Name)
err = r.createOrUpdateService(ctx, replicaSvcName, redis)
if err != nil {
return err
}
headlessSvcName := fmt.Sprintf("%s-headless", redis.Name)
err = r.createOrUpdateService(ctx, headlessSvcName, redis)
if err != nil {
return err
}
return nil
}

func (r *ServiceReconciler) createOrUpdateService(ctx context.Context, svcName string, redis *cachev1alpha1.Redis) error {
labels := util.GetLabels(redis, util.MasterLabels)
svc := generateService(labels, svcName, redis.Namespace)

_, err := controllerutil.CreateOrUpdate(ctx, *r.client, svc, func() error {
// we generate the service again to override any change that might've been done to the live resource
svc = generateService(labels, svcName, redis.Namespace)
svc.ObjectMeta.SetLabels(labels)
return controllerutil.SetControllerReference(redis, svc, r.scheme)
})
return err
}

func generateService(labels map[string]string, name string, namespace string) *corev1.Service {
return &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Labels: labels,
},
Spec: corev1.ServiceSpec{
Type: "ClusterIP",
Ports: []corev1.ServicePort{
{
Name: "tcp-redis",
Port: RedisPort,
TargetPort: intstr.FromString(RedisPortName),
},
},
Selector: labels,
},
}
}
39 changes: 39 additions & 0 deletions internal/controller/reconcilers/serviceaccount_reconciler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package reconcilers

import (
"context"
cachev1alpha1 "github.com/tomp21/yazio-challenge/api/v1alpha1"
"github.com/tomp21/yazio-challenge/internal/util"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

type ServiceAccountReconciler struct {
client *client.Client
scheme *runtime.Scheme
}

func NewServiceAccountReconciler(client *client.Client, scheme *runtime.Scheme) *ServiceAccountReconciler {
return &ServiceAccountReconciler{
client: client,
scheme: scheme,
}
}

func (r *ServiceAccountReconciler) Reconcile(ctx context.Context, redis *cachev1alpha1.Redis) error {

sa := &corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: redis.Name,
Namespace: redis.Namespace,
},
}
_, err := controllerutil.CreateOrUpdate(ctx, *r.client, sa, func() error {
sa.GetObjectMeta().SetLabels(util.GetLabels(redis, nil))
return controllerutil.SetControllerReference(redis, sa, r.scheme)
})
return err
}
Loading

0 comments on commit db4101c

Please sign in to comment.