Skip to content

Commit

Permalink
overrides: Allow for volume size, env, and label overrides (PROJQUAY-…
Browse files Browse the repository at this point in the history
…1090)

- Add override field to Operator CRD to allow for volume size overrides
- Add tests to reflect changes in API
  • Loading branch information
jonathankingfc committed Nov 10, 2021
1 parent caa8303 commit 6861556
Show file tree
Hide file tree
Showing 6 changed files with 370 additions and 11 deletions.
83 changes: 73 additions & 10 deletions apis/quay/v1/quayregistry_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,16 @@ var QuayVersionCurrent QuayVersion = QuayVersion(os.Getenv("QUAY_VERSION"))
type ComponentKind string

const (
ComponentBase ComponentKind = "base"
ComponentPostgres = "postgres"
ComponentClair = "clair"
ComponentRedis = "redis"
ComponentHPA = "horizontalpodautoscaler"
ComponentObjectStorage = "objectstorage"
ComponentRoute = "route"
ComponentMirror = "mirror"
ComponentMonitoring = "monitoring"
ComponentTLS = "tls"
ComponentBase = "base"
ComponentPostgres = "postgres"
ComponentClair = "clair"
ComponentRedis = "redis"
ComponentHPA = "horizontalpodautoscaler"
ComponentObjectStorage = "objectstorage"
ComponentRoute = "route"
ComponentMirror = "mirror"
ComponentMonitoring = "monitoring"
ComponentTLS = "tls"
)

