Skip to content

Commit 633998e

Browse files
committed
HIVE-2859: Add ManifestsSecretRef to ClusterPool for manifest management.
Add support for user-provided manifests in ClusterPool to apply custom manifests to all clusters created from the pool, similar to ClusterDeployment.
1 parent 301f422 commit 633998e

File tree

12 files changed

+345
-1
lines changed

12 files changed

+345
-1
lines changed

apis/hive/v1/clusterpool_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ type ClusterPoolSpec struct {
109109
// ClusterDeployment generated by the ClusterPool.
110110
// +optional
111111
CustomizationRef *corev1.LocalObjectReference `json:"customizationRef,omitempty"`
112+
113+
// ManifestsSecretRef is a reference to user-provided manifests to add to or replace manifests
114+
// that are generated by the installer for all clusters created by this pool.
115+
// +optional
116+
ManifestsSecretRef *corev1.LocalObjectReference `json:"manifestsSecretRef,omitempty"`
112117
}
113118

114119
type HibernationConfig struct {

apis/hive/v1/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crds/hive.openshift.io_clusterpools.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,22 @@ spec:
313313
Labels to be applied to new ClusterDeployments created for the pool. ClusterDeployments that have already been
314314
claimed will not be affected when this value is modified.
315315
type: object
316+
manifestsSecretRef:
317+
description: |-
318+
ManifestsSecretRef is a reference to user-provided manifests to add to or replace manifests
319+
that are generated by the installer for all clusters created by this pool.
320+
properties:
321+
name:
322+
default: ""
323+
description: |-
324+
Name of the referent.
325+
This field is effectively required, but due to backwards compatibility is
326+
allowed to be empty. Instances of this type with an empty value here are
327+
almost certainly wrong.
328+
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
329+
type: string
330+
type: object
331+
x-kubernetes-map-type: atomic
316332
maxConcurrent:
317333
description: |-
318334
MaxConcurrent is the maximum number of clusters that will be provisioned or deprovisioned at an time. This includes the

hack/app-sre/saas-template.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3654,6 +3654,29 @@ objects:
36543654

36553655
claimed will not be affected when this value is modified.'
36563656
type: object
3657+
manifestsSecretRef:
3658+
description: 'ManifestsSecretRef is a reference to user-provided
3659+
manifests to add to or replace manifests
3660+
3661+
that are generated by the installer for all clusters created by
3662+
this pool.'
3663+
properties:
3664+
name:
3665+
default: ''
3666+
description: 'Name of the referent.
3667+
3668+
This field is effectively required, but due to backwards compatibility
3669+
is
3670+
3671+
allowed to be empty. Instances of this type with an empty
3672+
value here are
3673+
3674+
almost certainly wrong.
3675+
3676+
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
3677+
type: string
3678+
type: object
3679+
x-kubernetes-map-type: atomic
36573680
maxConcurrent:
36583681
description: 'MaxConcurrent is the maximum number of clusters that
36593682
will be provisioned or deprovisioned at an time. This includes

pkg/awsclient/mock/client_generated.go

Lines changed: 58 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/controller/clusterpool/clusterpool_controller.go

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,15 @@ func (r *ReconcileClusterPool) addClusters(
695695
errs = append(errs, fmt.Errorf("%s: %w", credentialsSecretDependent, err))
696696
}
697697

698+
// Get manifests if specified
699+
manifests, err := r.getManifests(clp, logger)
700+
if err != nil {
701+
errs = append(errs, fmt.Errorf("error getting manifests: %w", err))
702+
}
703+
if manifests != nil {
704+
logger.WithField("manifestCount", len(manifests)).Info("retrieved manifests for cluster pool")
705+
}
706+
698707
if clp.Spec.CustomizationRef != nil && clp.Spec.CustomizationRef.Name != "" {
699708
custCDC := clp.Spec.CustomizationRef.Name
700709
if cdcs.nonInventory == nil || cdcs.nonInventory.Name != custCDC {
@@ -713,7 +722,7 @@ func (r *ReconcileClusterPool) addClusters(
713722
}
714723

715724
for i := 0; i < newClusterCount; i++ {
716-
cd, err := r.createCluster(clp, cloudBuilder, pullSecret, installConfigTemplate, poolVersion, cdcs, logger)
725+
cd, err := r.createCluster(clp, cloudBuilder, pullSecret, installConfigTemplate, poolVersion, cdcs, manifests, logger)
717726
if err != nil {
718727
return err
719728
}
@@ -730,6 +739,7 @@ func (r *ReconcileClusterPool) createCluster(
730739
installConfigTemplate string,
731740
poolVersion string,
732741
cdcs *cdcCollection,
742+
manifests map[string][]byte,
733743
logger log.FieldLogger,
734744
) (*hivev1.ClusterDeployment, error) {
735745
var err error
@@ -778,6 +788,12 @@ func (r *ReconcileClusterPool) createCluster(
778788
builder.HibernateAfter = &clp.Spec.HibernateAfter.Duration
779789
}
780790

791+
// Set manifests if provided
792+
if manifests != nil {
793+
logger.WithField("manifestCount", len(manifests)).Info("applying manifests to cluster deployment")
794+
builder.InstallerManifests = manifests
795+
}
796+
781797
objs, err := builder.Build()
782798
if err != nil {
783799
return nil, errors.Wrap(err, "error building resources")
@@ -1259,6 +1275,28 @@ func (r *ReconcileClusterPool) getPullSecret(pool *hivev1.ClusterPool, logger lo
12591275
return string(pullSecret), nil
12601276
}
12611277

1278+
func (r *ReconcileClusterPool) getManifests(pool *hivev1.ClusterPool, logger log.FieldLogger) (map[string][]byte, error) {
1279+
if pool.Spec.ManifestsSecretRef == nil {
1280+
logger.Debug("no manifests secret reference specified")
1281+
return nil, nil
1282+
}
1283+
1284+
logger.WithField("secretName", pool.Spec.ManifestsSecretRef.Name).Info("retrieving manifests from secret")
1285+
manifestsSecret := &corev1.Secret{}
1286+
err := r.Client.Get(
1287+
context.Background(),
1288+
types.NamespacedName{Namespace: pool.Namespace, Name: pool.Spec.ManifestsSecretRef.Name},
1289+
manifestsSecret,
1290+
)
1291+
if err != nil {
1292+
logger.WithError(err).Log(controllerutils.LogLevel(err), "error reading manifests secret")
1293+
return nil, err
1294+
}
1295+
1296+
logger.WithField("manifestCount", len(manifestsSecret.Data)).Info("successfully retrieved manifests from secret")
1297+
return manifestsSecret.Data, nil
1298+
}
1299+
12621300
func (r *ReconcileClusterPool) createCloudBuilder(pool *hivev1.ClusterPool, logger log.FieldLogger) (clusterresource.CloudBuilder, error) {
12631301
switch platform := pool.Spec.Platform; {
12641302
case platform.AWS != nil:

pkg/controller/clusterpool/clusterpool_controller_test.go

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3263,3 +3263,153 @@ func Test_isBroken(t *testing.T) {
32633263
})
32643264
}
32653265
}
3266+
3267+
func TestGetManifests(t *testing.T) {
3268+
scheme := scheme.GetScheme()
3269+
logger := log.NewEntry(log.New())
3270+
3271+
tests := []struct {
3272+
name string
3273+
pool *hivev1.ClusterPool
3274+
existing []runtime.Object
3275+
expected map[string][]byte
3276+
expectError bool
3277+
}{
3278+
{
3279+
name: "no manifests specified",
3280+
pool: testcp.FullBuilder(testNamespace, "test-pool", scheme).
3281+
Options(
3282+
testcp.ForAWS(credsSecretName, "us-east-1"),
3283+
testcp.WithBaseDomain("test-domain"),
3284+
testcp.WithImageSet(imageSetName),
3285+
).Build(),
3286+
existing: []runtime.Object{},
3287+
expected: nil,
3288+
expectError: false,
3289+
},
3290+
{
3291+
name: "manifests secret exists",
3292+
pool: testcp.FullBuilder(testNamespace, "test-pool", scheme).
3293+
Options(
3294+
testcp.ForAWS(credsSecretName, "us-east-1"),
3295+
testcp.WithBaseDomain("test-domain"),
3296+
testcp.WithImageSet(imageSetName),
3297+
testcp.WithManifestsSecretRef("test-manifests-secret"),
3298+
).Build(),
3299+
existing: []runtime.Object{
3300+
&corev1.Secret{
3301+
ObjectMeta: metav1.ObjectMeta{
3302+
Name: "test-manifests-secret",
3303+
Namespace: testNamespace,
3304+
},
3305+
Data: map[string][]byte{
3306+
"manifest1.yaml": []byte("apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: test"),
3307+
"manifest2.yaml": []byte("apiVersion: v1\nkind: Secret\nmetadata:\n name: test"),
3308+
},
3309+
},
3310+
},
3311+
expected: map[string][]byte{
3312+
"manifest1.yaml": []byte("apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: test"),
3313+
"manifest2.yaml": []byte("apiVersion: v1\nkind: Secret\nmetadata:\n name: test"),
3314+
},
3315+
expectError: false,
3316+
},
3317+
{
3318+
name: "manifests secret not found",
3319+
pool: testcp.FullBuilder(testNamespace, "test-pool", scheme).
3320+
Options(
3321+
testcp.ForAWS(credsSecretName, "us-east-1"),
3322+
testcp.WithBaseDomain("test-domain"),
3323+
testcp.WithImageSet(imageSetName),
3324+
testcp.WithManifestsSecretRef("missing-secret"),
3325+
).Build(),
3326+
existing: []runtime.Object{},
3327+
expected: nil,
3328+
expectError: true,
3329+
},
3330+
}
3331+
3332+
for _, tt := range tests {
3333+
t.Run(tt.name, func(t *testing.T) {
3334+
c := testfake.NewFakeClientBuilder().WithRuntimeObjects(tt.existing...).Build()
3335+
r := &ReconcileClusterPool{
3336+
Client: c,
3337+
}
3338+
3339+
result, err := r.getManifests(tt.pool, logger)
3340+
3341+
if tt.expectError {
3342+
assert.Error(t, err)
3343+
} else {
3344+
assert.NoError(t, err)
3345+
assert.Equal(t, tt.expected, result)
3346+
}
3347+
})
3348+
}
3349+
}
3350+
3351+
func TestClusterPoolWithManifests(t *testing.T) {
3352+
scheme := scheme.GetScheme()
3353+
logger := log.NewEntry(log.New())
3354+
3355+
poolBuilder := testcp.FullBuilder(testNamespace, testLeasePoolName, scheme).
3356+
GenericOptions(
3357+
testgeneric.WithFinalizer(finalizer),
3358+
).
3359+
Options(
3360+
testcp.ForAWS(credsSecretName, "us-east-1"),
3361+
testcp.WithBaseDomain("test-domain"),
3362+
testcp.WithImageSet(imageSetName),
3363+
)
3364+
3365+
tests := []struct {
3366+
name string
3367+
pool *hivev1.ClusterPool
3368+
existing []runtime.Object
3369+
expectedTotalClusters int
3370+
expectedManifests map[string][]byte
3371+
expectError bool
3372+
}{
3373+
{
3374+
name: "clusterpool with manifests secret",
3375+
pool: poolBuilder.Build(
3376+
testcp.WithSize(1),
3377+
testcp.WithManifestsSecretRef("test-manifests-secret"),
3378+
),
3379+
existing: []runtime.Object{
3380+
&corev1.Secret{
3381+
ObjectMeta: metav1.ObjectMeta{
3382+
Name: "test-manifests-secret",
3383+
Namespace: testNamespace,
3384+
},
3385+
Data: map[string][]byte{
3386+
"manifest1.yaml": []byte("apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: test"),
3387+
},
3388+
},
3389+
},
3390+
expectedManifests: map[string][]byte{
3391+
"manifest1.yaml": []byte("apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: test"),
3392+
},
3393+
expectError: false,
3394+
},
3395+
}
3396+
3397+
for _, tt := range tests {
3398+
t.Run(tt.name, func(t *testing.T) {
3399+
c := testfake.NewFakeClientBuilder().WithRuntimeObjects(tt.existing...).Build()
3400+
r := &ReconcileClusterPool{
3401+
Client: c,
3402+
}
3403+
3404+
// Test the getManifests function directly
3405+
result, err := r.getManifests(tt.pool, logger)
3406+
3407+
if tt.expectError {
3408+
assert.Error(t, err)
3409+
} else {
3410+
assert.NoError(t, err)
3411+
assert.Equal(t, tt.expectedManifests, result)
3412+
}
3413+
})
3414+
}
3415+
}

pkg/test/clusterpool/clusterpool.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,11 @@ func WithCustomizationRef(cdcName string) Option {
222222
}
223223
}
224224
}
225+
226+
func WithManifestsSecretRef(secretName string) Option {
227+
return func(clusterPool *hivev1.ClusterPool) {
228+
clusterPool.Spec.ManifestsSecretRef = &corev1.LocalObjectReference{
229+
Name: secretName,
230+
}
231+
}
232+
}

0 commit comments

Comments
 (0)