Skip to content

Commit

Permalink
Copy additonal secrets into Shoot
Browse files Browse the repository at this point in the history
  • Loading branch information
afritzler authored and maboehm committed Jul 23, 2024
1 parent 64fb69b commit 5f14e0f
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 5 deletions.
55 changes: 55 additions & 0 deletions hack/api-reference/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,48 @@
<h2 id="flux.extensions.gardener.cloud/v1alpha1">flux.extensions.gardener.cloud/v1alpha1</h2>
Resource Types:
<ul></ul>
<h3 id="flux.extensions.gardener.cloud/v1alpha1.AdditionalResource">AdditionalResource
</h3>
<p>
(<em>Appears on:</em>
<a href="#flux.extensions.gardener.cloud/v1alpha1.FluxConfig">FluxConfig</a>)
</p>
<p>
<p>AdditionalResource to sync to the shoot.</p>
</p>
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>name</code></br>
<em>
string
</em>
</td>
<td>
<p>Name references a resource under Shoot.spec.resources.</p>
</td>
</tr>
<tr>
<td>
<code>targetName</code></br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>TargetName optionally overwrites the name of the secret in the shoot.</p>
</td>
</tr>
</tbody>
</table>
<h3 id="flux.extensions.gardener.cloud/v1alpha1.FluxConfig">FluxConfig
</h3>
<p>
Expand Down Expand Up @@ -66,6 +108,19 @@ Kustomization
If provided, &ldquo;Source&rdquo; must also be provided.</p>
</td>
</tr>
<tr>
<td>
<code>additionalSecretResources</code></br>
<em>
<a href="#flux.extensions.gardener.cloud/v1alpha1.AdditionalResource">
[]AdditionalResource
</a>
</em>
</td>
<td>
<p>AdditionalSecretResources to sync to the shoot.</p>
</td>
</tr>
</tbody>
</table>
<h3 id="flux.extensions.gardener.cloud/v1alpha1.FluxInstallation">FluxInstallation
Expand Down
12 changes: 12 additions & 0 deletions pkg/apis/flux/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ type FluxConfig struct {
// If provided, "Source" must also be provided.
// +optional
Kustomization *Kustomization `json:"kustomization,omitempty"`

// AdditionalSecretResources to sync to the shoot.
AdditionalSecretResources []AdditionalResource `json:"additionalSecretResources,omitempty"`
}

// AdditionalResource to sync to the shoot.
type AdditionalResource struct {
// Name references a resource under Shoot.spec.resources.
Name string `json:"name"`
// TargetName optionally overwrites the name of the secret in the shoot.
// +optional
TargetName *string `json:"targetName,omitempty"`
}

// FluxInstallation configures the Flux installation in the Shoot cluster.
Expand Down
28 changes: 28 additions & 0 deletions pkg/apis/flux/v1alpha1/zz_generated.deepcopy.go

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

14 changes: 9 additions & 5 deletions pkg/controller/extension/actuator.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,20 @@ func (a *actuator) Reconcile(ctx context.Context, log logr.Logger, ext *extensio
return fmt.Errorf("invalid providerConfig: %w", allErrs.ToAggregate())
}

if IsFluxBootstrapped(ext) {
log.V(1).Info("Flux installation has been bootstrapped already, skipping reconciliation of Flux resources")
return nil
}

_, shootClient, err := util.NewClientForShoot(ctx, a.client, ext.Namespace, client.Options{Scheme: a.client.Scheme()}, extensionsconfig.RESTOptions{})
if err != nil {
return fmt.Errorf("error creating shoot client: %w", err)
}

if err := ReconcileSecrets(ctx, log, shootClient, a.client, ext.Namespace, config, cluster.Shoot.Spec.Resources); err != nil {
return fmt.Errorf("error reconciling secrets: %w", err)
}

if IsFluxBootstrapped(ext) {
log.V(1).Info("Flux installation has been bootstrapped already, skipping reconciliation of Flux resources")
return nil
}

if err := InstallFlux(ctx, log, shootClient, config.Flux); err != nil {
return fmt.Errorf("error installing Flux: %w", err)
}
Expand Down
139 changes: 139 additions & 0 deletions pkg/controller/extension/secret.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package extension

