Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 40 additions & 11 deletions cmd/maintenance.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ var maintenanceServices = map[service][]string{
var serviceName service

func newMaintenanceCMD() *cobra.Command {

command := &cobra.Command{
Use: "maintenance",
Short: "Maintenance runner",
Expand All @@ -108,13 +107,38 @@ func newMaintenanceCMD() *cobra.Command {
func (c *controller) runMaintenance(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()

serviceID = viper.GetString("SERVICE_ID")

log := logr.FromContextOrDiscard(ctx).WithValues(
"instanceNamespace", viper.GetString("INSTANCE_NAMESPACE"),
"claimName", viper.GetString("CLAIM_NAME"),
"claimNamespace", viper.GetString("CLAIM_NAMESPACE"),
"serviceId", serviceID,
)

kubeClient, err := client.NewWithWatch(ctrl.GetConfigOrDie(), client.Options{
Scheme: pkg.SetupScheme(),
})
if err != nil {
return fmt.Errorf("failed to initialize kube client: %w", err)
}

controlNamespace := viper.GetString("CONTROL_NAMESPACE")
if controlNamespace == "" {
controlNamespace = "syn-appcat-control"
}

maintenanceConfigMapName := viper.GetString("MAINTENANCE_CONFIGMAP_NAME")
if maintenanceConfigMapName == "" {
maintenanceConfigMapName = "maintenance-config"
}

maintConfig, err := maintenance.GetMaintenanceConfig(ctx, kubeClient, maintenanceConfigMapName, controlNamespace, serviceID)
if err != nil {
log.Error(err, "Failed to read maintenance config, continuing with defaults")
maintConfig = maintenance.MaintenanceConfig{}
}

maintClient, err := getMaintClient(ctx, kubeClient)
if err != nil {
return fmt.Errorf("cannot initialize control plane client: %w", err)
Expand All @@ -124,13 +148,6 @@ func (c *controller) runMaintenance(cmd *cobra.Command, _ []string) error {
return fmt.Errorf("missing required environment variables: %w", err)
}

log := logr.FromContextOrDiscard(ctx).WithValues(
"instanceNamespace", viper.GetString("INSTANCE_NAMESPACE"),
"claimName", viper.GetString("CLAIM_NAME"),
"claimNamespace", viper.GetString("CLAIM_NAMESPACE"),
"serviceId", viper.GetString("SERVICE_ID"),
)

vh := getVersionHandler(kubeClient, log)

var m Maintenance
Expand Down Expand Up @@ -165,11 +182,19 @@ func (c *controller) runMaintenance(cmd *cobra.Command, _ []string) error {
}

pinImageTag := viper.GetString("PIN_IMAGE_TAG")
disableAppcatRelease, err := strconv.ParseBool(viper.GetString("DISABLE_APPCAT_RELEASE"))
envDisableAppcatRelease, err := strconv.ParseBool(viper.GetString("DISABLE_APPCAT_RELEASE"))
if err != nil {
return fmt.Errorf("cannot parse env variable DISABLE_APPCAT_RELEASE to bool: %w", err)
}

disableAppcatRelease := envDisableAppcatRelease || maintConfig.DisableAppcatRelease
disableServiceMaint := maintConfig.DisableServiceMaint

if disableAppcatRelease && disableServiceMaint {
log.Info("Both appcat release and service maintenance disabled, skipping all maintenance")
return nil
}

if disableAppcatRelease && pinImageTag != "" {
log.Info("AppCat release disabled and image tag pinned, skipping...")
return nil
Expand Down Expand Up @@ -213,6 +238,12 @@ func (c *controller) runMaintenance(cmd *cobra.Command, _ []string) error {
log.Info("Image tag pinned by user configuration, skipping service maintenance", "pinnedTag", pinImageTag)
return nil
}

if disableServiceMaint {
log.Info("Service maintenance disabled via component, skipping")
return nil
}

if err := m.DoMaintenance(ctx); err != nil {
return fmt.Errorf("maintenance failed: %w", err)
}
Expand Down Expand Up @@ -254,7 +285,6 @@ func validateMandatoryEnvs(s service) error {
}

func getVersionHandler(k8sCLient client.Client, log logr.Logger) release.VersionHandler {

opts := release.ReleaserOpts{
ClaimName: viper.GetString("CLAIM_NAME"),
ClaimNamespace: viper.GetString("CLAIM_NAMESPACE"),
Expand All @@ -280,7 +310,6 @@ func getStackgresClient() (*stackgres.StackgresClient, error) {
}

func getControlPlaneKubeConfig(ctx context.Context, kubeClient client.Client) (client.WithWatch, error) {

secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "controlclustercredentials",
Expand Down
92 changes: 92 additions & 0 deletions cmd/maintenance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package cmd

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"github.com/vshn/appcat/v4/pkg/maintenance"
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/fake"
)

func TestShouldDisable(t *testing.T) {
scheme := runtime.NewScheme()
_ = corev1.AddToScheme(scheme)

cmName := "maintenance-config"
cmNamespace := "syn-appcat-control"

tests := []struct {
name string
service string
envDisableAppcatRelease string
cmData map[string]string
wantDisableAppcat bool
wantDisableService bool
}{
{
name: "env disables appcat, cm does not",
service: "keycloak",
envDisableAppcatRelease: "true",
cmData: map[string]string{},
wantDisableAppcat: true,
wantDisableService: false,
},
{
name: "cm disables both, env does not",
service: "redis",
envDisableAppcatRelease: "false",
cmData: map[string]string{
"redis.disableServiceMaint": "true",
"redis.disableAppcatRelease": "true",
},
wantDisableAppcat: true,
wantDisableService: true,
},
{
name: "both sources disable appcat",
service: "mariadb",
envDisableAppcatRelease: "true",
cmData: map[string]string{
"mariadb.disableAppcatRelease": "true",
},
wantDisableAppcat: true,
wantDisableService: false,
},
{
name: "nothing disabled",
service: "nextcloud",
envDisableAppcatRelease: "false",
cmData: map[string]string{},
wantDisableAppcat: false,
wantDisableService: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cm := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: cmName,
Namespace: cmNamespace,
},
Data: tt.cmData,
}
c := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(cm).Build()

cfg, err := maintenance.GetMaintenanceConfig(context.Background(), c, cmName, cmNamespace, tt.service)
assert.NoError(t, err)

envDisable := tt.envDisableAppcatRelease == "true"

disableAppcat := envDisable || cfg.DisableAppcatRelease
disableService := cfg.DisableServiceMaint

assert.Equal(t, tt.wantDisableAppcat, disableAppcat)
assert.Equal(t, tt.wantDisableService, disableService)
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,14 @@ func (m *Maintenance) buildMaintenancePodTemplateSpec(imageTag, serviceAccount s
Name: "DISABLE_APPCAT_RELEASE",
Value: strconv.FormatBool(m.schedule.DisableAppcatRelease),
},
{
Name: "CONTROL_NAMESPACE",
Value: m.svc.Config.Data["controlNamespace"],
},
{
Name: "MAINTENANCE_CONFIGMAP_NAME",
Value: m.svc.Config.Data["maintenanceConfigMapName"],
},
}

return corev1.PodTemplateSpec{
Expand Down
39 changes: 39 additions & 0 deletions pkg/maintenance/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package maintenance

import (
"context"
"strconv"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// MaintenanceConfig holds the per-service mainteancne configuration
type MaintenanceConfig struct {
DisableServiceMaint bool
DisableAppcatRelease bool
}

func GetMaintenanceConfig(ctx context.Context, c client.Reader, cmName, namespace, serviceID string) (MaintenanceConfig, error) {
conf := MaintenanceConfig{}

cm := &corev1.ConfigMap{}

err := c.Get(ctx, types.NamespacedName{Name: cmName, Namespace: namespace}, cm)
if err != nil {
return conf, err
}

conf.DisableServiceMaint = parseBool(cm.Data[serviceID+".disableServiceMaint"])
conf.DisableAppcatRelease = parseBool(cm.Data[serviceID+".disableAppcatRelease"])

return conf, nil
}

// parseBool will parse the string to a boolean
// Invalid and empty strings are treated as false
func parseBool(s string) bool {
v, _ := strconv.ParseBool(s)
return v
}
100 changes: 100 additions & 0 deletions pkg/maintenance/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package maintenance

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
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/fake"
)

func TestGetMaintenanceConfig(t *testing.T) {
scheme := runtime.NewScheme()
_ = corev1.AddToScheme(scheme)

tests := []struct {
name string
service string
configMap *corev1.ConfigMap
wantServiceDis bool
wantAppcatDis bool
wantErr bool
}{
{
name: "both disabled for keycloak",
service: "keycloak",
configMap: &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "maintenance-config",
Namespace: "syn-appcat-control",
},
Data: map[string]string{
"keycloak.disableServiceMaint": "true",
"keycloak.disableAppcatRelease": "true",
},
},
wantServiceDis: true,
wantAppcatDis: true,
},
{
name: "nothing disabled for redis",
service: "redis",
configMap: &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "maintenance-config",
Namespace: "syn-appcat-control",
},
Data: map[string]string{
"redis.disableServiceMaint": "false",
"redis.disableAppcatRelease": "false",
},
},
wantServiceDis: false,
wantAppcatDis: false,
},
{
name: "missing keys default to false",
service: "mariadb",
configMap: &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "maintenance-config",
Namespace: "syn-appcat-control",
},
Data: map[string]string{},
},
wantServiceDis: false,
wantAppcatDis: false,
},
{
name: "missing configmap returns an error, defaults false",
service: "forgejo",
configMap: nil,
wantServiceDis: false,
wantAppcatDis: false,
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
objs := []runtime.Object{}
if tt.configMap != nil {
objs = append(objs, tt.configMap)
}
c := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objs...).Build()

cfg, err := GetMaintenanceConfig(context.Background(), c, "maintenance-config", "syn-appcat-control", tt.service)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.wantServiceDis, cfg.DisableServiceMaint)
assert.Equal(t, tt.wantAppcatDis, cfg.DisableAppcatRelease)
})
}
}
Loading