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

Update existing resources when CR changes #119

Merged
merged 8 commits into from
Jan 13, 2024
Merged
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ endif
# This recipe invokes 'opm' in 'semver' bundle add mode. For more information on add modes, see:
# https://github.com/operator-framework/community-operators/blob/7f1438c/docs/packaging-operator.md#updating-your-existing-operator
.PHONY: catalog-build
catalog-build: opm bundle-push ## Generate operator-catalog dockerfile using the operator-bundle image built and published above; then build catalog image
catalog-build: bundle-push opm ## Generate operator-catalog dockerfile using the operator-bundle image built and published above; then build catalog image
$(OPM) index add --container-tool $(CONTAINER_ENGINE) --mode semver --tag $(CATALOG_IMG) --bundles $(BUNDLE_IMGS) $(FROM_INDEX_OPT) --generate
$(CONTAINER_ENGINE) build --platform $(PLATFORM) -f index.Dockerfile -t $(CATALOG_IMG) --label $(LABEL) .

Expand Down
11 changes: 8 additions & 3 deletions api/v1alpha1/backstage_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ import (
const (
RuntimeConditionRunning string = "RuntimeRunning"
RuntimeConditionSynced string = "RuntimeSyncedWithConfig"
RouteSynced string = "RouteSynced"
LocalDbSynced string = "LocalDbSynced"
SyncOK string = "SyncOK"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How it differs to RuntimeSyncedWithConfig = True condition?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RuntimeSyncedWithConfig status is not currently implemented and we can have a follow up PR to get it done.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean RuntimeSyncedWithConfig looks like the same thing as SyncOK, no?

SyncFailed string = "SyncFailed"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How it differs to RuntimeSyncedWithConfig = False condition?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above.

Deleted string = "Deleted"
EnvPostGresImage string = "RELATED_IMAGE_postgresql"
EnvBackstageImage string = "RELATED_IMAGE_backstage"
)
Expand All @@ -43,8 +48,8 @@ type Database struct {
//+kubebuilder:default=true
EnableLocalDb *bool `json:"enableLocalDb,omitempty"`

// Name of the secret for database authentication. Required for external database access.
// Optional for a local database (EnableLocalDb=true) and if absent a secret will be auto generated.
// Name of the secret for database authentication. Optional.
// For a local database deployment (EnableLocalDb=true), a secret will be auto generated if it does not exist.
// The secret shall include information used for the database access.
// An example for PostgreSQL DB access:
// "POSTGRES_PASSWORD": "rl4s3Fh4ng3M4"
Expand Down Expand Up @@ -95,7 +100,7 @@ type Application struct {

// Image Pull Secrets to use in all containers (including Init Containers)
// +optional
ImagePullSecrets []string `json:"imagePullSecrets,omitempty"`
ImagePullSecrets *[]string `json:"imagePullSecrets,omitempty"`

// Route configuration. Used for OpenShift only.
Route *Route `json:"route,omitempty"`
Expand Down
8 changes: 6 additions & 2 deletions api/v1alpha1/zz_generated.deepcopy.go

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

15 changes: 7 additions & 8 deletions bundle/manifests/janus-idp.io_backstages.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -267,14 +267,13 @@ spec:
properties:
authSecretName:
description: 'Name of the secret for database authentication.
Required for external database access. Optional for a local
database (EnableLocalDb=true) and if absent a secret will be
auto generated. The secret shall include information used for
the database access. An example for PostgreSQL DB access: "POSTGRES_PASSWORD":
"rl4s3Fh4ng3M4" "POSTGRES_PORT": "5432" "POSTGRES_USER": "postgres"
"POSTGRESQL_ADMIN_PASSWORD": "rl4s3Fh4ng3M4" "POSTGRES_HOST":
"backstage-psql-bs1" # For local database, set to "backstage-psql-<CR
name>".'
Optional. For a local database deployment (EnableLocalDb=true),
a secret will be auto generated if it does not exist. The secret
shall include information used for the database access. An example
for PostgreSQL DB access: "POSTGRES_PASSWORD": "rl4s3Fh4ng3M4"
"POSTGRES_PORT": "5432" "POSTGRES_USER": "postgres" "POSTGRESQL_ADMIN_PASSWORD":
"rl4s3Fh4ng3M4" "POSTGRES_HOST": "backstage-psql-bs1" # For
local database, set to "backstage-psql-<CR name>".'
type: string
enableLocalDb:
default: true
Expand Down
15 changes: 7 additions & 8 deletions config/crd/bases/janus-idp.io_backstages.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -268,14 +268,13 @@ spec:
properties:
authSecretName:
description: 'Name of the secret for database authentication.
Required for external database access. Optional for a local
database (EnableLocalDb=true) and if absent a secret will be
auto generated. The secret shall include information used for
the database access. An example for PostgreSQL DB access: "POSTGRES_PASSWORD":
"rl4s3Fh4ng3M4" "POSTGRES_PORT": "5432" "POSTGRES_USER": "postgres"
"POSTGRESQL_ADMIN_PASSWORD": "rl4s3Fh4ng3M4" "POSTGRES_HOST":
"backstage-psql-bs1" # For local database, set to "backstage-psql-<CR
name>".'
Optional. For a local database deployment (EnableLocalDb=true),
a secret will be auto generated if it does not exist. The secret
shall include information used for the database access. An example
for PostgreSQL DB access: "POSTGRES_PASSWORD": "rl4s3Fh4ng3M4"
"POSTGRES_PORT": "5432" "POSTGRES_USER": "postgres" "POSTGRESQL_ADMIN_PASSWORD":
"rl4s3Fh4ng3M4" "POSTGRES_HOST": "backstage-psql-bs1" # For
local database, set to "backstage-psql-<CR name>".'
type: string
enableLocalDb:
default: true
Expand Down
92 changes: 70 additions & 22 deletions controllers/backstage_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,17 @@ func (r *BackstageReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
return ctrl.Result{}, fmt.Errorf("failed to load backstage deployment from the cluster: %w", err)
}

// This update will make sure the status is always updated in case of any errors or successful result
defer func(bs *bs.Backstage) {
if err := r.Client.Status().Update(ctx, bs); err != nil {
if errors.IsConflict(err) {
lg.V(1).Info("Backstage object modified, retry syncing status", "Backstage Object", bs)
return
}
lg.Error(err, "Error updating the Backstage resource status", "Backstage Object", bs)
}
}(&backstage)

if pointer.BoolDeref(backstage.Spec.Database.EnableLocalDb, true) {

/* We use default strogeclass currently, and no PV is needed in that case.
Expand All @@ -108,41 +119,44 @@ func (r *BackstageReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
}
*/

err := r.applyLocalDbStatefulSet(ctx, backstage, req.Namespace)
err := r.reconcileLocalDbStatefulSet(ctx, backstage, req.Namespace)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to apply Database StatefulSet: %w", err)
setStatusCondition(&backstage, bs.LocalDbSynced, v1.ConditionFalse, bs.SyncFailed, fmt.Sprintf("failed to sync Database StatefulSet:%s", err.Error()))
return ctrl.Result{}, fmt.Errorf("failed to sync Database StatefulSet: %w", err)
}

err = r.applyLocalDbServices(ctx, backstage, req.Namespace)
err = r.reconcileLocalDbServices(ctx, backstage, req.Namespace)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to apply Database Service: %w", err)
setStatusCondition(&backstage, bs.LocalDbSynced, v1.ConditionFalse, bs.SyncFailed, fmt.Sprintf("failed to sync Database Services:%s", err.Error()))
return ctrl.Result{}, fmt.Errorf("failed to sync Database Service: %w", err)
}

setStatusCondition(&backstage, bs.LocalDbSynced, v1.ConditionTrue, bs.SyncOK, "")
} else { // Clean up the deployed local db resources if any
if err := r.cleanupLocalDbResources(ctx, backstage); err != nil {
setStatusCondition(&backstage, bs.LocalDbSynced, v1.ConditionFalse, bs.SyncFailed, fmt.Sprintf("failed to delete Database Services:%s", err.Error()))
return ctrl.Result{}, fmt.Errorf("failed to delete Database Service: %w", err)
}
setStatusCondition(&backstage, bs.LocalDbSynced, v1.ConditionTrue, bs.Deleted, "")
}

err := r.applyBackstageDeployment(ctx, backstage, req.Namespace)
err := r.reconcileBackstageDeployment(ctx, backstage, req.Namespace)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to apply Backstage Deployment: %w", err)
return ctrl.Result{}, fmt.Errorf("failed to reconcile Backstage Deployment: %w", err)
}

