mirror of
				https://github.com/1Password/onepassword-operator.git
				synced 2025-10-31 03:39:39 +00:00 
			
		
		
		
	Compare commits
	
		
			75 Commits
		
	
	
		
			release/v1
			...
			release/v1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 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 | ||
|   | d807e92c36 | ||
|   | 244771717c | ||
|   | a760e524ea | ||
|   | 7aeb36e383 | ||
|   | 5c2f840623 | ||
|   | 670040477e | ||
|   | a45a310611 | ||
|   | d80e8dd799 | ||
|   | 88728909ff | ||
|   | e365ebfdfa | ||
|   | 2c4b4df01a | ||
|   | 49d984c6f2 | ||
|   | 19f774bb2d | ||
|   | 0193a98681 | ||
|   | f241d7423d | ||
|   | c0037526b0 | ||
|   | dff934cbc3 | ||
|   | 2096f4440f | ||
|   | b3fc707337 | ||
|   | 32643651d9 | ||
|   | ba8d3fa698 | ||
|   | c57aa22a9c | ||
|   | 48944b0d56 | ||
|   | 313cd1169b | ||
|   | fb1262f1bd | ||
|   | a428fe7462 | ||
|   | ea2d1f8a09 | ||
|   | bd96d50a9b | 
							
								
								
									
										112
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										112
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -12,60 +12,124 @@ | |||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| [//]: # (START/v1.0.2) | [//]: # "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} | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | [//]: # "START/v1.0.2" | ||||||
|  |  | ||||||
| # v1.0.2 | # v1.0.2 | ||||||
|  |  | ||||||
| ## Fixes | ## 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 | # v1.0.1 | ||||||
|  |  | ||||||
| ## Features | ## 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 | # v1.0.0 | ||||||
|  |  | ||||||
| ## Features: | ## Features: | ||||||
| * Option to automatically deploy 1Password Connect via the operator |  | ||||||
| * Ignore restart annotation when looking for 1Password annotations | - Option to automatically deploy 1Password Connect via the operator | ||||||
| * Release Automation | - Ignore restart annotation when looking for 1Password annotations | ||||||
| * Upgrading apiextensions.k8s.io/v1beta apiversion from the operator custom resource | - Release Automation | ||||||
| * Adding configuration for auto rolling restart on deployments | - Upgrading apiextensions.k8s.io/v1beta apiversion from the operator custom resource | ||||||
| * Configure Auto Restarts for a OnePasswordItem Custom Resource | - Adding configuration for auto rolling restart on deployments | ||||||
| * Update Connect Dependencies to latest | - Configure Auto Restarts for a OnePasswordItem Custom Resource | ||||||
| * Add Github action for building and testing operator | - Update Connect Dependencies to latest | ||||||
|  | - Add Github action for building and testing operator | ||||||
|  |  | ||||||
| ## Fixes: | ## 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 | # v0.0.2 | ||||||
|  |  | ||||||
| ## Features: | ## 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 | # v0.0.1 | ||||||
|  |  | ||||||
| Initial 1Password Operator release | Initial 1Password Operator release | ||||||
|  |  | ||||||
| ## Features | ## 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 deployment creations with `onepassword` annotations and creates an associated kubernetes secret | ||||||
| * watches for changes to 1Password secrets associated with kubernetes secrets and updates the kubernetes secret with changes | - watches for `onepasswordsecret` crd creations and creates an associated kubernetes secrets | ||||||
| * restart pods when secret has been updated | - watches for changes to 1Password secrets associated with kubernetes secrets and updates the kubernetes secret with changes | ||||||
| * cleanup of kubernetes secrets when deployment or `onepasswordsecret` is deleted | - 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: | Encode the 1password-credentials.json file you generated in the prerequisite steps and save it to a file named op-session: | ||||||
|  |  | ||||||
| ```bash | ```bash | ||||||
| $ cat 1password-credentials.json | base64 | \ | cat 1password-credentials.json | base64 | \ | ||||||
|   tr '/+' '_-' | tr -d '=' | tr -d '\n' > op-session |   tr '/+' '_-' | tr -d '=' | tr -d '\n' > op-session | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| Create a Kubernetes secret from the op-session file: | Create a Kubernetes secret from the op-session file: | ||||||
| ```bash | ```bash | ||||||
|  | kubectl create secret generic op-credentials --from-file=op-session | ||||||
| $  kubectl create secret generic op-credentials --from-file=1password-credentials.json |  | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| Add the following environment variable to the onepassword-connect-operator container in `deploy/operator.yaml`: | 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:  | "Create a Connect token for the operator and save it as a Kubernetes Secret:  | ||||||
|  |  | ||||||
| ```bash | ```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: | 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 | ```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) | [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. | We must create a service account, role, and role binding and Kubernetes. Examples can be found in the `/deploy` folder. | ||||||
|  |  | ||||||
| ```bash | ```bash | ||||||
| $ kubectl apply -f deploy/permissions.yaml | kubectl apply -f deploy/permissions.yaml | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| **Create Custom One Password Secret Resource** | **Create Custom One Password Secret Resource** | ||||||
|  |  | ||||||
| ```bash | ```bash | ||||||
| $ kubectl apply -f deploy/crds/onepassword.com_onepassworditems_crd.yaml | kubectl apply -f deploy/crds/onepassword.com_onepassworditems_crd.yaml | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| **Deploying the Operator** | **Deploying the Operator** | ||||||
| @@ -112,13 +111,13 @@ spec: | |||||||
| Deploy the OnePasswordItem to Kubernetes: | Deploy the OnePasswordItem to Kubernetes: | ||||||
|  |  | ||||||
| ```bash | ```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: | To test that the Kubernetes Secret check that the following command returns a secret: | ||||||
|  |  | ||||||
| ```bash | ```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. | 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. | 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. | 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. | 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: |   annotations: | ||||||
|     operator.1password.io/auto-restart: "true" |     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** | **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: | 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: |   annotations: | ||||||
|     operator.1password.io/auto-restart: "true" |     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** | **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: | 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: |   annotations: | ||||||
|     operator.1password.io/auto-restart: "true" |     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 | ## Development | ||||||
|  |  | ||||||
|   | |||||||
| @@ -178,7 +178,10 @@ func main() { | |||||||
| 				ticker.Stop() | 				ticker.Stop() | ||||||
| 				return | 				return | ||||||
| 			case <-ticker.C: | 			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: |           status: | ||||||
|             description: OnePasswordItemStatus defines the observed state of OnePasswordItem |             description: OnePasswordItemStatus defines the observed state of OnePasswordItem | ||||||
|             type: object |             type: object | ||||||
|  |           type: | ||||||
|  |             description: 'Kubernetes secret type. More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types' | ||||||
|  |             type: string | ||||||
|         type: object |         type: object | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.mod
									
									
									
									
									
								
							| @@ -3,11 +3,11 @@ module github.com/1Password/onepassword-operator | |||||||
| go 1.13 | go 1.13 | ||||||
|  |  | ||||||
| require ( | 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/operator-framework/operator-sdk v0.19.0 | ||||||
| 	github.com/prometheus/common v0.14.0 // indirect | 	github.com/prometheus/common v0.14.0 // indirect | ||||||
| 	github.com/spf13/pflag v1.0.5 | 	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/api v0.18.2 | ||||||
| 	k8s.io/apimachinery v0.18.2 | 	k8s.io/apimachinery v0.18.2 | ||||||
| 	k8s.io/client-go v12.0.0+incompatible | 	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= | 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 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.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.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-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= | 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.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 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= | ||||||
| github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | 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/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/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= | github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= | ||||||
|   | |||||||
| @@ -26,6 +26,7 @@ type OnePasswordItemStatus struct { | |||||||
| type OnePasswordItem struct { | type OnePasswordItem struct { | ||||||
| 	metav1.TypeMeta   `json:",inline"` | 	metav1.TypeMeta   `json:",inline"` | ||||||
| 	metav1.ObjectMeta `json:"metadata,omitempty"` | 	metav1.ObjectMeta `json:"metadata,omitempty"` | ||||||
|  | 	Type              string `json:"type,omitempty"` | ||||||
|  |  | ||||||
| 	Spec   OnePasswordItemSpec   `json:"spec,omitempty"` | 	Spec   OnePasswordItemSpec   `json:"spec,omitempty"` | ||||||
| 	Status OnePasswordItemStatus `json:"status,omitempty"` | 	Status OnePasswordItemStatus `json:"status,omitempty"` | ||||||
|   | |||||||
| @@ -14,9 +14,11 @@ import ( | |||||||
| 	appsv1 "k8s.io/api/apps/v1" | 	appsv1 "k8s.io/api/apps/v1" | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	corev1 "k8s.io/api/core/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/api/errors" | 	"k8s.io/apimachinery/pkg/api/errors" | ||||||
|  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/runtime" | 	"k8s.io/apimachinery/pkg/runtime" | ||||||
| 	ctrl "sigs.k8s.io/controller-runtime" | 	ctrl "sigs.k8s.io/controller-runtime" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/client" | 	"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/controller" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/handler" | 	"sigs.k8s.io/controller-runtime/pkg/handler" | ||||||
| 	logf "sigs.k8s.io/controller-runtime/pkg/log" | 	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 | 		// 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{}, err | ||||||
| 		} | 		} | ||||||
| 		return reconcile.Result{}, nil | 		return reconcile.Result{}, nil | ||||||
| @@ -187,10 +189,13 @@ func (r *ReconcileDeployment) removeOnePasswordFinalizerFromDeployment(deploymen | |||||||
| 	return r.kubeClient.Update(context.Background(), deployment) | 	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) | 	reqLog := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) | ||||||
|  |  | ||||||
| 	secretName := annotations[op.NameAnnotation] | 	secretName := annotations[op.NameAnnotation] | ||||||
|  | 	secretLabels := map[string]string(nil) | ||||||
|  | 	secretType := "" | ||||||
|  |  | ||||||
| 	if len(secretName) == 0 { | 	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.") | 		reqLog.Info("No 'item-name' annotation set. 'item-path' and 'item-name' must be set as annotations to add new secret.") | ||||||
| 		return nil | 		return nil | ||||||
| @@ -201,5 +206,17 @@ func (r *ReconcileDeployment) HandleApplyingDeployment(namespace string, annotat | |||||||
| 		return fmt.Errorf("Failed to retrieve item: %v", err) | 		return fmt.Errorf("Failed to retrieve item: %v", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, namespace, item, annotations[op.RestartDeploymentsAnnotation]) | 	// 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) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -258,7 +258,7 @@ var tests = []testReconcileItem{ | |||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		testName: "Test Do not update if OnePassword Item Version has not changed", | 		testName: "Test Do not update if Annotations have not changed", | ||||||
| 		deploymentResource: &appsv1.Deployment{ | 		deploymentResource: &appsv1.Deployment{ | ||||||
| 			TypeMeta: metav1.TypeMeta{ | 			TypeMeta: metav1.TypeMeta{ | ||||||
| 				Kind:       deploymentKind, | 				Kind:       deploymentKind, | ||||||
| @@ -271,6 +271,7 @@ var tests = []testReconcileItem{ | |||||||
| 					op.ItemPathAnnotation: itemPath, | 					op.ItemPathAnnotation: itemPath, | ||||||
| 					op.NameAnnotation:     name, | 					op.NameAnnotation:     name, | ||||||
| 				}, | 				}, | ||||||
|  | 				Labels: map[string]string{}, | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		existingSecret: &corev1.Secret{ | 		existingSecret: &corev1.Secret{ | ||||||
| @@ -279,6 +280,7 @@ var tests = []testReconcileItem{ | |||||||
| 				Namespace: namespace, | 				Namespace: namespace, | ||||||
| 				Annotations: map[string]string{ | 				Annotations: map[string]string{ | ||||||
| 					op.VersionAnnotation:  fmt.Sprint(version), | 					op.VersionAnnotation:  fmt.Sprint(version), | ||||||
|  | 					op.ItemPathAnnotation: itemPath, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 			Data: expectedSecretData, | 			Data: expectedSecretData, | ||||||
| @@ -290,7 +292,9 @@ var tests = []testReconcileItem{ | |||||||
| 				Namespace: namespace, | 				Namespace: namespace, | ||||||
| 				Annotations: map[string]string{ | 				Annotations: map[string]string{ | ||||||
| 					op.VersionAnnotation:  fmt.Sprint(version), | 					op.VersionAnnotation:  fmt.Sprint(version), | ||||||
|  | 					op.ItemPathAnnotation: itemPath, | ||||||
| 				}, | 				}, | ||||||
|  | 				Labels: map[string]string(nil), | ||||||
| 			}, | 			}, | ||||||
| 			Data: expectedSecretData, | 			Data: expectedSecretData, | ||||||
| 		}, | 		}, | ||||||
| @@ -323,6 +327,7 @@ var tests = []testReconcileItem{ | |||||||
| 					op.VersionAnnotation: "456", | 					op.VersionAnnotation: "456", | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
|  | 			Type: corev1.SecretType(""), | ||||||
| 			Data: expectedSecretData, | 			Data: expectedSecretData, | ||||||
| 		}, | 		}, | ||||||
| 		expectedError: nil, | 		expectedError: nil, | ||||||
| @@ -334,6 +339,7 @@ var tests = []testReconcileItem{ | |||||||
| 					op.VersionAnnotation: fmt.Sprint(version), | 					op.VersionAnnotation: fmt.Sprint(version), | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
|  | 			Type: corev1.SecretType(""), | ||||||
| 			Data: expectedSecretData, | 			Data: expectedSecretData, | ||||||
| 		}, | 		}, | ||||||
| 		opItem: map[string]string{ | 		opItem: map[string]string{ | ||||||
| @@ -367,6 +373,7 @@ var tests = []testReconcileItem{ | |||||||
| 					op.VersionAnnotation: fmt.Sprint(version), | 					op.VersionAnnotation: fmt.Sprint(version), | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
|  | 			Type: corev1.SecretType(""), | ||||||
| 			Data: expectedSecretData, | 			Data: expectedSecretData, | ||||||
| 		}, | 		}, | ||||||
| 		opItem: map[string]string{ | 		opItem: map[string]string{ | ||||||
| @@ -376,7 +383,7 @@ var tests = []testReconcileItem{ | |||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestReconcileDepoyment(t *testing.T) { | func TestReconcileDeployment(t *testing.T) { | ||||||
| 	for _, testData := range tests { | 	for _, testData := range tests { | ||||||
| 		t.Run(testData.testName, func(t *testing.T) { | 		t.Run(testData.testName, func(t *testing.T) { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ package onepassworditem | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  |  | ||||||
| 	onepasswordv1 "github.com/1Password/onepassword-operator/pkg/apis/onepassword/v1" | 	onepasswordv1 "github.com/1Password/onepassword-operator/pkg/apis/onepassword/v1" | ||||||
| 	kubeSecrets "github.com/1Password/onepassword-operator/pkg/kubernetessecrets" | 	kubeSecrets "github.com/1Password/onepassword-operator/pkg/kubernetessecrets" | ||||||
| 	"github.com/1Password/onepassword-operator/pkg/onepassword" | 	"github.com/1Password/onepassword-operator/pkg/onepassword" | ||||||
| @@ -13,9 +14,11 @@ import ( | |||||||
|  |  | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	corev1 "k8s.io/api/core/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/api/errors" | 	"k8s.io/apimachinery/pkg/api/errors" | ||||||
|  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/runtime" | 	"k8s.io/apimachinery/pkg/runtime" | ||||||
| 	ctrl "sigs.k8s.io/controller-runtime" | 	ctrl "sigs.k8s.io/controller-runtime" | ||||||
| 	kubeClient "sigs.k8s.io/controller-runtime/pkg/client" | 	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/controller" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/handler" | 	"sigs.k8s.io/controller-runtime/pkg/handler" | ||||||
| 	logf "sigs.k8s.io/controller-runtime/pkg/log" | 	logf "sigs.k8s.io/controller-runtime/pkg/log" | ||||||
| @@ -143,6 +146,8 @@ func (r *ReconcileOnePasswordItem) removeOnePasswordFinalizerFromOnePasswordItem | |||||||
|  |  | ||||||
| func (r *ReconcileOnePasswordItem) HandleOnePasswordItem(resource *onepasswordv1.OnePasswordItem, request reconcile.Request) error { | func (r *ReconcileOnePasswordItem) HandleOnePasswordItem(resource *onepasswordv1.OnePasswordItem, request reconcile.Request) error { | ||||||
| 	secretName := resource.GetName() | 	secretName := resource.GetName() | ||||||
|  | 	labels := resource.Labels | ||||||
|  | 	secretType := resource.Type | ||||||
| 	autoRestart := resource.Annotations[op.RestartDeploymentsAnnotation] | 	autoRestart := resource.Annotations[op.RestartDeploymentsAnnotation] | ||||||
|  |  | ||||||
| 	item, err := onepassword.GetOnePasswordItemByPath(r.opConnectClient, resource.Spec.ItemPath) | 	item, err := onepassword.GetOnePasswordItemByPath(r.opConnectClient, resource.Spec.ItemPath) | ||||||
| @@ -150,5 +155,17 @@ func (r *ReconcileOnePasswordItem) HandleOnePasswordItem(resource *onepasswordv1 | |||||||
| 		return fmt.Errorf("Failed to retrieve item: %v", err) | 		return fmt.Errorf("Failed to retrieve item: %v", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, resource.Namespace, item, autoRestart) | 	// 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" | 	"fmt" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/1Password/onepassword-operator/pkg/kubernetessecrets" | ||||||
| 	"github.com/1Password/onepassword-operator/pkg/mocks" | 	"github.com/1Password/onepassword-operator/pkg/mocks" | ||||||
| 	op "github.com/1Password/onepassword-operator/pkg/onepassword" | 	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{ | 		customResource: &onepasswordv1.OnePasswordItem{ | ||||||
| 			TypeMeta: metav1.TypeMeta{ | 			TypeMeta: metav1.TypeMeta{ | ||||||
| 				Kind:       onePasswordItemKind, | 				Kind:       onePasswordItemKind, | ||||||
| @@ -120,6 +121,7 @@ var tests = []testReconcileItem{ | |||||||
| 				Namespace: namespace, | 				Namespace: namespace, | ||||||
| 				Annotations: map[string]string{ | 				Annotations: map[string]string{ | ||||||
| 					op.VersionAnnotation:  fmt.Sprint(version), | 					op.VersionAnnotation:  fmt.Sprint(version), | ||||||
|  | 					op.ItemPathAnnotation: itemPath, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 			Data: expectedSecretData, | 			Data: expectedSecretData, | ||||||
| @@ -131,6 +133,7 @@ var tests = []testReconcileItem{ | |||||||
| 				Namespace: namespace, | 				Namespace: namespace, | ||||||
| 				Annotations: map[string]string{ | 				Annotations: map[string]string{ | ||||||
| 					op.VersionAnnotation:  fmt.Sprint(version), | 					op.VersionAnnotation:  fmt.Sprint(version), | ||||||
|  | 					op.ItemPathAnnotation: itemPath, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 			Data: expectedSecretData, | 			Data: expectedSecretData, | ||||||
| @@ -150,6 +153,11 @@ var tests = []testReconcileItem{ | |||||||
| 			ObjectMeta: metav1.ObjectMeta{ | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
| 				Name:      name, | 				Name:      name, | ||||||
| 				Namespace: namespace, | 				Namespace: namespace, | ||||||
|  | 				Annotations: map[string]string{ | ||||||
|  | 					op.VersionAnnotation:  fmt.Sprint(version), | ||||||
|  | 					op.ItemPathAnnotation: itemPath, | ||||||
|  | 				}, | ||||||
|  | 				Labels: map[string]string{}, | ||||||
| 			}, | 			}, | ||||||
| 			Spec: onepasswordv1.OnePasswordItemSpec{ | 			Spec: onepasswordv1.OnePasswordItemSpec{ | ||||||
| 				ItemPath: itemPath, | 				ItemPath: itemPath, | ||||||
| @@ -161,7 +169,9 @@ var tests = []testReconcileItem{ | |||||||
| 				Namespace: namespace, | 				Namespace: namespace, | ||||||
| 				Annotations: map[string]string{ | 				Annotations: map[string]string{ | ||||||
| 					op.VersionAnnotation:  "456", | 					op.VersionAnnotation:  "456", | ||||||
|  | 					op.ItemPathAnnotation: itemPath, | ||||||
| 				}, | 				}, | ||||||
|  | 				Labels: map[string]string{}, | ||||||
| 			}, | 			}, | ||||||
| 			Data: expectedSecretData, | 			Data: expectedSecretData, | ||||||
| 		}, | 		}, | ||||||
| @@ -172,8 +182,63 @@ var tests = []testReconcileItem{ | |||||||
| 				Namespace: namespace, | 				Namespace: namespace, | ||||||
| 				Annotations: map[string]string{ | 				Annotations: map[string]string{ | ||||||
| 					op.VersionAnnotation:  fmt.Sprint(version), | 					op.VersionAnnotation:  fmt.Sprint(version), | ||||||
|  | 					op.ItemPathAnnotation: itemPath, | ||||||
|  | 				}, | ||||||
|  | 				Labels: map[string]string{}, | ||||||
|  | 			}, | ||||||
|  | 			Data: expectedSecretData, | ||||||
|  | 		}, | ||||||
|  | 		opItem: map[string]string{ | ||||||
|  | 			userKey: username, | ||||||
|  | 			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, | 			Data: expectedSecretData, | ||||||
| 		}, | 		}, | ||||||
| 		opItem: map[string]string{ | 		opItem: map[string]string{ | ||||||
| @@ -195,6 +260,7 @@ var tests = []testReconcileItem{ | |||||||
| 			Spec: onepasswordv1.OnePasswordItemSpec{ | 			Spec: onepasswordv1.OnePasswordItemSpec{ | ||||||
| 				ItemPath: itemPath, | 				ItemPath: itemPath, | ||||||
| 			}, | 			}, | ||||||
|  | 			Type: "custom", | ||||||
| 		}, | 		}, | ||||||
| 		existingSecret: nil, | 		existingSecret: nil, | ||||||
| 		expectedError:  nil, | 		expectedError:  nil, | ||||||
| @@ -206,6 +272,51 @@ var tests = []testReconcileItem{ | |||||||
| 					op.VersionAnnotation: fmt.Sprint(version), | 					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, | 			Data: expectedSecretData, | ||||||
| 		}, | 		}, | ||||||
| 		opItem: map[string]string{ | 		opItem: map[string]string{ | ||||||
| @@ -274,7 +385,7 @@ var tests = []testReconcileItem{ | |||||||
| 				"password":       []byte(password), | 				"password":       []byte(password), | ||||||
| 				"username":       []byte(username), | 				"username":       []byte(username), | ||||||
| 				"first-host":     []byte(firstHost), | 				"first-host":     []byte(firstHost), | ||||||
| 				"aws-access-key": []byte(awsKey), | 				"AWS-Access-Key": []byte(awsKey), | ||||||
| 				"ice-cream-type": []byte(iceCream), | 				"ice-cream-type": []byte(iceCream), | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| @@ -286,6 +397,47 @@ var tests = []testReconcileItem{ | |||||||
| 			"😄 ice-cream type": iceCream, | 			"😄 ice-cream type": iceCream, | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
|  | 	{ | ||||||
|  | 		testName: "Secret from 1Password item with `-`, `_` and `.`", | ||||||
|  | 		customResource: &onepasswordv1.OnePasswordItem{ | ||||||
|  | 			TypeMeta: metav1.TypeMeta{ | ||||||
|  | 				Kind:       onePasswordItemKind, | ||||||
|  | 				APIVersion: onePasswordItemAPIVersion, | ||||||
|  | 			}, | ||||||
|  | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 				Name:      "!.my_sECReT.it3m%-_", | ||||||
|  | 				Namespace: namespace, | ||||||
|  | 			}, | ||||||
|  | 			Spec: onepasswordv1.OnePasswordItemSpec{ | ||||||
|  | 				ItemPath: itemPath, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		existingSecret: nil, | ||||||
|  | 		expectedError:  nil, | ||||||
|  | 		expectedResultSecret: &corev1.Secret{ | ||||||
|  | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 				Name:      "my-secret.it3m", | ||||||
|  | 				Namespace: namespace, | ||||||
|  | 				Annotations: map[string]string{ | ||||||
|  | 					op.VersionAnnotation: fmt.Sprint(version), | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			Data: map[string][]byte{ | ||||||
|  | 				"password":          []byte(password), | ||||||
|  | 				"username":          []byte(username), | ||||||
|  | 				"first-host":        []byte(firstHost), | ||||||
|  | 				"AWS-Access-Key":    []byte(awsKey), | ||||||
|  | 				"-_ice_cream.type.": []byte(iceCream), | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		opItem: map[string]string{ | ||||||
|  | 			userKey:               username, | ||||||
|  | 			passKey:               password, | ||||||
|  | 			"first host":          firstHost, | ||||||
|  | 			"AWS Access Key":      awsKey, | ||||||
|  | 			"😄 -_ice_cream.type.": iceCream, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestReconcileOnePasswordItem(t *testing.T) { | func TestReconcileOnePasswordItem(t *testing.T) { | ||||||
|   | |||||||
| @@ -7,7 +7,12 @@ import ( | |||||||
| 	"regexp" | 	"regexp" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
|  | 	"reflect" | ||||||
|  |  | ||||||
|  | 	errs "errors" | ||||||
|  |  | ||||||
| 	"github.com/1Password/connect-sdk-go/onepassword" | 	"github.com/1Password/connect-sdk-go/onepassword" | ||||||
|  |  | ||||||
| 	"github.com/1Password/onepassword-operator/pkg/utils" | 	"github.com/1Password/onepassword-operator/pkg/utils" | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	corev1 "k8s.io/api/core/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/api/errors" | 	"k8s.io/apimachinery/pkg/api/errors" | ||||||
| @@ -26,24 +31,28 @@ const restartAnnotation = OnepasswordPrefix + "/last-restarted" | |||||||
| const ItemPathAnnotation = OnepasswordPrefix + "/item-path" | const ItemPathAnnotation = OnepasswordPrefix + "/item-path" | ||||||
| const RestartDeploymentsAnnotation = OnepasswordPrefix + "/auto-restart" | const RestartDeploymentsAnnotation = OnepasswordPrefix + "/auto-restart" | ||||||
|  |  | ||||||
|  | var ErrCannotUpdateSecretType = errs.New("Cannot change secret type. Secret type is immutable") | ||||||
|  |  | ||||||
| var log = logf.Log | var log = logf.Log | ||||||
|  |  | ||||||
| func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretName, namespace string, item *onepassword.Item, autoRestart 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) | 	itemVersion := fmt.Sprint(item.Version) | ||||||
| 	annotations := map[string]string{ | 	secretAnnotations := map[string]string{ | ||||||
| 		VersionAnnotation:  itemVersion, | 		VersionAnnotation:  itemVersion, | ||||||
| 		ItemPathAnnotation: fmt.Sprintf("vaults/%v/items/%v", item.Vault.ID, item.ID), | 		ItemPathAnnotation: fmt.Sprintf("vaults/%v/items/%v", item.Vault.ID, item.ID), | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if autoRestart != "" { | 	if autoRestart != "" { | ||||||
| 		_, err := utils.StringToBool(autoRestart) | 		_, err := utils.StringToBool(autoRestart) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Error(err, "Error parsing %v annotation on Secret %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, secretName) | 			log.Error(err, "Error parsing %v annotation on Secret %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, secretName) | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		annotations[RestartDeploymentsAnnotation] = autoRestart | 		secretAnnotations[RestartDeploymentsAnnotation] = autoRestart | ||||||
| 	} | 	} | ||||||
| 	secret := BuildKubernetesSecretFromOnePasswordItem(secretName, namespace, annotations, *item) |  | ||||||
|  | 	// "Opaque" and "" secret types are treated the same by Kubernetes. | ||||||
|  | 	secret := BuildKubernetesSecretFromOnePasswordItem(secretName, namespace, secretAnnotations, labels, secretType, *item, ownerRef) | ||||||
|  |  | ||||||
| 	currentSecret := &corev1.Secret{} | 	currentSecret := &corev1.Secret{} | ||||||
| 	err := kubeClient.Get(context.Background(), types.NamespacedName{Name: secret.Name, Namespace: secret.Namespace}, currentSecret) | 	err := kubeClient.Get(context.Background(), types.NamespacedName{Name: secret.Name, Namespace: secret.Namespace}, currentSecret) | ||||||
| @@ -54,9 +63,17 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if currentSecret.Annotations[VersionAnnotation] != itemVersion { | 	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)) | 		log.Info(fmt.Sprintf("Updating Secret %v at namespace '%v'", secret.Name, secret.Namespace)) | ||||||
| 		currentSecret.ObjectMeta.Annotations = annotations | 		currentSecret.ObjectMeta.Annotations = secretAnnotations | ||||||
|  | 		currentSecret.ObjectMeta.Labels = labels | ||||||
| 		currentSecret.Data = secret.Data | 		currentSecret.Data = secret.Data | ||||||
| 		return kubeClient.Update(context.Background(), currentSecret) | 		return kubeClient.Update(context.Background(), currentSecret) | ||||||
| 	} | 	} | ||||||
| @@ -65,31 +82,57 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func BuildKubernetesSecretFromOnePasswordItem(name, namespace string, annotations 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{ | 	return &corev1.Secret{ | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
| 			Name:            formatSecretName(name), | 			Name:            formatSecretName(name), | ||||||
| 			Namespace:       namespace, | 			Namespace:       namespace, | ||||||
| 			Annotations:     annotations, | 			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{} | 	secretData := map[string][]byte{} | ||||||
| 	for i := 0; i < len(fields); i++ { | 	for i := 0; i < len(fields); i++ { | ||||||
| 		if fields[i].Value != "" { | 		if fields[i].Value != "" { | ||||||
| 			key := formatSecretName(fields[i].Label) | 			key := formatSecretDataName(fields[i].Label) | ||||||
| 			secretData[key] = []byte(fields[i].Value) | 			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 | 	return secretData | ||||||
| } | } | ||||||
|  |  | ||||||
| // formatSecretName rewrites a value to be a valid Secret name or Secret data key. | // formatSecretName rewrites a value to be a valid Secret name. | ||||||
| // | // | ||||||
| // The Secret meta.name and data keys must be valid DNS subdomain names (https://kubernetes.io/docs/concepts/configuration/secret/#overview-of-secrets) | // The Secret meta.name and data keys must be valid DNS subdomain names | ||||||
|  | // (https://kubernetes.io/docs/concepts/configuration/secret/#overview-of-secrets) | ||||||
| func formatSecretName(value string) string { | func formatSecretName(value string) string { | ||||||
| 	if errs := kubeValidate.IsDNS1123Subdomain(value); len(errs) == 0 { | 	if errs := kubeValidate.IsDNS1123Subdomain(value); len(errs) == 0 { | ||||||
| 		return value | 		return value | ||||||
| @@ -97,7 +140,18 @@ func formatSecretName(value string) string { | |||||||
| 	return createValidSecretName(value) | 	return createValidSecretName(value) | ||||||
| } | } | ||||||
|  |  | ||||||
| var invalidDNS1123Chars = regexp.MustCompile("[^a-z0-9-]+") | // formatSecretDataName rewrites a value to be a valid Secret data key. | ||||||
|  | // | ||||||
|  | // The Secret data keys must consist of alphanumeric numbers, `-`, `_` or `.` | ||||||
|  | // (https://kubernetes.io/docs/concepts/configuration/secret/#overview-of-secrets) | ||||||
|  | func formatSecretDataName(value string) string { | ||||||
|  | 	if errs := kubeValidate.IsConfigMapKey(value); len(errs) == 0 { | ||||||
|  | 		return value | ||||||
|  | 	} | ||||||
|  | 	return createValidSecretDataName(value) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var invalidDNS1123Chars = regexp.MustCompile("[^a-z0-9-.]+") | ||||||
|  |  | ||||||
| func createValidSecretName(value string) string { | func createValidSecretName(value string) string { | ||||||
| 	result := strings.ToLower(value) | 	result := strings.ToLower(value) | ||||||
| @@ -108,5 +162,19 @@ func createValidSecretName(value string) string { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// first and last character MUST be alphanumeric | 	// first and last character MUST be alphanumeric | ||||||
| 	return strings.Trim(result, "-") | 	return strings.Trim(result, "-.") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var invalidDataChars = regexp.MustCompile("[^a-zA-Z0-9-._]+") | ||||||
|  | var invalidStartEndChars = regexp.MustCompile("(^[^a-zA-Z0-9-._]+|[^a-zA-Z0-9-._]+$)") | ||||||
|  |  | ||||||
|  | func createValidSecretDataName(value string) string { | ||||||
|  | 	result := invalidStartEndChars.ReplaceAllString(value, "") | ||||||
|  | 	result = invalidDataChars.ReplaceAllString(result, "-") | ||||||
|  |  | ||||||
|  | 	if len(result) > kubeValidate.DNS1123SubdomainMaxLength { | ||||||
|  | 		result = result[0:kubeValidate.DNS1123SubdomainMaxLength] | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return result | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,13 +3,14 @@ package kubernetessecrets | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	kubeValidate "k8s.io/apimachinery/pkg/util/validation" |  | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/1Password/connect-sdk-go/onepassword" | 	"github.com/1Password/connect-sdk-go/onepassword" | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	corev1 "k8s.io/api/core/v1" | ||||||
|  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/types" | 	"k8s.io/apimachinery/pkg/types" | ||||||
|  | 	kubeValidate "k8s.io/apimachinery/pkg/util/validation" | ||||||
| 	"k8s.io/client-go/kubernetes" | 	"k8s.io/client-go/kubernetes" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/client/fake" | 	"sigs.k8s.io/controller-runtime/pkg/client/fake" | ||||||
| ) | ) | ||||||
| @@ -31,7 +32,10 @@ func TestCreateKubernetesSecretFromOnePasswordItem(t *testing.T) { | |||||||
| 	item.ID = "h46bb3jddvay7nxopfhvlwg35q" | 	item.ID = "h46bb3jddvay7nxopfhvlwg35q" | ||||||
|  |  | ||||||
| 	kubeClient := fake.NewFakeClient() | 	kubeClient := fake.NewFakeClient() | ||||||
| 	err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation) | 	secretLabels := map[string]string{} | ||||||
|  | 	secretType := "" | ||||||
|  |  | ||||||
|  | 	err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("Unexpected error: %v", err) | 		t.Errorf("Unexpected error: %v", err) | ||||||
| 	} | 	} | ||||||
| @@ -45,6 +49,51 @@ func TestCreateKubernetesSecretFromOnePasswordItem(t *testing.T) { | |||||||
| 	compareAnnotationsToItem(createdSecret.Annotations, item, t) | 	compareAnnotationsToItem(createdSecret.Annotations, item, 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) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) { | func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) { | ||||||
| 	secretName := "test-secret-update" | 	secretName := "test-secret-update" | ||||||
| 	namespace := "test" | 	namespace := "test" | ||||||
| @@ -56,7 +105,11 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) { | |||||||
| 	item.ID = "h46bb3jddvay7nxopfhvlwg35q" | 	item.ID = "h46bb3jddvay7nxopfhvlwg35q" | ||||||
|  |  | ||||||
| 	kubeClient := fake.NewFakeClient() | 	kubeClient := fake.NewFakeClient() | ||||||
| 	err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation) | 	secretLabels := map[string]string{} | ||||||
|  | 	secretType := "" | ||||||
|  |  | ||||||
|  | 	err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil) | ||||||
|  |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("Unexpected error: %v", err) | 		t.Errorf("Unexpected error: %v", err) | ||||||
| 	} | 	} | ||||||
| @@ -67,7 +120,7 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) { | |||||||
| 	newItem.Version = 456 | 	newItem.Version = 456 | ||||||
| 	newItem.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda" | 	newItem.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda" | ||||||
| 	newItem.ID = "h46bb3jddvay7nxopfhvlwg35q" | 	newItem.ID = "h46bb3jddvay7nxopfhvlwg35q" | ||||||
| 	err = CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &newItem, restartDeploymentAnnotation) | 	err = CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &newItem, restartDeploymentAnnotation, secretLabels, secretType, nil) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("Unexpected error: %v", err) | 		t.Errorf("Unexpected error: %v", err) | ||||||
| 	} | 	} | ||||||
| @@ -83,7 +136,7 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) { | |||||||
| func TestBuildKubernetesSecretData(t *testing.T) { | func TestBuildKubernetesSecretData(t *testing.T) { | ||||||
| 	fields := generateFields(5) | 	fields := generateFields(5) | ||||||
|  |  | ||||||
| 	secretData := BuildKubernetesSecretData(fields) | 	secretData := BuildKubernetesSecretData(fields, nil) | ||||||
| 	if len(secretData) != len(fields) { | 	if len(secretData) != len(fields) { | ||||||
| 		t.Errorf("Unexpected number of secret fields returned. Expected 3, got %v", len(secretData)) | 		t.Errorf("Unexpected number of secret fields returned. Expected 3, got %v", len(secretData)) | ||||||
| 	} | 	} | ||||||
| @@ -100,8 +153,10 @@ func TestBuildKubernetesSecretFromOnePasswordItem(t *testing.T) { | |||||||
| 	} | 	} | ||||||
| 	item := onepassword.Item{} | 	item := onepassword.Item{} | ||||||
| 	item.Fields = generateFields(5) | 	item.Fields = generateFields(5) | ||||||
|  | 	labels := map[string]string{} | ||||||
|  | 	secretType := "" | ||||||
|  |  | ||||||
| 	kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, item) | 	kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels, secretType, item, nil) | ||||||
| 	if kubeSecret.Name != strings.ToLower(name) { | 	if kubeSecret.Name != strings.ToLower(name) { | ||||||
| 		t.Errorf("Expected name value: %v but got: %v", name, kubeSecret.Name) | 		t.Errorf("Expected name value: %v but got: %v", name, kubeSecret.Name) | ||||||
| 	} | 	} | ||||||
| @@ -121,7 +176,9 @@ func TestBuildKubernetesSecretFixesInvalidLabels(t *testing.T) { | |||||||
| 	annotations := map[string]string{ | 	annotations := map[string]string{ | ||||||
| 		"annotationKey": "annotationValue", | 		"annotationKey": "annotationValue", | ||||||
| 	} | 	} | ||||||
|  | 	labels := map[string]string{} | ||||||
| 	item := onepassword.Item{} | 	item := onepassword.Item{} | ||||||
|  | 	secretType := "" | ||||||
|  |  | ||||||
| 	item.Fields = []*onepassword.ItemField{ | 	item.Fields = []*onepassword.ItemField{ | ||||||
| 		{ | 		{ | ||||||
| @@ -134,7 +191,7 @@ func TestBuildKubernetesSecretFixesInvalidLabels(t *testing.T) { | |||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, item) | 	kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels, secretType, item, nil) | ||||||
|  |  | ||||||
| 	// Assert Secret's meta.name was fixed | 	// Assert Secret's meta.name was fixed | ||||||
| 	if kubeSecret.Name != expectedName { | 	if kubeSecret.Name != expectedName { | ||||||
| @@ -152,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) { | func compareAnnotationsToItem(annotations map[string]string, item onepassword.Item, t *testing.T) { | ||||||
| 	actualVaultId, actualItemId, err := ParseVaultIdAndItemIdFromPath(annotations[ItemPathAnnotation]) | 	actualVaultId, actualItemId, err := ParseVaultIdAndItemIdFromPath(annotations[ItemPathAnnotation]) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -205,7 +292,7 @@ func ParseVaultIdAndItemIdFromPath(path string) (string, string, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func validLabel(v string) bool { | func validLabel(v string) bool { | ||||||
| 	if err := kubeValidate.IsDNS1123Subdomain(v); len(err) > 0 { | 	if err := kubeValidate.IsConfigMapKey(v); len(err) > 0 { | ||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
| 	return true | 	return true | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ import ( | |||||||
| type TestClient struct { | type TestClient struct { | ||||||
| 	GetVaultsFunc        func() ([]onepassword.Vault, error) | 	GetVaultsFunc        func() ([]onepassword.Vault, error) | ||||||
| 	GetVaultsByTitleFunc func(title string) ([]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) | 	GetItemFunc          func(uuid string, vaultUUID string) (*onepassword.Item, error) | ||||||
| 	GetItemsFunc         func(vaultUUID string) ([]onepassword.Item, error) | 	GetItemsFunc         func(vaultUUID string) ([]onepassword.Item, error) | ||||||
| 	GetItemsByTitleFunc  func(title string, 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) | 	CreateItemFunc       func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) | ||||||
| 	UpdateItemFunc       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 | 	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 ( | var ( | ||||||
| 	GetGetVaultsFunc       func() ([]onepassword.Vault, error) | 	GetGetVaultsFunc       func() ([]onepassword.Vault, error) | ||||||
| 	DoGetVaultsByTitleFunc func(title string) ([]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) | 	GetGetItemFunc         func(uuid string, vaultUUID string) (*onepassword.Item, error) | ||||||
| 	DoGetItemsByTitleFunc  func(title string, vaultUUID string) ([]onepassword.Item, error) | 	DoGetItemsByTitleFunc  func(title string, vaultUUID string) ([]onepassword.Item, error) | ||||||
| 	DoGetItemByTitleFunc   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 | 	DoDeleteItemFunc       func(item *onepassword.Item, vaultUUID string) error | ||||||
| 	DoGetItemsFunc         func(vaultUUID string) ([]onepassword.Item, error) | 	DoGetItemsFunc         func(vaultUUID string) ([]onepassword.Item, error) | ||||||
| 	DoUpdateItemFunc       func(item *onepassword.Item, 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 | // Do is the mock client's `Do` func | ||||||
| @@ -37,6 +43,10 @@ func (m *TestClient) GetVaultsByTitle(title string) ([]onepassword.Vault, error) | |||||||
| 	return DoGetVaultsByTitleFunc(title) | 	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) { | func (m *TestClient) GetItem(uuid string, vaultUUID string) (*onepassword.Item, error) { | ||||||
| 	return GetGetItemFunc(uuid, vaultUUID) | 	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) { | func (m *TestClient) UpdateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) { | ||||||
| 	return DoUpdateItemFunc(item, vaultUUID) | 	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 | 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 { | func AreContainersUsingSecrets(containers []corev1.Container, secrets map[string]*corev1.Secret) bool { | ||||||
| 	for i := 0; i < len(containers); i++ { | 	for i := 0; i < len(containers); i++ { | ||||||
| @@ -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 | 	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 | 	return updatedDeploymentSecrets | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,9 +4,10 @@ import ( | |||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	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{ | 	secretNamesToSearch := map[string]*corev1.Secret{ | ||||||
| 		"onepassword-database-secret": &corev1.Secret{}, | 		"onepassword-database-secret": &corev1.Secret{}, | ||||||
| 		"onepassword-api-key":         &corev1.Secret{}, | 		"onepassword-api-key":         &corev1.Secret{}, | ||||||
| @@ -18,7 +19,26 @@ func TestAreContainersUsingSecrets(t *testing.T) { | |||||||
| 		"some_other_key", | 		"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) { | 	if !AreContainersUsingSecrets(containers, secretNamesToSearch) { | ||||||
| 		t.Errorf("Expected that containers were using secrets but they were not detected.") | 		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) { | func TestAreContainersNotUsingSecrets(t *testing.T) { | ||||||
| 	secretNamesToSearch := map[string]*corev1.Secret{ | 	secretNamesToSearch := map[string]*corev1.Secret{ | ||||||
| 		"onepassword-database-secret": &corev1.Secret{}, | 		"onepassword-database-secret": {}, | ||||||
| 		"onepassword-api-key":         &corev1.Secret{}, | 		"onepassword-api-key":         {}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	containerSecretNames := []string{ | 	containerSecretNames := []string{ | ||||||
| 		"some_other_key", | 		"some_other_key", | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	containers := generateContainers(containerSecretNames) | 	containers := generateContainersWithSecretRefsFromEnv(containerSecretNames) | ||||||
|  |  | ||||||
| 	if AreContainersUsingSecrets(containers, secretNamesToSearch) { | 	if AreContainersUsingSecrets(containers, secretNamesToSearch) { | ||||||
| 		t.Errorf("Expected that containers were not using secrets but they were detected.") | 		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 := &appsv1.Deployment{} | ||||||
| 	deployment.Spec.Template.Spec.Containers = generateContainers(containerSecretNames) | 	deployment.Spec.Template.Spec.Containers = generateContainersWithSecretRefsFromEnv(containerSecretNames) | ||||||
| 	if !IsDeploymentUsingSecrets(deployment, secretNamesToSearch) { | 	if !IsDeploymentUsingSecrets(deployment, secretNamesToSearch) { | ||||||
| 		t.Errorf("Expected that deployment was using secrets but they were not detected.") | 		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 { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	for _, file := range item.Files { | ||||||
|  | 		_, err := opConnectClient.GetFileContent(file) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return item, nil | 	return item, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -17,8 +17,7 @@ func generateVolumes(names []string) []corev1.Volume { | |||||||
| 	} | 	} | ||||||
| 	return volumes | 	return volumes | ||||||
| } | } | ||||||
|  | func generateContainersWithSecretRefsFromEnv(names []string) []corev1.Container { | ||||||
| func generateContainers(names []string) []corev1.Container { |  | ||||||
| 	containers := []corev1.Container{} | 	containers := []corev1.Container{} | ||||||
| 	for i := 0; i < len(names); i++ { | 	for i := 0; i < len(names); i++ { | ||||||
| 		container := corev1.Container{ | 		container := corev1.Container{ | ||||||
| @@ -40,3 +39,16 @@ func generateContainers(names []string) []corev1.Container { | |||||||
| 	} | 	} | ||||||
| 	return containers | 	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" | 	"fmt" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	v1 "github.com/1Password/onepassword-operator/pkg/apis/onepassword/v1" | ||||||
|  |  | ||||||
| 	kubeSecrets "github.com/1Password/onepassword-operator/pkg/kubernetessecrets" | 	kubeSecrets "github.com/1Password/onepassword-operator/pkg/kubernetessecrets" | ||||||
| 	"github.com/1Password/onepassword-operator/pkg/utils" | 	"github.com/1Password/onepassword-operator/pkg/utils" | ||||||
|  |  | ||||||
| @@ -116,23 +118,37 @@ func (h *SecretUpdateHandler) updateKubernetesSecrets() (map[string]map[string]* | |||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		item, err := GetOnePasswordItemByPath(h.opConnectClient, secret.Annotations[ItemPathAnnotation]) | 		OnePasswordItemPath := h.getPathFromOnePasswordItem(secret) | ||||||
|  |  | ||||||
|  | 		item, err := GetOnePasswordItemByPath(h.opConnectClient, OnePasswordItemPath) | ||||||
| 		if err != nil { | 		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) | 		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) { | 			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())) | 				log.Info(fmt.Sprintf("Secret '%v' has been updated in 1Password but is set to be ignored. Updates to an ignored secret will not trigger an update to a kubernetes secret or a rolling restart.", secret.GetName())) | ||||||
| 				secret.Annotations[VersionAnnotation] = itemVersion | 				secret.Annotations[VersionAnnotation] = itemVersion | ||||||
| 				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 | 				continue | ||||||
| 			} | 			} | ||||||
| 			log.Info(fmt.Sprintf("Updating kubernetes secret '%v'", secret.GetName())) | 			log.Info(fmt.Sprintf("Updating kubernetes secret '%v'", secret.GetName())) | ||||||
| 			secret.Annotations[VersionAnnotation] = itemVersion | 			secret.Annotations[VersionAnnotation] = itemVersion | ||||||
| 			updatedSecret := kubeSecrets.BuildKubernetesSecretFromOnePasswordItem(secret.Name, secret.Namespace, secret.Annotations, *item) | 			secret.Annotations[ItemPathAnnotation] = itemPathString | ||||||
| 			h.client.Update(context.Background(), updatedSecret) | 			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 { | 			if updatedSecrets[secret.Namespace] == nil { | ||||||
| 				updatedSecrets[secret.Namespace] = make(map[string]*corev1.Secret) | 				updatedSecrets[secret.Namespace] = make(map[string]*corev1.Secret) | ||||||
| 			} | 			} | ||||||
| @@ -176,6 +192,22 @@ func (h *SecretUpdateHandler) getIsSetForAutoRestartByNamespaceMap() (map[string | |||||||
| 	return namespacesMap, nil | 	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 { | func isSecretSetForAutoRestart(secret *corev1.Secret, deployment *appsv1.Deployment, setForAutoRestartByNamespace map[string]bool) bool { | ||||||
| 	restartDeployment := secret.Annotations[RestartDeploymentsAnnotation] | 	restartDeployment := secret.Annotations[RestartDeploymentsAnnotation] | ||||||
| 	//If annotation for auto restarts for deployment is not set. Check for the annotation on its namepsace | 	//If annotation for auto restarts for deployment is not set. Check for the annotation on its namepsace | ||||||
|   | |||||||
							
								
								
									
										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 ( | import ( | ||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
|  | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| @@ -10,11 +11,12 @@ import ( | |||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"os" | 	"os" | ||||||
|  |  | ||||||
| 	"github.com/1Password/connect-sdk-go/onepassword" |  | ||||||
| 	opentracing "github.com/opentracing/opentracing-go" | 	opentracing "github.com/opentracing/opentracing-go" | ||||||
| 	"github.com/opentracing/opentracing-go/ext" | 	"github.com/opentracing/opentracing-go/ext" | ||||||
| 	jaegerClientConfig "github.com/uber/jaeger-client-go/config" | 	jaegerClientConfig "github.com/uber/jaeger-client-go/config" | ||||||
| 	"github.com/uber/jaeger-client-go/zipkin" | 	"github.com/uber/jaeger-client-go/zipkin" | ||||||
|  |  | ||||||
|  | 	"github.com/1Password/connect-sdk-go/onepassword" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| @@ -24,6 +26,7 @@ const ( | |||||||
| // Client Represents an available 1Password Connect API to connect to | // Client Represents an available 1Password Connect API to connect to | ||||||
| type Client interface { | type Client interface { | ||||||
| 	GetVaults() ([]onepassword.Vault, error) | 	GetVaults() ([]onepassword.Vault, error) | ||||||
|  | 	GetVault(uuid string) (*onepassword.Vault, error) | ||||||
| 	GetVaultsByTitle(uuid string) ([]onepassword.Vault, error) | 	GetVaultsByTitle(uuid string) ([]onepassword.Vault, error) | ||||||
| 	GetItem(uuid string, vaultUUID string) (*onepassword.Item, error) | 	GetItem(uuid string, vaultUUID string) (*onepassword.Item, error) | ||||||
| 	GetItems(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) | 	CreateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) | ||||||
| 	UpdateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) | 	UpdateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) | ||||||
| 	DeleteItem(item *onepassword.Item, vaultUUID string) 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 { | type httpClient interface { | ||||||
| @@ -112,23 +117,41 @@ func (rs *restClient) GetVaults() ([]onepassword.Vault, error) { | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if response.StatusCode != http.StatusOK { | 	var vaults []onepassword.Vault | ||||||
| 		return nil, fmt.Errorf("Unable to retrieve vaults. Receieved %q for %q", response.Status, vaultURL) | 	if err := parseResponse(response, http.StatusOK, &vaults); err != nil { | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	body, err := ioutil.ReadAll(response.Body) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	vaults := []onepassword.Vault{} |  | ||||||
| 	if err := json.Unmarshal(body, &vaults); err != nil { |  | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return vaults, nil | 	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) { | func (rs *restClient) GetVaultsByTitle(title string) ([]onepassword.Vault, error) { | ||||||
| 	span := rs.tracer.StartSpan("GetVaultsByTitle") | 	span := rs.tracer.StartSpan("GetVaultsByTitle") | ||||||
| 	defer span.Finish() | 	defer span.Finish() | ||||||
| @@ -145,17 +168,8 @@ func (rs *restClient) GetVaultsByTitle(title string) ([]onepassword.Vault, error | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if response.StatusCode != http.StatusOK { | 	var vaults []onepassword.Vault | ||||||
| 		return nil, fmt.Errorf("Unable to retrieve vaults. Receieved %q for %q", response.Status, itemURL) | 	if err := parseResponse(response, http.StatusOK, &vaults); err != nil { | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	body, err := ioutil.ReadAll(response.Body) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	vaults := []onepassword.Vault{} |  | ||||||
| 	if err := json.Unmarshal(body, &vaults); err != nil { |  | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -177,18 +191,8 @@ func (rs *restClient) GetItem(uuid string, vaultUUID string) (*onepassword.Item, | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  | 	var item onepassword.Item | ||||||
| 	if response.StatusCode != http.StatusOK { | 	if err := parseResponse(response, http.StatusOK, &item); err != nil { | ||||||
| 		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 { |  | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -226,17 +230,8 @@ func (rs *restClient) GetItemsByTitle(title string, vaultUUID string) ([]onepass | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if response.StatusCode != http.StatusOK { | 	var items []onepassword.Item | ||||||
| 		return nil, fmt.Errorf("Unable to retrieve item. Receieved %q for %q", response.Status, itemURL) | 	if err := parseResponse(response, http.StatusOK, &items); err != nil { | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	body, err := ioutil.ReadAll(response.Body) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	items := []onepassword.Item{} |  | ||||||
| 	if err := json.Unmarshal(body, &items); err != nil { |  | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -258,17 +253,8 @@ func (rs *restClient) GetItems(vaultUUID string) ([]onepassword.Item, error) { | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if response.StatusCode != http.StatusOK { | 	var items []onepassword.Item | ||||||
| 		return nil, fmt.Errorf("Unable to retrieve items. Receieved %q for %q", response.Status, itemURL) | 	if err := parseResponse(response, http.StatusOK, &items); err != nil { | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	body, err := ioutil.ReadAll(response.Body) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	items := []onepassword.Item{} |  | ||||||
| 	if err := json.Unmarshal(body, &items); err != nil { |  | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -296,17 +282,8 @@ func (rs *restClient) CreateItem(item *onepassword.Item, vaultUUID string) (*one | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if response.StatusCode != http.StatusOK { | 	var newItem onepassword.Item | ||||||
| 		return nil, fmt.Errorf("Unable to create item. Receieved %q for %q", response.Status, itemURL) | 	if err := parseResponse(response, http.StatusOK, &newItem); err != nil { | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	body, err := ioutil.ReadAll(response.Body) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	newItem := onepassword.Item{} |  | ||||||
| 	if err := json.Unmarshal(body, &newItem); err != nil { |  | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -334,17 +311,8 @@ func (rs *restClient) UpdateItem(item *onepassword.Item, vaultUUID string) (*one | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if response.StatusCode != http.StatusOK { | 	var newItem onepassword.Item | ||||||
| 		return nil, fmt.Errorf("Unable to update item. Receieved %q for %q", response.Status, itemURL) | 	if err := parseResponse(response, http.StatusOK, &newItem); err != nil { | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	body, err := ioutil.ReadAll(response.Body) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	newItem := onepassword.Item{} |  | ||||||
| 	if err := json.Unmarshal(body, &newItem); err != nil { |  | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -367,13 +335,73 @@ func (rs *restClient) DeleteItem(item *onepassword.Item, vaultUUID string) error | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if response.StatusCode != http.StatusNoContent { | 	if err := parseResponse(response, http.StatusNoContent, nil); err != nil { | ||||||
| 		return fmt.Errorf("Unable to retrieve item. Receieved %q for %q", response.Status, itemURL) | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	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) { | 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) | 	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 | 	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 | package connect | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"net/http" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
| // SDKVersion is the latest Semantic Version of the library | // SDKVersion is the latest Semantic Version of the library | ||||||
| // Do not rename this variable without changing the regex in the Makefile | // 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" | 	Document             ItemCategory = "DOCUMENT" | ||||||
| 	EmailAccount         ItemCategory = "EMAIL_ACCOUNT" | 	EmailAccount         ItemCategory = "EMAIL_ACCOUNT" | ||||||
| 	SocialSecurityNumber ItemCategory = "SOCIAL_SECURITY_NUMBER" | 	SocialSecurityNumber ItemCategory = "SOCIAL_SECURITY_NUMBER" | ||||||
|  | 	ApiCredential        ItemCategory = "API_CREDENTIAL" | ||||||
| 	Custom               ItemCategory = "CUSTOM" | 	Custom               ItemCategory = "CUSTOM" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -39,7 +40,7 @@ func (ic *ItemCategory) UnmarshalJSON(b []byte) error { | |||||||
| 	switch category { | 	switch category { | ||||||
| 	case Login, Password, Server, Database, CreditCard, Membership, Passport, SoftwareLicense, | 	case Login, Password, Server, Database, CreditCard, Membership, Passport, SoftwareLicense, | ||||||
| 		OutdoorLicense, SecureNote, WirelessRouter, BankAccount, DriverLicense, Identity, RewardProgram, | 		OutdoorLicense, SecureNote, WirelessRouter, BankAccount, DriverLicense, Identity, RewardProgram, | ||||||
| 		Document, EmailAccount, SocialSecurityNumber: | 		Document, EmailAccount, SocialSecurityNumber, ApiCredential: | ||||||
| 		*ic = category | 		*ic = category | ||||||
| 	default: | 	default: | ||||||
| 		*ic = Custom | 		*ic = Custom | ||||||
| @@ -64,6 +65,7 @@ type Item struct { | |||||||
|  |  | ||||||
| 	Sections []*ItemSection `json:"sections,omitempty"` | 	Sections []*ItemSection `json:"sections,omitempty"` | ||||||
| 	Fields   []*ItemField   `json:"fields,omitempty"` | 	Fields   []*ItemField   `json:"fields,omitempty"` | ||||||
|  | 	Files    []*File        `json:"files,omitempty"` | ||||||
|  |  | ||||||
| 	LastEditedBy string    `json:"lastEditedBy,omitempty"` | 	LastEditedBy string    `json:"lastEditedBy,omitempty"` | ||||||
| 	CreatedAt    time.Time `json:"createdAt,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 | 	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) { | 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 { | 	switch kind { | ||||||
| 	case reflect.Int: | 	case reflect.Int: | ||||||
| 		{ | 		{ | ||||||
| 			intobj1 := obj1.(int) | 			intobj1, ok := obj1.(int) | ||||||
| 			intobj2 := obj2.(int) | 			if !ok { | ||||||
|  | 				intobj1 = obj1Value.Convert(intType).Interface().(int) | ||||||
|  | 			} | ||||||
|  | 			intobj2, ok := obj2.(int) | ||||||
|  | 			if !ok { | ||||||
|  | 				intobj2 = obj2Value.Convert(intType).Interface().(int) | ||||||
|  | 			} | ||||||
| 			if intobj1 > intobj2 { | 			if intobj1 > intobj2 { | ||||||
| 				return compareGreater, true | 				return compareGreater, true | ||||||
| 			} | 			} | ||||||
| @@ -31,8 +61,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | |||||||
| 		} | 		} | ||||||
| 	case reflect.Int8: | 	case reflect.Int8: | ||||||
| 		{ | 		{ | ||||||
| 			int8obj1 := obj1.(int8) | 			int8obj1, ok := obj1.(int8) | ||||||
| 			int8obj2 := obj2.(int8) | 			if !ok { | ||||||
|  | 				int8obj1 = obj1Value.Convert(int8Type).Interface().(int8) | ||||||
|  | 			} | ||||||
|  | 			int8obj2, ok := obj2.(int8) | ||||||
|  | 			if !ok { | ||||||
|  | 				int8obj2 = obj2Value.Convert(int8Type).Interface().(int8) | ||||||
|  | 			} | ||||||
| 			if int8obj1 > int8obj2 { | 			if int8obj1 > int8obj2 { | ||||||
| 				return compareGreater, true | 				return compareGreater, true | ||||||
| 			} | 			} | ||||||
| @@ -45,8 +81,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | |||||||
| 		} | 		} | ||||||
| 	case reflect.Int16: | 	case reflect.Int16: | ||||||
| 		{ | 		{ | ||||||
| 			int16obj1 := obj1.(int16) | 			int16obj1, ok := obj1.(int16) | ||||||
| 			int16obj2 := obj2.(int16) | 			if !ok { | ||||||
|  | 				int16obj1 = obj1Value.Convert(int16Type).Interface().(int16) | ||||||
|  | 			} | ||||||
|  | 			int16obj2, ok := obj2.(int16) | ||||||
|  | 			if !ok { | ||||||
|  | 				int16obj2 = obj2Value.Convert(int16Type).Interface().(int16) | ||||||
|  | 			} | ||||||
| 			if int16obj1 > int16obj2 { | 			if int16obj1 > int16obj2 { | ||||||
| 				return compareGreater, true | 				return compareGreater, true | ||||||
| 			} | 			} | ||||||
| @@ -59,8 +101,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | |||||||
| 		} | 		} | ||||||
| 	case reflect.Int32: | 	case reflect.Int32: | ||||||
| 		{ | 		{ | ||||||
| 			int32obj1 := obj1.(int32) | 			int32obj1, ok := obj1.(int32) | ||||||
| 			int32obj2 := obj2.(int32) | 			if !ok { | ||||||
|  | 				int32obj1 = obj1Value.Convert(int32Type).Interface().(int32) | ||||||
|  | 			} | ||||||
|  | 			int32obj2, ok := obj2.(int32) | ||||||
|  | 			if !ok { | ||||||
|  | 				int32obj2 = obj2Value.Convert(int32Type).Interface().(int32) | ||||||
|  | 			} | ||||||
| 			if int32obj1 > int32obj2 { | 			if int32obj1 > int32obj2 { | ||||||
| 				return compareGreater, true | 				return compareGreater, true | ||||||
| 			} | 			} | ||||||
| @@ -73,8 +121,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | |||||||
| 		} | 		} | ||||||
| 	case reflect.Int64: | 	case reflect.Int64: | ||||||
| 		{ | 		{ | ||||||
| 			int64obj1 := obj1.(int64) | 			int64obj1, ok := obj1.(int64) | ||||||
| 			int64obj2 := obj2.(int64) | 			if !ok { | ||||||
|  | 				int64obj1 = obj1Value.Convert(int64Type).Interface().(int64) | ||||||
|  | 			} | ||||||
|  | 			int64obj2, ok := obj2.(int64) | ||||||
|  | 			if !ok { | ||||||
|  | 				int64obj2 = obj2Value.Convert(int64Type).Interface().(int64) | ||||||
|  | 			} | ||||||
| 			if int64obj1 > int64obj2 { | 			if int64obj1 > int64obj2 { | ||||||
| 				return compareGreater, true | 				return compareGreater, true | ||||||
| 			} | 			} | ||||||
| @@ -87,8 +141,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | |||||||
| 		} | 		} | ||||||
| 	case reflect.Uint: | 	case reflect.Uint: | ||||||
| 		{ | 		{ | ||||||
| 			uintobj1 := obj1.(uint) | 			uintobj1, ok := obj1.(uint) | ||||||
| 			uintobj2 := obj2.(uint) | 			if !ok { | ||||||
|  | 				uintobj1 = obj1Value.Convert(uintType).Interface().(uint) | ||||||
|  | 			} | ||||||
|  | 			uintobj2, ok := obj2.(uint) | ||||||
|  | 			if !ok { | ||||||
|  | 				uintobj2 = obj2Value.Convert(uintType).Interface().(uint) | ||||||
|  | 			} | ||||||
| 			if uintobj1 > uintobj2 { | 			if uintobj1 > uintobj2 { | ||||||
| 				return compareGreater, true | 				return compareGreater, true | ||||||
| 			} | 			} | ||||||
| @@ -101,8 +161,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | |||||||
| 		} | 		} | ||||||
| 	case reflect.Uint8: | 	case reflect.Uint8: | ||||||
| 		{ | 		{ | ||||||
| 			uint8obj1 := obj1.(uint8) | 			uint8obj1, ok := obj1.(uint8) | ||||||
| 			uint8obj2 := obj2.(uint8) | 			if !ok { | ||||||
|  | 				uint8obj1 = obj1Value.Convert(uint8Type).Interface().(uint8) | ||||||
|  | 			} | ||||||
|  | 			uint8obj2, ok := obj2.(uint8) | ||||||
|  | 			if !ok { | ||||||
|  | 				uint8obj2 = obj2Value.Convert(uint8Type).Interface().(uint8) | ||||||
|  | 			} | ||||||
| 			if uint8obj1 > uint8obj2 { | 			if uint8obj1 > uint8obj2 { | ||||||
| 				return compareGreater, true | 				return compareGreater, true | ||||||
| 			} | 			} | ||||||
| @@ -115,8 +181,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | |||||||
| 		} | 		} | ||||||
| 	case reflect.Uint16: | 	case reflect.Uint16: | ||||||
| 		{ | 		{ | ||||||
| 			uint16obj1 := obj1.(uint16) | 			uint16obj1, ok := obj1.(uint16) | ||||||
| 			uint16obj2 := obj2.(uint16) | 			if !ok { | ||||||
|  | 				uint16obj1 = obj1Value.Convert(uint16Type).Interface().(uint16) | ||||||
|  | 			} | ||||||
|  | 			uint16obj2, ok := obj2.(uint16) | ||||||
|  | 			if !ok { | ||||||
|  | 				uint16obj2 = obj2Value.Convert(uint16Type).Interface().(uint16) | ||||||
|  | 			} | ||||||
| 			if uint16obj1 > uint16obj2 { | 			if uint16obj1 > uint16obj2 { | ||||||
| 				return compareGreater, true | 				return compareGreater, true | ||||||
| 			} | 			} | ||||||
| @@ -129,8 +201,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | |||||||
| 		} | 		} | ||||||
| 	case reflect.Uint32: | 	case reflect.Uint32: | ||||||
| 		{ | 		{ | ||||||
| 			uint32obj1 := obj1.(uint32) | 			uint32obj1, ok := obj1.(uint32) | ||||||
| 			uint32obj2 := obj2.(uint32) | 			if !ok { | ||||||
|  | 				uint32obj1 = obj1Value.Convert(uint32Type).Interface().(uint32) | ||||||
|  | 			} | ||||||
|  | 			uint32obj2, ok := obj2.(uint32) | ||||||
|  | 			if !ok { | ||||||
|  | 				uint32obj2 = obj2Value.Convert(uint32Type).Interface().(uint32) | ||||||
|  | 			} | ||||||
| 			if uint32obj1 > uint32obj2 { | 			if uint32obj1 > uint32obj2 { | ||||||
| 				return compareGreater, true | 				return compareGreater, true | ||||||
| 			} | 			} | ||||||
| @@ -143,8 +221,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | |||||||
| 		} | 		} | ||||||
| 	case reflect.Uint64: | 	case reflect.Uint64: | ||||||
| 		{ | 		{ | ||||||
| 			uint64obj1 := obj1.(uint64) | 			uint64obj1, ok := obj1.(uint64) | ||||||
| 			uint64obj2 := obj2.(uint64) | 			if !ok { | ||||||
|  | 				uint64obj1 = obj1Value.Convert(uint64Type).Interface().(uint64) | ||||||
|  | 			} | ||||||
|  | 			uint64obj2, ok := obj2.(uint64) | ||||||
|  | 			if !ok { | ||||||
|  | 				uint64obj2 = obj2Value.Convert(uint64Type).Interface().(uint64) | ||||||
|  | 			} | ||||||
| 			if uint64obj1 > uint64obj2 { | 			if uint64obj1 > uint64obj2 { | ||||||
| 				return compareGreater, true | 				return compareGreater, true | ||||||
| 			} | 			} | ||||||
| @@ -157,8 +241,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | |||||||
| 		} | 		} | ||||||
| 	case reflect.Float32: | 	case reflect.Float32: | ||||||
| 		{ | 		{ | ||||||
| 			float32obj1 := obj1.(float32) | 			float32obj1, ok := obj1.(float32) | ||||||
| 			float32obj2 := obj2.(float32) | 			if !ok { | ||||||
|  | 				float32obj1 = obj1Value.Convert(float32Type).Interface().(float32) | ||||||
|  | 			} | ||||||
|  | 			float32obj2, ok := obj2.(float32) | ||||||
|  | 			if !ok { | ||||||
|  | 				float32obj2 = obj2Value.Convert(float32Type).Interface().(float32) | ||||||
|  | 			} | ||||||
| 			if float32obj1 > float32obj2 { | 			if float32obj1 > float32obj2 { | ||||||
| 				return compareGreater, true | 				return compareGreater, true | ||||||
| 			} | 			} | ||||||
| @@ -171,8 +261,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | |||||||
| 		} | 		} | ||||||
| 	case reflect.Float64: | 	case reflect.Float64: | ||||||
| 		{ | 		{ | ||||||
| 			float64obj1 := obj1.(float64) | 			float64obj1, ok := obj1.(float64) | ||||||
| 			float64obj2 := obj2.(float64) | 			if !ok { | ||||||
|  | 				float64obj1 = obj1Value.Convert(float64Type).Interface().(float64) | ||||||
|  | 			} | ||||||
|  | 			float64obj2, ok := obj2.(float64) | ||||||
|  | 			if !ok { | ||||||
|  | 				float64obj2 = obj2Value.Convert(float64Type).Interface().(float64) | ||||||
|  | 			} | ||||||
| 			if float64obj1 > float64obj2 { | 			if float64obj1 > float64obj2 { | ||||||
| 				return compareGreater, true | 				return compareGreater, true | ||||||
| 			} | 			} | ||||||
| @@ -185,8 +281,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { | |||||||
| 		} | 		} | ||||||
| 	case reflect.String: | 	case reflect.String: | ||||||
| 		{ | 		{ | ||||||
| 			stringobj1 := obj1.(string) | 			stringobj1, ok := obj1.(string) | ||||||
| 			stringobj2 := obj2.(string) | 			if !ok { | ||||||
|  | 				stringobj1 = obj1Value.Convert(stringType).Interface().(string) | ||||||
|  | 			} | ||||||
|  | 			stringobj2, ok := obj2.(string) | ||||||
|  | 			if !ok { | ||||||
|  | 				stringobj2 = obj2Value.Convert(stringType).Interface().(string) | ||||||
|  | 			} | ||||||
| 			if stringobj1 > stringobj2 { | 			if stringobj1 > stringobj2 { | ||||||
| 				return compareGreater, true | 				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) | 	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 { | func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool { | ||||||
| 	if h, ok := t.(tHelper); ok { | 	if h, ok := t.(tHelper); ok { | ||||||
| 		h.Helper() | 		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...)...) | 	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, | // Eventuallyf asserts that given condition will be met in waitFor time, | ||||||
| // periodically checking target function each tick. | // 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...)...) | 	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. | // IsTypef asserts that the specified objects are of the same type. | ||||||
| func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool { | func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool { | ||||||
| 	if h, ok := t.(tHelper); ok { | 	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...)...) | 	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, | // Neverf asserts that the given condition doesn't satisfy in waitFor time, | ||||||
| // periodically checking the target function each tick. | // 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...)...) | 	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. | // NotNilf asserts that the specified object is not nil. | ||||||
| // | // | ||||||
| //    assert.NotNilf(t, err, "error message %s", "formatted") | //    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...)...) | 	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. | // Regexpf asserts that a specified regexp matches a string. | ||||||
| // | // | ||||||
| //  assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") | //  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...) | 	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`). | // Errorf asserts that a function returned an error (i.e. not `nil`). | ||||||
| // | // | ||||||
| //   actualObj, err := SomeFunction() | //   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...) | 	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. | // IsType asserts that the specified objects are of the same type. | ||||||
| func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool { | func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool { | ||||||
| 	if h, ok := a.t.(tHelper); ok { | 	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...) | 	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, | // Never asserts that the given condition doesn't satisfy in waitFor time, | ||||||
| // periodically checking the target function each tick. | // 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...) | 	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. | // NotNil asserts that the specified object is not nil. | ||||||
| // | // | ||||||
| //    a.NotNil(err) | //    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...) | 	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. | // Regexp asserts that a specified regexp matches a string. | ||||||
| // | // | ||||||
| //  a.Regexp(regexp.MustCompile("start"), "it's starting") | //  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 | 	if len(name) == len(prefix) { // "Test" is ok | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
| 	rune, _ := utf8.DecodeRuneInString(name[len(prefix):]) | 	r, _ := utf8.DecodeRuneInString(name[len(prefix):]) | ||||||
| 	return !unicode.IsLower(rune) | 	return !unicode.IsLower(r) | ||||||
| } | } | ||||||
|  |  | ||||||
| func messageFromMsgAndArgs(msgAndArgs ...interface{}) string { | func messageFromMsgAndArgs(msgAndArgs ...interface{}) string { | ||||||
| @@ -1622,6 +1622,7 @@ var spewConfig = spew.ConfigState{ | |||||||
| 	DisableCapacities:       true, | 	DisableCapacities:       true, | ||||||
| 	SortKeys:                true, | 	SortKeys:                true, | ||||||
| 	DisableMethods:          true, | 	DisableMethods:          true, | ||||||
|  | 	MaxDepth:                10, | ||||||
| } | } | ||||||
|  |  | ||||||
| type tHelper interface { | 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 v0.49.0 | ||||||
| cloud.google.com/go/compute/metadata | 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/connect | ||||||
| github.com/1Password/connect-sdk-go/onepassword | github.com/1Password/connect-sdk-go/onepassword | ||||||
| # github.com/Azure/go-autorest/autorest v0.9.3-0.20191028180845-3492b2aff503 | # 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/prometheus/procfs/internal/util | ||||||
| # github.com/spf13/pflag v1.0.5 | # github.com/spf13/pflag v1.0.5 | ||||||
| github.com/spf13/pflag | 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/stretchr/testify/assert | ||||||
| # github.com/uber/jaeger-client-go v2.25.0+incompatible | # github.com/uber/jaeger-client-go v2.25.0+incompatible | ||||||
| github.com/uber/jaeger-client-go | github.com/uber/jaeger-client-go | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user