Skip to content
This repository has been archived by the owner on Aug 19, 2024. It is now read-only.

Commit

Permalink
Handle app-configs and config for dynamic plugins in Custom Resource (#…
Browse files Browse the repository at this point in the history
…27)

This merges pull request #27 from rm3l/19-handle-appconfigs-cr-field
  • Loading branch information
rm3l committed Nov 29, 2023
2 parents 0f254ed + 1634d95 commit b2b23ea
Show file tree
Hide file tree
Showing 12 changed files with 1,411 additions and 82 deletions.
56 changes: 54 additions & 2 deletions api/v1alpha1/backstage_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,67 @@ const (

// BackstageSpec defines the desired state of Backstage
type BackstageSpec struct {
// Backstage application AppConfigs
AppConfigs []string `json:"appConfigs,omitempty"`
// References to existing app-configs Config objects.
// Each element can be a reference to any ConfigMap or Secret,
// and will be mounted inside the main application container under a dedicated directory containing the ConfigMap
// or Secret name. Additionally, each file will be passed as a `--config /path/to/secret_or_configmap/key` to the
// main container args in the order of the entries defined in the AppConfigs list.
// But bear in mind that for a single AppConfig element containing several files,
// the order in which those files will be appended to the container args, the main container args cannot be guaranteed.
// So if you want to pass multiple app-config files, it is recommended to pass one ConfigMap/Secret per app-config file.
AppConfigs []AppConfigRef `json:"appConfigs,omitempty"`

// Optional Backend Auth Secret Name. A new one will be generated if not set.
// This Secret is used to set an environment variable named 'APP_CONFIG_backend_auth_keys' in the
// main container, which takes precedence over any 'backend.auth.keys' field defined
// in default or custom application configuration files.
// This is required for service-to-service auth and is shared by all backend plugins.
BackendAuthSecretRef *BackendAuthSecretRef `json:"backendAuthSecretRef,omitempty"`

// Reference to an existing configuration object for Dynamic Plugins.
// This can be a reference to any ConfigMap or Secret,
// but the object must have an existing key named: 'dynamic-plugins.yaml'
DynamicPluginsConfig *DynamicPluginsConfigRef `json:"dynamicPluginsConfig,omitempty"`

// Raw Runtime Objects configuration
RawRuntimeConfig RuntimeConfig `json:"rawRuntimeConfig,omitempty"`

//+kubebuilder:default=false
SkipLocalDb bool `json:"skipLocalDb,omitempty"`
}

type AppConfigRef struct {
// Name of an existing App Config object
//+kubebuilder:validation:Required
Name string `json:"name"`

// Type of the existing App Config object, either ConfigMap or Secret
//+kubebuilder:validation:Required
//+kubebuilder:validation:Enum=ConfigMap;Secret
Kind string `json:"kind"`
}

type DynamicPluginsConfigRef struct {
// Name of the Dynamic Plugins config object
// +kubebuilder:validation:Required
Name string `json:"name"`

// Type of the Dynamic Plugins config object, either ConfigMap or Secret
//+kubebuilder:validation:Required
//+kubebuilder:validation:Enum=ConfigMap;Secret
Kind string `json:"kind"`
}

type BackendAuthSecretRef struct {
// Name of the secret to use for the backend auth
//+kubebuilder:validation:Required
Name string `json:"name"`

// Key in the secret to use for the backend auth. Default value is: backend-secret
//+kubebuilder:default=backend-secret
Key string `json:"key,omitempty"`
}

type RuntimeConfig struct {
// Name of ConfigMap containing Backstage runtime objects configuration
BackstageConfigName string `json:"backstageConfig,omitempty"`
Expand Down
57 changes: 56 additions & 1 deletion api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

69 changes: 66 additions & 3 deletions config/crd/bases/backstage.io_backstages.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.10.0
controller-gen.kubebuilder.io/version: v0.11.3
creationTimestamp: null
name: backstages.backstage.io
spec:
Expand Down Expand Up @@ -36,10 +36,73 @@ spec:
description: BackstageSpec defines the desired state of Backstage
properties:
appConfigs:
description: Backstage application AppConfigs
description: References to existing app-configs Config objects. Each
element can be a reference to any ConfigMap or Secret, and will
be mounted inside the main application container under a dedicated
directory containing the ConfigMap or Secret name. Additionally,
each file will be passed as a `--config /path/to/secret_or_configmap/key`
to the main container args in the order of the entries defined in
the AppConfigs list. But bear in mind that for a single AppConfig
element containing several files, the order in which those files
will be appended to the container args, the main container args
cannot be guaranteed. So if you want to pass multiple app-config
files, it is recommended to pass one ConfigMap/Secret per app-config
file.
items:
type: string
properties:
kind:
description: Type of the existing App Config object, either
ConfigMap or Secret
enum:
- ConfigMap
- Secret
type: string
name:
description: Name of an existing App Config object
type: string
required:
- kind
- name
type: object
type: array
backendAuthSecretRef:
description: Optional Backend Auth Secret Name. A new one will be
generated if not set. This Secret is used to set an environment
variable named 'APP_CONFIG_backend_auth_keys' in the main container,
which takes precedence over any 'backend.auth.keys' field defined
in default or custom application configuration files. This is required
for service-to-service auth and is shared by all backend plugins.
properties:
key:
default: backend-secret
description: 'Key in the secret to use for the backend auth. Default
value is: backend-secret'
type: string
name:
description: Name of the secret to use for the backend auth
type: string
required:
- name
type: object
dynamicPluginsConfig:
description: 'Reference to an existing configuration object for Dynamic
Plugins. This can be a reference to any ConfigMap or Secret, but
the object must have an existing key named: ''dynamic-plugins.yaml'''
properties:
kind:
description: Type of the Dynamic Plugins config object, either
ConfigMap or Secret
enum:
- ConfigMap
- Secret
type: string
name:
description: Name of the Dynamic Plugins config object
type: string
required:
- kind
- name
type: object
rawRuntimeConfig:
description: Raw Runtime Objects configuration
properties:
Expand Down
139 changes: 139 additions & 0 deletions controllers/backstage_app_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
//
// Copyright (c) 2023 Red Hat, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package controller

import (
"context"
"fmt"

bs "backstage.io/backstage-operator/api/v1alpha1"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/pointer"
)

type appConfigData struct {
ref string
files []string
}

func (r *BackstageReconciler) appConfigsToVolumes(backstage bs.Backstage) (result []v1.Volume) {
for _, appConfig := range backstage.Spec.AppConfigs {
var volumeSource v1.VolumeSource
switch appConfig.Kind {
case "ConfigMap":
volumeSource.ConfigMap = &v1.ConfigMapVolumeSource{
DefaultMode: pointer.Int32(420),
LocalObjectReference: v1.LocalObjectReference{Name: appConfig.Name},
}
case "Secret":
volumeSource.Secret = &v1.SecretVolumeSource{
DefaultMode: pointer.Int32(420),
SecretName: appConfig.Name,
}
}
result = append(result,
v1.Volume{
Name: appConfig.Name,
VolumeSource: volumeSource,
},
)
}

return result
}

func (r *BackstageReconciler) addAppConfigsVolumeMounts(ctx context.Context, backstage bs.Backstage, ns string, deployment *appsv1.Deployment) error {
appConfigFilenamesList, err := r.extractAppConfigFileNames(ctx, backstage, ns)
if err != nil {
return err
}

for i, c := range deployment.Spec.Template.Spec.Containers {
if c.Name == _defaultBackstageMainContainerName {
for _, appConfigFilenames := range appConfigFilenamesList {
deployment.Spec.Template.Spec.Containers[i].VolumeMounts = append(deployment.Spec.Template.Spec.Containers[i].VolumeMounts,
v1.VolumeMount{
Name: appConfigFilenames.ref,
MountPath: fmt.Sprintf("%s/%s", _containersWorkingDir, appConfigFilenames.ref),
})
}
break
}
}
return nil
}

func (r *BackstageReconciler) addAppConfigsContainerArgs(ctx context.Context, backstage bs.Backstage, ns string, deployment *appsv1.Deployment) error {
appConfigFilenamesList, err := r.extractAppConfigFileNames(ctx, backstage, ns)
if err != nil {
return err
}

for i, c := range deployment.Spec.Template.Spec.Containers {
if c.Name == _defaultBackstageMainContainerName {
for _, appConfigFilenames := range appConfigFilenamesList {
// Args
for _, fileName := range appConfigFilenames.files {
deployment.Spec.Template.Spec.Containers[i].Args =
append(deployment.Spec.Template.Spec.Containers[i].Args, "--config",
fmt.Sprintf("%s/%s/%s", _containersWorkingDir, appConfigFilenames.ref, fileName))
}
}
break
}
}
return nil
}

// extractAppConfigFileNames returns a mapping of app-config object name and the list of files in it.
// We intentionally do not return a Map, to preserve the iteration order of the AppConfigs in the Custom Resource,
// even though we can't guarantee the iteration order of the files listed inside each ConfigMap or Secret.
func (r *BackstageReconciler) extractAppConfigFileNames(ctx context.Context, backstage bs.Backstage, ns string) ([]appConfigData, error) {
var result []appConfigData
for _, appConfig := range backstage.Spec.AppConfigs {
var files []string
switch appConfig.Kind {
case "ConfigMap":
cm := v1.ConfigMap{}
if err := r.Get(ctx, types.NamespacedName{Name: appConfig.Name, Namespace: ns}, &cm); err != nil {
return nil, err
}
for filename := range cm.Data {
// Bear in mind that iteration order over this map is not guaranteed by Go
files = append(files, filename)
}
for filename := range cm.BinaryData {
// Bear in mind that iteration order over this map is not guaranteed by Go
files = append(files, filename)
}
case "Secret":
sec := v1.Secret{}
if err := r.Get(ctx, types.NamespacedName{Name: appConfig.Name, Namespace: ns}, &sec); err != nil {
return nil, err
}
for filename := range sec.Data {
// Bear in mind that iteration order over this map is not guaranteed by Go
files = append(files, filename)
}
}
result = append(result, appConfigData{
ref: appConfig.Name,
files: files,
})
}
return result, nil
}
Loading

0 comments on commit b2b23ea

Please sign in to comment.