if err := r.applyBackstageService(ctx, backstage, req.Namespace); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to apply Backstage Service: %w", err)
if err := r.reconcileBackstageService(ctx, backstage, req.Namespace); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to reconcile Backstage Service: %w", err)
}

if r.IsOpenShift {
if err := r.applyBackstageRoute(ctx, backstage, req.Namespace); err != nil {
if err := r.reconcileBackstageRoute(ctx, &backstage, req.Namespace); err != nil {
return ctrl.Result{}, err
}
}

//TODO: it is just a placeholder for the time
r.setRunningStatus(ctx, &backstage, req.Namespace)
r.setSyncStatus(&backstage)
err = r.Status().Update(ctx, &backstage)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to set status: %w", err)
//log.FromContext(ctx).Error(err, "unable to update backstage.status")
}

return ctrl.Result{}, nil
}
Expand Down Expand Up @@ -238,20 +252,54 @@ func (r *BackstageReconciler) setSyncStatus(backstage *bs.Backstage) {
})
}

// sets status condition
func setStatusCondition(backstage *bs.Backstage, condType string, status v1.ConditionStatus, reason, msg string) {
meta.SetStatusCondition(&backstage.Status.Conditions, v1.Condition{
Type: condType,
Status: status,
LastTransitionTime: v1.Time{},
Reason: reason,
Message: msg,
})
}