import (
"context"
"fmt"
"maps"

gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
v1beta1helper "github.com/gardener/gardener/pkg/apis/core/v1beta1/helper"
resourcesv1alpha1 "github.com/gardener/gardener/pkg/apis/resources/v1alpha1"
"github.com/gardener/gardener/pkg/utils"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

fluxv1alpha1 "github.com/stackitcloud/gardener-extension-shoot-flux/pkg/apis/flux/v1alpha1"
)

const (
managedByLabelKey = "app.kubernetes.io/managed-by"
managedByLabelValue = "gardener-extension-" + fluxv1alpha1.ExtensionType
)

// ReconcileSecrets copies all secrets referenced in the extension (additionalSecretResources or
// source.SecretResourceName), and deletes all secrets that are no longer referenced.
func ReconcileSecrets(
ctx context.Context,
log logr.Logger,
shootClient client.Client,
seedClient client.Client,
seedNamespace string,
config *fluxv1alpha1.FluxConfig,
resources []gardencorev1beta1.NamedResourceReference,
) error {
shootNamespace := *config.Flux.Namespace
secretsToKeep := sets.Set[string]{}

secretResources := config.AdditionalSecretResources
if config.Source.SecretResourceName != nil {
secretResources = append(secretResources, fluxv1alpha1.AdditionalResource{
Name: *config.Source.SecretResourceName,
TargetName: ptr.To(config.Source.Template.Spec.SecretRef.Name),
})
}
for _, resource := range secretResources {
name, err := copySecretToShoot(ctx, log, seedClient, shootClient, seedNamespace, shootNamespace, resources, resource)
if err != nil {
return fmt.Errorf("failed to copy secret: %w", err)
}
secretsToKeep.Insert(name)
}

// cleanup unreferenced secrets
secretList := &corev1.SecretList{}
if err := shootClient.List(ctx, secretList,
client.InNamespace(shootNamespace),
client.MatchingLabels{managedByLabelKey: managedByLabelValue},
); err != nil {
return fmt.Errorf("failed to list managed secrets in shoot: %w", err)
}
for _, secret := range secretList.Items {
if secretsToKeep.Has(secret.Name) {
continue
}
if err := shootClient.Delete(ctx, &secret); client.IgnoreNotFound(err) != nil {
return fmt.Errorf("failed to delete secret that is no longer referenced: %w", err)
}
log.Info("deleted secret that is no longer referenced by the extension", "secretName", secret.Name)
}
return nil
}

func copySecretToShoot(
ctx context.Context,
log logr.Logger,
seedClient client.Client,
shootClient client.Client,
seedNamespace string,
targetNamespace string,
resources []gardencorev1beta1.NamedResourceReference,
additionalResource fluxv1alpha1.AdditionalResource,
) (string, error) {
resource := v1beta1helper.GetResourceByName(resources, additionalResource.Name)
if resource == nil {
return "", fmt.Errorf("secret resource name does not match any of the resource names in Shoot.spec.resources[].name")
}

seedSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: v1beta1constants.ReferencedResourcesPrefix + resource.ResourceRef.Name,
Namespace: seedNamespace,
},
}
if err := seedClient.Get(ctx, client.ObjectKeyFromObject(seedSecret), seedSecret); err != nil {
return "", fmt.Errorf("error reading referenced secret: %w", err)
}

name := resource.ResourceRef.Name
if additionalResource.TargetName != nil {
name = *additionalResource.TargetName
}
shootSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: targetNamespace,
},
}
// TODO: this should only create
res, err := controllerutil.CreateOrUpdate(ctx, shootClient, shootSecret, func() error {
shootSecret.Data = maps.Clone(seedSecret.Data)
setSecretMeta(seedSecret, shootSecret)
return nil
})
if err != nil {
return "", fmt.Errorf("failed to ensure secret resource")
}
if res != controllerutil.OperationResultNone {
log.Info("Ensured secret", "secretName", shootSecret.Name, "result", res)
}

return shootSecret.Name, nil
}

func setSecretMeta(from, into client.Object) {
labels := from.GetLabels()
labels[managedByLabelKey] = managedByLabelValue
delete(labels, resourcesv1alpha1.ManagedBy)
into.SetLabels(utils.MergeStringMaps(into.GetLabels(), labels))

annotations := from.GetAnnotations()
delete(annotations, resourcesv1alpha1.OriginAnnotation)
delete(annotations, "resources.gardener.cloud/description")
into.SetAnnotations(utils.MergeStringMaps(into.GetAnnotations(), annotations))
}

0 comments on commit 5f14e0f

Please sign in to comment.