Skip to content
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

release operator #1172

Merged
merged 3 commits into from
Aug 21, 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
3 changes: 1 addition & 2 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@ jobs:
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: v1.58.2
working-directory: './radix-operator'
version: v1.59.1

verify-code-generation:
name: Verify Code Generation
Expand Down
4 changes: 2 additions & 2 deletions charts/radix-operator/Chart.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
apiVersion: v2
name: radix-operator
version: 1.37.5
appVersion: 1.57.13
version: 1.37.8
appVersion: 1.57.16
kubeVersion: ">=1.24.0"
description: Radix Operator
keywords:
Expand Down
44 changes: 29 additions & 15 deletions pkg/apis/alert/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,51 @@ import (
"fmt"

"github.com/equinor/radix-operator/pkg/apis/kube"
v1 "k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func (syncer *alertSyncer) createOrUpdateSecret(ctx context.Context) error {
secretName, ns := GetAlertSecretName(syncer.radixAlert.Name), syncer.radixAlert.Namespace
current, desired, err := syncer.getCurrentAndDesiredAlertSecret(ctx)
if err != nil {
return err
}

secret, err := syncer.kubeUtil.GetSecret(ctx, ns, secretName)
if err != nil && !errors.IsNotFound(err) {
if current != nil {
_, err = syncer.kubeUtil.UpdateSecret(ctx, current, desired)
return err
}

if secret == nil {
secret = &v1.Secret{
Type: v1.SecretType("Opaque"),
_, err = syncer.kubeUtil.CreateSecret(ctx, desired.Namespace, desired)
return err
}

func (syncer *alertSyncer) getCurrentAndDesiredAlertSecret(ctx context.Context) (current, desired *corev1.Secret, err error) {
secretName, ns := GetAlertSecretName(syncer.radixAlert.Name), syncer.radixAlert.Namespace
currentInternal, err := syncer.kubeUtil.GetSecret(ctx, ns, secretName)
if err != nil {
if !errors.IsNotFound(err) {
return nil, nil, err
}
desired = &corev1.Secret{
Type: corev1.SecretType("Opaque"),
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Name: secretName,
Namespace: ns,
},
}
} else {
syncer.removedOrphanedSecretKeys(secret)
desired = currentInternal.DeepCopy()
current = currentInternal
}

syncer.setSecretCommonProps(secret)

_, err = syncer.kubeUtil.ApplySecret(ctx, ns, secret)
return err
syncer.removedOrphanedSecretKeys(desired)
syncer.setSecretCommonProps(desired)
return current, desired, nil
}

func (syncer *alertSyncer) setSecretCommonProps(secret *v1.Secret) {
func (syncer *alertSyncer) setSecretCommonProps(secret *corev1.Secret) {
secret.OwnerReferences = syncer.getOwnerReference()

labels := map[string]string{}
Expand All @@ -45,7 +59,7 @@ func (syncer *alertSyncer) setSecretCommonProps(secret *v1.Secret) {
secret.Labels = labels
}

func (syncer *alertSyncer) removedOrphanedSecretKeys(secret *v1.Secret) {
func (syncer *alertSyncer) removedOrphanedSecretKeys(secret *corev1.Secret) {
expectedKeys := map[string]interface{}{}

// Secret keys related to receiver configuration
Expand Down
5 changes: 3 additions & 2 deletions pkg/apis/application/application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,9 @@ func TestOnSync_RegistrationCreated_AppNamespaceWithResourcesCreated(t *testing.
assert.Equal(t, 1, len(appAdminRoleBinding.Subjects))

secrets, _ := client.CoreV1().Secrets("any-app-app").List(context.Background(), metav1.ListOptions{})
assert.Equal(t, 1, len(secrets.Items))
assert.Equal(t, "git-ssh-keys", secrets.Items[0].Name)
secretNames := slice.Map(secrets.Items, func(s corev1.Secret) string { return s.Name })
expectedSecrets := []string{defaults.GitPrivateKeySecretName, defaults.AzureACRServicePrincipleBuildahSecretName, defaults.AzureACRServicePrincipleSecretName, defaults.AzureACRTokenPasswordAppRegistrySecretName}
assert.ElementsMatch(t, expectedSecrets, secretNames)

serviceAccounts, _ := client.CoreV1().ServiceAccounts("any-app-app").List(context.Background(), metav1.ListOptions{})
assert.Equal(t, 2, len(serviceAccounts.Items))
Expand Down
202 changes: 111 additions & 91 deletions pkg/apis/application/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,97 +15,149 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
knownHostsSecretKey = "known_hosts"
)

// ApplySecretsForPipelines creates secrets needed by pipeline to run
func (app *Application) applySecretsForPipelines(ctx context.Context) error {
radixRegistration := app.registration

log.Ctx(ctx).Debug().Msg("Apply secrets for pipelines")
buildNamespace := utils.GetAppNamespace(radixRegistration.Name)

err := app.applyGitDeployKeyToBuildNamespace(ctx, buildNamespace)
if err != nil {
return err
if err := app.applyGitDeployKeyToBuildNamespace(ctx); err != nil {
return fmt.Errorf("failed to apply pipeline git deploy keys: %w", err)
}
err = app.applyServicePrincipalACRSecretToBuildNamespace(ctx, buildNamespace)
if err != nil {
log.Ctx(ctx).Warn().Msgf("Failed to apply service principle acr secrets (%s, %s) to namespace %s", defaults.AzureACRServicePrincipleSecretName, defaults.AzureACRServicePrincipleBuildahSecretName, buildNamespace)

if err := app.applyServicePrincipalACRSecretToBuildNamespace(ctx); err != nil {
return fmt.Errorf("failed to apply pipeline service principal ACR secrets: %w", err)
}

return nil
}

func (app *Application) applyGitDeployKeyToBuildNamespace(ctx context.Context, namespace string) error {
radixRegistration := app.registration
secret, err := app.kubeutil.GetSecret(ctx, namespace, defaults.GitPrivateKeySecretName)
func (app *Application) applyGitDeployKeyToBuildNamespace(ctx context.Context) error {
currentSecret, desiredSecret, derivedPublicKey, err := app.getCurrentAndDesiredGitPrivateDeployKeySecret(ctx)
if err != nil {
if !k8serrors.IsNotFound(err) {
return err
}

currentCm, desiredCm, err := app.getCurrentAndDesiredGitPublicDeployKeyConfigMap(ctx, derivedPublicKey)
if err != nil {
return err
}

if currentSecret != nil {
if _, err := app.kubeutil.UpdateSecret(ctx, currentSecret, desiredSecret); err != nil {
return err
}
} else {
if _, err := app.kubeutil.CreateSecret(ctx, desiredSecret.Namespace, desiredSecret); err != nil {
return err
}
}

deployKey := &utils.DeployKey{}

privateKeyExists := app.gitPrivateKeyExists(secret) // checking if private key exists
if privateKeyExists {
deployKey, err = deriveKeyPairFromSecret(secret) // if private key exists, retrieve public key from secret
if err != nil {
if currentCm != nil {
if _, err = app.kubeutil.UpdateConfigMap(ctx, currentCm, desiredCm); err != nil {
return err
}
} else {
// if private key secret does not exist, check if RR has private key
deployKeyString := radixRegistration.Spec.DeployKey
if deployKeyString == "" {
// if RR does not have private key, generate new key pair
deployKey, err = utils.GenerateDeployKey()
if err != nil {
return err
}
} else {
// if RR has private key, retrieve both public and private key from RR
deployKey.PublicKey = radixRegistration.Spec.DeployKeyPublic
deployKey.PrivateKey = radixRegistration.Spec.DeployKey
if _, err := app.kubeutil.CreateConfigMap(ctx, desiredCm.Namespace, desiredCm); err != nil {
return err
}
}

// create corresponding secret with private key
secret, err = app.createNewGitDeployKey(ctx, namespace, deployKey.PrivateKey, radixRegistration)
return nil
}

func (app *Application) getCurrentAndDesiredGitPrivateDeployKeySecret(ctx context.Context) (current, desired *corev1.Secret, derivedPublicKey string, err error) {
namespace := utils.GetAppNamespace(app.registration.Name)
// Cannot assign `current` directly from GetSecret since kube client returns a non-nil value even when an error is returned
currentInternal, err := app.kubeutil.GetSecret(ctx, namespace, defaults.GitPrivateKeySecretName)
if err != nil {
return err
if !k8serrors.IsNotFound(err) {
return nil, nil, "", err
}
desired = &corev1.Secret{
Type: "Opaque",
ObjectMeta: metav1.ObjectMeta{
Name: defaults.GitPrivateKeySecretName,
Namespace: namespace,
},
}
} else {
desired = currentInternal.DeepCopy()
current = currentInternal
}
_, err = app.kubeutil.ApplySecret(ctx, namespace, secret)

knownHostsSecret, err := app.kubeutil.GetSecret(ctx, corev1.NamespaceDefault, "radix-known-hosts-git")
if err != nil {
return err
return nil, nil, "", fmt.Errorf("failed to get known hosts secret: %w", err)
}

newCm := app.createGitPublicKeyConfigMap(namespace, deployKey.PublicKey)
_, err = app.kubeutil.CreateConfigMap(ctx, namespace, newCm)
deployKey, err := getExistingOrGenerateNewDeployKey(current, app.registration)
if err != nil {
if k8serrors.IsAlreadyExists(err) {
existingCm, err := app.kubeutil.GetConfigMap(ctx, namespace, defaults.GitPublicKeyConfigMapName)
if err != nil {
return err
}
err = app.kubeutil.ApplyConfigMap(ctx, namespace, existingCm, newCm)
if err != nil {
return err
}
} else {
return err
}
return nil, nil, "", fmt.Errorf("failed to get deploy key: %w", err)
}

return nil
desired.ObjectMeta.Labels = labels.ForApplicationName(app.registration.Name) // Required when restoring with Velero. We only restore secrets with the "radix-app" label
desired.Data = map[string][]byte{
defaults.GitPrivateKeySecretKey: []byte(deployKey.PrivateKey),
knownHostsSecretKey: knownHostsSecret.Data[knownHostsSecretKey],
}

return current, desired, deployKey.PublicKey, nil
}

func getExistingOrGenerateNewDeployKey(fromSecret *corev1.Secret, fromRadixRegistration *v1.RadixRegistration) (*utils.DeployKey, error) {
switch {
case fromSecret != nil && secretHasGitPrivateDeployKey(fromSecret):
privateKey := fromSecret.Data[defaults.GitPrivateKeySecretKey]
keypair, err := utils.DeriveDeployKeyFromPrivateKey(string(privateKey))
if err != nil {
return nil, fmt.Errorf("failed to parse deploy key from existing secret: %w", err)
}
return keypair, nil
case len(fromRadixRegistration.Spec.DeployKey) > 0:
return &utils.DeployKey{
PrivateKey: fromRadixRegistration.Spec.DeployKey,
PublicKey: fromRadixRegistration.Spec.DeployKeyPublic,
}, nil
default:
keypair, err := utils.GenerateDeployKey()
if err != nil {
return nil, fmt.Errorf("failed to generate new git deploy key: %w", err)
}
return keypair, nil
}
}

func deriveKeyPairFromSecret(secret *corev1.Secret) (*utils.DeployKey, error) {
privateKey := string(secret.Data[defaults.GitPrivateKeySecretKey])
deployKey, err := utils.DeriveDeployKeyFromPrivateKey(privateKey)
func (app *Application) getCurrentAndDesiredGitPublicDeployKeyConfigMap(ctx context.Context, publicKey string) (current, desired *corev1.ConfigMap, err error) {
namespace := utils.GetAppNamespace(app.registration.Name)
// Cannot assign `current` directly from GetConfigMap since kube client returns a non-nil value even when an error is returned
currentInternal, err := app.kubeutil.GetConfigMap(ctx, namespace, defaults.GitPublicKeyConfigMapName)
if err != nil {
return nil, err
if !k8serrors.IsNotFound(err) {
return nil, nil, err
}
desired = &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: defaults.GitPublicKeyConfigMapName,
Namespace: namespace,
},
}
} else {
desired = currentInternal.DeepCopy()
current = currentInternal
}
return deployKey, nil

desired.Data = map[string]string{
defaults.GitPublicKeyConfigMapKey: publicKey,
}

return current, desired, nil
}

func (app *Application) applyServicePrincipalACRSecretToBuildNamespace(ctx context.Context, buildNamespace string) error {
func (app *Application) applyServicePrincipalACRSecretToBuildNamespace(ctx context.Context) error {
buildNamespace := utils.GetAppNamespace(app.registration.Name)
servicePrincipalSecretForBuild, err := app.createNewServicePrincipalACRSecret(ctx, buildNamespace, defaults.AzureACRServicePrincipleSecretName)
if err != nil {
return err
Expand All @@ -122,7 +174,7 @@ func (app *Application) applyServicePrincipalACRSecretToBuildNamespace(ctx conte
}

for _, secret := range []*corev1.Secret{servicePrincipalSecretForBuild, servicePrincipalSecretForBuildahBuild, tokenSecretForAppRegistry} {
_, err = app.kubeutil.ApplySecret(ctx, buildNamespace, secret)
_, err = app.kubeutil.ApplySecret(ctx, buildNamespace, secret) //nolint:staticcheck // must be updated to use UpdateSecret or CreateSecret
if err != nil {
return err
}
Expand All @@ -131,28 +183,6 @@ func (app *Application) applyServicePrincipalACRSecretToBuildNamespace(ctx conte
return nil
}

func (app *Application) createNewGitDeployKey(ctx context.Context, namespace, deployKey string, registration *v1.RadixRegistration) (*corev1.Secret, error) {
knownHostsSecret, err := app.kubeutil.GetSecret(ctx, corev1.NamespaceDefault, "radix-known-hosts-git")
if err != nil {
return nil, fmt.Errorf("failed to get known hosts secret: %w", err)
}

secret := corev1.Secret{
Type: "Opaque",
ObjectMeta: metav1.ObjectMeta{
Name: defaults.GitPrivateKeySecretName,
Namespace: namespace,
// Required when restoring with Velero. We only restore secrets with the "radix-app" label
Labels: labels.ForApplicationName(registration.GetName()),
},
Data: map[string][]byte{
defaults.GitPrivateKeySecretKey: []byte(deployKey),
"known_hosts": knownHostsSecret.Data["known_hosts"],
},
}
return &secret, nil
}

func (app *Application) createNewServicePrincipalACRSecret(ctx context.Context, namespace, secretName string) (*corev1.Secret, error) {
servicePrincipalSecret, err := app.kubeutil.GetSecret(ctx, corev1.NamespaceDefault, secretName)
if err != nil {
Expand All @@ -175,19 +205,9 @@ func (app *Application) createNewServicePrincipalACRSecret(ctx context.Context,
return &secret, nil
}

func (app *Application) gitPrivateKeyExists(secret *corev1.Secret) bool {
func secretHasGitPrivateDeployKey(secret *corev1.Secret) bool {
if secret == nil {
return false
}
return len(strings.TrimSpace(string(secret.Data[defaults.GitPrivateKeySecretKey]))) > 0
}
func (app *Application) createGitPublicKeyConfigMap(namespace string, key string) *corev1.ConfigMap {
// Create a configmap with the public key
cm := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: defaults.GitPublicKeyConfigMapName,
Namespace: namespace,
}, Data: map[string]string{defaults.GitPublicKeyConfigMapKey: key}}

return cm
}
Loading