// cleanupResource deletes the resource that was previously deployed by the operator from the cluster
func (r *BackstageReconciler) cleanupResource(ctx context.Context, obj client.Object, backstage bs.Backstage) (bool, error) {
err := r.Get(ctx, types.NamespacedName{Name: obj.GetName(), Namespace: obj.GetNamespace()}, obj)
if err != nil {
if errors.IsNotFound(err) {
return false, nil // Nothing to delete
}
return false, err // For retry
}
ownedByCR := false
for _, ownerRef := range obj.GetOwnerReferences() {
if ownerRef.APIVersion == bs.GroupVersion.String() && ownerRef.Kind == "Backstage" && ownerRef.Name == backstage.Name {
ownedByCR = true
break
}
}
if !ownedByCR { // The object is not owned by the backstage CR
return false, nil
}
err = r.Delete(ctx, obj)
if err == nil {
return true, nil // Deleted
}
return false, err
}

// sets backstage-{Id} for labels and selectors
func setBackstageAppLabel(labels *map[string]string, backstage bs.Backstage) {
if *labels == nil {
*labels = map[string]string{}
}
(*labels)[BackstageAppLabel] = fmt.Sprintf("backstage-%s", backstage.Name)
setLabel(labels, getDefaultObjName(backstage))
}

// sets backstage-psql-{Id} for labels and selectors
func setBackstageLocalDbLabel(labels *map[string]string, name string) {
func setLabel(labels *map[string]string, label string) {
if *labels == nil {
*labels = map[string]string{}
}
(*labels)[BackstageAppLabel] = name
(*labels)[BackstageAppLabel] = label
}

// sets labels on Backstage's instance resources
Expand All @@ -261,7 +309,7 @@ func (r *BackstageReconciler) labels(meta *v1.ObjectMeta, backstage bs.Backstage
}
meta.Labels["app.kubernetes.io/name"] = "backstage"
meta.Labels["app.kubernetes.io/instance"] = backstage.Name
//meta.Labels[BackstageAppLabel] = fmt.Sprintf("backstage-%s", backstage.Name)
//meta.Labels[BackstageAppLabel] = getDefaultObjName(backstage)
}

// SetupWithManager sets up the controller with the Manager.
Expand Down
Loading
Loading