Skip to content
Open
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
18 changes: 9 additions & 9 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ var (
)

const (
envPollingIntervalVariable = "POLLING_INTERVAL"
manageConnect = "MANAGE_CONNECT"
restartDeploymentsEnvVariable = "AUTO_RESTART"
defaultPollingInterval = 600
envPollingIntervalVariable = "POLLING_INTERVAL"
manageConnect = "MANAGE_CONNECT"
restartWorkloadsEnvVariable = "AUTO_RESTART"
defaultPollingInterval = 600

annotationRegExpString = "^operator.1password.io\\/[a-zA-Z\\.]+"
)
Expand Down Expand Up @@ -202,7 +202,7 @@ func main() {
}

// Setup update secrets task
updatedSecretsPoller := op.NewManager(mgr.GetClient(), opConnectClient, shouldAutoRestartDeployments())
updatedSecretsPoller := op.NewManager(mgr.GetClient(), opConnectClient, shouldAutoRestartWorkloads())
done := make(chan bool)
ticker := time.NewTicker(getPollingIntervalForUpdatingSecrets())
go func() {
Expand Down Expand Up @@ -263,15 +263,15 @@ func shouldManageConnect() bool {
return false
}

func shouldAutoRestartDeployments() bool {
shouldAutoRestartDeployments, found := os.LookupEnv(restartDeploymentsEnvVariable)
func shouldAutoRestartWorkloads() bool {
value, found := os.LookupEnv(restartWorkloadsEnvVariable)
if found {
shouldAutoRestartDeploymentsBool, err := strconv.ParseBool(strings.ToLower(shouldAutoRestartDeployments))
shouldAutoRestartWorkloadsBool, err := strconv.ParseBool(strings.ToLower(value))
if err != nil {
setupLog.Error(err, "")
os.Exit(1)
}
return shouldAutoRestartDeploymentsBool
return shouldAutoRestartWorkloadsBool
}
return false
}
Expand Down
2 changes: 1 addition & 1 deletion internal/controller/deployment_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,5 +213,5 @@ func (r *DeploymentReconciler) handleApplyingDeployment(deployment *appsv1.Deplo
UID: deployment.GetUID(),
}

return kubeSecrets.CreateKubernetesSecretFromItem(r.Client, secretName, namespace, item, annotations[op.RestartDeploymentsAnnotation], secretLabels, secretType, ownerRef)
return kubeSecrets.CreateKubernetesSecretFromItem(r.Client, secretName, namespace, item, annotations[op.AutoRestartWorkloadAnnotation], secretLabels, secretType, ownerRef)
}
2 changes: 1 addition & 1 deletion internal/controller/onepassworditem_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ func (r *OnePasswordItemReconciler) handleOnePasswordItem(resource *onepasswordv
secretName := resource.GetName()
labels := resource.Labels
secretType := resource.Type
autoRestart := resource.Annotations[op.RestartDeploymentsAnnotation]
autoRestart := resource.Annotations[op.AutoRestartWorkloadAnnotation]

item, err := op.GetOnePasswordItemByPath(r.OpConnectClient, resource.Spec.ItemPath)
if err != nil {
Expand Down
14 changes: 7 additions & 7 deletions pkg/onepassword/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import (
)

const (
OnepasswordPrefix = "operator.1password.io"
ItemPathAnnotation = OnepasswordPrefix + "/item-path"
NameAnnotation = OnepasswordPrefix + "/item-name"
VersionAnnotation = OnepasswordPrefix + "/item-version"
RestartAnnotation = OnepasswordPrefix + "/last-restarted"
RestartDeploymentsAnnotation = OnepasswordPrefix + "/auto-restart"
OnepasswordPrefix = "operator.1password.io"
ItemPathAnnotation = OnepasswordPrefix + "/item-path"
NameAnnotation = OnepasswordPrefix + "/item-name"
VersionAnnotation = OnepasswordPrefix + "/item-version"
RestartAnnotation = OnepasswordPrefix + "/last-restarted"
AutoRestartWorkloadAnnotation = OnepasswordPrefix + "/auto-restart"
)

func GetAnnotationsForDeployment(deployment *appsv1.Deployment, regex *regexp.Regexp) (map[string]string, bool) {
Expand All @@ -36,7 +36,7 @@ func GetAnnotationsForDeployment(deployment *appsv1.Deployment, regex *regexp.Re
func FilterAnnotations(annotations map[string]string, regex *regexp.Regexp) map[string]string {
filteredAnnotations := make(map[string]string)
for key, value := range annotations {
if regex.MatchString(key) && key != RestartAnnotation && key != RestartDeploymentsAnnotation {
if regex.MatchString(key) && key != RestartAnnotation && key != AutoRestartWorkloadAnnotation {
filteredAnnotations[key] = value
}
}
Expand Down
13 changes: 0 additions & 13 deletions pkg/onepassword/deployments.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,3 @@ func IsDeploymentUsingSecrets(deployment *appsv1.Deployment, secrets map[string]
containers = append(containers, deployment.Spec.Template.Spec.InitContainers...)
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
}
180 changes: 122 additions & 58 deletions pkg/onepassword/secret_update_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package onepassword
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/api/meta"
"time"

onepasswordv1 "github.com/1Password/onepassword-operator/api/v1"
Expand All @@ -23,18 +24,18 @@ const lockTag = "operator.1password.io:ignore-secret"

var log = logf.Log.WithName("update_op_kubernetes_secrets_task")

func NewManager(kubernetesClient client.Client, opConnectClient connect.Client, shouldAutoRestartDeploymentsGlobal bool) *SecretUpdateHandler {
func NewManager(kubernetesClient client.Client, opConnectClient connect.Client, autoRestartWorkloadsGlobally bool) *SecretUpdateHandler {
return &SecretUpdateHandler{
client: kubernetesClient,
opConnectClient: opConnectClient,
shouldAutoRestartDeploymentsGlobal: shouldAutoRestartDeploymentsGlobal,
client: kubernetesClient,
opConnectClient: opConnectClient,
autoRestartWorkloadsGlobally: autoRestartWorkloadsGlobally,
}
}

type SecretUpdateHandler struct {
client client.Client
opConnectClient connect.Client
shouldAutoRestartDeploymentsGlobal bool
client client.Client
opConnectClient connect.Client
autoRestartWorkloadsGlobally bool
}

func (h *SecretUpdateHandler) UpdateKubernetesSecretsTask() error {
Expand All @@ -43,61 +44,94 @@ func (h *SecretUpdateHandler) UpdateKubernetesSecretsTask() error {
return err
}

return h.restartDeploymentsWithUpdatedSecrets(updatedKubernetesSecrets)
return h.restartWorkloadsWithUpdatedSecrets(updatedKubernetesSecrets)
}

func (h *SecretUpdateHandler) restartDeploymentsWithUpdatedSecrets(updatedSecretsByNamespace map[string]map[string]*corev1.Secret) error {
func (h *SecretUpdateHandler) restartWorkloadsWithUpdatedSecrets(updatedSecretsByNamespace map[string]map[string]*corev1.Secret) error {
// No secrets to update. Exit
if len(updatedSecretsByNamespace) == 0 || updatedSecretsByNamespace == nil {
return nil
}

deployments := &appsv1.DeploymentList{}
err := h.client.List(context.Background(), deployments)
if err != nil {
log.Error(err, "Failed to list kubernetes deployments")
return err
}

if len(deployments.Items) == 0 {
return nil
workloadTypes := []client.ObjectList{
&appsv1.DeploymentList{},
}

setForAutoRestartByNamespaceMap, err := h.getIsSetForAutoRestartByNamespaceMap()
if err != nil {
return err
}

for i := 0; i < len(deployments.Items); i++ {
deployment := &deployments.Items[i]
updatedSecrets := updatedSecretsByNamespace[deployment.Namespace]
for _, list := range workloadTypes {
if err := h.client.List(context.Background(), list); err != nil {
log.Error(err, "Failed to list workloads", "type", fmt.Sprintf("%T", list))
return err
}

updatedDeploymentSecrets := GetUpdatedSecretsForDeployment(deployment, updatedSecrets)
if len(updatedDeploymentSecrets) == 0 {
continue
items, err := meta.ExtractList(list)
if err != nil {
log.Error(err, "Failed to extract list items", "type", fmt.Sprintf("%T", list))
return err
}
for _, secret := range updatedDeploymentSecrets {
if isSecretSetForAutoRestart(secret, deployment, setForAutoRestartByNamespaceMap) {
h.restartDeployment(deployment)

for _, obj := range items {
workload, ok := obj.(client.Object)
if !ok {
log.Error(fmt.Errorf("unexpected type %T", obj), "Skipping non-client.Object")
continue
}
}

log.V(logs.DebugLevel).Info(fmt.Sprintf("Deployment %q at namespace %q is up to date", deployment.GetName(), deployment.Namespace))
podTemplate, err := GetPodTemplate(workload)
if err != nil {
log.Error(err, "Failed to get pod template", "workload", workload.GetName())
continue
}

updatedSecrets := updatedSecretsByNamespace[workload.GetNamespace()]
if len(updatedSecrets) == 0 {
continue
}

matchedSecrets := GetUpdatedSecretsForPodTemplate(workload.GetAnnotations(), podTemplate, updatedSecrets)
if len(matchedSecrets) == 0 {
continue
}

for _, secret := range matchedSecrets {
if isSecretSetForAutoRestart(secret, workload, setForAutoRestartByNamespaceMap) {
h.restartWorkload(workload)
break
}
}

log.V(logs.DebugLevel).Info(fmt.Sprintf("%q %q at namespace %q is up to date", workload.GetObjectKind().GroupVersionKind().Kind, workload.GetName(), workload.GetNamespace()))
}
}

return nil
}

func (h *SecretUpdateHandler) restartDeployment(deployment *appsv1.Deployment) {
log.Info(fmt.Sprintf("Deployment %q at namespace %q references an updated secret. Restarting", deployment.GetName(), deployment.Namespace))
if deployment.Spec.Template.Annotations == nil {
deployment.Spec.Template.Annotations = map[string]string{}
func (h *SecretUpdateHandler) restartWorkload(workload client.Object) {
var podTemplate *corev1.PodTemplateSpec

switch obj := workload.(type) {
case *appsv1.Deployment:
podTemplate = &obj.Spec.Template
default:
log.Info("Unsupported workload type for restart", "type", fmt.Sprintf("%T", obj))
return
}

log.Info(fmt.Sprintf("%T %q in namespace %q references an updated secret. Restarting", workload, workload.GetName(), workload.GetNamespace()))

if podTemplate.Annotations == nil {
podTemplate.Annotations = map[string]string{}
}
deployment.Spec.Template.Annotations[RestartAnnotation] = time.Now().String()
err := h.client.Update(context.Background(), deployment)
podTemplate.Annotations[RestartAnnotation] = time.Now().Format(time.RFC3339)

err := h.client.Update(context.Background(), workload)
if err != nil {
log.Error(err, "Problem restarting deployment")
log.Error(err, "Problem restarting workload", "name", workload.GetName())
}
}

Expand Down Expand Up @@ -209,47 +243,77 @@ func (h *SecretUpdateHandler) getPathFromOnePasswordItem(secret corev1.Secret) s
return secret.Annotations[ItemPathAnnotation]
}

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)
func isSecretSetForAutoRestart(secret *corev1.Secret, workload client.Object, setForAutoRestartByNamespace map[string]bool) bool {
restartAnnotation := secret.Annotations[AutoRestartWorkloadAnnotation]
if restartAnnotation == "" {
return isWorkloadSetForAutoRestart(workload, setForAutoRestartByNamespace)
}

restartDeploymentBool, err := utils.StringToBool(restartDeployment)
restartBool, err := utils.StringToBool(restartAnnotation)
if err != nil {
log.Error(err, "Error parsing %v annotation on Secret %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, secret.Name)
log.Error(err, "Error parsing %v annotation on Secret %v. Must be true or false. Defaulting to false.", AutoRestartWorkloadAnnotation, secret.Name)
return false
}
return restartDeploymentBool

return restartBool
}

func isDeploymentSetForAutoRestart(deployment *appsv1.Deployment, setForAutoRestartByNamespace map[string]bool) bool {
restartDeployment := deployment.Annotations[RestartDeploymentsAnnotation]
//If annotation for auto restarts for deployment is not set. Check for the annotation on its namepsace
if restartDeployment == "" {
return setForAutoRestartByNamespace[deployment.Namespace]
func isWorkloadSetForAutoRestart(obj client.Object, setForAutoRestartByNamespace map[string]bool) bool {
annotations := obj.GetAnnotations()
namespace := obj.GetNamespace()
name := obj.GetName()

restartAnnotation := annotations[AutoRestartWorkloadAnnotation]
if restartAnnotation == "" {
return setForAutoRestartByNamespace[namespace]
}

restartDeploymentBool, err := utils.StringToBool(restartDeployment)
restartBool, err := utils.StringToBool(restartAnnotation)
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 %T %v. Must be true or false. Defaulting to false.", AutoRestartWorkloadAnnotation, obj, name)
return false
}
return restartDeploymentBool

return restartBool
}

func (h *SecretUpdateHandler) isNamespaceSetToAutoRestart(namespace *corev1.Namespace) bool {
restartDeployment := namespace.Annotations[RestartDeploymentsAnnotation]
restartWorkload := namespace.Annotations[AutoRestartWorkloadAnnotation]
//If annotation for auto restarts for deployment is not set. Check environment variable set on the operator
if restartDeployment == "" {
return h.shouldAutoRestartDeploymentsGlobal
if restartWorkload == "" {
return h.autoRestartWorkloadsGlobally
}

restartDeploymentBool, err := utils.StringToBool(restartDeployment)
restartWorkloadBool, err := utils.StringToBool(restartWorkload)
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.", AutoRestartWorkloadAnnotation, namespace.Name)
return false
}
return restartDeploymentBool
return restartWorkloadBool
}

func GetPodTemplate(obj client.Object) (*corev1.PodTemplateSpec, error) {
switch o := obj.(type) {
case *appsv1.Deployment:
return &o.Spec.Template, nil
default:
return nil, fmt.Errorf("unsupported type %T", obj)
}
}

func GetUpdatedSecretsForPodTemplate(annotations map[string]string, podTemplate *corev1.PodTemplateSpec, secrets map[string]*corev1.Secret) map[string]*corev1.Secret {
if podTemplate == nil {
return nil
}

volumes := podTemplate.Spec.Volumes
containers := append([]corev1.Container{}, podTemplate.Spec.Containers...)
containers = append(containers, podTemplate.Spec.InitContainers...)

updatedSecrets := map[string]*corev1.Secret{}
AppendAnnotationUpdatedSecret(annotations, secrets, updatedSecrets)
AppendUpdatedContainerSecrets(containers, secrets, updatedSecrets)
AppendUpdatedVolumeSecrets(volumes, secrets, updatedSecrets)

return updatedSecrets
}
Loading