mirror of
				https://github.com/1Password/onepassword-operator.git
				synced 2025-10-31 03:39:39 +00:00 
			
		
		
		
	Compare commits
	
		
			55 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 0796b9c5e2 | ||
|   | 37a0f4b51e | ||
|   | 004e0101ff | ||
|   | 6326a856ae | ||
|   | 1ddf92c5a0 | ||
|   | f5c6fa5860 | ||
|   | afa076d321 | ||
|   | d4b04c233c | ||
|   | ea68cfc2b4 | ||
|   | 58b4ff8ecf | ||
|   | d93fecdc76 | ||
|   | 486465247d | ||
|   | 79868ae374 | ||
|   | 6286f7e306 | ||
|   | 0b5efc8690 | ||
|   | c00baeedcb | ||
|   | a37bddbfd9 | ||
|   | bd9922f635 | ||
|   | 8fa4413880 | ||
|   | 62e55a3f19 | ||
|   | d6f7b80c40 | ||
|   | a903f9b1af | ||
|   | 5cddc9d8a9 | ||
|   | 7e1b94fae7 | ||
|   | 6953a89c89 | ||
|   | 0d9e07f543 | ||
|   | 098d504d2a | ||
|   | b68d9a5d79 | ||
|   | befcaae457 | ||
|   | b24aa48bd6 | ||
|   | b1e251dee6 | ||
|   | a34c6e8b38 | ||
|   | b16960057a | ||
|   | 285496dc7e | ||
|   | f38cf7e1c2 | ||
|   | bb7a0c8ca9 | ||
|   | 302653832e | ||
|   | a1bcfdfdcb | ||
|   | c0f1632638 | ||
|   | c46065fa7a | ||
|   | 5d229c42d5 | ||
|   | c7235b4f09 | ||
|   | 5183fc129a | ||
|   | 7d619165b2 | ||
|   | 0363ae1e4e | ||
|   | d9e003bdb7 | ||
|   | b25f943b3a | ||
|   | 5fab662424 | ||
|   | a760e524ea | ||
|   | 19f774bb2d | ||
|   | 32643651d9 | ||
|   | ba8d3fa698 | ||
|   | c57aa22a9c | ||
|   | 48944b0d56 | ||
|   | 313cd1169b | 
							
								
								
									
										108
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										108
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -12,68 +12,124 @@ | ||||
|  | ||||
| --- | ||||
|  | ||||
| [//]: # (START/v1.1.0) | ||||
| [//]: # "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} | ||||
|  | ||||
| --- | ||||
|  | ||||
| [//]: # "START/v1.2.0" | ||||
|  | ||||
| # v1.2.0 | ||||
|  | ||||
| ## Features | ||||
|  | ||||
| - Support secrets provisioned through FromEnv. {#74} | ||||
| - Support configuration of Kubernetes Secret type. {#87} | ||||
| - Improved logging. (#72) | ||||
|  | ||||
| --- | ||||
|  | ||||
| [//]: # "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 | ||||
|  | ||||
| --- | ||||
|   | ||||
							
								
								
									
										28
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								README.md
									
									
									
									
									
								
							| @@ -30,14 +30,13 @@ If 1Password Connect is already running, you can skip this step. This guide will | ||||
| Encode the 1password-credentials.json file you generated in the prerequisite steps and save it to a file named op-session: | ||||
|  | ||||
| ```bash | ||||
| $ cat 1password-credentials.json | base64 | \ | ||||
| cat 1password-credentials.json | base64 | \ | ||||
|   tr '/+' '_-' | tr -d '=' | tr -d '\n' > op-session | ||||
| ``` | ||||
|  | ||||
| Create a Kubernetes secret from the op-session file: | ||||
| ```bash | ||||
|  | ||||
| $  kubectl create secret generic op-credentials --from-file=1password-credentials.json | ||||
| kubectl create secret generic op-credentials --from-file=op-session | ||||
| ``` | ||||
|  | ||||
| Add the following environment variable to the onepassword-connect-operator container in `deploy/operator.yaml`: | ||||
| @@ -53,12 +52,12 @@ Adding this environment variable will have the operator automatically deploy a d | ||||
| "Create a Connect token for the operator and save it as a Kubernetes Secret:  | ||||
|  | ||||
| ```bash | ||||
| $ kubectl create secret generic onepassword-token --from-literal=token=<OP_CONNECT_TOKEN>" | ||||
| kubectl create secret generic onepassword-token --from-literal=token=<OP_CONNECT_TOKEN>" | ||||
| ``` | ||||
|  | ||||
| If you do not have a token for the operator, you can generate a token and save it to kubernetes with the following command: | ||||
| ```bash | ||||
| $ kubectl create secret generic onepassword-token --from-literal=token=$(op create connect token <server> op-k8s-operator --vault <vault>) | ||||
| kubectl create secret generic onepassword-token --from-literal=token=$(op create connect token <server> op-k8s-operator --vault <vault>) | ||||
| ``` | ||||
|  | ||||
| [More information on generating a token can be found here](https://support.1password.com/secrets-automation/#appendix-issue-additional-access-tokens) | ||||
| @@ -68,13 +67,13 @@ $ kubectl create secret generic onepassword-token --from-literal=token=$(op crea | ||||
| We must create a service account, role, and role binding and Kubernetes. Examples can be found in the `/deploy` folder. | ||||
|  | ||||
| ```bash | ||||
| $ kubectl apply -f deploy/permissions.yaml | ||||
| kubectl apply -f deploy/permissions.yaml | ||||
| ``` | ||||
|  | ||||
| **Create Custom One Password Secret Resource** | ||||
|  | ||||
| ```bash | ||||
| $ kubectl apply -f deploy/crds/onepassword.com_onepassworditems_crd.yaml | ||||
| kubectl apply -f deploy/crds/onepassword.com_onepassworditems_crd.yaml | ||||
| ``` | ||||
|  | ||||
| **Deploying the Operator** | ||||
| @@ -112,13 +111,13 @@ spec: | ||||
| Deploy the OnePasswordItem to Kubernetes: | ||||
|  | ||||
| ```bash | ||||
| $ kubectl apply -f <your_item>.yaml | ||||
| kubectl apply -f <your_item>.yaml | ||||
| ``` | ||||
|  | ||||
| To test that the Kubernetes Secret check that the following command returns a secret: | ||||
|  | ||||
| ```bash | ||||
| $ kubectl get secret <secret_name> | ||||
| kubectl get secret <secret_name> | ||||
| ``` | ||||
|  | ||||
| Note: Deleting the `OnePasswordItem` that you've created will automatically delete the created Kubernetes Secret. | ||||
| @@ -137,6 +136,11 @@ metadata: | ||||
|  | ||||
| Applying this yaml file will create a Kubernetes Secret with the name `<secret_name>` and contents from the location specified at the specified Item Path. | ||||
|  | ||||
| The contents of the Kubernetes secret will be key-value pairs in which the keys are the fields of the 1Password item and the values are the corresponding values stored in 1Password.  | ||||
| In case of fields that store files, the file's contents will be used as the value. | ||||
|  | ||||
| Within an item, if both a field storing a file and a field of another type have the same name, the file field will be ignored and the other field will take precedence. | ||||
|  | ||||
| Note: Deleting the Deployment that you've created will automatically delete the created Kubernetes Secret only if the deployment is still annotated with `operator.1password.io/item-path` and `operator.1password.io/item-name` and no other deployment is using the secret. | ||||
|  | ||||
| If a 1Password Item that is linked to a Kubernetes Secret is updated within the POLLING_INTERVAL the associated Kubernetes Secret will be updated. However, if you do not want a specific secret to be updated you can add the tag `operator.1password.io:ignore-secret` to the item stored in 1Password. While this tag is in place, any updates made to an item will not trigger an update to the associated secret in Kubernetes. | ||||
| @@ -171,7 +175,7 @@ metadata: | ||||
|   annotations: | ||||
|     operator.1password.io/auto-restart: "true" | ||||
| ``` | ||||
| If the value is not set, the auto reset settings on the operator will be used. This value can be overwritten by deployment. | ||||
| If the value is not set, the auto restart settings on the operator will be used. This value can be overwritten by deployment. | ||||
|  | ||||
| **Per Deployment** | ||||
| This method allows for managing auto restarts on a given deployment. Auto restarts can by managed by setting the annotation `operator.1password.io/auto-restart` to either `true` or `false` on the desired deployment. An example of this is shown below: | ||||
| @@ -184,7 +188,7 @@ metadata: | ||||
|   annotations: | ||||
|     operator.1password.io/auto-restart: "true" | ||||
| ``` | ||||
| If the value is not set, the auto reset settings on the namespace will be used. | ||||
| If the value is not set, the auto restart settings on the namespace will be used. | ||||
|  | ||||
| **Per OnePasswordItem Custom Resource** | ||||
| This method allows for managing auto restarts on a given OnePasswordItem custom resource. Auto restarts can by managed by setting the annotation `operator.1password.io/auto_restart` to either `true` or `false` on the desired OnePasswordItem. An example of this is shown below: | ||||
| @@ -197,7 +201,7 @@ metadata: | ||||
|   annotations: | ||||
|     operator.1password.io/auto-restart: "true" | ||||
| ``` | ||||
| If the value is not set, the auto reset settings on the deployment will be used. | ||||
| If the value is not set, the auto restart settings on the deployment will be used. | ||||
|  | ||||
| ## Development | ||||
|  | ||||
|   | ||||
| @@ -178,7 +178,10 @@ func main() { | ||||
| 				ticker.Stop() | ||||
| 				return | ||||
| 			case <-ticker.C: | ||||
| 				updatedSecretsPoller.UpdateKubernetesSecretsTask() | ||||
| 				err := updatedSecretsPoller.UpdateKubernetesSecretsTask() | ||||
| 				if err != nil { | ||||
| 					log.Error(err, "error running update kubernetes secret task") | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
|   | ||||
| @@ -39,4 +39,7 @@ spec: | ||||
|           status: | ||||
|             description: OnePasswordItemStatus defines the observed state of OnePasswordItem | ||||
|             type: object | ||||
|           type: | ||||
|             description: 'Kubernetes secret type. More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types' | ||||
|             type: string | ||||
|         type: object | ||||
|   | ||||
							
								
								
									
										4
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.mod
									
									
									
									
									
								
							| @@ -3,11 +3,11 @@ module github.com/1Password/onepassword-operator | ||||
| go 1.13 | ||||
|  | ||||
| require ( | ||||
| 	github.com/1Password/connect-sdk-go v1.0.1 | ||||
| 	github.com/1Password/connect-sdk-go v1.2.0 | ||||
| 	github.com/operator-framework/operator-sdk v0.19.0 | ||||
| 	github.com/prometheus/common v0.14.0 // indirect | ||||
| 	github.com/spf13/pflag v1.0.5 | ||||
| 	github.com/stretchr/testify v1.6.1 | ||||
| 	github.com/stretchr/testify v1.7.0 | ||||
| 	k8s.io/api v0.18.2 | ||||
| 	k8s.io/apimachinery v0.18.2 | ||||
| 	k8s.io/client-go v12.0.0+incompatible | ||||
|   | ||||
							
								
								
									
										4
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.sum
									
									
									
									
									
								
							| @@ -20,6 +20,8 @@ contrib.go.opencensus.io/exporter/ocagent v0.6.0/go.mod h1:zmKjrJcdo0aYcVS7bmEeS | ||||
| dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= | ||||
| github.com/1Password/connect-sdk-go v1.0.1 h1:BOeMIxVk6/ISmLNWUkSxEbVI7tNr5+aNXIobMM0/I0U= | ||||
| github.com/1Password/connect-sdk-go v1.0.1/go.mod h1:br2BWk2sqgJFnOFK5WSDfBBmwQ6E7hV9LoPqrtHGRNY= | ||||
| github.com/1Password/connect-sdk-go v1.2.0 h1:WbIvmbDUpA89nyH0l3LF2iRSFJAv86d2D7IjVNjw6iw= | ||||
| github.com/1Password/connect-sdk-go v1.2.0/go.mod h1:qK2bF/GweAq812xj+HGfbauaE6cKX1MXfKhpAvoHEq8= | ||||
| github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= | ||||
| github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= | ||||
| github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= | ||||
| @@ -885,6 +887,8 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H | ||||
| github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= | ||||
| github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= | ||||
| github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= | ||||
| github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= | ||||
| github.com/thanos-io/thanos v0.11.0/go.mod h1:N/Yes7J68KqvmY+xM6J5CJqEvWIvKSR5sqGtmuD6wDc= | ||||
| github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= | ||||
|   | ||||
| @@ -26,6 +26,7 @@ type OnePasswordItemStatus struct { | ||||
| type OnePasswordItem struct { | ||||
| 	metav1.TypeMeta   `json:",inline"` | ||||
| 	metav1.ObjectMeta `json:"metadata,omitempty"` | ||||
| 	Type              string `json:"type,omitempty"` | ||||
|  | ||||
| 	Spec   OnePasswordItemSpec   `json:"spec,omitempty"` | ||||
| 	Status OnePasswordItemStatus `json:"status,omitempty"` | ||||
|   | ||||
| @@ -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,11 +189,13 @@ 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] | ||||
| 	secretLabels := map[string]string(nil) | ||||
| 	secretType := "" | ||||
|  | ||||
| 	if len(secretName) == 0 { | ||||
| 		reqLog.Info("No 'item-name' annotation set. 'item-path' and 'item-name' must be set as annotations to add new secret.") | ||||
| 		return nil | ||||
| @@ -202,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, 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) | ||||
| } | ||||
|   | ||||
| @@ -279,9 +279,8 @@ var tests = []testReconcileItem{ | ||||
| 				Name:      name, | ||||
| 				Namespace: namespace, | ||||
| 				Annotations: map[string]string{ | ||||
| 					op.VersionAnnotation: fmt.Sprint(version), | ||||
| 					op.VersionAnnotation:  fmt.Sprint(version), | ||||
| 					op.ItemPathAnnotation: itemPath, | ||||
| 					op.NameAnnotation:     name, | ||||
| 				}, | ||||
| 			}, | ||||
| 			Data: expectedSecretData, | ||||
| @@ -292,9 +291,8 @@ var tests = []testReconcileItem{ | ||||
| 				Name:      name, | ||||
| 				Namespace: namespace, | ||||
| 				Annotations: map[string]string{ | ||||
| 					op.VersionAnnotation: fmt.Sprint(version), | ||||
| 					op.VersionAnnotation:  fmt.Sprint(version), | ||||
| 					op.ItemPathAnnotation: itemPath, | ||||
| 					op.NameAnnotation:     name, | ||||
| 				}, | ||||
| 				Labels: map[string]string(nil), | ||||
| 			}, | ||||
| @@ -329,6 +327,7 @@ var tests = []testReconcileItem{ | ||||
| 					op.VersionAnnotation: "456", | ||||
| 				}, | ||||
| 			}, | ||||
| 			Type: corev1.SecretType(""), | ||||
| 			Data: expectedSecretData, | ||||
| 		}, | ||||
| 		expectedError: nil, | ||||
| @@ -340,6 +339,7 @@ var tests = []testReconcileItem{ | ||||
| 					op.VersionAnnotation: fmt.Sprint(version), | ||||
| 				}, | ||||
| 			}, | ||||
| 			Type: corev1.SecretType(""), | ||||
| 			Data: expectedSecretData, | ||||
| 		}, | ||||
| 		opItem: map[string]string{ | ||||
| @@ -373,6 +373,7 @@ var tests = []testReconcileItem{ | ||||
| 					op.VersionAnnotation: fmt.Sprint(version), | ||||
| 				}, | ||||
| 			}, | ||||
| 			Type: corev1.SecretType(""), | ||||
| 			Data: expectedSecretData, | ||||
| 		}, | ||||
| 		opItem: map[string]string{ | ||||
| @@ -382,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) { | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,7 @@ package onepassworditem | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
|  | ||||
| 	onepasswordv1 "github.com/1Password/onepassword-operator/pkg/apis/onepassword/v1" | ||||
| 	kubeSecrets "github.com/1Password/onepassword-operator/pkg/kubernetessecrets" | ||||
| 	"github.com/1Password/onepassword-operator/pkg/onepassword" | ||||
| @@ -13,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" | ||||
| @@ -144,13 +147,25 @@ func (r *ReconcileOnePasswordItem) removeOnePasswordFinalizerFromOnePasswordItem | ||||
| func (r *ReconcileOnePasswordItem) HandleOnePasswordItem(resource *onepasswordv1.OnePasswordItem, request reconcile.Request) error { | ||||
| 	secretName := resource.GetName() | ||||
| 	labels := resource.Labels | ||||
| 	annotations := resource.Annotations | ||||
| 	autoRestart := annotations[op.RestartDeploymentsAnnotation] | ||||
| 	secretType := resource.Type | ||||
| 	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, 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) | ||||
| } | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import ( | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/1Password/onepassword-operator/pkg/kubernetessecrets" | ||||
| 	"github.com/1Password/onepassword-operator/pkg/mocks" | ||||
| 	op "github.com/1Password/onepassword-operator/pkg/onepassword" | ||||
|  | ||||
| @@ -100,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, | ||||
| @@ -119,7 +120,7 @@ var tests = []testReconcileItem{ | ||||
| 				Name:      name, | ||||
| 				Namespace: namespace, | ||||
| 				Annotations: map[string]string{ | ||||
| 					op.VersionAnnotation: fmt.Sprint(version), | ||||
| 					op.VersionAnnotation:  fmt.Sprint(version), | ||||
| 					op.ItemPathAnnotation: itemPath, | ||||
| 				}, | ||||
| 			}, | ||||
| @@ -131,7 +132,7 @@ var tests = []testReconcileItem{ | ||||
| 				Name:      name, | ||||
| 				Namespace: namespace, | ||||
| 				Annotations: map[string]string{ | ||||
| 					op.VersionAnnotation: fmt.Sprint(version), | ||||
| 					op.VersionAnnotation:  fmt.Sprint(version), | ||||
| 					op.ItemPathAnnotation: itemPath, | ||||
| 				}, | ||||
| 			}, | ||||
| @@ -153,7 +154,7 @@ var tests = []testReconcileItem{ | ||||
| 				Name:      name, | ||||
| 				Namespace: namespace, | ||||
| 				Annotations: map[string]string{ | ||||
| 					op.VersionAnnotation: fmt.Sprint(version), | ||||
| 					op.VersionAnnotation:  fmt.Sprint(version), | ||||
| 					op.ItemPathAnnotation: itemPath, | ||||
| 				}, | ||||
| 				Labels: map[string]string{}, | ||||
| @@ -167,7 +168,7 @@ var tests = []testReconcileItem{ | ||||
| 				Name:      name, | ||||
| 				Namespace: namespace, | ||||
| 				Annotations: map[string]string{ | ||||
| 					op.VersionAnnotation: "456", | ||||
| 					op.VersionAnnotation:  "456", | ||||
| 					op.ItemPathAnnotation: itemPath, | ||||
| 				}, | ||||
| 				Labels: map[string]string{}, | ||||
| @@ -180,7 +181,7 @@ var tests = []testReconcileItem{ | ||||
| 				Name:      name, | ||||
| 				Namespace: namespace, | ||||
| 				Annotations: map[string]string{ | ||||
| 					op.VersionAnnotation: fmt.Sprint(version), | ||||
| 					op.VersionAnnotation:  fmt.Sprint(version), | ||||
| 					op.ItemPathAnnotation: itemPath, | ||||
| 				}, | ||||
| 				Labels: map[string]string{}, | ||||
| @@ -192,6 +193,59 @@ var tests = []testReconcileItem{ | ||||
| 			passKey: password, | ||||
| 		}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		testName: "Test Updating Type of Existing Kubernetes Secret using OnePasswordItem", | ||||
| 		customResource: &onepasswordv1.OnePasswordItem{ | ||||
| 			TypeMeta: metav1.TypeMeta{ | ||||
| 				Kind:       onePasswordItemKind, | ||||
| 				APIVersion: onePasswordItemAPIVersion, | ||||
| 			}, | ||||
| 			ObjectMeta: metav1.ObjectMeta{ | ||||
| 				Name:      name, | ||||
| 				Namespace: namespace, | ||||
| 				Annotations: map[string]string{ | ||||
| 					op.VersionAnnotation:  fmt.Sprint(version), | ||||
| 					op.ItemPathAnnotation: itemPath, | ||||
| 				}, | ||||
| 				Labels: map[string]string{}, | ||||
| 			}, | ||||
| 			Spec: onepasswordv1.OnePasswordItemSpec{ | ||||
| 				ItemPath: itemPath, | ||||
| 			}, | ||||
| 			Type: string(corev1.SecretTypeBasicAuth), | ||||
| 		}, | ||||
| 		existingSecret: &corev1.Secret{ | ||||
| 			ObjectMeta: metav1.ObjectMeta{ | ||||
| 				Name:      name, | ||||
| 				Namespace: namespace, | ||||
| 				Annotations: map[string]string{ | ||||
| 					op.VersionAnnotation:  fmt.Sprint(version), | ||||
| 					op.ItemPathAnnotation: itemPath, | ||||
| 				}, | ||||
| 				Labels: map[string]string{}, | ||||
| 			}, | ||||
| 			Type: corev1.SecretTypeBasicAuth, | ||||
| 			Data: expectedSecretData, | ||||
| 		}, | ||||
| 		expectedError: nil, | ||||
| 		expectedResultSecret: &corev1.Secret{ | ||||
| 			ObjectMeta: metav1.ObjectMeta{ | ||||
| 				Name:      name, | ||||
| 				Namespace: namespace, | ||||
| 				Annotations: map[string]string{ | ||||
| 					op.VersionAnnotation:  fmt.Sprint(version), | ||||
| 					op.ItemPathAnnotation: itemPath, | ||||
| 				}, | ||||
| 				Labels: map[string]string{}, | ||||
| 			}, | ||||
| 			Type: corev1.SecretTypeBasicAuth, | ||||
| 			Data: expectedSecretData, | ||||
| 		}, | ||||
| 		opItem: map[string]string{ | ||||
| 			userKey: username, | ||||
| 			passKey: password, | ||||
| 		}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		testName: "Custom secret type", | ||||
| 		customResource: &onepasswordv1.OnePasswordItem{ | ||||
| @@ -206,6 +260,7 @@ var tests = []testReconcileItem{ | ||||
| 			Spec: onepasswordv1.OnePasswordItemSpec{ | ||||
| 				ItemPath: itemPath, | ||||
| 			}, | ||||
| 			Type: "custom", | ||||
| 		}, | ||||
| 		existingSecret: nil, | ||||
| 		expectedError:  nil, | ||||
| @@ -217,6 +272,51 @@ var tests = []testReconcileItem{ | ||||
| 					op.VersionAnnotation: fmt.Sprint(version), | ||||
| 				}, | ||||
| 			}, | ||||
| 			Type: corev1.SecretType("custom"), | ||||
| 			Data: expectedSecretData, | ||||
| 		}, | ||||
| 		opItem: map[string]string{ | ||||
| 			userKey: username, | ||||
| 			passKey: password, | ||||
| 		}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		testName: "Error if secret type is changed", | ||||
| 		customResource: &onepasswordv1.OnePasswordItem{ | ||||
| 			TypeMeta: metav1.TypeMeta{ | ||||
| 				Kind:       onePasswordItemKind, | ||||
| 				APIVersion: onePasswordItemAPIVersion, | ||||
| 			}, | ||||
| 			ObjectMeta: metav1.ObjectMeta{ | ||||
| 				Name:      name, | ||||
| 				Namespace: namespace, | ||||
| 			}, | ||||
| 			Spec: onepasswordv1.OnePasswordItemSpec{ | ||||
| 				ItemPath: itemPath, | ||||
| 			}, | ||||
| 			Type: "custom", | ||||
| 		}, | ||||
| 		existingSecret: &corev1.Secret{ | ||||
| 			ObjectMeta: metav1.ObjectMeta{ | ||||
| 				Name:      name, | ||||
| 				Namespace: namespace, | ||||
| 				Annotations: map[string]string{ | ||||
| 					op.VersionAnnotation: fmt.Sprint(version), | ||||
| 				}, | ||||
| 			}, | ||||
| 			Type: corev1.SecretTypeOpaque, | ||||
| 			Data: expectedSecretData, | ||||
| 		}, | ||||
| 		expectedError: kubernetessecrets.ErrCannotUpdateSecretType, | ||||
| 		expectedResultSecret: &corev1.Secret{ | ||||
| 			ObjectMeta: metav1.ObjectMeta{ | ||||
| 				Name:      name, | ||||
| 				Namespace: namespace, | ||||
| 				Annotations: map[string]string{ | ||||
| 					op.VersionAnnotation: fmt.Sprint(version), | ||||
| 				}, | ||||
| 			}, | ||||
| 			Type: corev1.SecretTypeOpaque, | ||||
| 			Data: expectedSecretData, | ||||
| 		}, | ||||
| 		opItem: map[string]string{ | ||||
|   | ||||
| @@ -7,13 +7,17 @@ import ( | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
|  | ||||
| 	"reflect" | ||||
|  | ||||
| 	errs "errors" | ||||
|  | ||||
| 	"github.com/1Password/connect-sdk-go/onepassword" | ||||
|  | ||||
| 	"github.com/1Password/onepassword-operator/pkg/utils" | ||||
| 	corev1 "k8s.io/api/core/v1" | ||||
| 	"k8s.io/apimachinery/pkg/api/errors" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/types" | ||||
| 	"reflect" | ||||
| 	kubeValidate "k8s.io/apimachinery/pkg/util/validation" | ||||
|  | ||||
| 	kubernetesClient "sigs.k8s.io/controller-runtime/pkg/client" | ||||
| @@ -27,20 +31,17 @@ const restartAnnotation = OnepasswordPrefix + "/last-restarted" | ||||
| const ItemPathAnnotation = OnepasswordPrefix + "/item-path" | ||||
| const RestartDeploymentsAnnotation = OnepasswordPrefix + "/auto-restart" | ||||
|  | ||||
| var ErrCannotUpdateSecretType = errs.New("Cannot change secret type. Secret type is immutable") | ||||
|  | ||||
| var log = logf.Log | ||||
|  | ||||
| func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretName, namespace string, item *onepassword.Item, autoRestart string, labels map[string]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 { | ||||
| @@ -49,7 +50,9 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa | ||||
| 		} | ||||
| 		secretAnnotations[RestartDeploymentsAnnotation] = autoRestart | ||||
| 	} | ||||
| 	secret := BuildKubernetesSecretFromOnePasswordItem(secretName, namespace, secretAnnotations, labels, *item) | ||||
|  | ||||
| 	// "Opaque" and "" secret types are treated the same by Kubernetes. | ||||
| 	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) | ||||
| @@ -60,7 +63,14 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if ! reflect.DeepEqual(currentSecret.Annotations, secretAnnotations) || ! reflect.DeepEqual(currentSecret.Labels, labels) { | ||||
| 	currentAnnotations := currentSecret.Annotations | ||||
| 	currentLabels := currentSecret.Labels | ||||
| 	currentSecretType := string(currentSecret.Type) | ||||
| 	if !reflect.DeepEqual(currentSecretType, secretType) { | ||||
| 		return ErrCannotUpdateSecretType | ||||
| 	} | ||||
|  | ||||
| 	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 | ||||
| @@ -72,19 +82,26 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func BuildKubernetesSecretFromOnePasswordItem(name, namespace string, annotations map[string]string, labels map[string]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, | ||||
| 			Name:            formatSecretName(name), | ||||
| 			Namespace:       namespace, | ||||
| 			Annotations:     annotations, | ||||
| 			Labels:          labels, | ||||
| 			OwnerReferences: ownerRefs, | ||||
| 		}, | ||||
| 		Data: BuildKubernetesSecretData(item.Fields), | ||||
| 		Data: BuildKubernetesSecretData(item.Fields, item.Files), | ||||
| 		Type: corev1.SecretType(secretType), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func BuildKubernetesSecretData(fields []*onepassword.ItemField) map[string][]byte { | ||||
| func BuildKubernetesSecretData(fields []*onepassword.ItemField, files []*onepassword.File) map[string][]byte { | ||||
| 	secretData := map[string][]byte{} | ||||
| 	for i := 0; i < len(fields); i++ { | ||||
| 		if fields[i].Value != "" { | ||||
| @@ -92,6 +109,23 @@ func BuildKubernetesSecretData(fields []*onepassword.ItemField) map[string][]byt | ||||
| 			secretData[key] = []byte(fields[i].Value) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// populate unpopulated fields from files | ||||
| 	for _, file := range files { | ||||
| 		content, err := file.Content() | ||||
| 		if err != nil { | ||||
| 			log.Error(err, "Could not load contents of file %s", file.Name) | ||||
| 			continue | ||||
| 		} | ||||
| 		if content != nil { | ||||
| 			key := file.Name | ||||
| 			if secretData[key] == nil { | ||||
| 				secretData[key] = content | ||||
| 			} else { | ||||
| 				log.Info(fmt.Sprintf("File '%s' ignored because of a field with the same name", file.Name)) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return secretData | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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,10 +33,9 @@ func TestCreateKubernetesSecretFromOnePasswordItem(t *testing.T) { | ||||
|  | ||||
| 	kubeClient := fake.NewFakeClient() | ||||
| 	secretLabels := map[string]string{} | ||||
| 	secretAnnotations := map[string]string{ | ||||
| 		"testAnnotation": "exists", | ||||
| 	} | ||||
| 	err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretAnnotations) | ||||
| 	secretType := "" | ||||
|  | ||||
| 	err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Unexpected error: %v", err) | ||||
| 	} | ||||
| @@ -47,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) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -65,8 +106,10 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) { | ||||
|  | ||||
| 	kubeClient := fake.NewFakeClient() | ||||
| 	secretLabels := map[string]string{} | ||||
| 	secretAnnotations := map[string]string{} | ||||
| 	err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretAnnotations) | ||||
| 	secretType := "" | ||||
|  | ||||
| 	err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Unexpected error: %v", err) | ||||
| 	} | ||||
| @@ -77,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, secretAnnotations) | ||||
| 	err = CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &newItem, restartDeploymentAnnotation, secretLabels, secretType, nil) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Unexpected error: %v", err) | ||||
| 	} | ||||
| @@ -93,7 +136,7 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) { | ||||
| func TestBuildKubernetesSecretData(t *testing.T) { | ||||
| 	fields := generateFields(5) | ||||
|  | ||||
| 	secretData := BuildKubernetesSecretData(fields) | ||||
| 	secretData := BuildKubernetesSecretData(fields, nil) | ||||
| 	if len(secretData) != len(fields) { | ||||
| 		t.Errorf("Unexpected number of secret fields returned. Expected 3, got %v", len(secretData)) | ||||
| 	} | ||||
| @@ -111,8 +154,9 @@ func TestBuildKubernetesSecretFromOnePasswordItem(t *testing.T) { | ||||
| 	item := onepassword.Item{} | ||||
| 	item.Fields = generateFields(5) | ||||
| 	labels := map[string]string{} | ||||
| 	secretType := "" | ||||
|  | ||||
| 	kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels, 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) | ||||
| 	} | ||||
| @@ -134,6 +178,7 @@ func TestBuildKubernetesSecretFixesInvalidLabels(t *testing.T) { | ||||
| 	} | ||||
| 	labels := map[string]string{} | ||||
| 	item := onepassword.Item{} | ||||
| 	secretType := "" | ||||
|  | ||||
| 	item.Fields = []*onepassword.ItemField{ | ||||
| 		{ | ||||
| @@ -146,7 +191,7 @@ func TestBuildKubernetesSecretFixesInvalidLabels(t *testing.T) { | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels,  item) | ||||
| 	kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels, secretType, item, nil) | ||||
|  | ||||
| 	// Assert Secret's meta.name was fixed | ||||
| 	if kubeSecret.Name != expectedName { | ||||
| @@ -164,6 +209,36 @@ func TestBuildKubernetesSecretFixesInvalidLabels(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestCreateKubernetesTLSSecretFromOnePasswordItem(t *testing.T) { | ||||
| 	secretName := "tls-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 := "kubernetes.io/tls" | ||||
|  | ||||
| 	err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Unexpected error: %v", err) | ||||
| 	} | ||||
| 	createdSecret := &corev1.Secret{} | ||||
| 	err = kubeClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: namespace}, createdSecret) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Secret was not created: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	if createdSecret.Type != corev1.SecretTypeTLS { | ||||
| 		t.Errorf("Expected secretType to be of tyype corev1.SecretTypeTLS, got %s", string(createdSecret.Type)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func compareAnnotationsToItem(annotations map[string]string, item onepassword.Item, t *testing.T) { | ||||
| 	actualVaultId, actualItemId, err := ParseVaultIdAndItemIdFromPath(annotations[ItemPathAnnotation]) | ||||
| 	if err != nil { | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import ( | ||||
| type TestClient struct { | ||||
| 	GetVaultsFunc        func() ([]onepassword.Vault, error) | ||||
| 	GetVaultsByTitleFunc func(title string) ([]onepassword.Vault, error) | ||||
| 	GetVaultFunc         func(uuid string) (*onepassword.Vault, error) | ||||
| 	GetItemFunc          func(uuid string, vaultUUID string) (*onepassword.Item, error) | ||||
| 	GetItemsFunc         func(vaultUUID string) ([]onepassword.Item, error) | ||||
| 	GetItemsByTitleFunc  func(title string, vaultUUID string) ([]onepassword.Item, error) | ||||
| @@ -14,11 +15,14 @@ type TestClient struct { | ||||
| 	CreateItemFunc       func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) | ||||
| 	UpdateItemFunc       func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) | ||||
| 	DeleteItemFunc       func(item *onepassword.Item, vaultUUID string) error | ||||
| 	GetFileFunc          func(uuid string, itemUUID string, vaultUUID string) (*onepassword.File, error) | ||||
| 	GetFileContentFunc   func(file *onepassword.File) ([]byte, error) | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	GetGetVaultsFunc       func() ([]onepassword.Vault, error) | ||||
| 	DoGetVaultsByTitleFunc func(title string) ([]onepassword.Vault, error) | ||||
| 	DoGetVaultFunc         func(uuid string) (*onepassword.Vault, error) | ||||
| 	GetGetItemFunc         func(uuid string, vaultUUID string) (*onepassword.Item, error) | ||||
| 	DoGetItemsByTitleFunc  func(title string, vaultUUID string) ([]onepassword.Item, error) | ||||
| 	DoGetItemByTitleFunc   func(title string, vaultUUID string) (*onepassword.Item, error) | ||||
| @@ -26,6 +30,8 @@ var ( | ||||
| 	DoDeleteItemFunc       func(item *onepassword.Item, vaultUUID string) error | ||||
| 	DoGetItemsFunc         func(vaultUUID string) ([]onepassword.Item, error) | ||||
| 	DoUpdateItemFunc       func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) | ||||
| 	DoGetFileFunc          func(uuid string, itemUUID string, vaultUUID string) (*onepassword.File, error) | ||||
| 	DoGetFileContentFunc   func(file *onepassword.File) ([]byte, error) | ||||
| ) | ||||
|  | ||||
| // Do is the mock client's `Do` func | ||||
| @@ -37,6 +43,10 @@ func (m *TestClient) GetVaultsByTitle(title string) ([]onepassword.Vault, error) | ||||
| 	return DoGetVaultsByTitleFunc(title) | ||||
| } | ||||
|  | ||||
| func (m *TestClient) GetVault(uuid string) (*onepassword.Vault, error) { | ||||
| 	return DoGetVaultFunc(uuid) | ||||
| } | ||||
|  | ||||
| func (m *TestClient) GetItem(uuid string, vaultUUID string) (*onepassword.Item, error) { | ||||
| 	return GetGetItemFunc(uuid, vaultUUID) | ||||
| } | ||||
| @@ -64,3 +74,11 @@ func (m *TestClient) DeleteItem(item *onepassword.Item, vaultUUID string) error | ||||
| func (m *TestClient) UpdateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) { | ||||
| 	return DoUpdateItemFunc(item, vaultUUID) | ||||
| } | ||||
|  | ||||
| func (m *TestClient) GetFile(uuid string, itemUUID string, vaultUUID string) (*onepassword.File, error) { | ||||
| 	return DoGetFileFunc(uuid, itemUUID, vaultUUID) | ||||
| } | ||||
|  | ||||
| func (m *TestClient) GetFileContent(file *onepassword.File) ([]byte, error) { | ||||
| 	return DoGetFileContentFunc(file) | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| package onepassword | ||||
|  | ||||
| import corev1 "k8s.io/api/core/v1" | ||||
| import ( | ||||
| 	corev1 "k8s.io/api/core/v1" | ||||
| ) | ||||
|  | ||||
| func AreContainersUsingSecrets(containers []corev1.Container, secrets map[string]*corev1.Secret) bool { | ||||
| 	for i := 0; i < len(containers); i++ { | ||||
| @@ -13,6 +15,15 @@ func AreContainersUsingSecrets(containers []corev1.Container, secrets map[string | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		envFromVariables := containers[i].EnvFrom | ||||
| 		for j := 0; j < len(envFromVariables); j++ { | ||||
| 			if envFromVariables[j].SecretRef != nil { | ||||
| 				_, ok := secrets[envFromVariables[j].SecretRef.Name] | ||||
| 				if ok { | ||||
| 					return true | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
| @@ -28,6 +39,15 @@ func AppendUpdatedContainerSecrets(containers []corev1.Container, secrets map[st | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		envFromVariables := containers[i].EnvFrom | ||||
| 		for j := 0; j < len(envFromVariables); j++ { | ||||
| 			if envFromVariables[j].SecretRef != nil { | ||||
| 				secret, ok := secrets[envFromVariables[j].SecretRef.LocalObjectReference.Name] | ||||
| 				if ok { | ||||
| 					updatedDeploymentSecrets[secret.Name] = secret | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return updatedDeploymentSecrets | ||||
| } | ||||
|   | ||||
| @@ -4,9 +4,10 @@ import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	corev1 "k8s.io/api/core/v1" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| ) | ||||
|  | ||||
| func TestAreContainersUsingSecrets(t *testing.T) { | ||||
| func TestAreContainersUsingSecretsFromEnv(t *testing.T) { | ||||
| 	secretNamesToSearch := map[string]*corev1.Secret{ | ||||
| 		"onepassword-database-secret": &corev1.Secret{}, | ||||
| 		"onepassword-api-key":         &corev1.Secret{}, | ||||
| @@ -18,7 +19,26 @@ func TestAreContainersUsingSecrets(t *testing.T) { | ||||
| 		"some_other_key", | ||||
| 	} | ||||
|  | ||||
| 	containers := generateContainers(containerSecretNames) | ||||
| 	containers := generateContainersWithSecretRefsFromEnv(containerSecretNames) | ||||
|  | ||||
| 	if !AreContainersUsingSecrets(containers, secretNamesToSearch) { | ||||
| 		t.Errorf("Expected that containers were using secrets but they were not detected.") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestAreContainersUsingSecretsFromEnvFrom(t *testing.T) { | ||||
| 	secretNamesToSearch := map[string]*corev1.Secret{ | ||||
| 		"onepassword-database-secret": {}, | ||||
| 		"onepassword-api-key":         {}, | ||||
| 	} | ||||
|  | ||||
| 	containerSecretNames := []string{ | ||||
| 		"onepassword-database-secret", | ||||
| 		"onepassword-api-key", | ||||
| 		"some_other_key", | ||||
| 	} | ||||
|  | ||||
| 	containers := generateContainersWithSecretRefsFromEnvFrom(containerSecretNames) | ||||
|  | ||||
| 	if !AreContainersUsingSecrets(containers, secretNamesToSearch) { | ||||
| 		t.Errorf("Expected that containers were using secrets but they were not detected.") | ||||
| @@ -27,17 +47,39 @@ func TestAreContainersUsingSecrets(t *testing.T) { | ||||
|  | ||||
| func TestAreContainersNotUsingSecrets(t *testing.T) { | ||||
| 	secretNamesToSearch := map[string]*corev1.Secret{ | ||||
| 		"onepassword-database-secret": &corev1.Secret{}, | ||||
| 		"onepassword-api-key":         &corev1.Secret{}, | ||||
| 		"onepassword-database-secret": {}, | ||||
| 		"onepassword-api-key":         {}, | ||||
| 	} | ||||
|  | ||||
| 	containerSecretNames := []string{ | ||||
| 		"some_other_key", | ||||
| 	} | ||||
|  | ||||
| 	containers := generateContainers(containerSecretNames) | ||||
| 	containers := generateContainersWithSecretRefsFromEnv(containerSecretNames) | ||||
|  | ||||
| 	if AreContainersUsingSecrets(containers, secretNamesToSearch) { | ||||
| 		t.Errorf("Expected that containers were not using secrets but they were detected.") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestAppendUpdatedContainerSecretsParsesEnvFromEnv(t *testing.T) { | ||||
| 	secretNamesToSearch := map[string]*corev1.Secret{ | ||||
| 		"onepassword-database-secret": {}, | ||||
| 		"onepassword-api-key":         {ObjectMeta: metav1.ObjectMeta{Name: "onepassword-api-key"}}, | ||||
| 	} | ||||
|  | ||||
| 	containerSecretNames := []string{ | ||||
| 		"onepassword-api-key", | ||||
| 	} | ||||
|  | ||||
| 	containers := generateContainersWithSecretRefsFromEnvFrom(containerSecretNames) | ||||
|  | ||||
| 	updatedDeploymentSecrets := map[string]*corev1.Secret{} | ||||
| 	updatedDeploymentSecrets = AppendUpdatedContainerSecrets(containers, secretNamesToSearch, updatedDeploymentSecrets) | ||||
|  | ||||
| 	secretKeyName := "onepassword-api-key" | ||||
|  | ||||
| 	if updatedDeploymentSecrets[secretKeyName] != secretNamesToSearch[secretKeyName] { | ||||
| 		t.Errorf("Expected that updated Secret from envfrom is found.") | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -39,7 +39,7 @@ func TestIsDeploymentUsingSecretsUsingContainers(t *testing.T) { | ||||
| 	} | ||||
|  | ||||
| 	deployment := &appsv1.Deployment{} | ||||
| 	deployment.Spec.Template.Spec.Containers = generateContainers(containerSecretNames) | ||||
| 	deployment.Spec.Template.Spec.Containers = generateContainersWithSecretRefsFromEnv(containerSecretNames) | ||||
| 	if !IsDeploymentUsingSecrets(deployment, secretNamesToSearch) { | ||||
| 		t.Errorf("Expected that deployment was using secrets but they were not detected.") | ||||
| 	} | ||||
|   | ||||
| @@ -30,6 +30,14 @@ func GetOnePasswordItemByPath(opConnectClient connect.Client, path string) (*one | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	for _, file := range item.Files { | ||||
| 		_, err := opConnectClient.GetFileContent(file) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return item, nil | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -17,8 +17,7 @@ func generateVolumes(names []string) []corev1.Volume { | ||||
| 	} | ||||
| 	return volumes | ||||
| } | ||||
|  | ||||
| func generateContainers(names []string) []corev1.Container { | ||||
| func generateContainersWithSecretRefsFromEnv(names []string) []corev1.Container { | ||||
| 	containers := []corev1.Container{} | ||||
| 	for i := 0; i < len(names); i++ { | ||||
| 		container := corev1.Container{ | ||||
| @@ -40,3 +39,16 @@ func generateContainers(names []string) []corev1.Container { | ||||
| 	} | ||||
| 	return containers | ||||
| } | ||||
|  | ||||
| func generateContainersWithSecretRefsFromEnvFrom(names []string) []corev1.Container { | ||||
| 	containers := []corev1.Container{} | ||||
| 	for i := 0; i < len(names); i++ { | ||||
| 		container := corev1.Container{ | ||||
| 			EnvFrom: []corev1.EnvFromSource{ | ||||
| 				{SecretRef: &corev1.SecretEnvSource{LocalObjectReference: corev1.LocalObjectReference{Name: names[i]}}}, | ||||
| 			}, | ||||
| 		} | ||||
| 		containers = append(containers, container) | ||||
| 	} | ||||
| 	return containers | ||||
| } | ||||
|   | ||||
| @@ -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" | ||||
|  | ||||
| @@ -116,23 +118,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 { | ||||
| 			return nil, fmt.Errorf("Failed to retrieve item: %v", err) | ||||
| 			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, *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) | ||||
| 			} | ||||
| @@ -176,6 +192,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 | ||||
|   | ||||
							
								
								
									
										21
									
								
								vendor/github.com/1Password/connect-sdk-go/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/1Password/connect-sdk-go/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| MIT License | ||||
|  | ||||
| Copyright (c) 2021 1Password | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
							
								
								
									
										219
									
								
								vendor/github.com/1Password/connect-sdk-go/connect/client.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										219
									
								
								vendor/github.com/1Password/connect-sdk-go/connect/client.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -3,6 +3,7 @@ package connect | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| @@ -10,11 +11,12 @@ import ( | ||||
| 	"net/url" | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/1Password/connect-sdk-go/onepassword" | ||||
| 	opentracing "github.com/opentracing/opentracing-go" | ||||
| 	"github.com/opentracing/opentracing-go/ext" | ||||
| 	jaegerClientConfig "github.com/uber/jaeger-client-go/config" | ||||
| 	"github.com/uber/jaeger-client-go/zipkin" | ||||
|  | ||||
| 	"github.com/1Password/connect-sdk-go/onepassword" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| @@ -24,6 +26,7 @@ const ( | ||||
| // Client Represents an available 1Password Connect API to connect to | ||||
| type Client interface { | ||||
| 	GetVaults() ([]onepassword.Vault, error) | ||||
| 	GetVault(uuid string) (*onepassword.Vault, error) | ||||
| 	GetVaultsByTitle(uuid string) ([]onepassword.Vault, error) | ||||
| 	GetItem(uuid string, vaultUUID string) (*onepassword.Item, error) | ||||
| 	GetItems(vaultUUID string) ([]onepassword.Item, error) | ||||
| @@ -32,6 +35,8 @@ type Client interface { | ||||
| 	CreateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) | ||||
| 	UpdateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) | ||||
| 	DeleteItem(item *onepassword.Item, vaultUUID string) error | ||||
| 	GetFile(fileUUID string, itemUUID string, vaultUUID string) (*onepassword.File, error) | ||||
| 	GetFileContent(file *onepassword.File) ([]byte, error) | ||||
| } | ||||
|  | ||||
| type httpClient interface { | ||||
| @@ -112,23 +117,41 @@ func (rs *restClient) GetVaults() ([]onepassword.Vault, error) { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if response.StatusCode != http.StatusOK { | ||||
| 		return nil, fmt.Errorf("Unable to retrieve vaults. Receieved %q for %q", response.Status, vaultURL) | ||||
| 	} | ||||
|  | ||||
| 	body, err := ioutil.ReadAll(response.Body) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	vaults := []onepassword.Vault{} | ||||
| 	if err := json.Unmarshal(body, &vaults); err != nil { | ||||
| 	var vaults []onepassword.Vault | ||||
| 	if err := parseResponse(response, http.StatusOK, &vaults); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return vaults, nil | ||||
| } | ||||
|  | ||||
| // GetVaults Get a list of all available vaults | ||||
| func (rs *restClient) GetVault(uuid string) (*onepassword.Vault, error) { | ||||
| 	if uuid == "" { | ||||
| 		return nil, errors.New("no uuid provided") | ||||
| 	} | ||||
|  | ||||
| 	span := rs.tracer.StartSpan("GetVault") | ||||
| 	defer span.Finish() | ||||
|  | ||||
| 	vaultURL := fmt.Sprintf("/v1/vaults/%s", uuid) | ||||
| 	request, err := rs.buildRequest(http.MethodGet, vaultURL, http.NoBody, span) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	response, err := rs.client.Do(request) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	var vault onepassword.Vault | ||||
| 	if err := parseResponse(response, http.StatusOK, &vault); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return &vault, nil | ||||
| } | ||||
|  | ||||
| func (rs *restClient) GetVaultsByTitle(title string) ([]onepassword.Vault, error) { | ||||
| 	span := rs.tracer.StartSpan("GetVaultsByTitle") | ||||
| 	defer span.Finish() | ||||
| @@ -145,17 +168,8 @@ func (rs *restClient) GetVaultsByTitle(title string) ([]onepassword.Vault, error | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if response.StatusCode != http.StatusOK { | ||||
| 		return nil, fmt.Errorf("Unable to retrieve vaults. Receieved %q for %q", response.Status, itemURL) | ||||
| 	} | ||||
|  | ||||
| 	body, err := ioutil.ReadAll(response.Body) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	vaults := []onepassword.Vault{} | ||||
| 	if err := json.Unmarshal(body, &vaults); err != nil { | ||||
| 	var vaults []onepassword.Vault | ||||
| 	if err := parseResponse(response, http.StatusOK, &vaults); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| @@ -177,18 +191,8 @@ func (rs *restClient) GetItem(uuid string, vaultUUID string) (*onepassword.Item, | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if response.StatusCode != http.StatusOK { | ||||
| 		return nil, fmt.Errorf("Unable to retrieve item. Receieved %q for %q", response.Status, itemURL) | ||||
| 	} | ||||
|  | ||||
| 	body, err := ioutil.ReadAll(response.Body) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	item := onepassword.Item{} | ||||
| 	if err := json.Unmarshal(body, &item); err != nil { | ||||
| 	var item onepassword.Item | ||||
| 	if err := parseResponse(response, http.StatusOK, &item); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| @@ -226,17 +230,8 @@ func (rs *restClient) GetItemsByTitle(title string, vaultUUID string) ([]onepass | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if response.StatusCode != http.StatusOK { | ||||
| 		return nil, fmt.Errorf("Unable to retrieve item. Receieved %q for %q", response.Status, itemURL) | ||||
| 	} | ||||
|  | ||||
| 	body, err := ioutil.ReadAll(response.Body) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	items := []onepassword.Item{} | ||||
| 	if err := json.Unmarshal(body, &items); err != nil { | ||||
| 	var items []onepassword.Item | ||||
| 	if err := parseResponse(response, http.StatusOK, &items); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| @@ -258,17 +253,8 @@ func (rs *restClient) GetItems(vaultUUID string) ([]onepassword.Item, error) { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if response.StatusCode != http.StatusOK { | ||||
| 		return nil, fmt.Errorf("Unable to retrieve items. Receieved %q for %q", response.Status, itemURL) | ||||
| 	} | ||||
|  | ||||
| 	body, err := ioutil.ReadAll(response.Body) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	items := []onepassword.Item{} | ||||
| 	if err := json.Unmarshal(body, &items); err != nil { | ||||
| 	var items []onepassword.Item | ||||
| 	if err := parseResponse(response, http.StatusOK, &items); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| @@ -296,17 +282,8 @@ func (rs *restClient) CreateItem(item *onepassword.Item, vaultUUID string) (*one | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if response.StatusCode != http.StatusOK { | ||||
| 		return nil, fmt.Errorf("Unable to create item. Receieved %q for %q", response.Status, itemURL) | ||||
| 	} | ||||
|  | ||||
| 	body, err := ioutil.ReadAll(response.Body) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	newItem := onepassword.Item{} | ||||
| 	if err := json.Unmarshal(body, &newItem); err != nil { | ||||
| 	var newItem onepassword.Item | ||||
| 	if err := parseResponse(response, http.StatusOK, &newItem); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| @@ -334,17 +311,8 @@ func (rs *restClient) UpdateItem(item *onepassword.Item, vaultUUID string) (*one | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if response.StatusCode != http.StatusOK { | ||||
| 		return nil, fmt.Errorf("Unable to update item. Receieved %q for %q", response.Status, itemURL) | ||||
| 	} | ||||
|  | ||||
| 	body, err := ioutil.ReadAll(response.Body) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	newItem := onepassword.Item{} | ||||
| 	if err := json.Unmarshal(body, &newItem); err != nil { | ||||
| 	var newItem onepassword.Item | ||||
| 	if err := parseResponse(response, http.StatusOK, &newItem); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| @@ -367,13 +335,73 @@ func (rs *restClient) DeleteItem(item *onepassword.Item, vaultUUID string) error | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if response.StatusCode != http.StatusNoContent { | ||||
| 		return fmt.Errorf("Unable to retrieve item. Receieved %q for %q", response.Status, itemURL) | ||||
| 	if err := parseResponse(response, http.StatusNoContent, nil); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GetFile Get a specific File in a specified item. | ||||
| // This does not include the file contents. Call GetFileContent() to load the file's content. | ||||
| func (rs *restClient) GetFile(uuid string, itemUUID string, vaultUUID string) (*onepassword.File, error) { | ||||
| 	span := rs.tracer.StartSpan("GetFile") | ||||
| 	defer span.Finish() | ||||
|  | ||||
| 	itemURL := fmt.Sprintf("/v1/vaults/%s/items/%s/files/%s", vaultUUID, itemUUID, uuid) | ||||
| 	request, err := rs.buildRequest(http.MethodGet, itemURL, http.NoBody, span) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	response, err := rs.client.Do(request) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if err := expectMinimumConnectVersion(response, version{1, 3, 0}); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	var file onepassword.File | ||||
| 	if err := parseResponse(response, http.StatusOK, &file); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return &file, nil | ||||
| } | ||||
|  | ||||
| // GetFileContent retrieves the file's content. | ||||
| // If the file's content have previously been fetched, those contents are returned without making another request. | ||||
| func (rs *restClient) GetFileContent(file *onepassword.File) ([]byte, error) { | ||||
| 	if content, err := file.Content(); err == nil { | ||||
| 		return content, nil | ||||
| 	} | ||||
|  | ||||
| 	span := rs.tracer.StartSpan("GetFileContent") | ||||
| 	defer span.Finish() | ||||
|  | ||||
| 	request, err := rs.buildRequest(http.MethodGet, file.ContentPath, http.NoBody, span) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	response, err := rs.client.Do(request) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if err := expectMinimumConnectVersion(response, version{1, 3, 0}); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	content, err := readResponseBody(response, http.StatusOK) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	file.SetContent(content) | ||||
| 	return content, nil | ||||
| } | ||||
|  | ||||
| func (rs *restClient) buildRequest(method string, path string, body io.Reader, span opentracing.Span) (*http.Request, error) { | ||||
| 	url := fmt.Sprintf("%s%s", rs.URL, path) | ||||
|  | ||||
| @@ -394,3 +422,32 @@ func (rs *restClient) buildRequest(method string, path string, body io.Reader, s | ||||
|  | ||||
| 	return request, nil | ||||
| } | ||||
|  | ||||
| func parseResponse(resp *http.Response, expectedStatusCode int, result interface{}) error { | ||||
| 	body, err := readResponseBody(resp, expectedStatusCode) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if result != nil { | ||||
| 		if err := json.Unmarshal(body, result); err != nil { | ||||
| 			return fmt.Errorf("decoding response: %s", err) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func readResponseBody(resp *http.Response, expectedStatusCode int) ([]byte, error) { | ||||
| 	defer resp.Body.Close() | ||||
| 	body, err := ioutil.ReadAll(resp.Body) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if resp.StatusCode != expectedStatusCode { | ||||
| 		var errResp *onepassword.Error | ||||
| 		if err := json.Unmarshal(body, &errResp); err != nil { | ||||
| 			return nil, fmt.Errorf("decoding error response: %s", err) | ||||
| 		} | ||||
| 		return nil, errResp | ||||
| 	} | ||||
| 	return body, nil | ||||
| } | ||||
|   | ||||
							
								
								
									
										101
									
								
								vendor/github.com/1Password/connect-sdk-go/connect/version.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										101
									
								
								vendor/github.com/1Password/connect-sdk-go/connect/version.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,5 +1,104 @@ | ||||
| package connect | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // SDKVersion is the latest Semantic Version of the library | ||||
| // Do not rename this variable without changing the regex in the Makefile | ||||
| const SDKVersion = "1.0.1" | ||||
| const SDKVersion = "1.2.0" | ||||
|  | ||||
| const VersionHeaderKey = "1Password-Connect-Version" | ||||
|  | ||||
| // expectMinimumConnectVersion returns an error if the provided minimum version for Connect is lower than the version | ||||
| // reported in the response from Connect. | ||||
| func expectMinimumConnectVersion(resp *http.Response, minimumVersion version) error { | ||||
| 	serverVersion, err := getServerVersion(resp) | ||||
| 	if err != nil { | ||||
| 		// Return gracefully if server version cannot be determined reliably | ||||
| 		return nil | ||||
| 	} | ||||
| 	if !serverVersion.IsGreaterOrEqualThan(minimumVersion) { | ||||
| 		return fmt.Errorf("need at least version %s of Connect for this function, detected version %s. Please update your Connect server", minimumVersion, serverVersion) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func getServerVersion(resp *http.Response) (serverVersion, error) { | ||||
| 	versionHeader := resp.Header.Get(VersionHeaderKey) | ||||
| 	if versionHeader == "" { | ||||
| 		// The last version without the version header was v1.2.0 | ||||
| 		return serverVersion{ | ||||
| 			version:   version{1, 2, 0}, | ||||
| 			orEarlier: true, | ||||
| 		}, nil | ||||
| 	} | ||||
| 	return parseServerVersion(versionHeader) | ||||
| } | ||||
|  | ||||
| type version struct { | ||||
| 	major int | ||||
| 	minor int | ||||
| 	patch int | ||||
| } | ||||
|  | ||||
| // serverVersion describes the version reported by the server. | ||||
| type serverVersion struct { | ||||
| 	version | ||||
| 	// orEarlier is true if the version is derived from the lack of a version header from the server. | ||||
| 	orEarlier bool | ||||
| } | ||||
|  | ||||
| func (v version) String() string { | ||||
| 	return fmt.Sprintf("%d.%d.%d", v.major, v.minor, v.patch) | ||||
| } | ||||
|  | ||||
| func (v serverVersion) String() string { | ||||
| 	if v.orEarlier { | ||||
| 		return v.version.String() + " (or earlier)" | ||||
| 	} | ||||
| 	return v.version.String() | ||||
| } | ||||
|  | ||||
| // IsGreaterOrEqualThan returns true if the lefthand-side version is equal to or or a higher version than the provided | ||||
| // minimum according to the semantic versioning rules. | ||||
| func (v version) IsGreaterOrEqualThan(min version) bool { | ||||
| 	if v.major != min.major { | ||||
| 		// Different major version | ||||
| 		return v.major > min.major | ||||
| 	} | ||||
|  | ||||
| 	if v.minor != min.minor { | ||||
| 		// Same major, but different minor version | ||||
| 		return v.minor > min.minor | ||||
| 	} | ||||
|  | ||||
| 	// Same major and minor version | ||||
| 	return v.patch >= min.patch | ||||
| } | ||||
|  | ||||
| func parseServerVersion(v string) (serverVersion, error) { | ||||
| 	spl := strings.Split(v, ".") | ||||
| 	if len(spl) != 3 { | ||||
| 		return serverVersion{}, errors.New("wrong length") | ||||
| 	} | ||||
| 	var res [3]int | ||||
| 	for i := range res { | ||||
| 		tmp, err := strconv.Atoi(spl[i]) | ||||
| 		if err != nil { | ||||
| 			return serverVersion{}, err | ||||
| 		} | ||||
| 		res[i] = tmp | ||||
| 	} | ||||
| 	return serverVersion{ | ||||
| 		version: version{ | ||||
| 			major: res[0], | ||||
| 			minor: res[1], | ||||
| 			patch: res[2], | ||||
| 		}, | ||||
| 	}, nil | ||||
| } | ||||
|   | ||||
							
								
								
									
										21
									
								
								vendor/github.com/1Password/connect-sdk-go/onepassword/errors.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/1Password/connect-sdk-go/onepassword/errors.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| package onepassword | ||||
|  | ||||
| import "fmt" | ||||
|  | ||||
| // Error is an error returned by the Connect API. | ||||
| type Error struct { | ||||
| 	StatusCode int    `json:"status"` | ||||
| 	Message    string `json:"message"` | ||||
| } | ||||
|  | ||||
| func (e *Error) Error() string { | ||||
| 	return fmt.Sprintf("status %d: %s", e.StatusCode, e.Message) | ||||
| } | ||||
|  | ||||
| func (e *Error) Is(target error) bool { | ||||
| 	t, ok := target.(*Error) | ||||
| 	if !ok { | ||||
| 		return false | ||||
| 	} | ||||
| 	return t.Message == e.Message && t.StatusCode == e.StatusCode | ||||
| } | ||||
							
								
								
									
										49
									
								
								vendor/github.com/1Password/connect-sdk-go/onepassword/files.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								vendor/github.com/1Password/connect-sdk-go/onepassword/files.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| package onepassword | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| ) | ||||
|  | ||||
| type File struct { | ||||
| 	ID          string       `json:"id"` | ||||
| 	Name        string       `json:"name"` | ||||
| 	Section     *ItemSection `json:"section,omitempty"` | ||||
| 	Size        int          `json:"size"` | ||||
| 	ContentPath string       `json:"content_path"` | ||||
| 	content     []byte | ||||
| } | ||||
|  | ||||
| func (f *File) UnmarshalJSON(data []byte) error { | ||||
| 	var jsonFile struct { | ||||
| 		ID          string       `json:"id"` | ||||
| 		Name        string       `json:"name"` | ||||
| 		Section     *ItemSection `json:"section,omitempty"` | ||||
| 		Size        int          `json:"size"` | ||||
| 		ContentPath string       `json:"content_path"` | ||||
| 		Content     []byte       `json:"content,omitempty"` | ||||
| 	} | ||||
| 	if err := json.Unmarshal(data, &jsonFile); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	f.ID = jsonFile.ID | ||||
| 	f.Name = jsonFile.Name | ||||
| 	f.Section = jsonFile.Section | ||||
| 	f.Size = jsonFile.Size | ||||
| 	f.ContentPath = jsonFile.ContentPath | ||||
| 	f.content = jsonFile.Content | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Content returns the content of the file if they have been loaded and returns an error if they have not been loaded. | ||||
| // Use `client.GetFileContent(file *File)` instead to make sure the content is fetched automatically if not present. | ||||
| func (f *File) Content() ([]byte, error) { | ||||
| 	if f.content == nil { | ||||
| 		return nil, errors.New("file content not loaded") | ||||
| 	} | ||||
| 	return f.content, nil | ||||
| } | ||||
|  | ||||
| func (f *File) SetContent(content []byte) { | ||||
| 	f.content = content | ||||
| } | ||||
							
								
								
									
										4
									
								
								vendor/github.com/1Password/connect-sdk-go/onepassword/items.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								vendor/github.com/1Password/connect-sdk-go/onepassword/items.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -28,6 +28,7 @@ const ( | ||||
| 	Document             ItemCategory = "DOCUMENT" | ||||
| 	EmailAccount         ItemCategory = "EMAIL_ACCOUNT" | ||||
| 	SocialSecurityNumber ItemCategory = "SOCIAL_SECURITY_NUMBER" | ||||
| 	ApiCredential        ItemCategory = "API_CREDENTIAL" | ||||
| 	Custom               ItemCategory = "CUSTOM" | ||||
| ) | ||||
|  | ||||
| @@ -39,7 +40,7 @@ func (ic *ItemCategory) UnmarshalJSON(b []byte) error { | ||||
| 	switch category { | ||||
| 	case Login, Password, Server, Database, CreditCard, Membership, Passport, SoftwareLicense, | ||||
| 		OutdoorLicense, SecureNote, WirelessRouter, BankAccount, DriverLicense, Identity, RewardProgram, | ||||
| 		Document, EmailAccount, SocialSecurityNumber: | ||||
| 		Document, EmailAccount, SocialSecurityNumber, ApiCredential: | ||||
| 		*ic = category | ||||
| 	default: | ||||
| 		*ic = Custom | ||||
| @@ -64,6 +65,7 @@ type Item struct { | ||||
|  | ||||
| 	Sections []*ItemSection `json:"sections,omitempty"` | ||||
| 	Fields   []*ItemField   `json:"fields,omitempty"` | ||||
| 	Files    []*File        `json:"files,omitempty"` | ||||
|  | ||||
| 	LastEditedBy string    `json:"lastEditedBy,omitempty"` | ||||
| 	CreatedAt    time.Time `json:"createdAt,omitempty"` | ||||
|   | ||||
							
								
								
									
										172
									
								
								vendor/github.com/stretchr/testify/assert/assertion_compare.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										172
									
								
								vendor/github.com/stretchr/testify/assert/assertion_compare.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -13,12 +13,42 @@ const ( | ||||
| 	compareGreater | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	intType   = reflect.TypeOf(int(1)) | ||||
| 	int8Type  = reflect.TypeOf(int8(1)) | ||||
| 	int16Type = reflect.TypeOf(int16(1)) | ||||
| 	int32Type = reflect.TypeOf(int32(1)) | ||||
| 	int64Type = reflect.TypeOf(int64(1)) | ||||
|  | ||||
| 	uintType   = reflect.TypeOf(uint(1)) | ||||
| 	uint8Type  = reflect.TypeOf(uint8(1)) | ||||
| 	uint16Type = reflect.TypeOf(uint16(1)) | ||||
| 	uint32Type = reflect.TypeOf(uint32(1)) | ||||
| 	uint64Type = reflect.TypeOf(uint64(1)) | ||||
|  | ||||
| 	float32Type = reflect.TypeOf(float32(1)) | ||||
| 	float64Type = reflect.TypeOf(float64(1)) | ||||
|  | ||||
| 	stringType = reflect.TypeOf("") | ||||
| ) | ||||
|  | ||||
| func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | ||||
| 	obj1Value := reflect.ValueOf(obj1) | ||||
| 	obj2Value := reflect.ValueOf(obj2) | ||||
|  | ||||
| 	// throughout this switch we try and avoid calling .Convert() if possible, | ||||
| 	// as this has a pretty big performance impact | ||||
| 	switch kind { | ||||
| 	case reflect.Int: | ||||
| 		{ | ||||
| 			intobj1 := obj1.(int) | ||||
| 			intobj2 := obj2.(int) | ||||
| 			intobj1, ok := obj1.(int) | ||||
| 			if !ok { | ||||
| 				intobj1 = obj1Value.Convert(intType).Interface().(int) | ||||
| 			} | ||||
| 			intobj2, ok := obj2.(int) | ||||
| 			if !ok { | ||||
| 				intobj2 = obj2Value.Convert(intType).Interface().(int) | ||||
| 			} | ||||
| 			if intobj1 > intobj2 { | ||||
| 				return compareGreater, true | ||||
| 			} | ||||
| @@ -31,8 +61,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | ||||
| 		} | ||||
| 	case reflect.Int8: | ||||
| 		{ | ||||
| 			int8obj1 := obj1.(int8) | ||||
| 			int8obj2 := obj2.(int8) | ||||
| 			int8obj1, ok := obj1.(int8) | ||||
| 			if !ok { | ||||
| 				int8obj1 = obj1Value.Convert(int8Type).Interface().(int8) | ||||
| 			} | ||||
| 			int8obj2, ok := obj2.(int8) | ||||
| 			if !ok { | ||||
| 				int8obj2 = obj2Value.Convert(int8Type).Interface().(int8) | ||||
| 			} | ||||
| 			if int8obj1 > int8obj2 { | ||||
| 				return compareGreater, true | ||||
| 			} | ||||
| @@ -45,8 +81,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | ||||
| 		} | ||||
| 	case reflect.Int16: | ||||
| 		{ | ||||
| 			int16obj1 := obj1.(int16) | ||||
| 			int16obj2 := obj2.(int16) | ||||
| 			int16obj1, ok := obj1.(int16) | ||||
| 			if !ok { | ||||
| 				int16obj1 = obj1Value.Convert(int16Type).Interface().(int16) | ||||
| 			} | ||||
| 			int16obj2, ok := obj2.(int16) | ||||
| 			if !ok { | ||||
| 				int16obj2 = obj2Value.Convert(int16Type).Interface().(int16) | ||||
| 			} | ||||
| 			if int16obj1 > int16obj2 { | ||||
| 				return compareGreater, true | ||||
| 			} | ||||
| @@ -59,8 +101,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | ||||
| 		} | ||||
| 	case reflect.Int32: | ||||
| 		{ | ||||
| 			int32obj1 := obj1.(int32) | ||||
| 			int32obj2 := obj2.(int32) | ||||
| 			int32obj1, ok := obj1.(int32) | ||||
| 			if !ok { | ||||
| 				int32obj1 = obj1Value.Convert(int32Type).Interface().(int32) | ||||
| 			} | ||||
| 			int32obj2, ok := obj2.(int32) | ||||
| 			if !ok { | ||||
| 				int32obj2 = obj2Value.Convert(int32Type).Interface().(int32) | ||||
| 			} | ||||
| 			if int32obj1 > int32obj2 { | ||||
| 				return compareGreater, true | ||||
| 			} | ||||
| @@ -73,8 +121,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | ||||
| 		} | ||||
| 	case reflect.Int64: | ||||
| 		{ | ||||
| 			int64obj1 := obj1.(int64) | ||||
| 			int64obj2 := obj2.(int64) | ||||
| 			int64obj1, ok := obj1.(int64) | ||||
| 			if !ok { | ||||
| 				int64obj1 = obj1Value.Convert(int64Type).Interface().(int64) | ||||
| 			} | ||||
| 			int64obj2, ok := obj2.(int64) | ||||
| 			if !ok { | ||||
| 				int64obj2 = obj2Value.Convert(int64Type).Interface().(int64) | ||||
| 			} | ||||
| 			if int64obj1 > int64obj2 { | ||||
| 				return compareGreater, true | ||||
| 			} | ||||
| @@ -87,8 +141,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | ||||
| 		} | ||||
| 	case reflect.Uint: | ||||
| 		{ | ||||
| 			uintobj1 := obj1.(uint) | ||||
| 			uintobj2 := obj2.(uint) | ||||
| 			uintobj1, ok := obj1.(uint) | ||||
| 			if !ok { | ||||
| 				uintobj1 = obj1Value.Convert(uintType).Interface().(uint) | ||||
| 			} | ||||
| 			uintobj2, ok := obj2.(uint) | ||||
| 			if !ok { | ||||
| 				uintobj2 = obj2Value.Convert(uintType).Interface().(uint) | ||||
| 			} | ||||
| 			if uintobj1 > uintobj2 { | ||||
| 				return compareGreater, true | ||||
| 			} | ||||
| @@ -101,8 +161,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | ||||
| 		} | ||||
| 	case reflect.Uint8: | ||||
| 		{ | ||||
| 			uint8obj1 := obj1.(uint8) | ||||
| 			uint8obj2 := obj2.(uint8) | ||||
| 			uint8obj1, ok := obj1.(uint8) | ||||
| 			if !ok { | ||||
| 				uint8obj1 = obj1Value.Convert(uint8Type).Interface().(uint8) | ||||
| 			} | ||||
| 			uint8obj2, ok := obj2.(uint8) | ||||
| 			if !ok { | ||||
| 				uint8obj2 = obj2Value.Convert(uint8Type).Interface().(uint8) | ||||
| 			} | ||||
| 			if uint8obj1 > uint8obj2 { | ||||
| 				return compareGreater, true | ||||
| 			} | ||||
| @@ -115,8 +181,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | ||||
| 		} | ||||
| 	case reflect.Uint16: | ||||
| 		{ | ||||
| 			uint16obj1 := obj1.(uint16) | ||||
| 			uint16obj2 := obj2.(uint16) | ||||
| 			uint16obj1, ok := obj1.(uint16) | ||||
| 			if !ok { | ||||
| 				uint16obj1 = obj1Value.Convert(uint16Type).Interface().(uint16) | ||||
| 			} | ||||
| 			uint16obj2, ok := obj2.(uint16) | ||||
| 			if !ok { | ||||
| 				uint16obj2 = obj2Value.Convert(uint16Type).Interface().(uint16) | ||||
| 			} | ||||
| 			if uint16obj1 > uint16obj2 { | ||||
| 				return compareGreater, true | ||||
| 			} | ||||
| @@ -129,8 +201,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | ||||
| 		} | ||||
| 	case reflect.Uint32: | ||||
| 		{ | ||||
| 			uint32obj1 := obj1.(uint32) | ||||
| 			uint32obj2 := obj2.(uint32) | ||||
| 			uint32obj1, ok := obj1.(uint32) | ||||
| 			if !ok { | ||||
| 				uint32obj1 = obj1Value.Convert(uint32Type).Interface().(uint32) | ||||
| 			} | ||||
| 			uint32obj2, ok := obj2.(uint32) | ||||
| 			if !ok { | ||||
| 				uint32obj2 = obj2Value.Convert(uint32Type).Interface().(uint32) | ||||
| 			} | ||||
| 			if uint32obj1 > uint32obj2 { | ||||
| 				return compareGreater, true | ||||
| 			} | ||||
| @@ -143,8 +221,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | ||||
| 		} | ||||
| 	case reflect.Uint64: | ||||
| 		{ | ||||
| 			uint64obj1 := obj1.(uint64) | ||||
| 			uint64obj2 := obj2.(uint64) | ||||
| 			uint64obj1, ok := obj1.(uint64) | ||||
| 			if !ok { | ||||
| 				uint64obj1 = obj1Value.Convert(uint64Type).Interface().(uint64) | ||||
| 			} | ||||
| 			uint64obj2, ok := obj2.(uint64) | ||||
| 			if !ok { | ||||
| 				uint64obj2 = obj2Value.Convert(uint64Type).Interface().(uint64) | ||||
| 			} | ||||
| 			if uint64obj1 > uint64obj2 { | ||||
| 				return compareGreater, true | ||||
| 			} | ||||
| @@ -157,8 +241,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | ||||
| 		} | ||||
| 	case reflect.Float32: | ||||
| 		{ | ||||
| 			float32obj1 := obj1.(float32) | ||||
| 			float32obj2 := obj2.(float32) | ||||
| 			float32obj1, ok := obj1.(float32) | ||||
| 			if !ok { | ||||
| 				float32obj1 = obj1Value.Convert(float32Type).Interface().(float32) | ||||
| 			} | ||||
| 			float32obj2, ok := obj2.(float32) | ||||
| 			if !ok { | ||||
| 				float32obj2 = obj2Value.Convert(float32Type).Interface().(float32) | ||||
| 			} | ||||
| 			if float32obj1 > float32obj2 { | ||||
| 				return compareGreater, true | ||||
| 			} | ||||
| @@ -171,8 +261,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | ||||
| 		} | ||||
| 	case reflect.Float64: | ||||
| 		{ | ||||
| 			float64obj1 := obj1.(float64) | ||||
| 			float64obj2 := obj2.(float64) | ||||
| 			float64obj1, ok := obj1.(float64) | ||||
| 			if !ok { | ||||
| 				float64obj1 = obj1Value.Convert(float64Type).Interface().(float64) | ||||
| 			} | ||||
| 			float64obj2, ok := obj2.(float64) | ||||
| 			if !ok { | ||||
| 				float64obj2 = obj2Value.Convert(float64Type).Interface().(float64) | ||||
| 			} | ||||
| 			if float64obj1 > float64obj2 { | ||||
| 				return compareGreater, true | ||||
| 			} | ||||
| @@ -185,8 +281,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | ||||
| 		} | ||||
| 	case reflect.String: | ||||
| 		{ | ||||
| 			stringobj1 := obj1.(string) | ||||
| 			stringobj2 := obj2.(string) | ||||
| 			stringobj1, ok := obj1.(string) | ||||
| 			if !ok { | ||||
| 				stringobj1 = obj1Value.Convert(stringType).Interface().(string) | ||||
| 			} | ||||
| 			stringobj2, ok := obj2.(string) | ||||
| 			if !ok { | ||||
| 				stringobj2 = obj2Value.Convert(stringType).Interface().(string) | ||||
| 			} | ||||
| 			if stringobj1 > stringobj2 { | ||||
| 				return compareGreater, true | ||||
| 			} | ||||
| @@ -240,6 +342,24 @@ func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...inter | ||||
| 	return compareTwoValues(t, e1, e2, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs) | ||||
| } | ||||
|  | ||||
| // Positive asserts that the specified element is positive | ||||
| // | ||||
| //    assert.Positive(t, 1) | ||||
| //    assert.Positive(t, 1.23) | ||||
| func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool { | ||||
| 	zero := reflect.Zero(reflect.TypeOf(e)) | ||||
| 	return compareTwoValues(t, e, zero.Interface(), []CompareType{compareGreater}, "\"%v\" is not positive", msgAndArgs) | ||||
| } | ||||
|  | ||||
| // Negative asserts that the specified element is negative | ||||
| // | ||||
| //    assert.Negative(t, -1) | ||||
| //    assert.Negative(t, -1.23) | ||||
| func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool { | ||||
| 	zero := reflect.Zero(reflect.TypeOf(e)) | ||||
| 	return compareTwoValues(t, e, zero.Interface(), []CompareType{compareLess}, "\"%v\" is not negative", msgAndArgs) | ||||
| } | ||||
|  | ||||
| func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool { | ||||
| 	if h, ok := t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
|   | ||||
							
								
								
									
										97
									
								
								vendor/github.com/stretchr/testify/assert/assertion_format.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										97
									
								
								vendor/github.com/stretchr/testify/assert/assertion_format.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -114,6 +114,24 @@ func Errorf(t TestingT, err error, msg string, args ...interface{}) bool { | ||||
| 	return Error(t, err, append([]interface{}{msg}, args...)...) | ||||
| } | ||||
|  | ||||
| // ErrorAsf asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value. | ||||
| // This is a wrapper for errors.As. | ||||
| func ErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) bool { | ||||
| 	if h, ok := t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return ErrorAs(t, err, target, append([]interface{}{msg}, args...)...) | ||||
| } | ||||
|  | ||||
| // ErrorIsf asserts that at least one of the errors in err's chain matches target. | ||||
| // This is a wrapper for errors.Is. | ||||
| func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool { | ||||
| 	if h, ok := t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return ErrorIs(t, err, target, append([]interface{}{msg}, args...)...) | ||||
| } | ||||
|  | ||||
| // Eventuallyf asserts that given condition will be met in waitFor time, | ||||
| // periodically checking target function each tick. | ||||
| // | ||||
| @@ -321,6 +339,54 @@ func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsil | ||||
| 	return InEpsilonSlice(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...) | ||||
| } | ||||
|  | ||||
| // IsDecreasingf asserts that the collection is decreasing | ||||
| // | ||||
| //    assert.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted") | ||||
| //    assert.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted") | ||||
| //    assert.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted") | ||||
| func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool { | ||||
| 	if h, ok := t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return IsDecreasing(t, object, append([]interface{}{msg}, args...)...) | ||||
| } | ||||
|  | ||||
| // IsIncreasingf asserts that the collection is increasing | ||||
| // | ||||
| //    assert.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted") | ||||
| //    assert.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted") | ||||
| //    assert.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted") | ||||
| func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool { | ||||
| 	if h, ok := t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return IsIncreasing(t, object, append([]interface{}{msg}, args...)...) | ||||
| } | ||||
|  | ||||
| // IsNonDecreasingf asserts that the collection is not decreasing | ||||
| // | ||||
| //    assert.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted") | ||||
| //    assert.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted") | ||||
| //    assert.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted") | ||||
| func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool { | ||||
| 	if h, ok := t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return IsNonDecreasing(t, object, append([]interface{}{msg}, args...)...) | ||||
| } | ||||
|  | ||||
| // IsNonIncreasingf asserts that the collection is not increasing | ||||
| // | ||||
| //    assert.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted") | ||||
| //    assert.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted") | ||||
| //    assert.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted") | ||||
| func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool { | ||||
| 	if h, ok := t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return IsNonIncreasing(t, object, append([]interface{}{msg}, args...)...) | ||||
| } | ||||
|  | ||||
| // IsTypef asserts that the specified objects are of the same type. | ||||
| func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool { | ||||
| 	if h, ok := t.(tHelper); ok { | ||||
| @@ -375,6 +441,17 @@ func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args . | ||||
| 	return LessOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...) | ||||
| } | ||||
|  | ||||
| // Negativef asserts that the specified element is negative | ||||
| // | ||||
| //    assert.Negativef(t, -1, "error message %s", "formatted") | ||||
| //    assert.Negativef(t, -1.23, "error message %s", "formatted") | ||||
| func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) bool { | ||||
| 	if h, ok := t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return Negative(t, e, append([]interface{}{msg}, args...)...) | ||||
| } | ||||
|  | ||||
| // Neverf asserts that the given condition doesn't satisfy in waitFor time, | ||||
| // periodically checking the target function each tick. | ||||
| // | ||||
| @@ -476,6 +553,15 @@ func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg s | ||||
| 	return NotEqualValues(t, expected, actual, append([]interface{}{msg}, args...)...) | ||||
| } | ||||
|  | ||||
| // NotErrorIsf asserts that at none of the errors in err's chain matches target. | ||||
| // This is a wrapper for errors.Is. | ||||
| func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool { | ||||
| 	if h, ok := t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return NotErrorIs(t, err, target, append([]interface{}{msg}, args...)...) | ||||
| } | ||||
|  | ||||
| // NotNilf asserts that the specified object is not nil. | ||||
| // | ||||
| //    assert.NotNilf(t, err, "error message %s", "formatted") | ||||
| @@ -572,6 +658,17 @@ func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg str | ||||
| 	return PanicsWithValue(t, expected, f, append([]interface{}{msg}, args...)...) | ||||
| } | ||||
|  | ||||
| // Positivef asserts that the specified element is positive | ||||
| // | ||||
| //    assert.Positivef(t, 1, "error message %s", "formatted") | ||||
| //    assert.Positivef(t, 1.23, "error message %s", "formatted") | ||||
| func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) bool { | ||||
| 	if h, ok := t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return Positive(t, e, append([]interface{}{msg}, args...)...) | ||||
| } | ||||
|  | ||||
| // Regexpf asserts that a specified regexp matches a string. | ||||
| // | ||||
| //  assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") | ||||
|   | ||||
							
								
								
									
										194
									
								
								vendor/github.com/stretchr/testify/assert/assertion_forward.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										194
									
								
								vendor/github.com/stretchr/testify/assert/assertion_forward.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -204,6 +204,42 @@ func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool { | ||||
| 	return Error(a.t, err, msgAndArgs...) | ||||
| } | ||||
|  | ||||
| // ErrorAs asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value. | ||||
| // This is a wrapper for errors.As. | ||||
| func (a *Assertions) ErrorAs(err error, target interface{}, msgAndArgs ...interface{}) bool { | ||||
| 	if h, ok := a.t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return ErrorAs(a.t, err, target, msgAndArgs...) | ||||
| } | ||||
|  | ||||
| // ErrorAsf asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value. | ||||
| // This is a wrapper for errors.As. | ||||
| func (a *Assertions) ErrorAsf(err error, target interface{}, msg string, args ...interface{}) bool { | ||||
| 	if h, ok := a.t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return ErrorAsf(a.t, err, target, msg, args...) | ||||
| } | ||||
|  | ||||
| // ErrorIs asserts that at least one of the errors in err's chain matches target. | ||||
| // This is a wrapper for errors.Is. | ||||
| func (a *Assertions) ErrorIs(err error, target error, msgAndArgs ...interface{}) bool { | ||||
| 	if h, ok := a.t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return ErrorIs(a.t, err, target, msgAndArgs...) | ||||
| } | ||||
|  | ||||
| // ErrorIsf asserts that at least one of the errors in err's chain matches target. | ||||
| // This is a wrapper for errors.Is. | ||||
| func (a *Assertions) ErrorIsf(err error, target error, msg string, args ...interface{}) bool { | ||||
| 	if h, ok := a.t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return ErrorIsf(a.t, err, target, msg, args...) | ||||
| } | ||||
|  | ||||
| // Errorf asserts that a function returned an error (i.e. not `nil`). | ||||
| // | ||||
| //   actualObj, err := SomeFunction() | ||||
| @@ -631,6 +667,102 @@ func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilo | ||||
| 	return InEpsilonf(a.t, expected, actual, epsilon, msg, args...) | ||||
| } | ||||
|  | ||||
| // IsDecreasing asserts that the collection is decreasing | ||||
| // | ||||
| //    a.IsDecreasing([]int{2, 1, 0}) | ||||
| //    a.IsDecreasing([]float{2, 1}) | ||||
| //    a.IsDecreasing([]string{"b", "a"}) | ||||
| func (a *Assertions) IsDecreasing(object interface{}, msgAndArgs ...interface{}) bool { | ||||
| 	if h, ok := a.t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return IsDecreasing(a.t, object, msgAndArgs...) | ||||
| } | ||||
|  | ||||
| // IsDecreasingf asserts that the collection is decreasing | ||||
| // | ||||
| //    a.IsDecreasingf([]int{2, 1, 0}, "error message %s", "formatted") | ||||
| //    a.IsDecreasingf([]float{2, 1}, "error message %s", "formatted") | ||||
| //    a.IsDecreasingf([]string{"b", "a"}, "error message %s", "formatted") | ||||
| func (a *Assertions) IsDecreasingf(object interface{}, msg string, args ...interface{}) bool { | ||||
| 	if h, ok := a.t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return IsDecreasingf(a.t, object, msg, args...) | ||||
| } | ||||
|  | ||||
| // IsIncreasing asserts that the collection is increasing | ||||
| // | ||||
| //    a.IsIncreasing([]int{1, 2, 3}) | ||||
| //    a.IsIncreasing([]float{1, 2}) | ||||
| //    a.IsIncreasing([]string{"a", "b"}) | ||||
| func (a *Assertions) IsIncreasing(object interface{}, msgAndArgs ...interface{}) bool { | ||||
| 	if h, ok := a.t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return IsIncreasing(a.t, object, msgAndArgs...) | ||||
| } | ||||
|  | ||||
| // IsIncreasingf asserts that the collection is increasing | ||||
| // | ||||
| //    a.IsIncreasingf([]int{1, 2, 3}, "error message %s", "formatted") | ||||
| //    a.IsIncreasingf([]float{1, 2}, "error message %s", "formatted") | ||||
| //    a.IsIncreasingf([]string{"a", "b"}, "error message %s", "formatted") | ||||
| func (a *Assertions) IsIncreasingf(object interface{}, msg string, args ...interface{}) bool { | ||||
| 	if h, ok := a.t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return IsIncreasingf(a.t, object, msg, args...) | ||||
| } | ||||
|  | ||||
| // IsNonDecreasing asserts that the collection is not decreasing | ||||
| // | ||||
| //    a.IsNonDecreasing([]int{1, 1, 2}) | ||||
| //    a.IsNonDecreasing([]float{1, 2}) | ||||
| //    a.IsNonDecreasing([]string{"a", "b"}) | ||||
| func (a *Assertions) IsNonDecreasing(object interface{}, msgAndArgs ...interface{}) bool { | ||||
| 	if h, ok := a.t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return IsNonDecreasing(a.t, object, msgAndArgs...) | ||||
| } | ||||
|  | ||||
| // IsNonDecreasingf asserts that the collection is not decreasing | ||||
| // | ||||
| //    a.IsNonDecreasingf([]int{1, 1, 2}, "error message %s", "formatted") | ||||
| //    a.IsNonDecreasingf([]float{1, 2}, "error message %s", "formatted") | ||||
| //    a.IsNonDecreasingf([]string{"a", "b"}, "error message %s", "formatted") | ||||
| func (a *Assertions) IsNonDecreasingf(object interface{}, msg string, args ...interface{}) bool { | ||||
| 	if h, ok := a.t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return IsNonDecreasingf(a.t, object, msg, args...) | ||||
| } | ||||
|  | ||||
| // IsNonIncreasing asserts that the collection is not increasing | ||||
| // | ||||
| //    a.IsNonIncreasing([]int{2, 1, 1}) | ||||
| //    a.IsNonIncreasing([]float{2, 1}) | ||||
| //    a.IsNonIncreasing([]string{"b", "a"}) | ||||
| func (a *Assertions) IsNonIncreasing(object interface{}, msgAndArgs ...interface{}) bool { | ||||
| 	if h, ok := a.t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return IsNonIncreasing(a.t, object, msgAndArgs...) | ||||
| } | ||||
|  | ||||
| // IsNonIncreasingf asserts that the collection is not increasing | ||||
| // | ||||
| //    a.IsNonIncreasingf([]int{2, 1, 1}, "error message %s", "formatted") | ||||
| //    a.IsNonIncreasingf([]float{2, 1}, "error message %s", "formatted") | ||||
| //    a.IsNonIncreasingf([]string{"b", "a"}, "error message %s", "formatted") | ||||
| func (a *Assertions) IsNonIncreasingf(object interface{}, msg string, args ...interface{}) bool { | ||||
| 	if h, ok := a.t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return IsNonIncreasingf(a.t, object, msg, args...) | ||||
| } | ||||
|  | ||||
| // IsType asserts that the specified objects are of the same type. | ||||
| func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool { | ||||
| 	if h, ok := a.t.(tHelper); ok { | ||||
| @@ -739,6 +871,28 @@ func (a *Assertions) Lessf(e1 interface{}, e2 interface{}, msg string, args ...i | ||||
| 	return Lessf(a.t, e1, e2, msg, args...) | ||||
| } | ||||
|  | ||||
| // Negative asserts that the specified element is negative | ||||
| // | ||||
| //    a.Negative(-1) | ||||
| //    a.Negative(-1.23) | ||||
| func (a *Assertions) Negative(e interface{}, msgAndArgs ...interface{}) bool { | ||||
| 	if h, ok := a.t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return Negative(a.t, e, msgAndArgs...) | ||||
| } | ||||
|  | ||||
| // Negativef asserts that the specified element is negative | ||||
| // | ||||
| //    a.Negativef(-1, "error message %s", "formatted") | ||||
| //    a.Negativef(-1.23, "error message %s", "formatted") | ||||
| func (a *Assertions) Negativef(e interface{}, msg string, args ...interface{}) bool { | ||||
| 	if h, ok := a.t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return Negativef(a.t, e, msg, args...) | ||||
| } | ||||
|  | ||||
| // Never asserts that the given condition doesn't satisfy in waitFor time, | ||||
| // periodically checking the target function each tick. | ||||
| // | ||||
| @@ -941,6 +1095,24 @@ func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg str | ||||
| 	return NotEqualf(a.t, expected, actual, msg, args...) | ||||
| } | ||||
|  | ||||
| // NotErrorIs asserts that at none of the errors in err's chain matches target. | ||||
| // This is a wrapper for errors.Is. | ||||
| func (a *Assertions) NotErrorIs(err error, target error, msgAndArgs ...interface{}) bool { | ||||
| 	if h, ok := a.t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return NotErrorIs(a.t, err, target, msgAndArgs...) | ||||
| } | ||||
|  | ||||
| // NotErrorIsf asserts that at none of the errors in err's chain matches target. | ||||
| // This is a wrapper for errors.Is. | ||||
| func (a *Assertions) NotErrorIsf(err error, target error, msg string, args ...interface{}) bool { | ||||
| 	if h, ok := a.t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return NotErrorIsf(a.t, err, target, msg, args...) | ||||
| } | ||||
|  | ||||
| // NotNil asserts that the specified object is not nil. | ||||
| // | ||||
| //    a.NotNil(err) | ||||
| @@ -1133,6 +1305,28 @@ func (a *Assertions) Panicsf(f PanicTestFunc, msg string, args ...interface{}) b | ||||
| 	return Panicsf(a.t, f, msg, args...) | ||||
| } | ||||
|  | ||||
| // Positive asserts that the specified element is positive | ||||
| // | ||||
| //    a.Positive(1) | ||||
| //    a.Positive(1.23) | ||||
| func (a *Assertions) Positive(e interface{}, msgAndArgs ...interface{}) bool { | ||||
| 	if h, ok := a.t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return Positive(a.t, e, msgAndArgs...) | ||||
| } | ||||
|  | ||||
| // Positivef asserts that the specified element is positive | ||||
| // | ||||
| //    a.Positivef(1, "error message %s", "formatted") | ||||
| //    a.Positivef(1.23, "error message %s", "formatted") | ||||
| func (a *Assertions) Positivef(e interface{}, msg string, args ...interface{}) bool { | ||||
| 	if h, ok := a.t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	return Positivef(a.t, e, msg, args...) | ||||
| } | ||||
|  | ||||
| // Regexp asserts that a specified regexp matches a string. | ||||
| // | ||||
| //  a.Regexp(regexp.MustCompile("start"), "it's starting") | ||||
|   | ||||
							
								
								
									
										81
									
								
								vendor/github.com/stretchr/testify/assert/assertion_order.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								vendor/github.com/stretchr/testify/assert/assertion_order.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| package assert | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| ) | ||||
|  | ||||
| // isOrdered checks that collection contains orderable elements. | ||||
| func isOrdered(t TestingT, object interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool { | ||||
| 	objKind := reflect.TypeOf(object).Kind() | ||||
| 	if objKind != reflect.Slice && objKind != reflect.Array { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	objValue := reflect.ValueOf(object) | ||||
| 	objLen := objValue.Len() | ||||
|  | ||||
| 	if objLen <= 1 { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	value := objValue.Index(0) | ||||
| 	valueInterface := value.Interface() | ||||
| 	firstValueKind := value.Kind() | ||||
|  | ||||
| 	for i := 1; i < objLen; i++ { | ||||
| 		prevValue := value | ||||
| 		prevValueInterface := valueInterface | ||||
|  | ||||
| 		value = objValue.Index(i) | ||||
| 		valueInterface = value.Interface() | ||||
|  | ||||
| 		compareResult, isComparable := compare(prevValueInterface, valueInterface, firstValueKind) | ||||
|  | ||||
| 		if !isComparable { | ||||
| 			return Fail(t, fmt.Sprintf("Can not compare type \"%s\" and \"%s\"", reflect.TypeOf(value), reflect.TypeOf(prevValue)), msgAndArgs...) | ||||
| 		} | ||||
|  | ||||
| 		if !containsValue(allowedComparesResults, compareResult) { | ||||
| 			return Fail(t, fmt.Sprintf(failMessage, prevValue, value), msgAndArgs...) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // IsIncreasing asserts that the collection is increasing | ||||
| // | ||||
| //    assert.IsIncreasing(t, []int{1, 2, 3}) | ||||
| //    assert.IsIncreasing(t, []float{1, 2}) | ||||
| //    assert.IsIncreasing(t, []string{"a", "b"}) | ||||
| func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { | ||||
| 	return isOrdered(t, object, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs) | ||||
| } | ||||
|  | ||||
| // IsNonIncreasing asserts that the collection is not increasing | ||||
| // | ||||
| //    assert.IsNonIncreasing(t, []int{2, 1, 1}) | ||||
| //    assert.IsNonIncreasing(t, []float{2, 1}) | ||||
| //    assert.IsNonIncreasing(t, []string{"b", "a"}) | ||||
| func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { | ||||
| 	return isOrdered(t, object, []CompareType{compareEqual, compareGreater}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs) | ||||
| } | ||||
|  | ||||
| // IsDecreasing asserts that the collection is decreasing | ||||
| // | ||||
| //    assert.IsDecreasing(t, []int{2, 1, 0}) | ||||
| //    assert.IsDecreasing(t, []float{2, 1}) | ||||
| //    assert.IsDecreasing(t, []string{"b", "a"}) | ||||
| func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { | ||||
| 	return isOrdered(t, object, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs) | ||||
| } | ||||
|  | ||||
| // IsNonDecreasing asserts that the collection is not decreasing | ||||
| // | ||||
| //    assert.IsNonDecreasing(t, []int{1, 1, 2}) | ||||
| //    assert.IsNonDecreasing(t, []float{1, 2}) | ||||
| //    assert.IsNonDecreasing(t, []string{"a", "b"}) | ||||
| func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { | ||||
| 	return isOrdered(t, object, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs) | ||||
| } | ||||
							
								
								
									
										83
									
								
								vendor/github.com/stretchr/testify/assert/assertions.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										83
									
								
								vendor/github.com/stretchr/testify/assert/assertions.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -172,8 +172,8 @@ func isTest(name, prefix string) bool { | ||||
| 	if len(name) == len(prefix) { // "Test" is ok | ||||
| 		return true | ||||
| 	} | ||||
| 	rune, _ := utf8.DecodeRuneInString(name[len(prefix):]) | ||||
| 	return !unicode.IsLower(rune) | ||||
| 	r, _ := utf8.DecodeRuneInString(name[len(prefix):]) | ||||
| 	return !unicode.IsLower(r) | ||||
| } | ||||
|  | ||||
| func messageFromMsgAndArgs(msgAndArgs ...interface{}) string { | ||||
| @@ -1622,6 +1622,7 @@ var spewConfig = spew.ConfigState{ | ||||
| 	DisableCapacities:       true, | ||||
| 	SortKeys:                true, | ||||
| 	DisableMethods:          true, | ||||
| 	MaxDepth:                10, | ||||
| } | ||||
|  | ||||
| type tHelper interface { | ||||
| @@ -1693,3 +1694,81 @@ func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.D | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ErrorIs asserts that at least one of the errors in err's chain matches target. | ||||
| // This is a wrapper for errors.Is. | ||||
| func ErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool { | ||||
| 	if h, ok := t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	if errors.Is(err, target) { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	var expectedText string | ||||
| 	if target != nil { | ||||
| 		expectedText = target.Error() | ||||
| 	} | ||||
|  | ||||
| 	chain := buildErrorChainString(err) | ||||
|  | ||||
| 	return Fail(t, fmt.Sprintf("Target error should be in err chain:\n"+ | ||||
| 		"expected: %q\n"+ | ||||
| 		"in chain: %s", expectedText, chain, | ||||
| 	), msgAndArgs...) | ||||
| } | ||||
|  | ||||
| // NotErrorIs asserts that at none of the errors in err's chain matches target. | ||||
| // This is a wrapper for errors.Is. | ||||
| func NotErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool { | ||||
| 	if h, ok := t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	if !errors.Is(err, target) { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	var expectedText string | ||||
| 	if target != nil { | ||||
| 		expectedText = target.Error() | ||||
| 	} | ||||
|  | ||||
| 	chain := buildErrorChainString(err) | ||||
|  | ||||
| 	return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+ | ||||
| 		"found: %q\n"+ | ||||
| 		"in chain: %s", expectedText, chain, | ||||
| 	), msgAndArgs...) | ||||
| } | ||||
|  | ||||
| // ErrorAs asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value. | ||||
| // This is a wrapper for errors.As. | ||||
| func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) bool { | ||||
| 	if h, ok := t.(tHelper); ok { | ||||
| 		h.Helper() | ||||
| 	} | ||||
| 	if errors.As(err, target) { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	chain := buildErrorChainString(err) | ||||
|  | ||||
| 	return Fail(t, fmt.Sprintf("Should be in error chain:\n"+ | ||||
| 		"expected: %q\n"+ | ||||
| 		"in chain: %s", target, chain, | ||||
| 	), msgAndArgs...) | ||||
| } | ||||
|  | ||||
| func buildErrorChainString(err error) string { | ||||
| 	if err == nil { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	e := errors.Unwrap(err) | ||||
| 	chain := fmt.Sprintf("%q", err.Error()) | ||||
| 	for e != nil { | ||||
| 		chain += fmt.Sprintf("\n\t%q", e.Error()) | ||||
| 		e = errors.Unwrap(e) | ||||
| 	} | ||||
| 	return chain | ||||
| } | ||||
|   | ||||
							
								
								
									
										4
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| # cloud.google.com/go v0.49.0 | ||||
| cloud.google.com/go/compute/metadata | ||||
| # github.com/1Password/connect-sdk-go v1.0.1 | ||||
| # github.com/1Password/connect-sdk-go v1.2.0 | ||||
| github.com/1Password/connect-sdk-go/connect | ||||
| github.com/1Password/connect-sdk-go/onepassword | ||||
| # github.com/Azure/go-autorest/autorest v0.9.3-0.20191028180845-3492b2aff503 | ||||
| @@ -110,7 +110,7 @@ github.com/prometheus/procfs/internal/fs | ||||
| github.com/prometheus/procfs/internal/util | ||||
| # github.com/spf13/pflag v1.0.5 | ||||
| github.com/spf13/pflag | ||||
| # github.com/stretchr/testify v1.6.1 | ||||
| # github.com/stretchr/testify v1.7.0 | ||||
| github.com/stretchr/testify/assert | ||||
| # github.com/uber/jaeger-client-go v2.25.0+incompatible | ||||
| github.com/uber/jaeger-client-go | ||||
|   | ||||
		Reference in New Issue
	
	Block a user