var allComponents = []ComponentKind{
Expand All @@ -68,6 +68,19 @@ var requiredComponents = []ComponentKind{
ComponentRedis,
}

// fixme (jonathankingfc) - These should not be strings, use a correct type
var overrideSupport = map[ComponentKind][]string{
ComponentPostgres: {"volumeSize", "labels", "env"},
ComponentClair: {"volumeSize", "labels", "env"},
ComponentRedis: {"labels", "env"},
ComponentHPA: {"labels"},
ComponentObjectStorage: {"volumeSize", "labels", "env"},
ComponentRoute: {"labels"},
ComponentMirror: {"labels", "env"},
ComponentMonitoring: {"labels"},
ComponentTLS: {"labels"},
}

const (
ManagedKeysName = "quay-registry-managed-secret-keys"
QuayConfigTLSSecretName = "quay-config-tls"
Expand All @@ -88,6 +101,18 @@ type Component struct {
// Managed indicates whether or not the Operator is responsible for the lifecycle of this component.
// Default is true.
Managed bool `json:"managed"`
// Overrides holds information regarding component specific configurations.
Overrides Override `json:"overrides,omitempty"`
}

// Override describes configuration overrides for the given managed component
type Override struct {
// Env holds environment variable overrides that will be added to the given managed components deployment (if available)
Env map[string]string `json:"env,omitempty"`
// Labels holds labels that will be added to the given managed components resources (if available)
Labels map[string]string `json:"labels,omitempty"`
// VolumeSize holds a volume size override that will be set on the given managed components PVC.
VolumeSize string `json:"volumeSize,omitempty"`
}

type ConditionType string
Expand Down Expand Up @@ -127,6 +152,7 @@ const (
ConditionReasonObjectStorageComponentDependencyError ConditionReason = "ObjectStorageComponentDependencyError"
ConditionReasonMonitoringComponentDependencyError ConditionReason = "MonitoringComponentDependencyError"
ConditionReasonConfigInvalid ConditionReason = "ConfigInvalid"
ConditionReasonInvalidComponentOverride ConditionReason = "ComponentOverrideInvalid"
)

// Condition is a single condition of a QuayRegistry.
Expand Down Expand Up @@ -330,6 +356,34 @@ func EnsureDefaultComponents(ctx *quaycontext.QuayRegistryContext, quay *QuayReg
return updatedQuay, nil
}

// ValidateOverrides validates that the overrides set for each component are valid.
func ValidateOverrides(quay *QuayRegistry) error {
for _, component := range quay.Spec.Components {

// If the component is unmanaged, we cannot set overrides
if !ComponentIsManaged(quay.Spec.Components, component.Kind) {
if component.Overrides.Env != nil || component.Overrides.Labels != nil || component.Overrides.VolumeSize != "" {
return errors.New("cannot set overrides on unmanaged component " + string(component.Kind))
}
}

// Validate fields
if component.Overrides.Env != nil && !ComponentSupportsOverride(component.Kind, "env") {
return fmt.Errorf("component %s does not support env overrides", component.Kind)
}
if component.Overrides.Labels != nil && !ComponentSupportsOverride(component.Kind, "labels") {
return fmt.Errorf("component %s does not support label overrides", component.Kind)
}
if component.Overrides.VolumeSize != "" && !ComponentSupportsOverride(component.Kind, "volumeSize") {
return fmt.Errorf("component %s does not support volumeSize overrides", component.Kind)
}

}

return nil

}

// EnsureRegistryEndpoint sets the `status.registryEndpoint` field and returns `ok` if it was unchanged.
func EnsureRegistryEndpoint(ctx *quaycontext.QuayRegistryContext, quay *QuayRegistry, config map[string]interface{}) (*QuayRegistry, bool) {
updatedQuay := quay.DeepCopy()
Expand Down Expand Up @@ -493,6 +547,15 @@ func FieldGroupNamesForManagedComponents(quay *QuayRegistry) ([]string, error) {
return fgns, nil
}

func ComponentSupportsOverride(component ComponentKind, override string) bool {
for _, supportedOverride := range overrideSupport[component] {
if override == supportedOverride {
return true
}
}
return false
}

func init() {
SchemeBuilder.Register(&QuayRegistry{}, &QuayRegistryList{})
}
109 changes: 109 additions & 0 deletions apis/quay/v1/quayregistry_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,115 @@ var componentsMatchTests = []struct {
},
}

var validateOverridesTests = []struct {
name string
quay QuayRegistry
expectedErr error
}{
{
"NoOverridesProvided",
QuayRegistry{
Spec: QuayRegistrySpec{
Components: []Component{
{Kind: "postgres", Managed: true},
{Kind: "redis", Managed: true},
{Kind: "clair", Managed: true},
{Kind: "objectstorage", Managed: true},
{Kind: "route", Managed: true},
{Kind: "tls", Managed: true},
{Kind: "horizontalpodautoscaler", Managed: true},
{Kind: "mirror", Managed: true},
{Kind: "monitoring", Managed: true},
},
},
},
nil,
},
{
"InvalidVolumeSizeOverride",
QuayRegistry{
Spec: QuayRegistrySpec{
Components: []Component{
{Kind: "postgres", Managed: true},
{Kind: "redis", Managed: true},
{Kind: "clair", Managed: true},
{Kind: "objectstorage", Managed: true},
{Kind: "route", Managed: true},
{Kind: "tls", Managed: true, Overrides: Override{VolumeSize: "50Gb"}},
{Kind: "horizontalpodautoscaler", Managed: true},
{Kind: "mirror", Managed: true},
{Kind: "monitoring", Managed: true},
},
},
},
errors.New("component tls does not support volumeSize overrides"),
},
{
"InvalidEnvOverride",
QuayRegistry{
Spec: QuayRegistrySpec{
Components: []Component{
{Kind: "postgres", Managed: true},
{Kind: "redis", Managed: true},
{Kind: "clair", Managed: true},
{Kind: "objectstorage", Managed: true},
{Kind: "route", Managed: true},
{Kind: "tls", Managed: true, Overrides: Override{Env: map[string]string{"DEBUGLOGS": "true"}}},
{Kind: "horizontalpodautoscaler", Managed: true},
{Kind: "mirror", Managed: true},
{Kind: "monitoring", Managed: true},
},
},
},
errors.New("component tls does not support env overrides"),
},
{
"AllValidOverrides",
QuayRegistry{
Spec: QuayRegistrySpec{
Components: []Component{
{Kind: "postgres", Managed: true, Overrides: Override{Labels: map[string]string{"test-label": "test-value"}, Env: map[string]string{"TESTVAR": "TESTAVL"}, VolumeSize: "50Gb"}},
{Kind: "redis", Managed: true, Overrides: Override{Labels: map[string]string{"test-label": "test-value"}, Env: map[string]string{"TESTVAR": "TESTAVL"}}},
{Kind: "clair", Managed: true, Overrides: Override{Labels: map[string]string{"test-label": "test-value"}, Env: map[string]string{"TESTVAR": "TESTAVL"}, VolumeSize: "50Gb"}},
{Kind: "objectstorage", Managed: true, Overrides: Override{Labels: map[string]string{"test-label": "test-value"}, Env: map[string]string{"TESTVAR": "TESTAVL"}, VolumeSize: "50Gb"}},
{Kind: "route", Managed: true, Overrides: Override{Labels: map[string]string{"test-label": "test-value"}}},
{Kind: "tls", Managed: true, Overrides: Override{Labels: map[string]string{"test-label": "test-value"}}},
{Kind: "horizontalpodautoscaler", Managed: true, Overrides: Override{Labels: map[string]string{"test-label": "test-value"}}},
{Kind: "mirror", Managed: true, Overrides: Override{Labels: map[string]string{"test-label": "test-value"}, Env: map[string]string{"TESTVAR": "TESTAVL"}}},
{Kind: "monitoring", Managed: true, Overrides: Override{Labels: map[string]string{"test-label": "test-value"}}},
},
},
},
nil,
},
{
"InvalidOverrideUnmanagedComponent",
QuayRegistry{
Spec: QuayRegistrySpec{
Components: []Component{
{Kind: "postgres", Managed: false, Overrides: Override{Labels: map[string]string{"test-label": "test-value"}, Env: map[string]string{"TESTVAR": "TESTAVL"}, VolumeSize: "50Gb"}},
},
},
},
errors.New("cannot set overrides on unmanaged component postgres"),
},
}

func TestValidOverrides(t *testing.T) {
assert := assert.New(t)

for _, test := range validateOverridesTests {
err := ValidateOverrides(&test.quay)

if test.expectedErr != nil {
assert.NotNil(err, test.name)
assert.Equal(test.expectedErr, err)
} else {
assert.Equal(test.expectedErr, err)
}
}
}

func TestComponentsMatch(t *testing.T) {
assert := assert.New(t)

Expand Down
17 changes: 17 additions & 0 deletions config/crd/bases/quay.redhat.com_quayregistries.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,23 @@ spec:
managed:
description: Managed indicates whether or not the Operator is responsible for the lifecycle of this component. Default is true.
type: boolean
overrides:
description: Overrides holds information regarding component specific configurations.
properties:
env:
additionalProperties:
type: string
description: Env holds environment variable overrides that will be added to the given managed components deployment (if available)
type: object
labels:
additionalProperties:
type: string
description: Labels holds labels that will be added to the given managed components resources (if available)
type: object
volumeSize:
description: VolumeSize holds a volume size override that will be set on the given managed components PVC.
type: string
type: object
required:
- kind
- managed
Expand Down
7 changes: 7 additions & 0 deletions controllers/quay/quayregistry_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,13 @@ func (r *QuayRegistryReconciler) Reconcile(ctx context.Context, req ctrl.Request
return ctrl.Result{}, nil
}

err = v1.ValidateOverrides(updatedQuay)
if err != nil {
msg := fmt.Sprintf("could not validate overrides on spec.components: %s", err)

return r.reconcileWithCondition(&quay, v1.ConditionTypeRolloutBlocked, metav1.ConditionTrue, v1.ConditionReasonInvalidComponentOverride, msg)
}

if !v1.ComponentsMatch(quay.Spec.Components, updatedQuay.Spec.Components) {
log.Info("updating QuayRegistry `spec.components` to include defaults")
if err = r.Client.Update(ctx, updatedQuay); err != nil {
Expand Down
56 changes: 55 additions & 1 deletion pkg/middleware/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -59,8 +60,22 @@ func Process(ctx *quaycontext.QuayRegistryContext, quay *v1.QuayRegistry, obj cl
return dep, nil
}

// Set env variable overrides
var envVars map[string]string
switch quayComponentLabel {
case "postgres":
envVars = getEnvOverrideForComponent(quay, v1.ComponentPostgres)
case "clair-postgres":
envVars = getEnvOverrideForComponent(quay, v1.ComponentClair)
case "objectstorage":
envVars = getEnvOverrideForComponent(quay, v1.ComponentObjectStorage)
}
for key, val := range envVars {
dep.Spec.Template.Spec.Containers[0].Env = append(dep.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{Name: key, Value: val})
}

if !strings.Contains(dep.GetName(), "quay-config-editor") {
return obj, nil
return dep, nil
}

fgns, err := v1.FieldGroupNamesForManagedComponents(quay)
Expand All @@ -72,6 +87,21 @@ func Process(ctx *quaycontext.QuayRegistryContext, quay *v1.QuayRegistry, obj cl
return dep, nil
}

// If the current object is a PVC, check for volume override
if pvc, ok := obj.(*corev1.PersistentVolumeClaim); ok {
var volumeSize resource.Quantity
switch quayComponentLabel {
case "postgres":
volumeSize = getVolumeSizeOverrideForComponent(quay, v1.ComponentPostgres)
case "clair-postgres":
volumeSize = getVolumeSizeOverrideForComponent(quay, v1.ComponentClair)
case "objectstorage":
volumeSize = getVolumeSizeOverrideForComponent(quay, v1.ComponentObjectStorage)
}
pvc.Spec.Resources.Requests = corev1.ResourceList{corev1.ResourceStorage: volumeSize}
return pvc, nil
}

rt, ok := obj.(*route.Route)
if !ok {
return obj, nil
Expand Down Expand Up @@ -137,3 +167,27 @@ func FlattenSecret(configBundle *corev1.Secret) (*corev1.Secret, error) {

return flattenedSecret, nil
}

func getVolumeSizeOverrideForComponent(quay *v1.QuayRegistry, componentKind v1.ComponentKind) resource.Quantity {
for _, component := range quay.Spec.Components {
if component.Kind == componentKind {
volumeSizeOverride := component.Overrides.VolumeSize
if volumeSizeOverride != "" {
return resource.MustParse(volumeSizeOverride)
}
}
}
return resource.MustParse("50Gi")
}

func getEnvOverrideForComponent(quay *v1.QuayRegistry, componentKind v1.ComponentKind) map[string]string {
for _, component := range quay.Spec.Components {
if component.Kind == componentKind {
envOverrides := component.Overrides.Env
if envOverrides != nil {
return envOverrides
}
}
}
return map[string]string{}
}
Loading

0 comments on commit 6861556

Please sign in to comment.