Skip to content

remove the bundled NSTemplateTiers that are no longer used #1180

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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
5 changes: 3 additions & 2 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,9 @@ func main() { // nolint:gocyclo
os.Exit(1)
}
if err := (&nstemplatetier.Reconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Namespace: namespace,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "NSTemplateTier")
os.Exit(1)
Expand Down
57 changes: 54 additions & 3 deletions controllers/nstemplatetier/nstemplatetier_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@ import (
"context"
"fmt"
"reflect"
"slices"
"time"

toolchainv1alpha1 "github.com/codeready-toolchain/api/api/v1alpha1"
"github.com/codeready-toolchain/host-operator/controllers/toolchainconfig"
"github.com/codeready-toolchain/host-operator/deploy"
"github.com/codeready-toolchain/host-operator/pkg/templates"
"github.com/codeready-toolchain/host-operator/pkg/templates/nstemplatetiers"
"github.com/codeready-toolchain/toolchain-common/pkg/condition"
"github.com/codeready-toolchain/toolchain-common/pkg/hash"
commonnstemplatetiers "github.com/codeready-toolchain/toolchain-common/pkg/template/nstemplatetiers"
"github.com/redhat-cop/operator-utils/pkg/util"
corev1 "k8s.io/api/core/v1"

Expand All @@ -29,6 +34,10 @@ import (

// SetupWithManager sets up the controller with the Manager.
func (r *Reconciler) SetupWithManager(mgr manager.Manager) error {
if err := r.initBundledNsTemplateTiersKeys(); err != nil {
return err
}

return ctrl.NewControllerManagedBy(mgr).
For(&toolchainv1alpha1.NSTemplateTier{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
Watches(&toolchainv1alpha1.TierTemplate{},
Expand All @@ -38,8 +47,31 @@ func (r *Reconciler) SetupWithManager(mgr manager.Manager) error {

// Reconciler reconciles a NSTemplateTier object (only when this latter's specs were updated)
type Reconciler struct {
Client runtimeclient.Client
Scheme *runtime.Scheme
Client runtimeclient.Client
Scheme *runtime.Scheme
Namespace string
// BundledNsTemplateTierKeys is the list of the object keys of the bundled ns template tiers. This should be initialized
// using SetupWithManager but the field is made public so that it can be set freely during the tests.
BundledNsTemplateTierKeys []runtimeclient.ObjectKey
}

// initBundledNsTemplateTiersKeys initializes the internal list of the NSTemplateTiers bundled with the binary. This method
// is only public because it is also used in the tests.
func (r *Reconciler) initBundledNsTemplateTiersKeys() error {
metadata, files, err := nstemplatetiers.LoadFiles(deploy.NSTemplateTiersFS, nstemplatetiers.NsTemplateTierRootDir)
if err != nil {
return err
}

return commonnstemplatetiers.GenerateTiers(r.Scheme, func(obj runtimeclient.Object, _ bool, _ string) (bool, error) {
// the bundled annotation is ensured on the template tiers only when they are created. It may not exist in the files on the embedded
// filesystem. Therefore, we're adding all the tiers from the filesystem unconditionally here and only check for the annotation
// in the reconcile method.
if _, ok := obj.(*toolchainv1alpha1.NSTemplateTier); ok {
r.BundledNsTemplateTierKeys = append(r.BundledNsTemplateTierKeys, runtimeclient.ObjectKeyFromObject(obj))
}
return true, nil
}, r.Namespace, metadata, files)
}

//+kubebuilder:rbac:groups=toolchain.dev.openshift.com,resources=nstemplatetiers,verbs=get;list;watch;create;update;patch;delete
Expand Down Expand Up @@ -67,6 +99,18 @@ func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.
return reconcile.Result{}, nil
}

if tier.Annotations[toolchainv1alpha1.BundledAnnotationKey] == templates.BundledWithHostOperatorAnnotationValue {
if !slices.Contains(r.BundledNsTemplateTierKeys, request.NamespacedName) {
// this tier used to be bundled with the operator but it is not anymore.
// if it is not used by anything, we are safe to delete it.
if used, err := r.isTierUsed(ctx, tier); err != nil {
return reconcile.Result{}, err
} else if !used {
return reconcile.Result{}, r.Client.Delete(ctx, tier)
}
}
}

_, err := toolchainconfig.GetToolchainConfig(r.Client)
if err != nil {
return reconcile.Result{}, fmt.Errorf("unable to get ToolchainConfig: %w", err)
Expand All @@ -91,6 +135,14 @@ func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.
return reconcile.Result{}, err
}

func (r *Reconciler) isTierUsed(ctx context.Context, tier *toolchainv1alpha1.NSTemplateTier) (bool, error) {
list := &toolchainv1alpha1.SpaceList{}
if err := r.Client.List(ctx, list, runtimeclient.InNamespace(r.Namespace), runtimeclient.HasLabels{hash.TemplateTierHashLabelKey(tier.Name)}); err != nil {
return false, err
}
return len(list.Items) > 0, nil
}

// ensureRevision ensures that there is a TierTemplateRevision CR for each of the TierTemplate.
// returns `true` if a new TierTemplateRevision CR was created, `err` if something wrong happened
func (r *Reconciler) ensureRevision(ctx context.Context, nsTmplTier *toolchainv1alpha1.NSTemplateTier) (bool, error) {
Expand Down Expand Up @@ -183,7 +235,6 @@ func (r *Reconciler) ensureTTRforTemplate(ctx context.Context, nsTmplTier *toolc
return false, "", err
}
return true, ttrName, nil

}

func (r *Reconciler) createNewTierTemplateRevision(ctx context.Context, nsTmplTier *toolchainv1alpha1.NSTemplateTier, tierTemplate *toolchainv1alpha1.TierTemplate) (string, error) {
Expand Down
107 changes: 95 additions & 12 deletions controllers/nstemplatetier/nstemplatetier_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import (
toolchainv1alpha1 "github.com/codeready-toolchain/api/api/v1alpha1"
"github.com/codeready-toolchain/host-operator/controllers/nstemplatetier"
"github.com/codeready-toolchain/host-operator/pkg/apis"
"github.com/codeready-toolchain/host-operator/pkg/templates"
tiertest "github.com/codeready-toolchain/host-operator/test/nstemplatetier"
"github.com/codeready-toolchain/host-operator/test/tiertemplaterevision"
"github.com/codeready-toolchain/toolchain-common/pkg/hash"
"github.com/codeready-toolchain/toolchain-common/pkg/test"
templatev1 "github.com/openshift/api/template/v1"
"github.com/stretchr/testify/assert"
Expand All @@ -37,9 +39,7 @@ const (
func TestReconcile(t *testing.T) {
// given
t.Run("failures", func(t *testing.T) {

t.Run("unable to get NSTemplateTier", func(t *testing.T) {

t.Run("tier not found", func(t *testing.T) {
// given
base1nsTier := tiertest.Base1nsTier(t, tiertest.CurrentBase1nsTemplates)
Expand Down Expand Up @@ -75,7 +75,6 @@ func TestReconcile(t *testing.T) {
assert.Equal(t, reconcile.Result{}, res) // no explicit requeue
})
})

})

t.Run("revisions management", func(t *testing.T) {
Expand Down Expand Up @@ -194,7 +193,6 @@ func TestReconcile(t *testing.T) {
require.Len(t, ttrs.Items, len(tierTemplatesRefs)) // it's one TTR per each tiertemplate in the NSTemplateTier
})
})

})

t.Run("revision field is set but TierTemplateRevision CRs are missing, they should be created", func(t *testing.T) {
Expand Down Expand Up @@ -264,7 +262,6 @@ func TestReconcile(t *testing.T) {
})

t.Run("errors", func(t *testing.T) {

t.Run("error when TierTemplate is missing ", func(t *testing.T) {
// given
// make sure revisions field is nill before starting the test
Expand All @@ -285,9 +282,7 @@ func TestReconcile(t *testing.T) {
// and the TierTemplateRevision CRs are not created
tiertemplaterevision.AssertThatTTRs(t, cl, base1nsTier.GetNamespace()).DoNotExist()
})

})

})

t.Run("if being deleted, then do nothing", func(t *testing.T) {
Expand Down Expand Up @@ -320,6 +315,94 @@ func TestReconcile(t *testing.T) {
})
})

t.Run("bundled tiers", func(t *testing.T) {
for _, setup := range []struct {
bundled bool
annotatedBundled bool
used bool
}{
{
bundled: false,
annotatedBundled: false,
used: false,
},
{
bundled: false,
annotatedBundled: false,
used: true,
},
{
bundled: false,
annotatedBundled: true,
used: false,
},
{
bundled: false,
annotatedBundled: true,
used: true,
},
{
bundled: true,
annotatedBundled: false,
used: false,
},
{
bundled: true,
annotatedBundled: false,
used: true,
},
{
bundled: true,
annotatedBundled: true,
used: false,
},
{
bundled: true,
annotatedBundled: true,
used: true,
},
} {
t.Run(fmt.Sprintf("tier: bundled=%v, annotatedBundled=%v, used=%v", setup.bundled, setup.annotatedBundled, setup.used), func(t *testing.T) {
// given
base1nsTier := tiertest.Base1nsTier(t, tiertest.CurrentBase1nsTemplates, tiertest.WithParameter("DEPLOYMENT_QUOTA", "60"))
objs := initTierTemplates(t, withTemplateObjects(newTestCRQ("600")), base1nsTier.Name)
objs = append(objs, base1nsTier)

if setup.annotatedBundled {
if base1nsTier.Annotations == nil {
base1nsTier.Annotations = map[string]string{}
}
base1nsTier.Annotations[toolchainv1alpha1.BundledAnnotationKey] = templates.BundledWithHostOperatorAnnotationValue
}

if setup.used {
space := &toolchainv1alpha1.Space{
ObjectMeta: metav1.ObjectMeta{
Name: "test-space",
Namespace: base1nsTier.Namespace,
Labels: map[string]string{hash.TemplateTierHashLabelKey(base1nsTier.Name): "1234"},
},
}
objs = append(objs, space)
}

r, req, cl := prepareReconcile(t, base1nsTier.Name, objs...)

if setup.bundled {
r.BundledNsTemplateTierKeys = append(r.BundledNsTemplateTierKeys, runtimeclient.ObjectKeyFromObject(base1nsTier))
}

// when
_, err := r.Reconcile(context.TODO(), req)
gerr := cl.Get(context.TODO(), runtimeclient.ObjectKeyFromObject(base1nsTier), &toolchainv1alpha1.NSTemplateTier{})

// then
shouldBeDeleted := !setup.bundled && setup.annotatedBundled && !setup.used
require.NoError(t, err)
assert.Equal(t, shouldBeDeleted, errors.IsNotFound(gerr))
})
}
})
}

func TestUpdateNSTemplateTier(t *testing.T) {
Expand Down Expand Up @@ -454,13 +537,11 @@ func TestUpdateNSTemplateTier(t *testing.T) {
HasStatusTierTemplateRevisions(tierTemplatesRefs).Tier()
require.NotEqual(t, tierBeingUpdated.Status.Revisions, newNSTmplTier.Status.Revisions)
})

})

}

func newTestCRQ(podsCount string) unstructured.Unstructured {
var crq = unstructured.Unstructured{Object: map[string]interface{}{
crq := unstructured.Unstructured{Object: map[string]interface{}{
"kind": "ClusterResourceQuota",
"metadata": map[string]interface{}{
"name": "for-{{.SPACE_NAME}}-deployments",
Expand Down Expand Up @@ -509,9 +590,11 @@ func prepareReconcile(t *testing.T, name string, initObjs ...runtimeclient.Objec
require.NoError(t, err)
cl := test.NewFakeClient(t, initObjs...)
r := &nstemplatetier.Reconciler{
Client: cl,
Scheme: s,
Client: cl,
Scheme: s,
Namespace: test.HostOperatorNs,
}

return r, reconcile.Request{
NamespacedName: types.NamespacedName{
Name: name,
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,5 @@ require (
go 1.22.0

toolchain go1.22.12

replace github.com/codeready-toolchain/api => github.com/metlos/api v0.0.0-20250604131500-1d064aa4a843
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtM
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/codeready-toolchain/api v0.0.0-20250506092100-39b4862e1271 h1:AHrFr/aPuJ4+0zHw4sFXcfMA92kChy12JAPS5bLODlw=
github.com/codeready-toolchain/api v0.0.0-20250506092100-39b4862e1271/go.mod h1:20258od6i5+jP406Z76YaI2ow/vc7URwsDU2bokpkRE=
github.com/codeready-toolchain/toolchain-common v0.0.0-20250506093954-2b65ad3a2e12 h1:w54sojJJ8PsHZzK1mC+/EUBrQ9F2sC/k7JUVc8LSqK4=
github.com/codeready-toolchain/toolchain-common v0.0.0-20250506093954-2b65ad3a2e12/go.mod h1:TrMvD0sP69wI6Rouzfs7OsOUSj4CGn/ZiIdiDBAFQjk=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
Expand Down Expand Up @@ -181,6 +179,8 @@ github.com/mailgun/mailgun-go/v4 v4.8.1 h1:1+MdKakJuXnW2JJDbyPdO1ngAANOyHyVPxQvF
github.com/mailgun/mailgun-go/v4 v4.8.1/go.mod h1:FJlF9rI5cQT+mrwujtJjPMbIVy3Ebor9bKTVsJ0QU40=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/metlos/api v0.0.0-20250604131500-1d064aa4a843 h1:ayHY/PrRsI4NnRhX3e3OxW6o1/oGC03WQo8AwFQiqWQ=
github.com/metlos/api v0.0.0-20250604131500-1d064aa4a843/go.mod h1:shTTQ+bYWinbdF/oK+ly4DO5jdXtIASZ+uPZElsmsKg=
github.com/migueleliasweb/go-github-mock v0.0.18 h1:0lWt9MYmZQGnQE2rFtjlft/YtD6hzxuN6JJRFpujzEI=
github.com/migueleliasweb/go-github-mock v0.0.18/go.mod h1:CcgXcbMoRnf3rRVHqGssuBquZDIcaplxL2W6G+xs7kM=
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
Expand Down
7 changes: 6 additions & 1 deletion pkg/templates/nstemplatetiers/nstemplatetier_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"path/filepath"
"strings"

toolchainv1alpha1 "github.com/codeready-toolchain/api/api/v1alpha1"
"github.com/codeready-toolchain/host-operator/deploy"
"github.com/codeready-toolchain/host-operator/pkg/templates"
commonclient "github.com/codeready-toolchain/toolchain-common/pkg/client"
Expand All @@ -20,17 +21,21 @@ import (

const NsTemplateTierRootDir = "templates/nstemplatetiers"

var bundledAnnotation = map[string]string{
toolchainv1alpha1.BundledAnnotationKey: templates.BundledWithHostOperatorAnnotationValue,
}

// CreateOrUpdateResources generates the NSTemplateTier resources from the cluster resource template and namespace templates,
// then uses the manager's client to create or update the resources on the cluster.
func CreateOrUpdateResources(ctx context.Context, s *runtime.Scheme, client runtimeclient.Client, namespace string) error {

metadata, files, err := LoadFiles(deploy.NSTemplateTiersFS, NsTemplateTierRootDir)
if err != nil {
return err
}

// initialize tier generator, loads templates from assets
return nstemplatetiers.GenerateTiers(s, func(toEnsure runtimeclient.Object, canUpdate bool, _ string) (bool, error) {
commonclient.MergeAnnotations(toEnsure, bundledAnnotation)
if !canUpdate {
if err := client.Create(ctx, toEnsure); err != nil && !apierrors.IsAlreadyExists(err) {
return false, err
Expand Down
5 changes: 4 additions & 1 deletion pkg/templates/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import (
"io/fs"
)

func GetAllFileNames(TemplateTierFS *embed.FS, root string) (files []string, err error) {
// BundledWithHostOperatorAnnotationValue is meant to be the value of the toolchainv1alpha1.BundledLabelKey that marks
// the objects as bundled with the host operator and therefore managed by it.
const BundledWithHostOperatorAnnotationValue = "host-operator"

func GetAllFileNames(TemplateTierFS *embed.FS, root string) (files []string, err error) {
if err := fs.WalkDir(TemplateTierFS, root, func(path string, d fs.DirEntry, err error) error {
if d.IsDir() {
return nil
Expand Down
Loading