Merge pull request #10 from 1Password/restart-cr

Configure Auto Restarts for a OnePasswordItem Custom Resource
This commit is contained in:
Jillian W
2021-04-07 14:39:39 -03:00
committed by GitHub
15 changed files with 323 additions and 69 deletions

View File

@@ -176,6 +176,18 @@ metadata:
``` ```
If the value is not set, the auto reset settings on the namespace will be used. If the value is not set, the auto reset settings on the namespace will be used.
**Per OnePasswordItem Custom Resource**
This method allows for managing auto restarts on a given OnePasswordItem custom resource. Auto restarts can by managed by setting the annotation `onepasswordoperator/auto_restart` to either `true` or `false` on the desired OnePasswordItem. An example of this is shown below:
```yaml
# enabled auto restarts for the OnePasswordItem
apiVersion: onepassword.com/v1
kind: OnePasswordItem
metadata:
name: example
onepasswordoperator/auto_restart: "true"
```
If the value is not set, the auto reset settings on the deployment will be used.
## Development ## Development
### Creating a Docker image ### Creating a Docker image

View File

@@ -142,7 +142,7 @@ func (r *ReconcileDeployment) cleanupKubernetesSecretForDeployment(secretName st
if len(secretName) == 0 { if len(secretName) == 0 {
return nil return nil
} }
updatedSecrets := map[string]bool{secretName: true} updatedSecrets := map[string]*corev1.Secret{secretName: kubernetesSecret}
multipleDeploymentsUsingSecret, err := r.areMultipleDeploymentsUsingSecret(updatedSecrets, *deletedDeployment) multipleDeploymentsUsingSecret, err := r.areMultipleDeploymentsUsingSecret(updatedSecrets, *deletedDeployment)
if err != nil { if err != nil {
@@ -160,7 +160,7 @@ func (r *ReconcileDeployment) cleanupKubernetesSecretForDeployment(secretName st
return nil return nil
} }
func (r *ReconcileDeployment) areMultipleDeploymentsUsingSecret(updatedSecrets map[string]bool, deletedDeployment appsv1.Deployment) (bool, error) { func (r *ReconcileDeployment) areMultipleDeploymentsUsingSecret(updatedSecrets map[string]*corev1.Secret, deletedDeployment appsv1.Deployment) (bool, error) {
deployments := &appsv1.DeploymentList{} deployments := &appsv1.DeploymentList{}
opts := []client.ListOption{ opts := []client.ListOption{
client.InNamespace(deletedDeployment.Namespace), client.InNamespace(deletedDeployment.Namespace),
@@ -201,5 +201,5 @@ func (r *ReconcileDeployment) HandleApplyingDeployment(namespace string, annotat
return fmt.Errorf("Failed to retrieve item: %v", err) return fmt.Errorf("Failed to retrieve item: %v", err)
} }
return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, namespace, item) return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, namespace, item, annotations[op.RestartDeploymentsAnnotation])
} }

View File

@@ -7,6 +7,7 @@ import (
onepasswordv1 "github.com/1Password/onepassword-operator/pkg/apis/onepassword/v1" onepasswordv1 "github.com/1Password/onepassword-operator/pkg/apis/onepassword/v1"
kubeSecrets "github.com/1Password/onepassword-operator/pkg/kubernetessecrets" kubeSecrets "github.com/1Password/onepassword-operator/pkg/kubernetessecrets"
"github.com/1Password/onepassword-operator/pkg/onepassword" "github.com/1Password/onepassword-operator/pkg/onepassword"
op "github.com/1Password/onepassword-operator/pkg/onepassword"
"github.com/1Password/onepassword-operator/pkg/utils" "github.com/1Password/onepassword-operator/pkg/utils"
"github.com/1Password/connect-sdk-go/connect" "github.com/1Password/connect-sdk-go/connect"
@@ -143,11 +144,12 @@ func (r *ReconcileOnePasswordItem) removeOnePasswordFinalizerFromOnePasswordItem
func (r *ReconcileOnePasswordItem) HandleOnePasswordItem(resource *onepasswordv1.OnePasswordItem, request reconcile.Request) error { func (r *ReconcileOnePasswordItem) HandleOnePasswordItem(resource *onepasswordv1.OnePasswordItem, request reconcile.Request) error {
secretName := resource.GetName() secretName := resource.GetName()
autoRestart := resource.Annotations[op.RestartDeploymentsAnnotation]
item, err := onepassword.GetOnePasswordItemByPath(r.opConnectClient, resource.Spec.ItemPath) item, err := onepassword.GetOnePasswordItemByPath(r.opConnectClient, resource.Spec.ItemPath)
if err != nil { if err != nil {
return fmt.Errorf("Failed to retrieve item: %v", err) return fmt.Errorf("Failed to retrieve item: %v", err)
} }
return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, resource.Namespace, item) return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, resource.Namespace, item, autoRestart)
} }

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/1Password/connect-sdk-go/onepassword" "github.com/1Password/connect-sdk-go/onepassword"
"github.com/1Password/onepassword-operator/pkg/utils"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -13,21 +14,30 @@ import (
logf "sigs.k8s.io/controller-runtime/pkg/log" logf "sigs.k8s.io/controller-runtime/pkg/log"
) )
const onepasswordPrefix = "onepasswordoperator" const OnepasswordPrefix = "onepasswordoperator"
const NameAnnotation = onepasswordPrefix + "/item-name" const NameAnnotation = OnepasswordPrefix + "/item-name"
const VersionAnnotation = onepasswordPrefix + "/item-version" const VersionAnnotation = OnepasswordPrefix + "/item-version"
const restartAnnotation = onepasswordPrefix + "/lastRestarted" const restartAnnotation = OnepasswordPrefix + "/lastRestarted"
const ItemPathAnnotation = onepasswordPrefix + "/item-path" const ItemPathAnnotation = OnepasswordPrefix + "/item-path"
const RestartDeploymentsAnnotation = OnepasswordPrefix + "/auto_restart"
var log = logf.Log var log = logf.Log
func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretName, namespace string, item *onepassword.Item) error { func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretName, namespace string, item *onepassword.Item, autoRestart string) error {
itemVersion := fmt.Sprint(item.Version) itemVersion := fmt.Sprint(item.Version)
annotations := map[string]string{ annotations := map[string]string{
VersionAnnotation: itemVersion, VersionAnnotation: itemVersion,
ItemPathAnnotation: fmt.Sprintf("vaults/%v/items/%v", item.Vault.ID, item.ID), ItemPathAnnotation: fmt.Sprintf("vaults/%v/items/%v", item.Vault.ID, item.ID),
} }
if autoRestart != "" {
_, err := utils.StringToBool(autoRestart)
if err != nil {
log.Error(err, "Error parsing %v annotation on Secret %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, secretName)
return err
}
annotations[RestartDeploymentsAnnotation] = autoRestart
}
secret := BuildKubernetesSecretFromOnePasswordItem(secretName, namespace, annotations, *item) secret := BuildKubernetesSecretFromOnePasswordItem(secretName, namespace, annotations, *item)
currentSecret := &corev1.Secret{} currentSecret := &corev1.Secret{}

View File

@@ -13,6 +13,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/client/fake"
) )
const restartDeploymentAnnotation = "false"
type k8s struct { type k8s struct {
clientset kubernetes.Interface clientset kubernetes.Interface
} }
@@ -28,7 +30,7 @@ func TestCreateKubernetesSecretFromOnePasswordItem(t *testing.T) {
item.ID = "h46bb3jddvay7nxopfhvlwg35q" item.ID = "h46bb3jddvay7nxopfhvlwg35q"
kubeClient := fake.NewFakeClient() kubeClient := fake.NewFakeClient()
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item) err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation)
if err != nil { if err != nil {
t.Errorf("Unexpected error: %v", err) t.Errorf("Unexpected error: %v", err)
} }
@@ -53,7 +55,7 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) {
item.ID = "h46bb3jddvay7nxopfhvlwg35q" item.ID = "h46bb3jddvay7nxopfhvlwg35q"
kubeClient := fake.NewFakeClient() kubeClient := fake.NewFakeClient()
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item) err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation)
if err != nil { if err != nil {
t.Errorf("Unexpected error: %v", err) t.Errorf("Unexpected error: %v", err)
} }
@@ -64,7 +66,7 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) {
newItem.Version = 456 newItem.Version = 456
newItem.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda" newItem.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda"
newItem.ID = "h46bb3jddvay7nxopfhvlwg35q" newItem.ID = "h46bb3jddvay7nxopfhvlwg35q"
err = CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &newItem) err = CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &newItem, restartDeploymentAnnotation)
if err != nil { if err != nil {
t.Errorf("Unexpected error: %v", err) t.Errorf("Unexpected error: %v", err)
} }
@@ -125,6 +127,10 @@ func compareAnnotationsToItem(annotations map[string]string, item onepassword.It
if annotations[VersionAnnotation] != fmt.Sprint(item.Version) { if annotations[VersionAnnotation] != fmt.Sprint(item.Version) {
t.Errorf("Expected annotation version to be %v but was %v", item.Version, annotations[VersionAnnotation]) t.Errorf("Expected annotation version to be %v but was %v", item.Version, annotations[VersionAnnotation])
} }
if annotations[RestartDeploymentsAnnotation] != "false" {
t.Errorf("Expected restart deployments annotation to be %v but was %v", restartDeploymentAnnotation, RestartDeploymentsAnnotation)
}
} }
func compareFields(actualFields []*onepassword.ItemField, secretData map[string][]byte, t *testing.T) { func compareFields(actualFields []*onepassword.ItemField, secretData map[string][]byte, t *testing.T) {

View File

@@ -4,6 +4,7 @@ import (
"regexp" "regexp"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
) )
const ( const (
@@ -42,10 +43,18 @@ func FilterAnnotations(annotations map[string]string, regex *regexp.Regexp) map[
return filteredAnnotations return filteredAnnotations
} }
func AreAnnotationsUsingSecrets(annotations map[string]string, secrets map[string]bool) bool { func AreAnnotationsUsingSecrets(annotations map[string]string, secrets map[string]*corev1.Secret) bool {
_, ok := secrets[annotations[NameAnnotation]] _, ok := secrets[annotations[NameAnnotation]]
if ok { if ok {
return true return true
} }
return false return false
} }
func AppendAnnotationUpdatedSecret(annotations map[string]string, secrets map[string]*corev1.Secret, updatedDeploymentSecrets map[string]*corev1.Secret) map[string]*corev1.Secret {
secret, ok := secrets[annotations[NameAnnotation]]
if ok {
updatedDeploymentSecrets[secret.Name] = secret
}
return updatedDeploymentSecrets
}

View File

@@ -2,7 +2,7 @@ package onepassword
import corev1 "k8s.io/api/core/v1" import corev1 "k8s.io/api/core/v1"
func AreContainersUsingSecrets(containers []corev1.Container, secrets map[string]bool) bool { func AreContainersUsingSecrets(containers []corev1.Container, secrets map[string]*corev1.Secret) bool {
for i := 0; i < len(containers); i++ { for i := 0; i < len(containers); i++ {
envVariables := containers[i].Env envVariables := containers[i].Env
for j := 0; j < len(envVariables); j++ { for j := 0; j < len(envVariables); j++ {
@@ -16,3 +16,18 @@ func AreContainersUsingSecrets(containers []corev1.Container, secrets map[string
} }
return false return false
} }
func AppendUpdatedContainerSecrets(containers []corev1.Container, secrets map[string]*corev1.Secret, updatedDeploymentSecrets map[string]*corev1.Secret) map[string]*corev1.Secret {
for i := 0; i < len(containers); i++ {
envVariables := containers[i].Env
for j := 0; j < len(envVariables); j++ {
if envVariables[j].ValueFrom != nil && envVariables[j].ValueFrom.SecretKeyRef != nil {
secret, ok := secrets[envVariables[j].ValueFrom.SecretKeyRef.Name]
if ok {
updatedDeploymentSecrets[secret.Name] = secret
}
}
}
}
return updatedDeploymentSecrets
}

View File

@@ -2,12 +2,14 @@ package onepassword
import ( import (
"testing" "testing"
corev1 "k8s.io/api/core/v1"
) )
func TestAreContainersUsingSecrets(t *testing.T) { func TestAreContainersUsingSecrets(t *testing.T) {
secretNamesToSearch := map[string]bool{ secretNamesToSearch := map[string]*corev1.Secret{
"onepassword-database-secret": true, "onepassword-database-secret": &corev1.Secret{},
"onepassword-api-key": true, "onepassword-api-key": &corev1.Secret{},
} }
containerSecretNames := []string{ containerSecretNames := []string{
@@ -24,9 +26,9 @@ func TestAreContainersUsingSecrets(t *testing.T) {
} }
func TestAreContainersNotUsingSecrets(t *testing.T) { func TestAreContainersNotUsingSecrets(t *testing.T) {
secretNamesToSearch := map[string]bool{ secretNamesToSearch := map[string]*corev1.Secret{
"onepassword-database-secret": true, "onepassword-database-secret": &corev1.Secret{},
"onepassword-api-key": true, "onepassword-api-key": &corev1.Secret{},
} }
containerSecretNames := []string{ containerSecretNames := []string{

View File

@@ -1,10 +1,26 @@
package onepassword package onepassword
import appsv1 "k8s.io/api/apps/v1" import (
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
)
func IsDeploymentUsingSecrets(deployment *appsv1.Deployment, secrets map[string]bool) bool { func IsDeploymentUsingSecrets(deployment *appsv1.Deployment, secrets map[string]*corev1.Secret) bool {
volumes := deployment.Spec.Template.Spec.Volumes volumes := deployment.Spec.Template.Spec.Volumes
containers := deployment.Spec.Template.Spec.Containers containers := deployment.Spec.Template.Spec.Containers
containers = append(containers, deployment.Spec.Template.Spec.InitContainers...) containers = append(containers, deployment.Spec.Template.Spec.InitContainers...)
return AreAnnotationsUsingSecrets(deployment.Annotations, secrets) || AreContainersUsingSecrets(containers, secrets) || AreVolumesUsingSecrets(volumes, secrets) return AreAnnotationsUsingSecrets(deployment.Annotations, secrets) || AreContainersUsingSecrets(containers, secrets) || AreVolumesUsingSecrets(volumes, secrets)
} }
func GetUpdatedSecretsForDeployment(deployment *appsv1.Deployment, secrets map[string]*corev1.Secret) map[string]*corev1.Secret {
volumes := deployment.Spec.Template.Spec.Volumes
containers := deployment.Spec.Template.Spec.Containers
containers = append(containers, deployment.Spec.Template.Spec.InitContainers...)
updatedSecretsForDeployment := map[string]*corev1.Secret{}
AppendAnnotationUpdatedSecret(deployment.Annotations, secrets, updatedSecretsForDeployment)
AppendUpdatedContainerSecrets(containers, secrets, updatedSecretsForDeployment)
AppendUpdatedVolumeSecrets(volumes, secrets, updatedSecretsForDeployment)
return updatedSecretsForDeployment
}

View File

@@ -4,12 +4,13 @@ import (
"testing" "testing"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
) )
func TestIsDeploymentUsingSecretsUsingVolumes(t *testing.T) { func TestIsDeploymentUsingSecretsUsingVolumes(t *testing.T) {
secretNamesToSearch := map[string]bool{ secretNamesToSearch := map[string]*corev1.Secret{
"onepassword-database-secret": true, "onepassword-database-secret": &corev1.Secret{},
"onepassword-api-key": true, "onepassword-api-key": &corev1.Secret{},
} }
volumeSecretNames := []string{ volumeSecretNames := []string{
@@ -26,9 +27,9 @@ func TestIsDeploymentUsingSecretsUsingVolumes(t *testing.T) {
} }
func TestIsDeploymentUsingSecretsUsingContainers(t *testing.T) { func TestIsDeploymentUsingSecretsUsingContainers(t *testing.T) {
secretNamesToSearch := map[string]bool{ secretNamesToSearch := map[string]*corev1.Secret{
"onepassword-database-secret": true, "onepassword-database-secret": &corev1.Secret{},
"onepassword-api-key": true, "onepassword-api-key": &corev1.Secret{},
} }
containerSecretNames := []string{ containerSecretNames := []string{
@@ -45,9 +46,9 @@ func TestIsDeploymentUsingSecretsUsingContainers(t *testing.T) {
} }
func TestIsDeploymentNotUSingSecrets(t *testing.T) { func TestIsDeploymentNotUSingSecrets(t *testing.T) {
secretNamesToSearch := map[string]bool{ secretNamesToSearch := map[string]*corev1.Secret{
"onepassword-database-secret": true, "onepassword-database-secret": &corev1.Secret{},
"onepassword-api-key": true, "onepassword-api-key": &corev1.Secret{},
} }
deployment := &appsv1.Deployment{} deployment := &appsv1.Deployment{}

View File

@@ -3,11 +3,10 @@ package onepassword
import ( import (
"context" "context"
"fmt" "fmt"
"strconv"
"strings"
"time" "time"
kubeSecrets "github.com/1Password/onepassword-operator/pkg/kubernetessecrets" kubeSecrets "github.com/1Password/onepassword-operator/pkg/kubernetessecrets"
"github.com/1Password/onepassword-operator/pkg/utils"
"github.com/1Password/connect-sdk-go/connect" "github.com/1Password/connect-sdk-go/connect"
"github.com/1Password/connect-sdk-go/onepassword" "github.com/1Password/connect-sdk-go/onepassword"
@@ -45,7 +44,7 @@ func (h *SecretUpdateHandler) UpdateKubernetesSecretsTask() error {
return h.restartDeploymentsWithUpdatedSecrets(updatedKubernetesSecrets) return h.restartDeploymentsWithUpdatedSecrets(updatedKubernetesSecrets)
} }
func (h *SecretUpdateHandler) restartDeploymentsWithUpdatedSecrets(updatedSecretsByNamespace map[string]map[string]bool) error { func (h *SecretUpdateHandler) restartDeploymentsWithUpdatedSecrets(updatedSecretsByNamespace map[string]map[string]*corev1.Secret) error {
// No secrets to update. Exit // No secrets to update. Exit
if len(updatedSecretsByNamespace) == 0 || updatedSecretsByNamespace == nil { if len(updatedSecretsByNamespace) == 0 || updatedSecretsByNamespace == nil {
return nil return nil
@@ -69,16 +68,21 @@ func (h *SecretUpdateHandler) restartDeploymentsWithUpdatedSecrets(updatedSecret
for i := 0; i < len(deployments.Items); i++ { for i := 0; i < len(deployments.Items); i++ {
deployment := &deployments.Items[i] deployment := &deployments.Items[i]
if !isDeploymentSetForAutoRestart(deployment, setForAutoRestartByNamespaceMap) { updatedSecrets := updatedSecretsByNamespace[deployment.Namespace]
updatedDeploymentSecrets := GetUpdatedSecretsForDeployment(deployment, updatedSecrets)
if len(updatedDeploymentSecrets) == 0 {
continue continue
} }
updatedSecrets := updatedSecretsByNamespace[deployment.Namespace] for _, secret := range updatedDeploymentSecrets {
secretName := deployment.Annotations[NameAnnotation] if isSecretSetForAutoRestart(secret, deployment, setForAutoRestartByNamespaceMap) {
if isUpdatedSecret(secretName, updatedSecrets) || IsDeploymentUsingSecrets(deployment, updatedSecrets) { h.restartDeployment(deployment)
h.restartDeployment(deployment) continue
} else { }
log.Info(fmt.Sprintf("Deployment %q at namespace %q is up to date", deployment.GetName(), deployment.Namespace))
} }
log.Info(fmt.Sprintf("Deployment %q at namespace %q is up to date", deployment.GetName(), deployment.Namespace))
} }
return nil return nil
} }
@@ -94,7 +98,7 @@ func (h *SecretUpdateHandler) restartDeployment(deployment *appsv1.Deployment) {
} }
} }
func (h *SecretUpdateHandler) updateKubernetesSecrets() (map[string]map[string]bool, error) { func (h *SecretUpdateHandler) updateKubernetesSecrets() (map[string]map[string]*corev1.Secret, error) {
secrets := &corev1.SecretList{} secrets := &corev1.SecretList{}
err := h.client.List(context.Background(), secrets) err := h.client.List(context.Background(), secrets)
if err != nil { if err != nil {
@@ -102,7 +106,7 @@ func (h *SecretUpdateHandler) updateKubernetesSecrets() (map[string]map[string]b
return nil, err return nil, err
} }
updatedSecrets := map[string]map[string]bool{} updatedSecrets := map[string]map[string]*corev1.Secret{}
for i := 0; i < len(secrets.Items); i++ { for i := 0; i < len(secrets.Items); i++ {
secret := secrets.Items[i] secret := secrets.Items[i]
@@ -130,9 +134,9 @@ func (h *SecretUpdateHandler) updateKubernetesSecrets() (map[string]map[string]b
updatedSecret := kubeSecrets.BuildKubernetesSecretFromOnePasswordItem(secret.Name, secret.Namespace, secret.Annotations, *item) updatedSecret := kubeSecrets.BuildKubernetesSecretFromOnePasswordItem(secret.Name, secret.Namespace, secret.Annotations, *item)
h.client.Update(context.Background(), updatedSecret) h.client.Update(context.Background(), updatedSecret)
if updatedSecrets[secret.Namespace] == nil { if updatedSecrets[secret.Namespace] == nil {
updatedSecrets[secret.Namespace] = make(map[string]bool) updatedSecrets[secret.Namespace] = make(map[string]*corev1.Secret)
} }
updatedSecrets[secret.Namespace][secret.Name] = true updatedSecrets[secret.Namespace][secret.Name] = &secret
} }
} }
return updatedSecrets, nil return updatedSecrets, nil
@@ -148,7 +152,7 @@ func isItemLockedForForcedRestarts(item *onepassword.Item) bool {
return false return false
} }
func isUpdatedSecret(secretName string, updatedSecrets map[string]bool) bool { func isUpdatedSecret(secretName string, updatedSecrets map[string]*corev1.Secret) bool {
_, ok := updatedSecrets[secretName] _, ok := updatedSecrets[secretName]
if ok { if ok {
return true return true
@@ -172,6 +176,21 @@ func (h *SecretUpdateHandler) getIsSetForAutoRestartByNamespaceMap() (map[string
return namespacesMap, nil return namespacesMap, nil
} }
func isSecretSetForAutoRestart(secret *corev1.Secret, deployment *appsv1.Deployment, setForAutoRestartByNamespace map[string]bool) bool {
restartDeployment := secret.Annotations[RestartDeploymentsAnnotation]
//If annotation for auto restarts for deployment is not set. Check for the annotation on its namepsace
if restartDeployment == "" {
return isDeploymentSetForAutoRestart(deployment, setForAutoRestartByNamespace)
}
restartDeploymentBool, err := utils.StringToBool(restartDeployment)
if err != nil {
log.Error(err, "Error parsing %v annotation on Secret %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, secret.Name)
return false
}
return restartDeploymentBool
}
func isDeploymentSetForAutoRestart(deployment *appsv1.Deployment, setForAutoRestartByNamespace map[string]bool) bool { func isDeploymentSetForAutoRestart(deployment *appsv1.Deployment, setForAutoRestartByNamespace map[string]bool) bool {
restartDeployment := deployment.Annotations[RestartDeploymentsAnnotation] restartDeployment := deployment.Annotations[RestartDeploymentsAnnotation]
//If annotation for auto restarts for deployment is not set. Check for the annotation on its namepsace //If annotation for auto restarts for deployment is not set. Check for the annotation on its namepsace
@@ -179,7 +198,7 @@ func isDeploymentSetForAutoRestart(deployment *appsv1.Deployment, setForAutoRest
return setForAutoRestartByNamespace[deployment.Namespace] return setForAutoRestartByNamespace[deployment.Namespace]
} }
restartDeploymentBool, err := stringToBool(restartDeployment) restartDeploymentBool, err := utils.StringToBool(restartDeployment)
if err != nil { if err != nil {
log.Error(err, "Error parsing %v annotation on Deployment %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, deployment.Name) log.Error(err, "Error parsing %v annotation on Deployment %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, deployment.Name)
return false return false
@@ -194,18 +213,10 @@ func (h *SecretUpdateHandler) isNamespaceSetToAutoRestart(namespace *corev1.Name
return h.shouldAutoRestartDeploymentsGlobal return h.shouldAutoRestartDeploymentsGlobal
} }
restartDeploymentBool, err := stringToBool(restartDeployment) restartDeploymentBool, err := utils.StringToBool(restartDeployment)
if err != nil { if err != nil {
log.Error(err, "Error parsing %v annotation on Namespace %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, namespace.Name) log.Error(err, "Error parsing %v annotation on Namespace %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, namespace.Name)
return false return false
} }
return restartDeploymentBool return restartDeploymentBool
} }
func stringToBool(str string) (bool, error) {
restartDeploymentBool, err := strconv.ParseBool(strings.ToLower(str))
if err != nil {
return false, err
}
return restartDeploymentBool, nil
}

View File

@@ -394,6 +394,148 @@ var tests = []testUpdateSecretTask{
expectedRestart: false, expectedRestart: false,
globalAutoRestartEnabled: false, globalAutoRestartEnabled: false,
}, },
{
testName: `Secret autostart true value takes precedence over false deployment value`,
existingNamespace: defaultNamespace,
existingDeployment: &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
Kind: deploymentKind,
APIVersion: deploymentAPIVersion,
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Annotations: map[string]string{
RestartDeploymentsAnnotation: "false",
},
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Env: []corev1.EnvVar{
{
Name: name,
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: name,
},
Key: passKey,
},
},
},
},
},
},
},
},
},
},
existingSecret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Annotations: map[string]string{
VersionAnnotation: "old version",
ItemPathAnnotation: itemPath,
RestartDeploymentsAnnotation: "true",
},
},
Data: expectedSecretData,
},
expectedError: nil,
expectedResultSecret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Annotations: map[string]string{
VersionAnnotation: fmt.Sprint(itemVersion),
ItemPathAnnotation: itemPath,
RestartDeploymentsAnnotation: "true",
},
},
Data: expectedSecretData,
},
opItem: map[string]string{
userKey: username,
passKey: password,
},
expectedRestart: true,
globalAutoRestartEnabled: false,
},
{
testName: `Secret autostart true value takes precedence over false deployment value`,
existingNamespace: defaultNamespace,
existingDeployment: &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
Kind: deploymentKind,
APIVersion: deploymentAPIVersion,
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Annotations: map[string]string{
RestartDeploymentsAnnotation: "true",
},
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Env: []corev1.EnvVar{
{
Name: name,
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: name,
},
Key: passKey,
},
},
},
},
},
},
},
},
},
},
existingSecret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Annotations: map[string]string{
VersionAnnotation: "old version",
ItemPathAnnotation: itemPath,
RestartDeploymentsAnnotation: "false",
},
},
Data: expectedSecretData,
},
expectedError: nil,
expectedResultSecret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Annotations: map[string]string{
VersionAnnotation: fmt.Sprint(itemVersion),
ItemPathAnnotation: itemPath,
RestartDeploymentsAnnotation: "false",
},
},
Data: expectedSecretData,
},
opItem: map[string]string{
userKey: username,
passKey: password,
},
expectedRestart: false,
globalAutoRestartEnabled: true,
},
{ {
testName: `Deployment autostart true value takes precedence over false global auto restart value`, testName: `Deployment autostart true value takes precedence over false global auto restart value`,
existingNamespace: defaultNamespace, existingNamespace: defaultNamespace,
@@ -461,7 +603,7 @@ var tests = []testUpdateSecretTask{
passKey: password, passKey: password,
}, },
expectedRestart: true, expectedRestart: true,
globalAutoRestartEnabled: true, globalAutoRestartEnabled: false,
}, },
{ {
testName: `Deployment autostart false value takes precedence over false global auto restart value, testName: `Deployment autostart false value takes precedence over false global auto restart value,
@@ -685,7 +827,7 @@ func TestUpdateSecretHandler(t *testing.T) {
if ok { if ok {
assert.True(t, testData.expectedRestart, "Expected deployment to restart but it did not") assert.True(t, testData.expectedRestart, "Expected deployment to restart but it did not")
} else { } else {
assert.False(t, testData.expectedRestart) assert.False(t, testData.expectedRestart, "Deployment was restarted but should not have been.")
} }
}) })
} }
@@ -694,12 +836,12 @@ func TestUpdateSecretHandler(t *testing.T) {
func TestIsUpdatedSecret(t *testing.T) { func TestIsUpdatedSecret(t *testing.T) {
secretName := "test-secret" secretName := "test-secret"
updatedSecrets := map[string]bool{ updatedSecrets := map[string]*corev1.Secret{
"some_secret": true, "some_secret": &corev1.Secret{},
} }
assert.False(t, isUpdatedSecret(secretName, updatedSecrets)) assert.False(t, isUpdatedSecret(secretName, updatedSecrets))
updatedSecrets[secretName] = true updatedSecrets[secretName] = &corev1.Secret{}
assert.True(t, isUpdatedSecret(secretName, updatedSecrets)) assert.True(t, isUpdatedSecret(secretName, updatedSecrets))
} }

View File

@@ -2,7 +2,7 @@ package onepassword
import corev1 "k8s.io/api/core/v1" import corev1 "k8s.io/api/core/v1"
func AreVolumesUsingSecrets(volumes []corev1.Volume, secrets map[string]bool) bool { func AreVolumesUsingSecrets(volumes []corev1.Volume, secrets map[string]*corev1.Secret) bool {
for i := 0; i < len(volumes); i++ { for i := 0; i < len(volumes); i++ {
if secret := volumes[i].Secret; secret != nil { if secret := volumes[i].Secret; secret != nil {
secretName := secret.SecretName secretName := secret.SecretName
@@ -14,3 +14,16 @@ func AreVolumesUsingSecrets(volumes []corev1.Volume, secrets map[string]bool) bo
} }
return false return false
} }
func AppendUpdatedVolumeSecrets(volumes []corev1.Volume, secrets map[string]*corev1.Secret, updatedDeploymentSecrets map[string]*corev1.Secret) map[string]*corev1.Secret {
for i := 0; i < len(volumes); i++ {
if secret := volumes[i].Secret; secret != nil {
secretName := secret.SecretName
secret, ok := secrets[secretName]
if ok {
updatedDeploymentSecrets[secret.Name] = secret
}
}
}
return updatedDeploymentSecrets
}

View File

@@ -2,12 +2,14 @@ package onepassword
import ( import (
"testing" "testing"
corev1 "k8s.io/api/core/v1"
) )
func TestAreVolmesUsingSecrets(t *testing.T) { func TestAreVolmesUsingSecrets(t *testing.T) {
secretNamesToSearch := map[string]bool{ secretNamesToSearch := map[string]*corev1.Secret{
"onepassword-database-secret": true, "onepassword-database-secret": &corev1.Secret{},
"onepassword-api-key": true, "onepassword-api-key": &corev1.Secret{},
} }
volumeSecretNames := []string{ volumeSecretNames := []string{
@@ -24,9 +26,9 @@ func TestAreVolmesUsingSecrets(t *testing.T) {
} }
func TestAreVolumesNotUsingSecrets(t *testing.T) { func TestAreVolumesNotUsingSecrets(t *testing.T) {
secretNamesToSearch := map[string]bool{ secretNamesToSearch := map[string]*corev1.Secret{
"onepassword-database-secret": true, "onepassword-database-secret": &corev1.Secret{},
"onepassword-api-key": true, "onepassword-api-key": &corev1.Secret{},
} }
volumeSecretNames := []string{ volumeSecretNames := []string{

View File

@@ -1,5 +1,10 @@
package utils package utils
import (
"strconv"
"strings"
)
func ContainsString(slice []string, s string) bool { func ContainsString(slice []string, s string) bool {
for _, item := range slice { for _, item := range slice {
if item == s { if item == s {
@@ -18,3 +23,11 @@ func RemoveString(slice []string, s string) (result []string) {
} }
return return
} }
func StringToBool(str string) (bool, error) {
restartDeploymentBool, err := strconv.ParseBool(strings.ToLower(str))
if err != nil {
return false, err
}
return restartDeploymentBool, nil
}