mirror of
https://github.com/1Password/onepassword-operator.git
synced 2025-10-22 23:48:05 +00:00
Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
69857c3d47 | ||
![]() |
ad276cb296 | ||
![]() |
eab5a4ad92 | ||
![]() |
128b9b2eb3 | ||
![]() |
867e699030 | ||
![]() |
ffab2cfdab | ||
![]() |
00436b4aee | ||
![]() |
0ca3415a47 | ||
![]() |
4aa1f7a669 | ||
![]() |
6c20db47d6 | ||
![]() |
874d5c57f9 | ||
![]() |
123cfa2c86 | ||
![]() |
0796b9c5e2 | ||
![]() |
37a0f4b51e | ||
![]() |
004e0101ff | ||
![]() |
6326a856ae | ||
![]() |
1ddf92c5a0 | ||
![]() |
f5c6fa5860 | ||
![]() |
afa076d321 | ||
![]() |
d4b04c233c | ||
![]() |
ea68cfc2b4 |
35
CHANGELOG.md
35
CHANGELOG.md
@@ -1,18 +1,37 @@
|
|||||||
[//]: # "START/LATEST"
|
[//]: # (START/LATEST)
|
||||||
|
|
||||||
# Latest
|
# Latest
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
* A user-friendly description of a new feature. {issue-number}
|
||||||
|
|
||||||
- A user-friendly description of a new feature. {issue-number}
|
## Fixes
|
||||||
|
* A user-friendly description of a fix. {issue-number}
|
||||||
|
|
||||||
|
## Security
|
||||||
|
* A user-friendly description of a security fix. {issue-number}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[//]: # (START/v1.5.0)
|
||||||
|
# v1.5.0
|
||||||
|
|
||||||
|
## Features
|
||||||
|
* `OnePasswordItem` now contains a `status` which contains the status of creating the kubernetes secret for a OnePasswordItem. {#52}
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
* The operator no longer logs an error about changing the secret type if the secret type is not actually being changed.
|
||||||
|
* Annotations on a deployment are no longer removed when the operator triggers a restart. {#112}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[//]: # "START/v1.4.1"
|
||||||
|
|
||||||
|
# v1.4.1
|
||||||
|
|
||||||
## Fixes
|
## Fixes
|
||||||
|
|
||||||
- A user-friendly description of a fix. {issue-number}
|
- OwnerReferences on secrets are now persisted after an item is updated. {#101}
|
||||||
|
- Annotations from a Deployment or OnePasswordItem are no longer applied to Secrets that are created for it. {#102}
|
||||||
## Security
|
|
||||||
|
|
||||||
- A user-friendly description of a security fix. {issue-number}
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@@ -12,8 +12,6 @@ spec:
|
|||||||
scope: Namespaced
|
scope: Namespaced
|
||||||
versions:
|
versions:
|
||||||
- name: v1
|
- name: v1
|
||||||
served: true
|
|
||||||
storage: true
|
|
||||||
schema:
|
schema:
|
||||||
openAPIV3Schema:
|
openAPIV3Schema:
|
||||||
description: OnePasswordItem is the Schema for the onepassworditems API
|
description: OnePasswordItem is the Schema for the onepassworditems API
|
||||||
@@ -38,8 +36,41 @@ spec:
|
|||||||
type: object
|
type: object
|
||||||
status:
|
status:
|
||||||
description: OnePasswordItemStatus defines the observed state of OnePasswordItem
|
description: OnePasswordItemStatus defines the observed state of OnePasswordItem
|
||||||
|
properties:
|
||||||
|
conditions:
|
||||||
|
description: 'Important: Run "operator-sdk generate k8s" to regenerate
|
||||||
|
code after modifying this file Add custom validation using kubebuilder
|
||||||
|
tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html'
|
||||||
|
items:
|
||||||
|
properties:
|
||||||
|
lastTransitionTime:
|
||||||
|
description: Last time the condition transit from one status
|
||||||
|
to another.
|
||||||
|
format: date-time
|
||||||
|
type: string
|
||||||
|
message:
|
||||||
|
description: Human-readable message indicating details about
|
||||||
|
last transition.
|
||||||
|
type: string
|
||||||
|
status:
|
||||||
|
description: Status of the condition, one of True, False, Unknown.
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
description: Type of job condition, Completed.
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
- type
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
required:
|
||||||
|
- conditions
|
||||||
type: object
|
type: object
|
||||||
type:
|
type:
|
||||||
description: 'Kubernetes secret type. More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types'
|
description: 'Kubernetes secret type. More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types'
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
served: true
|
||||||
|
storage: true
|
||||||
|
subresources:
|
||||||
|
status: {}
|
||||||
|
@@ -11,11 +11,31 @@ type OnePasswordItemSpec struct {
|
|||||||
ItemPath string `json:"itemPath,omitempty"`
|
ItemPath string `json:"itemPath,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OnePasswordItemConditionType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// OnePasswordItemReady means the Kubernetes secret is ready for use.
|
||||||
|
OnePasswordItemReady OnePasswordItemConditionType = "Ready"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OnePasswordItemCondition struct {
|
||||||
|
// Type of job condition, Completed.
|
||||||
|
Type OnePasswordItemConditionType `json:"type"`
|
||||||
|
// Status of the condition, one of True, False, Unknown.
|
||||||
|
Status metav1.ConditionStatus `json:"status"`
|
||||||
|
// Last time the condition transit from one status to another.
|
||||||
|
// +optional
|
||||||
|
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
|
||||||
|
// Human-readable message indicating details about last transition.
|
||||||
|
// +optional
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// OnePasswordItemStatus defines the observed state of OnePasswordItem
|
// OnePasswordItemStatus defines the observed state of OnePasswordItem
|
||||||
type OnePasswordItemStatus struct {
|
type OnePasswordItemStatus struct {
|
||||||
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
|
|
||||||
// Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file
|
// Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file
|
||||||
// Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html
|
// Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html
|
||||||
|
Conditions []OnePasswordItemCondition `json:"conditions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
@@ -26,7 +46,9 @@ type OnePasswordItemStatus struct {
|
|||||||
type OnePasswordItem struct {
|
type OnePasswordItem struct {
|
||||||
metav1.TypeMeta `json:",inline"`
|
metav1.TypeMeta `json:",inline"`
|
||||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||||
Type string `json:"type,omitempty"`
|
|
||||||
|
// Kubernetes secret type. More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
|
||||||
Spec OnePasswordItemSpec `json:"spec,omitempty"`
|
Spec OnePasswordItemSpec `json:"spec,omitempty"`
|
||||||
Status OnePasswordItemStatus `json:"status,omitempty"`
|
Status OnePasswordItemStatus `json:"status,omitempty"`
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build !ignore_autogenerated
|
||||||
// +build !ignore_autogenerated
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
// Code generated by operator-sdk. DO NOT EDIT.
|
// Code generated by operator-sdk. DO NOT EDIT.
|
||||||
@@ -14,7 +15,7 @@ func (in *OnePasswordItem) DeepCopyInto(out *OnePasswordItem) {
|
|||||||
out.TypeMeta = in.TypeMeta
|
out.TypeMeta = in.TypeMeta
|
||||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||||
out.Spec = in.Spec
|
out.Spec = in.Spec
|
||||||
out.Status = in.Status
|
in.Status.DeepCopyInto(&out.Status)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,6 +37,23 @@ func (in *OnePasswordItem) DeepCopyObject() runtime.Object {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *OnePasswordItemCondition) DeepCopyInto(out *OnePasswordItemCondition) {
|
||||||
|
*out = *in
|
||||||
|
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OnePasswordItemCondition.
|
||||||
|
func (in *OnePasswordItemCondition) DeepCopy() *OnePasswordItemCondition {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(OnePasswordItemCondition)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *OnePasswordItemList) DeepCopyInto(out *OnePasswordItemList) {
|
func (in *OnePasswordItemList) DeepCopyInto(out *OnePasswordItemList) {
|
||||||
*out = *in
|
*out = *in
|
||||||
@@ -88,6 +106,13 @@ func (in *OnePasswordItemSpec) DeepCopy() *OnePasswordItemSpec {
|
|||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *OnePasswordItemStatus) DeepCopyInto(out *OnePasswordItemStatus) {
|
func (in *OnePasswordItemStatus) DeepCopyInto(out *OnePasswordItemStatus) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
if in.Conditions != nil {
|
||||||
|
in, out := &in.Conditions, &out.Conditions
|
||||||
|
*out = make([]OnePasswordItemCondition, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -218,5 +218,5 @@ func (r *ReconcileDeployment) HandleApplyingDeployment(deployment *appsv1.Deploy
|
|||||||
UID: deployment.GetUID(),
|
UID: deployment.GetUID(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, namespace, item, annotations[op.RestartDeploymentsAnnotation], secretLabels, secretType, annotations, ownerRef)
|
return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, namespace, item, annotations[op.RestartDeploymentsAnnotation], secretLabels, secretType, ownerRef)
|
||||||
}
|
}
|
||||||
|
@@ -281,7 +281,6 @@ var tests = []testReconcileItem{
|
|||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
op.VersionAnnotation: fmt.Sprint(version),
|
op.VersionAnnotation: fmt.Sprint(version),
|
||||||
op.ItemPathAnnotation: itemPath,
|
op.ItemPathAnnotation: itemPath,
|
||||||
op.NameAnnotation: name,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: expectedSecretData,
|
Data: expectedSecretData,
|
||||||
@@ -294,7 +293,6 @@ var tests = []testReconcileItem{
|
|||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
op.VersionAnnotation: fmt.Sprint(version),
|
op.VersionAnnotation: fmt.Sprint(version),
|
||||||
op.ItemPathAnnotation: itemPath,
|
op.ItemPathAnnotation: itemPath,
|
||||||
op.NameAnnotation: name,
|
|
||||||
},
|
},
|
||||||
Labels: map[string]string(nil),
|
Labels: map[string]string(nil),
|
||||||
},
|
},
|
||||||
@@ -385,7 +383,7 @@ var tests = []testReconcileItem{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReconcileDepoyment(t *testing.T) {
|
func TestReconcileDeployment(t *testing.T) {
|
||||||
for _, testData := range tests {
|
for _, testData := range tests {
|
||||||
t.Run(testData.testName, func(t *testing.T) {
|
t.Run(testData.testName, func(t *testing.T) {
|
||||||
|
|
||||||
|
@@ -96,10 +96,11 @@ func (r *ReconcileOnePasswordItem) Reconcile(request reconcile.Request) (reconci
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handles creation or updating secrets for deployment if needed
|
// Handles creation or updating secrets for deployment if needed
|
||||||
if err := r.HandleOnePasswordItem(onepassworditem, request); err != nil {
|
err := r.HandleOnePasswordItem(onepassworditem, request)
|
||||||
return reconcile.Result{}, err
|
if updateStatusErr := r.updateStatus(onepassworditem, err); updateStatusErr != nil {
|
||||||
|
return reconcile.Result{}, fmt.Errorf("cannot update status: %s", updateStatusErr)
|
||||||
}
|
}
|
||||||
return reconcile.Result{}, nil
|
return reconcile.Result{}, err
|
||||||
}
|
}
|
||||||
// If one password finalizer exists then we must cleanup associated secrets
|
// If one password finalizer exists then we must cleanup associated secrets
|
||||||
if utils.ContainsString(onepassworditem.ObjectMeta.Finalizers, finalizer) {
|
if utils.ContainsString(onepassworditem.ObjectMeta.Finalizers, finalizer) {
|
||||||
@@ -147,9 +148,8 @@ 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()
|
||||||
labels := resource.Labels
|
labels := resource.Labels
|
||||||
annotations := resource.Annotations
|
|
||||||
secretType := resource.Type
|
secretType := resource.Type
|
||||||
autoRestart := annotations[op.RestartDeploymentsAnnotation]
|
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 {
|
||||||
@@ -168,5 +168,36 @@ func (r *ReconcileOnePasswordItem) HandleOnePasswordItem(resource *onepasswordv1
|
|||||||
UID: resource.GetUID(),
|
UID: resource.GetUID(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, resource.Namespace, item, autoRestart, labels, secretType, annotations, ownerRef)
|
return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, resource.Namespace, item, autoRestart, labels, secretType, ownerRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReconcileOnePasswordItem) updateStatus(resource *onepasswordv1.OnePasswordItem, err error) error {
|
||||||
|
existingCondition := findCondition(resource.Status.Conditions, onepasswordv1.OnePasswordItemReady)
|
||||||
|
updatedCondition := existingCondition
|
||||||
|
if err != nil {
|
||||||
|
updatedCondition.Message = err.Error()
|
||||||
|
updatedCondition.Status = metav1.ConditionFalse
|
||||||
|
} else {
|
||||||
|
updatedCondition.Message = ""
|
||||||
|
updatedCondition.Status = metav1.ConditionTrue
|
||||||
|
}
|
||||||
|
|
||||||
|
if existingCondition.Status != updatedCondition.Status {
|
||||||
|
updatedCondition.LastTransitionTime = metav1.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
resource.Status.Conditions = []onepasswordv1.OnePasswordItemCondition{updatedCondition}
|
||||||
|
return r.kubeClient.Status().Update(context.Background(), resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
func findCondition(conditions []onepasswordv1.OnePasswordItemCondition, t onepasswordv1.OnePasswordItemConditionType) onepasswordv1.OnePasswordItemCondition {
|
||||||
|
for _, c := range conditions {
|
||||||
|
if c.Type == t {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return onepasswordv1.OnePasswordItemCondition{
|
||||||
|
Type: t,
|
||||||
|
Status: metav1.ConditionUnknown,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,7 +27,6 @@ import (
|
|||||||
const OnepasswordPrefix = "operator.1password.io"
|
const OnepasswordPrefix = "operator.1password.io"
|
||||||
const NameAnnotation = OnepasswordPrefix + "/item-name"
|
const NameAnnotation = OnepasswordPrefix + "/item-name"
|
||||||
const VersionAnnotation = OnepasswordPrefix + "/item-version"
|
const VersionAnnotation = OnepasswordPrefix + "/item-version"
|
||||||
const restartAnnotation = OnepasswordPrefix + "/last-restarted"
|
|
||||||
const ItemPathAnnotation = OnepasswordPrefix + "/item-path"
|
const ItemPathAnnotation = OnepasswordPrefix + "/item-path"
|
||||||
const RestartDeploymentsAnnotation = OnepasswordPrefix + "/auto-restart"
|
const RestartDeploymentsAnnotation = OnepasswordPrefix + "/auto-restart"
|
||||||
|
|
||||||
@@ -35,23 +34,17 @@ var ErrCannotUpdateSecretType = errs.New("Cannot change secret type. Secret type
|
|||||||
|
|
||||||
var log = logf.Log
|
var log = logf.Log
|
||||||
|
|
||||||
func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretName, namespace string, item *onepassword.Item, autoRestart string, labels map[string]string, secretType string, secretAnnotations map[string]string, ownerRef *metav1.OwnerReference) error {
|
func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretName, namespace string, item *onepassword.Item, autoRestart string, labels map[string]string, secretType string, ownerRef *metav1.OwnerReference) error {
|
||||||
|
|
||||||
itemVersion := fmt.Sprint(item.Version)
|
itemVersion := fmt.Sprint(item.Version)
|
||||||
|
secretAnnotations := map[string]string{
|
||||||
// If secretAnnotations is nil we create an empty map so we can later assign values for the OP Annotations in the map
|
VersionAnnotation: itemVersion,
|
||||||
if secretAnnotations == nil {
|
ItemPathAnnotation: fmt.Sprintf("vaults/%v/items/%v", item.Vault.ID, item.ID),
|
||||||
secretAnnotations = map[string]string{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
secretAnnotations[VersionAnnotation] = itemVersion
|
|
||||||
secretAnnotations[ItemPathAnnotation] = fmt.Sprintf("vaults/%v/items/%v", item.Vault.ID, item.ID)
|
|
||||||
|
|
||||||
if autoRestart != "" {
|
if autoRestart != "" {
|
||||||
_, err := utils.StringToBool(autoRestart)
|
_, err := utils.StringToBool(autoRestart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err, "Error parsing %v annotation on Secret %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, secretName)
|
return fmt.Errorf("Error parsing %v annotation on Secret %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, secretName)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
secretAnnotations[RestartDeploymentsAnnotation] = autoRestart
|
secretAnnotations[RestartDeploymentsAnnotation] = autoRestart
|
||||||
}
|
}
|
||||||
@@ -68,19 +61,31 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
currentAnnotations := currentSecret.Annotations
|
// Check if the secret types are being changed on the update.
|
||||||
currentLabels := currentSecret.Labels
|
// Avoid Opaque and "" are treated as different on check.
|
||||||
|
wantSecretType := secretType
|
||||||
|
if wantSecretType == "" {
|
||||||
|
wantSecretType = string(corev1.SecretTypeOpaque)
|
||||||
|
}
|
||||||
currentSecretType := string(currentSecret.Type)
|
currentSecretType := string(currentSecret.Type)
|
||||||
if !reflect.DeepEqual(currentSecretType, secretType) {
|
if currentSecretType == "" {
|
||||||
|
currentSecretType = string(corev1.SecretTypeOpaque)
|
||||||
|
}
|
||||||
|
if currentSecretType != wantSecretType {
|
||||||
return ErrCannotUpdateSecretType
|
return ErrCannotUpdateSecretType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentAnnotations := currentSecret.Annotations
|
||||||
|
currentLabels := currentSecret.Labels
|
||||||
if !reflect.DeepEqual(currentAnnotations, secretAnnotations) || !reflect.DeepEqual(currentLabels, labels) {
|
if !reflect.DeepEqual(currentAnnotations, secretAnnotations) || !reflect.DeepEqual(currentLabels, labels) {
|
||||||
log.Info(fmt.Sprintf("Updating Secret %v at namespace '%v'", secret.Name, secret.Namespace))
|
log.Info(fmt.Sprintf("Updating Secret %v at namespace '%v'", secret.Name, secret.Namespace))
|
||||||
currentSecret.ObjectMeta.Annotations = secretAnnotations
|
currentSecret.ObjectMeta.Annotations = secretAnnotations
|
||||||
currentSecret.ObjectMeta.Labels = labels
|
currentSecret.ObjectMeta.Labels = labels
|
||||||
currentSecret.Data = secret.Data
|
currentSecret.Data = secret.Data
|
||||||
return kubeClient.Update(context.Background(), currentSecret)
|
if err := kubeClient.Update(context.Background(), currentSecret); err != nil {
|
||||||
|
return fmt.Errorf("Kubernetes secret update failed: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info(fmt.Sprintf("Secret with name %v and version %v already exists", secret.Name, secret.Annotations[VersionAnnotation]))
|
log.Info(fmt.Sprintf("Secret with name %v and version %v already exists", secret.Name, secret.Annotations[VersionAnnotation]))
|
||||||
|
@@ -33,12 +33,9 @@ func TestCreateKubernetesSecretFromOnePasswordItem(t *testing.T) {
|
|||||||
|
|
||||||
kubeClient := fake.NewFakeClient()
|
kubeClient := fake.NewFakeClient()
|
||||||
secretLabels := map[string]string{}
|
secretLabels := map[string]string{}
|
||||||
secretAnnotations := map[string]string{
|
|
||||||
"testAnnotation": "exists",
|
|
||||||
}
|
|
||||||
secretType := ""
|
secretType := ""
|
||||||
|
|
||||||
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, secretAnnotations, nil)
|
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected error: %v", err)
|
t.Errorf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -50,10 +47,6 @@ func TestCreateKubernetesSecretFromOnePasswordItem(t *testing.T) {
|
|||||||
}
|
}
|
||||||
compareFields(item.Fields, createdSecret.Data, t)
|
compareFields(item.Fields, createdSecret.Data, t)
|
||||||
compareAnnotationsToItem(createdSecret.Annotations, item, t)
|
compareAnnotationsToItem(createdSecret.Annotations, item, t)
|
||||||
|
|
||||||
if createdSecret.Annotations["testAnnotation"] != "exists" {
|
|
||||||
t.Errorf("Expected testAnnotation to be merged with existing annotations, but wasn't.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKubernetesSecretFromOnePasswordItemOwnerReferences(t *testing.T) {
|
func TestKubernetesSecretFromOnePasswordItemOwnerReferences(t *testing.T) {
|
||||||
@@ -68,9 +61,6 @@ func TestKubernetesSecretFromOnePasswordItemOwnerReferences(t *testing.T) {
|
|||||||
|
|
||||||
kubeClient := fake.NewFakeClient()
|
kubeClient := fake.NewFakeClient()
|
||||||
secretLabels := map[string]string{}
|
secretLabels := map[string]string{}
|
||||||
secretAnnotations := map[string]string{
|
|
||||||
"testAnnotation": "exists",
|
|
||||||
}
|
|
||||||
secretType := ""
|
secretType := ""
|
||||||
|
|
||||||
ownerRef := &metav1.OwnerReference{
|
ownerRef := &metav1.OwnerReference{
|
||||||
@@ -79,7 +69,7 @@ func TestKubernetesSecretFromOnePasswordItemOwnerReferences(t *testing.T) {
|
|||||||
Name: "test-deployment",
|
Name: "test-deployment",
|
||||||
UID: types.UID("test-uid"),
|
UID: types.UID("test-uid"),
|
||||||
}
|
}
|
||||||
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, secretAnnotations, ownerRef)
|
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, ownerRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected error: %v", err)
|
t.Errorf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -116,10 +106,9 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) {
|
|||||||
|
|
||||||
kubeClient := fake.NewFakeClient()
|
kubeClient := fake.NewFakeClient()
|
||||||
secretLabels := map[string]string{}
|
secretLabels := map[string]string{}
|
||||||
secretAnnotations := map[string]string{}
|
|
||||||
secretType := ""
|
secretType := ""
|
||||||
|
|
||||||
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, secretAnnotations, nil)
|
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected error: %v", err)
|
t.Errorf("Unexpected error: %v", err)
|
||||||
@@ -131,7 +120,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, restartDeploymentAnnotation, secretLabels, secretType, secretAnnotations, nil)
|
err = CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &newItem, restartDeploymentAnnotation, secretLabels, secretType, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected error: %v", err)
|
t.Errorf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -232,12 +221,9 @@ func TestCreateKubernetesTLSSecretFromOnePasswordItem(t *testing.T) {
|
|||||||
|
|
||||||
kubeClient := fake.NewFakeClient()
|
kubeClient := fake.NewFakeClient()
|
||||||
secretLabels := map[string]string{}
|
secretLabels := map[string]string{}
|
||||||
secretAnnotations := map[string]string{
|
|
||||||
"testAnnotation": "exists",
|
|
||||||
}
|
|
||||||
secretType := "kubernetes.io/tls"
|
secretType := "kubernetes.io/tls"
|
||||||
|
|
||||||
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, secretAnnotations, nil)
|
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected error: %v", err)
|
t.Errorf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
@@ -91,9 +91,10 @@ func (h *SecretUpdateHandler) restartDeploymentsWithUpdatedSecrets(updatedSecret
|
|||||||
|
|
||||||
func (h *SecretUpdateHandler) restartDeployment(deployment *appsv1.Deployment) {
|
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))
|
log.Info(fmt.Sprintf("Deployment %q at namespace %q references an updated secret. Restarting", deployment.GetName(), deployment.Namespace))
|
||||||
deployment.Spec.Template.Annotations = map[string]string{
|
if deployment.Spec.Template.Annotations == nil {
|
||||||
RestartAnnotation: time.Now().String(),
|
deployment.Spec.Template.Annotations = map[string]string{}
|
||||||
}
|
}
|
||||||
|
deployment.Spec.Template.Annotations[RestartAnnotation] = time.Now().String()
|
||||||
err := h.client.Update(context.Background(), deployment)
|
err := h.client.Update(context.Background(), deployment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err, "Problem restarting deployment")
|
log.Error(err, "Problem restarting deployment")
|
||||||
@@ -134,15 +135,21 @@ func (h *SecretUpdateHandler) updateKubernetesSecrets() (map[string]map[string]*
|
|||||||
log.Info(fmt.Sprintf("Secret '%v' has been updated in 1Password but is set to be ignored. Updates to an ignored secret will not trigger an update to a kubernetes secret or a rolling restart.", secret.GetName()))
|
log.Info(fmt.Sprintf("Secret '%v' has been updated in 1Password but is set to be ignored. Updates to an ignored secret will not trigger an update to a kubernetes secret or a rolling restart.", secret.GetName()))
|
||||||
secret.Annotations[VersionAnnotation] = itemVersion
|
secret.Annotations[VersionAnnotation] = itemVersion
|
||||||
secret.Annotations[ItemPathAnnotation] = itemPathString
|
secret.Annotations[ItemPathAnnotation] = itemPathString
|
||||||
h.client.Update(context.Background(), &secret)
|
if err := h.client.Update(context.Background(), &secret); err != nil {
|
||||||
|
log.Error(err, "failed to update secret %s annotations to version %d: %s", secret.Name, itemVersion, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.Info(fmt.Sprintf("Updating kubernetes secret '%v'", secret.GetName()))
|
log.Info(fmt.Sprintf("Updating kubernetes secret '%v'", secret.GetName()))
|
||||||
secret.Annotations[VersionAnnotation] = itemVersion
|
secret.Annotations[VersionAnnotation] = itemVersion
|
||||||
secret.Annotations[ItemPathAnnotation] = itemPathString
|
secret.Annotations[ItemPathAnnotation] = itemPathString
|
||||||
updatedSecret := kubeSecrets.BuildKubernetesSecretFromOnePasswordItem(secret.Name, secret.Namespace, secret.Annotations, secret.Labels, string(secret.Type), *item, nil)
|
secret.Data = kubeSecrets.BuildKubernetesSecretData(item.Fields, item.Files)
|
||||||
log.Info(fmt.Sprintf("New secret path: %v and version: %v", updatedSecret.Annotations[ItemPathAnnotation], updatedSecret.Annotations[VersionAnnotation]))
|
log.Info(fmt.Sprintf("New secret path: %v and version: %v", secret.Annotations[ItemPathAnnotation], secret.Annotations[VersionAnnotation]))
|
||||||
h.client.Update(context.Background(), updatedSecret)
|
if err := h.client.Update(context.Background(), &secret); err != nil {
|
||||||
|
log.Error(err, "failed to update secret %s to version %d: %s", secret.Name, itemVersion, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
if updatedSecrets[secret.Namespace] == nil {
|
if updatedSecrets[secret.Namespace] == nil {
|
||||||
updatedSecrets[secret.Namespace] = make(map[string]*corev1.Secret)
|
updatedSecrets[secret.Namespace] = make(map[string]*corev1.Secret)
|
||||||
}
|
}
|
||||||
|
@@ -122,6 +122,9 @@ var tests = []testUpdateSecretTask{
|
|||||||
},
|
},
|
||||||
Spec: appsv1.DeploymentSpec{
|
Spec: appsv1.DeploymentSpec{
|
||||||
Template: corev1.PodTemplateSpec{
|
Template: corev1.PodTemplateSpec{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: map[string]string{"external-annotation": "some-value"},
|
||||||
|
},
|
||||||
Spec: corev1.PodSpec{
|
Spec: corev1.PodSpec{
|
||||||
Containers: []corev1.Container{
|
Containers: []corev1.Container{
|
||||||
{
|
{
|
||||||
@@ -235,6 +238,9 @@ var tests = []testUpdateSecretTask{
|
|||||||
},
|
},
|
||||||
Spec: appsv1.DeploymentSpec{
|
Spec: appsv1.DeploymentSpec{
|
||||||
Template: corev1.PodTemplateSpec{
|
Template: corev1.PodTemplateSpec{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: map[string]string{"external-annotation": "some-value"},
|
||||||
|
},
|
||||||
Spec: corev1.PodSpec{
|
Spec: corev1.PodSpec{
|
||||||
Volumes: []corev1.Volume{
|
Volumes: []corev1.Volume{
|
||||||
{
|
{
|
||||||
@@ -342,6 +348,9 @@ var tests = []testUpdateSecretTask{
|
|||||||
},
|
},
|
||||||
Spec: appsv1.DeploymentSpec{
|
Spec: appsv1.DeploymentSpec{
|
||||||
Template: corev1.PodTemplateSpec{
|
Template: corev1.PodTemplateSpec{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: map[string]string{"external-annotation": "some-value"},
|
||||||
|
},
|
||||||
Spec: corev1.PodSpec{
|
Spec: corev1.PodSpec{
|
||||||
Containers: []corev1.Container{
|
Containers: []corev1.Container{
|
||||||
{
|
{
|
||||||
@@ -411,6 +420,9 @@ var tests = []testUpdateSecretTask{
|
|||||||
},
|
},
|
||||||
Spec: appsv1.DeploymentSpec{
|
Spec: appsv1.DeploymentSpec{
|
||||||
Template: corev1.PodTemplateSpec{
|
Template: corev1.PodTemplateSpec{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: map[string]string{"external-annotation": "some-value"},
|
||||||
|
},
|
||||||
Spec: corev1.PodSpec{
|
Spec: corev1.PodSpec{
|
||||||
Containers: []corev1.Container{
|
Containers: []corev1.Container{
|
||||||
{
|
{
|
||||||
@@ -482,6 +494,9 @@ var tests = []testUpdateSecretTask{
|
|||||||
},
|
},
|
||||||
Spec: appsv1.DeploymentSpec{
|
Spec: appsv1.DeploymentSpec{
|
||||||
Template: corev1.PodTemplateSpec{
|
Template: corev1.PodTemplateSpec{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: map[string]string{"external-annotation": "some-value"},
|
||||||
|
},
|
||||||
Spec: corev1.PodSpec{
|
Spec: corev1.PodSpec{
|
||||||
Containers: []corev1.Container{
|
Containers: []corev1.Container{
|
||||||
{
|
{
|
||||||
@@ -553,6 +568,9 @@ var tests = []testUpdateSecretTask{
|
|||||||
},
|
},
|
||||||
Spec: appsv1.DeploymentSpec{
|
Spec: appsv1.DeploymentSpec{
|
||||||
Template: corev1.PodTemplateSpec{
|
Template: corev1.PodTemplateSpec{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: map[string]string{"external-annotation": "some-value"},
|
||||||
|
},
|
||||||
Spec: corev1.PodSpec{
|
Spec: corev1.PodSpec{
|
||||||
Containers: []corev1.Container{
|
Containers: []corev1.Container{
|
||||||
{
|
{
|
||||||
@@ -630,6 +648,9 @@ var tests = []testUpdateSecretTask{
|
|||||||
},
|
},
|
||||||
Spec: appsv1.DeploymentSpec{
|
Spec: appsv1.DeploymentSpec{
|
||||||
Template: corev1.PodTemplateSpec{
|
Template: corev1.PodTemplateSpec{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: map[string]string{"external-annotation": "some-value"},
|
||||||
|
},
|
||||||
Spec: corev1.PodSpec{
|
Spec: corev1.PodSpec{
|
||||||
Containers: []corev1.Container{
|
Containers: []corev1.Container{
|
||||||
{
|
{
|
||||||
@@ -703,6 +724,9 @@ var tests = []testUpdateSecretTask{
|
|||||||
},
|
},
|
||||||
Spec: appsv1.DeploymentSpec{
|
Spec: appsv1.DeploymentSpec{
|
||||||
Template: corev1.PodTemplateSpec{
|
Template: corev1.PodTemplateSpec{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: map[string]string{"external-annotation": "some-value"},
|
||||||
|
},
|
||||||
Spec: corev1.PodSpec{
|
Spec: corev1.PodSpec{
|
||||||
Containers: []corev1.Container{
|
Containers: []corev1.Container{
|
||||||
{
|
{
|
||||||
@@ -829,6 +853,16 @@ func TestUpdateSecretHandler(t *testing.T) {
|
|||||||
} else {
|
} else {
|
||||||
assert.False(t, testData.expectedRestart, "Deployment was restarted but should not have been.")
|
assert.False(t, testData.expectedRestart, "Deployment was restarted but should not have been.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oldPodTemplateAnnotations := testData.existingDeployment.Spec.Template.ObjectMeta.Annotations
|
||||||
|
newPodTemplateAnnotations := deployment.Spec.Template.Annotations
|
||||||
|
for name, expected := range oldPodTemplateAnnotations {
|
||||||
|
actual, ok := newPodTemplateAnnotations[name]
|
||||||
|
if assert.Truef(t, ok, "Annotation %s was present in original pod template but was dropped after update", name) {
|
||||||
|
assert.Equalf(t, expected, actual, "Annotation value for %s original pod template has changed", name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user