mirror of
				https://github.com/1Password/onepassword-operator.git
				synced 2025-10-30 19:29:40 +00:00 
			
		
		
		
	Compare commits
	
		
			33 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 | ||
|   | 58b4ff8ecf | ||
|   | d93fecdc76 | ||
|   | 486465247d | ||
|   | 79868ae374 | ||
|   | 6286f7e306 | ||
|   | 0b5efc8690 | ||
|   | a760e524ea | ||
|   | 19f774bb2d | ||
|   | 32643651d9 | ||
|   | ba8d3fa698 | ||
|   | c57aa22a9c | ||
|   | 48944b0d56 | 
							
								
								
									
										115
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										115
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -12,85 +12,136 @@ | ||||
|  | ||||
| --- | ||||
|  | ||||
| [//]: # (START/v1.3.0) | ||||
| [//]: # (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 | ||||
|  | ||||
| - 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} | ||||
|  | ||||
| --- | ||||
|  | ||||
| [//]: # "START/v1.4.0" | ||||
|  | ||||
| # v1.4.0 | ||||
|  | ||||
| ## Features | ||||
|  | ||||
| - The operator now declares the an OwnerReference for the secrets it manages. This should stop secrets from getting pruned by tools like Argo CD. {#51,#84,#96} | ||||
|  | ||||
| --- | ||||
|  | ||||
| [//]: # "START/v1.3.0" | ||||
|  | ||||
| # v1.3.0 | ||||
|  | ||||
| ## Features | ||||
|   * Added support for loading secrets from files stored in 1Password. {#47} | ||||
|  | ||||
| - Added support for loading secrets from files stored in 1Password. {#47} | ||||
|  | ||||
| --- | ||||
|  | ||||
| [//]: # (START/v1.2.0) | ||||
| [//]: # "START/v1.2.0" | ||||
|  | ||||
| # v1.2.0 | ||||
|  | ||||
| ## Features | ||||
|   * Support secrets provisioned through FromEnv. {#74} | ||||
|   * Support configuration of Kubernetes Secret type. {#87} | ||||
|   * Improved logging. (#72) | ||||
|  | ||||
| - Support secrets provisioned through FromEnv. {#74} | ||||
| - Support configuration of Kubernetes Secret type. {#87} | ||||
| - Improved logging. (#72) | ||||
|  | ||||
| --- | ||||
|  | ||||
| [//]: # (START/v1.1.0) | ||||
| [//]: # "START/v1.1.0" | ||||
|  | ||||
| # v1.1.0 | ||||
|  | ||||
| ## Fixes | ||||
|  * Fix normalization for keys in a Secret's `data` section to allow upper- and lower-case alphanumeric characters. {#66} | ||||
|  | ||||
| - Fix normalization for keys in a Secret's `data` section to allow upper- and lower-case alphanumeric characters. {#66} | ||||
|  | ||||
| --- | ||||
|  | ||||
| [//]: # (START/v1.0.2) | ||||
| [//]: # "START/v1.0.2" | ||||
|  | ||||
| # v1.0.2 | ||||
|  | ||||
| ## Fixes | ||||
|  * Name normalizer added to handle non-conforming item names. | ||||
|  | ||||
| - Name normalizer added to handle non-conforming item names. | ||||
|  | ||||
| --- | ||||
|  | ||||
| [//]: # (START/v1.0.1) | ||||
| [//]: # "START/v1.0.1" | ||||
|  | ||||
| # v1.0.1 | ||||
|  | ||||
| ## Features | ||||
| * This release also contains an arm64 Docker image. {#20} | ||||
| * Docker images are also pushed to the :latest and :<major>.<minor> tags. | ||||
|  | ||||
| - This release also contains an arm64 Docker image. {#20} | ||||
| - Docker images are also pushed to the :latest and :<major>.<minor> tags. | ||||
|  | ||||
| --- | ||||
|  | ||||
| [//]: # (START/v1.0.0) | ||||
| [//]: # "START/v1.0.0" | ||||
|  | ||||
| # v1.0.0 | ||||
|  | ||||
| ## Features: | ||||
| * Option to automatically deploy 1Password Connect via the operator | ||||
| * Ignore restart annotation when looking for 1Password annotations | ||||
| * Release Automation | ||||
| * Upgrading apiextensions.k8s.io/v1beta apiversion from the operator custom resource | ||||
| * Adding configuration for auto rolling restart on deployments | ||||
| * Configure Auto Restarts for a OnePasswordItem Custom Resource | ||||
| * Update Connect Dependencies to latest | ||||
| * Add Github action for building and testing operator | ||||
|  | ||||
| - Option to automatically deploy 1Password Connect via the operator | ||||
| - Ignore restart annotation when looking for 1Password annotations | ||||
| - Release Automation | ||||
| - Upgrading apiextensions.k8s.io/v1beta apiversion from the operator custom resource | ||||
| - Adding configuration for auto rolling restart on deployments | ||||
| - Configure Auto Restarts for a OnePasswordItem Custom Resource | ||||
| - Update Connect Dependencies to latest | ||||
| - Add Github action for building and testing operator | ||||
|  | ||||
| ## Fixes: | ||||
| * Fix spec field example for OnePasswordItem in readme | ||||
| * Casing of annotations are now consistent | ||||
|  | ||||
| - Fix spec field example for OnePasswordItem in readme | ||||
| - Casing of annotations are now consistent | ||||
|  | ||||
| --- | ||||
|  | ||||
| [//]: # (START/v0.0.2) | ||||
| [//]: # "START/v0.0.2" | ||||
|  | ||||
| # v0.0.2 | ||||
|  | ||||
| ## Features: | ||||
| * Items can now be accessed by either `vaults/<vault_id>/items/<item_id>` or `vaults/<vault_title>/items/<item_title>` | ||||
|  | ||||
| - Items can now be accessed by either `vaults/<vault_id>/items/<item_id>` or `vaults/<vault_title>/items/<item_title>` | ||||
|  | ||||
| --- | ||||
|  | ||||
| [//]: # (START/v0.0.1) | ||||
| [//]: # "START/v0.0.1" | ||||
|  | ||||
| # v0.0.1 | ||||
|  | ||||
| Initial 1Password Operator release | ||||
|  | ||||
| ## Features | ||||
| * watches for deployment creations with `onepassword` annotations and creates an associated kubernetes secret | ||||
| * watches for `onepasswordsecret` crd creations and creates an associated kubernetes secrets | ||||
| * watches for changes to 1Password secrets associated with kubernetes secrets and updates the kubernetes secret with changes | ||||
| * restart pods when secret has been updated | ||||
| * cleanup of kubernetes secrets when deployment or `onepasswordsecret` is deleted | ||||
|  | ||||
| - watches for deployment creations with `onepassword` annotations and creates an associated kubernetes secret | ||||
| - watches for `onepasswordsecret` crd creations and creates an associated kubernetes secrets | ||||
| - watches for changes to 1Password secrets associated with kubernetes secrets and updates the kubernetes secret with changes | ||||
| - restart pods when secret has been updated | ||||
| - cleanup of kubernetes secrets when deployment or `onepasswordsecret` is deleted | ||||
|  | ||||
| --- | ||||
|   | ||||
| @@ -12,8 +12,6 @@ spec: | ||||
|   scope: Namespaced | ||||
|   versions: | ||||
|   - name: v1 | ||||
|     served: true | ||||
|     storage: true | ||||
|     schema: | ||||
|       openAPIV3Schema: | ||||
|         description: OnePasswordItem is the Schema for the onepassworditems API | ||||
| @@ -38,8 +36,41 @@ spec: | ||||
|             type: object | ||||
|           status: | ||||
|             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: | ||||
|             description: 'Kubernetes secret type. More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types' | ||||
|             type: string | ||||
|         type: object | ||||
|     served: true | ||||
|     storage: true | ||||
|     subresources: | ||||
|       status: {} | ||||
|   | ||||
| @@ -11,11 +11,31 @@ type OnePasswordItemSpec struct { | ||||
| 	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 | ||||
| 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 | ||||
| 	// 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 | ||||
| @@ -26,6 +46,8 @@ type OnePasswordItemStatus struct { | ||||
| type OnePasswordItem struct { | ||||
| 	metav1.TypeMeta   `json:",inline"` | ||||
| 	metav1.ObjectMeta `json:"metadata,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"` | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| //go:build !ignore_autogenerated | ||||
| // +build !ignore_autogenerated | ||||
|  | ||||
| // Code generated by operator-sdk. DO NOT EDIT. | ||||
| @@ -14,7 +15,7 @@ func (in *OnePasswordItem) DeepCopyInto(out *OnePasswordItem) { | ||||
| 	out.TypeMeta = in.TypeMeta | ||||
| 	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) | ||||
| 	out.Spec = in.Spec | ||||
| 	out.Status = in.Status | ||||
| 	in.Status.DeepCopyInto(&out.Status) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| @@ -36,6 +37,23 @@ func (in *OnePasswordItem) DeepCopyObject() runtime.Object { | ||||
| 	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. | ||||
| func (in *OnePasswordItemList) DeepCopyInto(out *OnePasswordItemList) { | ||||
| 	*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. | ||||
| func (in *OnePasswordItemStatus) DeepCopyInto(out *OnePasswordItemStatus) { | ||||
| 	*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 | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -14,9 +14,11 @@ import ( | ||||
| 	appsv1 "k8s.io/api/apps/v1" | ||||
| 	corev1 "k8s.io/api/core/v1" | ||||
| 	"k8s.io/apimachinery/pkg/api/errors" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	ctrl "sigs.k8s.io/controller-runtime" | ||||
| 	"sigs.k8s.io/controller-runtime/pkg/client" | ||||
| 	"sigs.k8s.io/controller-runtime/pkg/client/apiutil" | ||||
| 	"sigs.k8s.io/controller-runtime/pkg/controller" | ||||
| 	"sigs.k8s.io/controller-runtime/pkg/handler" | ||||
| 	logf "sigs.k8s.io/controller-runtime/pkg/log" | ||||
| @@ -114,7 +116,7 @@ func (r *ReconcileDeployment) Reconcile(request reconcile.Request) (reconcile.Re | ||||
| 			} | ||||
| 		} | ||||
| 		// Handles creation or updating secrets for deployment if needed | ||||
| 		if err := r.HandleApplyingDeployment(deployment.Namespace, annotations, request); err != nil { | ||||
| 		if err := r.HandleApplyingDeployment(deployment, deployment.Namespace, annotations, request); err != nil { | ||||
| 			return reconcile.Result{}, err | ||||
| 		} | ||||
| 		return reconcile.Result{}, nil | ||||
| @@ -187,7 +189,7 @@ func (r *ReconcileDeployment) removeOnePasswordFinalizerFromDeployment(deploymen | ||||
| 	return r.kubeClient.Update(context.Background(), deployment) | ||||
| } | ||||
|  | ||||
| func (r *ReconcileDeployment) HandleApplyingDeployment(namespace string, annotations map[string]string, request reconcile.Request) error { | ||||
| func (r *ReconcileDeployment) HandleApplyingDeployment(deployment *appsv1.Deployment, namespace string, annotations map[string]string, request reconcile.Request) error { | ||||
| 	reqLog := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) | ||||
|  | ||||
| 	secretName := annotations[op.NameAnnotation] | ||||
| @@ -204,5 +206,17 @@ func (r *ReconcileDeployment) HandleApplyingDeployment(namespace string, annotat | ||||
| 		return fmt.Errorf("Failed to retrieve item: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, namespace, item, annotations[op.RestartDeploymentsAnnotation], secretLabels, secretType, annotations) | ||||
| 	// Create owner reference. | ||||
| 	gvk, err := apiutil.GVKForObject(deployment, r.scheme) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("could not to retrieve group version kind: %v", err) | ||||
| 	} | ||||
| 	ownerRef := &metav1.OwnerReference{ | ||||
| 		APIVersion: gvk.GroupVersion().String(), | ||||
| 		Kind:       gvk.Kind, | ||||
| 		Name:       deployment.GetName(), | ||||
| 		UID:        deployment.GetUID(), | ||||
| 	} | ||||
|  | ||||
| 	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{ | ||||
| 					op.VersionAnnotation:  fmt.Sprint(version), | ||||
| 					op.ItemPathAnnotation: itemPath, | ||||
| 					op.NameAnnotation:     name, | ||||
| 				}, | ||||
| 			}, | ||||
| 			Data: expectedSecretData, | ||||
| @@ -294,7 +293,6 @@ var tests = []testReconcileItem{ | ||||
| 				Annotations: map[string]string{ | ||||
| 					op.VersionAnnotation:  fmt.Sprint(version), | ||||
| 					op.ItemPathAnnotation: itemPath, | ||||
| 					op.NameAnnotation:     name, | ||||
| 				}, | ||||
| 				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 { | ||||
| 		t.Run(testData.testName, func(t *testing.T) { | ||||
|  | ||||
|   | ||||
| @@ -14,9 +14,11 @@ import ( | ||||
|  | ||||
| 	corev1 "k8s.io/api/core/v1" | ||||
| 	"k8s.io/apimachinery/pkg/api/errors" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	ctrl "sigs.k8s.io/controller-runtime" | ||||
| 	kubeClient "sigs.k8s.io/controller-runtime/pkg/client" | ||||
| 	"sigs.k8s.io/controller-runtime/pkg/client/apiutil" | ||||
| 	"sigs.k8s.io/controller-runtime/pkg/controller" | ||||
| 	"sigs.k8s.io/controller-runtime/pkg/handler" | ||||
| 	logf "sigs.k8s.io/controller-runtime/pkg/log" | ||||
| @@ -94,10 +96,11 @@ func (r *ReconcileOnePasswordItem) Reconcile(request reconcile.Request) (reconci | ||||
| 		} | ||||
|  | ||||
| 		// Handles creation or updating secrets for deployment if needed | ||||
| 		if err := r.HandleOnePasswordItem(onepassworditem, request); err != nil { | ||||
| 			return reconcile.Result{}, err | ||||
| 		err := r.HandleOnePasswordItem(onepassworditem, request) | ||||
| 		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 utils.ContainsString(onepassworditem.ObjectMeta.Finalizers, finalizer) { | ||||
| @@ -145,14 +148,56 @@ func (r *ReconcileOnePasswordItem) removeOnePasswordFinalizerFromOnePasswordItem | ||||
| func (r *ReconcileOnePasswordItem) HandleOnePasswordItem(resource *onepasswordv1.OnePasswordItem, request reconcile.Request) error { | ||||
| 	secretName := resource.GetName() | ||||
| 	labels := resource.Labels | ||||
| 	annotations := resource.Annotations | ||||
| 	secretType := resource.Type | ||||
| 	autoRestart := annotations[op.RestartDeploymentsAnnotation] | ||||
| 	autoRestart := resource.Annotations[op.RestartDeploymentsAnnotation] | ||||
|  | ||||
| 	item, err := onepassword.GetOnePasswordItemByPath(r.opConnectClient, resource.Spec.ItemPath) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Failed to retrieve item: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, resource.Namespace, item, autoRestart, labels, secretType, annotations) | ||||
| 	// Create owner reference. | ||||
| 	gvk, err := apiutil.GVKForObject(resource, r.scheme) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("could not to retrieve group version kind: %v", err) | ||||
| 	} | ||||
| 	ownerRef := &metav1.OwnerReference{ | ||||
| 		APIVersion: gvk.GroupVersion().String(), | ||||
| 		Kind:       gvk.Kind, | ||||
| 		Name:       resource.GetName(), | ||||
| 		UID:        resource.GetUID(), | ||||
| 	} | ||||
|  | ||||
| 	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, | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -101,7 +101,7 @@ var tests = []testReconcileItem{ | ||||
| 		}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		testName: "Test Do not update if OnePassword Version has not changed", | ||||
| 		testName: "Test Do not update if OnePassword Version or VaultPath has not changed", | ||||
| 		customResource: &onepasswordv1.OnePasswordItem{ | ||||
| 			TypeMeta: metav1.TypeMeta{ | ||||
| 				Kind:       onePasswordItemKind, | ||||
|   | ||||
| @@ -27,7 +27,6 @@ import ( | ||||
| const OnepasswordPrefix = "operator.1password.io" | ||||
| const NameAnnotation = OnepasswordPrefix + "/item-name" | ||||
| const VersionAnnotation = OnepasswordPrefix + "/item-version" | ||||
| const restartAnnotation = OnepasswordPrefix + "/last-restarted" | ||||
| const ItemPathAnnotation = OnepasswordPrefix + "/item-path" | ||||
| const RestartDeploymentsAnnotation = OnepasswordPrefix + "/auto-restart" | ||||
|  | ||||
| @@ -35,29 +34,23 @@ var ErrCannotUpdateSecretType = errs.New("Cannot change secret type. Secret type | ||||
|  | ||||
| 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) 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) | ||||
|  | ||||
| 	// If secretAnnotations is nil we create an empty map so we can later assign values for the OP Annotations in the map | ||||
| 	if secretAnnotations == nil { | ||||
| 		secretAnnotations = map[string]string{} | ||||
| 	secretAnnotations := map[string]string{ | ||||
| 		VersionAnnotation:  itemVersion, | ||||
| 		ItemPathAnnotation: fmt.Sprintf("vaults/%v/items/%v", item.Vault.ID, item.ID), | ||||
| 	} | ||||
|  | ||||
| 	secretAnnotations[VersionAnnotation] = itemVersion | ||||
| 	secretAnnotations[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 | ||||
| 			return fmt.Errorf("Error parsing %v annotation on Secret %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, secretName) | ||||
| 		} | ||||
| 		secretAnnotations[RestartDeploymentsAnnotation] = autoRestart | ||||
| 	} | ||||
|  | ||||
| 	// "Opaque" and "" secret types are treated the same by Kubernetes. | ||||
| 	secret := BuildKubernetesSecretFromOnePasswordItem(secretName, namespace, secretAnnotations, labels, secretType, *item) | ||||
| 	secret := BuildKubernetesSecretFromOnePasswordItem(secretName, namespace, secretAnnotations, labels, secretType, *item, ownerRef) | ||||
|  | ||||
| 	currentSecret := &corev1.Secret{} | ||||
| 	err := kubeClient.Get(context.Background(), types.NamespacedName{Name: secret.Name, Namespace: secret.Namespace}, currentSecret) | ||||
| @@ -68,32 +61,50 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	currentAnnotations := currentSecret.Annotations | ||||
| 	currentLabels := currentSecret.Labels | ||||
| 	// Check if the secret types are being changed on the update. | ||||
| 	// Avoid Opaque and "" are treated as different on check. | ||||
| 	wantSecretType := secretType | ||||
| 	if wantSecretType == "" { | ||||
| 		wantSecretType = string(corev1.SecretTypeOpaque) | ||||
| 	} | ||||
| 	currentSecretType := string(currentSecret.Type) | ||||
| 	if !reflect.DeepEqual(currentSecretType, secretType) { | ||||
| 	if currentSecretType == "" { | ||||
| 		currentSecretType = string(corev1.SecretTypeOpaque) | ||||
| 	} | ||||
| 	if currentSecretType != wantSecretType { | ||||
| 		return ErrCannotUpdateSecretType | ||||
| 	} | ||||
|  | ||||
| 	currentAnnotations := currentSecret.Annotations | ||||
| 	currentLabels := currentSecret.Labels | ||||
| 	if !reflect.DeepEqual(currentAnnotations, secretAnnotations) || !reflect.DeepEqual(currentLabels, labels) { | ||||
| 		log.Info(fmt.Sprintf("Updating Secret %v at namespace '%v'", secret.Name, secret.Namespace)) | ||||
| 		currentSecret.ObjectMeta.Annotations = secretAnnotations | ||||
| 		currentSecret.ObjectMeta.Labels = labels | ||||
| 		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])) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func BuildKubernetesSecretFromOnePasswordItem(name, namespace string, annotations map[string]string, labels map[string]string, secretType string, item onepassword.Item) *corev1.Secret { | ||||
| func BuildKubernetesSecretFromOnePasswordItem(name, namespace string, annotations map[string]string, labels map[string]string, secretType string, item onepassword.Item, ownerRef *metav1.OwnerReference) *corev1.Secret { | ||||
| 	var ownerRefs []metav1.OwnerReference | ||||
| 	if ownerRef != nil { | ||||
| 		ownerRefs = []metav1.OwnerReference{*ownerRef} | ||||
| 	} | ||||
|  | ||||
| 	return &corev1.Secret{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			Name:            formatSecretName(name), | ||||
| 			Namespace:       namespace, | ||||
| 			Annotations:     annotations, | ||||
| 			Labels:          labels, | ||||
| 			OwnerReferences: ownerRefs, | ||||
| 		}, | ||||
| 		Data: BuildKubernetesSecretData(item.Fields, item.Files), | ||||
| 		Type: corev1.SecretType(secretType), | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import ( | ||||
|  | ||||
| 	"github.com/1Password/connect-sdk-go/onepassword" | ||||
| 	corev1 "k8s.io/api/core/v1" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/types" | ||||
| 	kubeValidate "k8s.io/apimachinery/pkg/util/validation" | ||||
| 	"k8s.io/client-go/kubernetes" | ||||
| @@ -32,12 +33,9 @@ func TestCreateKubernetesSecretFromOnePasswordItem(t *testing.T) { | ||||
|  | ||||
| 	kubeClient := fake.NewFakeClient() | ||||
| 	secretLabels := map[string]string{} | ||||
| 	secretAnnotations := map[string]string{ | ||||
| 		"testAnnotation": "exists", | ||||
| 	} | ||||
| 	secretType := "" | ||||
|  | ||||
| 	err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, secretAnnotations) | ||||
| 	err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Unexpected error: %v", err) | ||||
| 	} | ||||
| @@ -49,9 +47,50 @@ func TestCreateKubernetesSecretFromOnePasswordItem(t *testing.T) { | ||||
| 	} | ||||
| 	compareFields(item.Fields, createdSecret.Data, 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) { | ||||
| 	secretName := "test-secret-name" | ||||
| 	namespace := "test" | ||||
|  | ||||
| 	item := onepassword.Item{} | ||||
| 	item.Fields = generateFields(5) | ||||
| 	item.Version = 123 | ||||
| 	item.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda" | ||||
| 	item.ID = "h46bb3jddvay7nxopfhvlwg35q" | ||||
|  | ||||
| 	kubeClient := fake.NewFakeClient() | ||||
| 	secretLabels := map[string]string{} | ||||
| 	secretType := "" | ||||
|  | ||||
| 	ownerRef := &metav1.OwnerReference{ | ||||
| 		Kind:       "Deployment", | ||||
| 		APIVersion: "apps/v1", | ||||
| 		Name:       "test-deployment", | ||||
| 		UID:        types.UID("test-uid"), | ||||
| 	} | ||||
| 	err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, ownerRef) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Unexpected error: %v", err) | ||||
| 	} | ||||
| 	createdSecret := &corev1.Secret{} | ||||
| 	err = kubeClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: namespace}, createdSecret) | ||||
|  | ||||
| 	// Check owner references. | ||||
| 	gotOwnerRefs := createdSecret.ObjectMeta.OwnerReferences | ||||
| 	if len(gotOwnerRefs) != 1 { | ||||
| 		t.Errorf("Expected owner references length: 1 but got: %d", len(gotOwnerRefs)) | ||||
| 	} | ||||
|  | ||||
| 	expOwnerRef := metav1.OwnerReference{ | ||||
| 		Kind:       "Deployment", | ||||
| 		APIVersion: "apps/v1", | ||||
| 		Name:       "test-deployment", | ||||
| 		UID:        types.UID("test-uid"), | ||||
| 	} | ||||
| 	gotOwnerRef := gotOwnerRefs[0] | ||||
| 	if gotOwnerRef != expOwnerRef { | ||||
| 		t.Errorf("Expected owner reference value: %v but got: %v", expOwnerRef, gotOwnerRef) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -67,10 +106,9 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) { | ||||
|  | ||||
| 	kubeClient := fake.NewFakeClient() | ||||
| 	secretLabels := map[string]string{} | ||||
| 	secretAnnotations := map[string]string{} | ||||
| 	secretType := "" | ||||
|  | ||||
| 	err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, secretAnnotations) | ||||
| 	err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Unexpected error: %v", err) | ||||
| @@ -82,7 +120,7 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) { | ||||
| 	newItem.Version = 456 | ||||
| 	newItem.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda" | ||||
| 	newItem.ID = "h46bb3jddvay7nxopfhvlwg35q" | ||||
| 	err = CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &newItem, restartDeploymentAnnotation, secretLabels, secretType, secretAnnotations) | ||||
| 	err = CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &newItem, restartDeploymentAnnotation, secretLabels, secretType, nil) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Unexpected error: %v", err) | ||||
| 	} | ||||
| @@ -118,7 +156,7 @@ func TestBuildKubernetesSecretFromOnePasswordItem(t *testing.T) { | ||||
| 	labels := map[string]string{} | ||||
| 	secretType := "" | ||||
|  | ||||
| 	kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels, secretType, item) | ||||
| 	kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels, secretType, item, nil) | ||||
| 	if kubeSecret.Name != strings.ToLower(name) { | ||||
| 		t.Errorf("Expected name value: %v but got: %v", name, kubeSecret.Name) | ||||
| 	} | ||||
| @@ -153,7 +191,7 @@ func TestBuildKubernetesSecretFixesInvalidLabels(t *testing.T) { | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels, secretType, item) | ||||
| 	kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels, secretType, item, nil) | ||||
|  | ||||
| 	// Assert Secret's meta.name was fixed | ||||
| 	if kubeSecret.Name != expectedName { | ||||
| @@ -183,12 +221,9 @@ func TestCreateKubernetesTLSSecretFromOnePasswordItem(t *testing.T) { | ||||
|  | ||||
| 	kubeClient := fake.NewFakeClient() | ||||
| 	secretLabels := map[string]string{} | ||||
| 	secretAnnotations := map[string]string{ | ||||
| 		"testAnnotation": "exists", | ||||
| 	} | ||||
| 	secretType := "kubernetes.io/tls" | ||||
|  | ||||
| 	err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, secretAnnotations) | ||||
| 	err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Unexpected error: %v", err) | ||||
| 	} | ||||
|   | ||||
| @@ -5,6 +5,8 @@ import ( | ||||
| 	"fmt" | ||||
| 	"time" | ||||
|  | ||||
| 	v1 "github.com/1Password/onepassword-operator/pkg/apis/onepassword/v1" | ||||
|  | ||||
| 	kubeSecrets "github.com/1Password/onepassword-operator/pkg/kubernetessecrets" | ||||
| 	"github.com/1Password/onepassword-operator/pkg/utils" | ||||
|  | ||||
| @@ -89,9 +91,10 @@ func (h *SecretUpdateHandler) restartDeploymentsWithUpdatedSecrets(updatedSecret | ||||
|  | ||||
| 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)) | ||||
| 	deployment.Spec.Template.Annotations = map[string]string{ | ||||
| 		RestartAnnotation: time.Now().String(), | ||||
| 	if deployment.Spec.Template.Annotations == nil { | ||||
| 		deployment.Spec.Template.Annotations = map[string]string{} | ||||
| 	} | ||||
| 	deployment.Spec.Template.Annotations[RestartAnnotation] = time.Now().String() | ||||
| 	err := h.client.Update(context.Background(), deployment) | ||||
| 	if err != nil { | ||||
| 		log.Error(err, "Problem restarting deployment") | ||||
| @@ -116,24 +119,37 @@ func (h *SecretUpdateHandler) updateKubernetesSecrets() (map[string]map[string]* | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		item, err := GetOnePasswordItemByPath(h.opConnectClient, secret.Annotations[ItemPathAnnotation]) | ||||
| 		OnePasswordItemPath := h.getPathFromOnePasswordItem(secret) | ||||
|  | ||||
| 		item, err := GetOnePasswordItemByPath(h.opConnectClient, OnePasswordItemPath) | ||||
| 		if err != nil { | ||||
| 			log.Error(err, "failed to retrieve 1Password item at path \"%s\" for secret \"%s\"", secret.Annotations[ItemPathAnnotation], secret.Name) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		itemVersion := fmt.Sprint(item.Version) | ||||
| 		if currentVersion != itemVersion { | ||||
| 		itemPathString := fmt.Sprintf("vaults/%v/items/%v", item.Vault.ID, item.ID) | ||||
|  | ||||
| 		if currentVersion != itemVersion || secret.Annotations[ItemPathAnnotation] != itemPathString { | ||||
| 			if isItemLockedForForcedRestarts(item) { | ||||
| 				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 | ||||
| 				h.client.Update(context.Background(), &secret) | ||||
| 				secret.Annotations[ItemPathAnnotation] = itemPathString | ||||
| 				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 | ||||
| 			} | ||||
| 			log.Info(fmt.Sprintf("Updating kubernetes secret '%v'", secret.GetName())) | ||||
| 			secret.Annotations[VersionAnnotation] = itemVersion | ||||
| 			updatedSecret := kubeSecrets.BuildKubernetesSecretFromOnePasswordItem(secret.Name, secret.Namespace, secret.Annotations, secret.Labels, string(secret.Type), *item) | ||||
| 			h.client.Update(context.Background(), updatedSecret) | ||||
| 			secret.Annotations[ItemPathAnnotation] = itemPathString | ||||
| 			secret.Data = kubeSecrets.BuildKubernetesSecretData(item.Fields, item.Files) | ||||
| 			log.Info(fmt.Sprintf("New secret path: %v and version: %v", secret.Annotations[ItemPathAnnotation], secret.Annotations[VersionAnnotation])) | ||||
| 			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 { | ||||
| 				updatedSecrets[secret.Namespace] = make(map[string]*corev1.Secret) | ||||
| 			} | ||||
| @@ -177,6 +193,22 @@ func (h *SecretUpdateHandler) getIsSetForAutoRestartByNamespaceMap() (map[string | ||||
| 	return namespacesMap, nil | ||||
| } | ||||
|  | ||||
| func (h *SecretUpdateHandler) getPathFromOnePasswordItem(secret corev1.Secret) string { | ||||
| 	onePasswordItem := &v1.OnePasswordItem{} | ||||
|  | ||||
| 	// Search for our original OnePasswordItem if it exists | ||||
| 	err := h.client.Get(context.TODO(), client.ObjectKey{ | ||||
| 		Namespace: secret.Namespace, | ||||
| 		Name:      secret.Name}, onePasswordItem) | ||||
|  | ||||
| 	if err == nil { | ||||
| 		return onePasswordItem.Spec.ItemPath | ||||
| 	} | ||||
|  | ||||
| 	// If we can't find the OnePassword Item we'll just return the annotation from the secret item. | ||||
| 	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 | ||||
|   | ||||
| @@ -122,6 +122,9 @@ var tests = []testUpdateSecretTask{ | ||||
| 			}, | ||||
| 			Spec: appsv1.DeploymentSpec{ | ||||
| 				Template: corev1.PodTemplateSpec{ | ||||
| 					ObjectMeta: metav1.ObjectMeta{ | ||||
| 						Annotations: map[string]string{"external-annotation": "some-value"}, | ||||
| 					}, | ||||
| 					Spec: corev1.PodSpec{ | ||||
| 						Containers: []corev1.Container{ | ||||
| 							{ | ||||
| @@ -235,6 +238,9 @@ var tests = []testUpdateSecretTask{ | ||||
| 			}, | ||||
| 			Spec: appsv1.DeploymentSpec{ | ||||
| 				Template: corev1.PodTemplateSpec{ | ||||
| 					ObjectMeta: metav1.ObjectMeta{ | ||||
| 						Annotations: map[string]string{"external-annotation": "some-value"}, | ||||
| 					}, | ||||
| 					Spec: corev1.PodSpec{ | ||||
| 						Volumes: []corev1.Volume{ | ||||
| 							{ | ||||
| @@ -342,6 +348,9 @@ var tests = []testUpdateSecretTask{ | ||||
| 			}, | ||||
| 			Spec: appsv1.DeploymentSpec{ | ||||
| 				Template: corev1.PodTemplateSpec{ | ||||
| 					ObjectMeta: metav1.ObjectMeta{ | ||||
| 						Annotations: map[string]string{"external-annotation": "some-value"}, | ||||
| 					}, | ||||
| 					Spec: corev1.PodSpec{ | ||||
| 						Containers: []corev1.Container{ | ||||
| 							{ | ||||
| @@ -411,6 +420,9 @@ var tests = []testUpdateSecretTask{ | ||||
| 			}, | ||||
| 			Spec: appsv1.DeploymentSpec{ | ||||
| 				Template: corev1.PodTemplateSpec{ | ||||
| 					ObjectMeta: metav1.ObjectMeta{ | ||||
| 						Annotations: map[string]string{"external-annotation": "some-value"}, | ||||
| 					}, | ||||
| 					Spec: corev1.PodSpec{ | ||||
| 						Containers: []corev1.Container{ | ||||
| 							{ | ||||
| @@ -482,6 +494,9 @@ var tests = []testUpdateSecretTask{ | ||||
| 			}, | ||||
| 			Spec: appsv1.DeploymentSpec{ | ||||
| 				Template: corev1.PodTemplateSpec{ | ||||
| 					ObjectMeta: metav1.ObjectMeta{ | ||||
| 						Annotations: map[string]string{"external-annotation": "some-value"}, | ||||
| 					}, | ||||
| 					Spec: corev1.PodSpec{ | ||||
| 						Containers: []corev1.Container{ | ||||
| 							{ | ||||
| @@ -553,6 +568,9 @@ var tests = []testUpdateSecretTask{ | ||||
| 			}, | ||||
| 			Spec: appsv1.DeploymentSpec{ | ||||
| 				Template: corev1.PodTemplateSpec{ | ||||
| 					ObjectMeta: metav1.ObjectMeta{ | ||||
| 						Annotations: map[string]string{"external-annotation": "some-value"}, | ||||
| 					}, | ||||
| 					Spec: corev1.PodSpec{ | ||||
| 						Containers: []corev1.Container{ | ||||
| 							{ | ||||
| @@ -630,6 +648,9 @@ var tests = []testUpdateSecretTask{ | ||||
| 			}, | ||||
| 			Spec: appsv1.DeploymentSpec{ | ||||
| 				Template: corev1.PodTemplateSpec{ | ||||
| 					ObjectMeta: metav1.ObjectMeta{ | ||||
| 						Annotations: map[string]string{"external-annotation": "some-value"}, | ||||
| 					}, | ||||
| 					Spec: corev1.PodSpec{ | ||||
| 						Containers: []corev1.Container{ | ||||
| 							{ | ||||
| @@ -703,6 +724,9 @@ var tests = []testUpdateSecretTask{ | ||||
| 			}, | ||||
| 			Spec: appsv1.DeploymentSpec{ | ||||
| 				Template: corev1.PodTemplateSpec{ | ||||
| 					ObjectMeta: metav1.ObjectMeta{ | ||||
| 						Annotations: map[string]string{"external-annotation": "some-value"}, | ||||
| 					}, | ||||
| 					Spec: corev1.PodSpec{ | ||||
| 						Containers: []corev1.Container{ | ||||
| 							{ | ||||
| @@ -829,6 +853,16 @@ func TestUpdateSecretHandler(t *testing.T) { | ||||
| 			} else { | ||||
| 				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