Compare commits

..

1 Commits

Author SHA1 Message Date
Marton Soos
717f9bc33f Skip shadowed env variables 2022-02-17 17:49:28 +01:00
30 changed files with 206 additions and 1391 deletions

View File

@@ -1 +1 @@
1.4.0 1.1.0

View File

@@ -1,128 +1,79 @@
[//]: # "START/LATEST" [//]: # (START/LATEST)
# Latest # Latest
## Features ## Features
* A user-friendly description of a new feature. {issue-number}
- A user-friendly description of a new feature. {issue-number}
## Fixes ## Fixes
* A user-friendly description of a fix. {issue-number}
- A user-friendly description of a fix. {issue-number}
## Security ## Security
* A user-friendly description of a security fix. {issue-number}
- A user-friendly description of a security fix. {issue-number}
--- ---
[//]: # "START/v1.4.0" [//]: # (START/v1.1.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 # v1.1.0
## Fixes ## Fixes
* Fix normalization for keys in a Secret's `data` section to allow upper- and lower-case alphanumeric characters. {#66}
- Fix normalization for keys in a Secret's `data` section to allow upper- and lower-case alphanumeric characters. {#66}
--- ---
[//]: # "START/v1.0.2" [//]: # (START/v1.0.2)
# v1.0.2 # 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}
- This release also contains an arm64 Docker image. {#20} * Docker images are also pushed to the :latest and :<major>.<minor> tags.
- 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
- Option to automatically deploy 1Password Connect via the operator * Ignore restart annotation when looking for 1Password annotations
- Ignore restart annotation when looking for 1Password annotations * Release Automation
- Release Automation * Upgrading apiextensions.k8s.io/v1beta apiversion from the operator custom resource
- Upgrading apiextensions.k8s.io/v1beta apiversion from the operator custom resource * Adding configuration for auto rolling restart on deployments
- Adding configuration for auto rolling restart on deployments * Configure Auto Restarts for a OnePasswordItem Custom Resource
- Configure Auto Restarts for a OnePasswordItem Custom Resource * Update Connect Dependencies to latest
- Update Connect Dependencies to latest * Add Github action for building and testing operator
- Add Github action for building and testing operator
## Fixes: ## Fixes:
* Fix spec field example for OnePasswordItem in readme
- Fix spec field example for OnePasswordItem in readme * Casing of annotations are now consistent
- 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 deployment creations with `onepassword` annotations and creates an associated kubernetes secret * watches for `onepasswordsecret` crd creations and creates an associated kubernetes secrets
- watches for `onepasswordsecret` crd creations and creates an associated kubernetes secrets * watches for changes to 1Password secrets associated with kubernetes secrets and updates the kubernetes secret with changes
- watches for changes to 1Password secrets associated with kubernetes secrets and updates the kubernetes secret with changes * restart pods when secret has been updated
- restart pods when secret has been updated * cleanup of kubernetes secrets when deployment or `onepasswordsecret` is deleted
- cleanup of kubernetes secrets when deployment or `onepasswordsecret` is deleted
--- ---

View File

@@ -30,13 +30,14 @@ 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`:
@@ -52,12 +53,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)
@@ -67,13 +68,13 @@ kubectl create secret generic onepassword-token --from-literal=token=$(op create
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**
@@ -111,13 +112,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.
@@ -136,11 +137,6 @@ 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.
@@ -175,7 +171,7 @@ metadata:
annotations: annotations:
operator.1password.io/auto-restart: "true" operator.1password.io/auto-restart: "true"
``` ```
If the value is not set, the auto restart settings on the operator will be used. This value can be overwritten by deployment. If the value is not set, the auto reset 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:
@@ -188,7 +184,7 @@ metadata:
annotations: annotations:
operator.1password.io/auto-restart: "true" operator.1password.io/auto-restart: "true"
``` ```
If the value is not set, the auto restart settings on the namespace will be used. If the value is not set, the auto reset 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:
@@ -201,7 +197,7 @@ metadata:
annotations: annotations:
operator.1password.io/auto-restart: "true" operator.1password.io/auto-restart: "true"
``` ```
If the value is not set, the auto restart settings on the deployment will be used. If the value is not set, the auto reset settings on the deployment will be used.
## Development ## Development

View File

@@ -178,10 +178,7 @@ func main() {
ticker.Stop() ticker.Stop()
return return
case <-ticker.C: case <-ticker.C:
err := updatedSecretsPoller.UpdateKubernetesSecretsTask() updatedSecretsPoller.UpdateKubernetesSecretsTask()
if err != nil {
log.Error(err, "error running update kubernetes secret task")
}
} }
} }
}() }()

View File

@@ -39,7 +39,4 @@ 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
View File

@@ -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.2.0 github.com/1Password/connect-sdk-go v1.0.1
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.7.0 github.com/stretchr/testify v1.6.1
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
View File

@@ -20,8 +20,6 @@ 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=
@@ -887,8 +885,6 @@ 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=

View File

@@ -26,7 +26,6 @@ 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"`

View File

@@ -14,11 +14,9 @@ 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"
@@ -116,7 +114,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, deployment.Namespace, annotations, request); err != nil { if err := r.HandleApplyingDeployment(deployment.Namespace, annotations, request); err != nil {
return reconcile.Result{}, err return reconcile.Result{}, err
} }
return reconcile.Result{}, nil return reconcile.Result{}, nil
@@ -189,13 +187,11 @@ func (r *ReconcileDeployment) removeOnePasswordFinalizerFromDeployment(deploymen
return r.kubeClient.Update(context.Background(), deployment) return r.kubeClient.Update(context.Background(), deployment)
} }
func (r *ReconcileDeployment) HandleApplyingDeployment(deployment *appsv1.Deployment, namespace string, annotations map[string]string, request reconcile.Request) error { func (r *ReconcileDeployment) HandleApplyingDeployment(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) 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
@@ -206,17 +202,5 @@ func (r *ReconcileDeployment) HandleApplyingDeployment(deployment *appsv1.Deploy
return fmt.Errorf("Failed to retrieve item: %v", err) return fmt.Errorf("Failed to retrieve item: %v", err)
} }
// Create owner reference. return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, namespace, item, annotations[op.RestartDeploymentsAnnotation], secretLabels, annotations)
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, annotations, ownerRef)
} }

View File

@@ -279,7 +279,7 @@ var tests = []testReconcileItem{
Name: name, Name: name,
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, op.ItemPathAnnotation: itemPath,
op.NameAnnotation: name, op.NameAnnotation: name,
}, },
@@ -292,7 +292,7 @@ var tests = []testReconcileItem{
Name: name, Name: name,
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, op.ItemPathAnnotation: itemPath,
op.NameAnnotation: name, op.NameAnnotation: name,
}, },
@@ -329,7 +329,6 @@ var tests = []testReconcileItem{
op.VersionAnnotation: "456", op.VersionAnnotation: "456",
}, },
}, },
Type: corev1.SecretType(""),
Data: expectedSecretData, Data: expectedSecretData,
}, },
expectedError: nil, expectedError: nil,
@@ -341,7 +340,6 @@ 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{
@@ -375,7 +373,6 @@ 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{

View File

@@ -3,7 +3,6 @@ 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"
@@ -14,11 +13,9 @@ 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"
@@ -148,7 +145,6 @@ func (r *ReconcileOnePasswordItem) HandleOnePasswordItem(resource *onepasswordv1
secretName := resource.GetName() secretName := resource.GetName()
labels := resource.Labels labels := resource.Labels
annotations := resource.Annotations annotations := resource.Annotations
secretType := resource.Type
autoRestart := annotations[op.RestartDeploymentsAnnotation] autoRestart := annotations[op.RestartDeploymentsAnnotation]
item, err := onepassword.GetOnePasswordItemByPath(r.opConnectClient, resource.Spec.ItemPath) item, err := onepassword.GetOnePasswordItemByPath(r.opConnectClient, resource.Spec.ItemPath)
@@ -156,17 +152,5 @@ func (r *ReconcileOnePasswordItem) HandleOnePasswordItem(resource *onepasswordv1
return fmt.Errorf("Failed to retrieve item: %v", err) return fmt.Errorf("Failed to retrieve item: %v", err)
} }
// Create owner reference. return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, resource.Namespace, item, autoRestart, labels, annotations)
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, annotations, ownerRef)
} }

View File

@@ -5,7 +5,6 @@ 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"
@@ -101,7 +100,7 @@ var tests = []testReconcileItem{
}, },
}, },
{ {
testName: "Test Do not update if OnePassword Version or VaultPath has not changed", testName: "Test Do not update if OnePassword Version has not changed",
customResource: &onepasswordv1.OnePasswordItem{ customResource: &onepasswordv1.OnePasswordItem{
TypeMeta: metav1.TypeMeta{ TypeMeta: metav1.TypeMeta{
Kind: onePasswordItemKind, Kind: onePasswordItemKind,
@@ -120,7 +119,7 @@ var tests = []testReconcileItem{
Name: name, Name: name,
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, op.ItemPathAnnotation: itemPath,
}, },
}, },
@@ -132,7 +131,7 @@ var tests = []testReconcileItem{
Name: name, Name: name,
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, op.ItemPathAnnotation: itemPath,
}, },
}, },
@@ -154,7 +153,7 @@ var tests = []testReconcileItem{
Name: name, Name: name,
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, op.ItemPathAnnotation: itemPath,
}, },
Labels: map[string]string{}, Labels: map[string]string{},
@@ -168,7 +167,7 @@ var tests = []testReconcileItem{
Name: name, Name: name,
Namespace: namespace, Namespace: namespace,
Annotations: map[string]string{ Annotations: map[string]string{
op.VersionAnnotation: "456", op.VersionAnnotation: "456",
op.ItemPathAnnotation: itemPath, op.ItemPathAnnotation: itemPath,
}, },
Labels: map[string]string{}, Labels: map[string]string{},
@@ -181,7 +180,7 @@ var tests = []testReconcileItem{
Name: name, Name: name,
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, op.ItemPathAnnotation: itemPath,
}, },
Labels: map[string]string{}, Labels: map[string]string{},
@@ -193,59 +192,6 @@ var tests = []testReconcileItem{
passKey: password, passKey: password,
}, },
}, },
{
testName: "Test Updating Type of Existing Kubernetes Secret using OnePasswordItem",
customResource: &onepasswordv1.OnePasswordItem{
TypeMeta: metav1.TypeMeta{
Kind: onePasswordItemKind,
APIVersion: onePasswordItemAPIVersion,
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Annotations: map[string]string{
op.VersionAnnotation: fmt.Sprint(version),
op.ItemPathAnnotation: itemPath,
},
Labels: map[string]string{},
},
Spec: onepasswordv1.OnePasswordItemSpec{
ItemPath: itemPath,
},
Type: string(corev1.SecretTypeBasicAuth),
},
existingSecret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Annotations: map[string]string{
op.VersionAnnotation: fmt.Sprint(version),
op.ItemPathAnnotation: itemPath,
},
Labels: map[string]string{},
},
Type: corev1.SecretTypeBasicAuth,
Data: expectedSecretData,
},
expectedError: nil,
expectedResultSecret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Annotations: map[string]string{
op.VersionAnnotation: fmt.Sprint(version),
op.ItemPathAnnotation: itemPath,
},
Labels: map[string]string{},
},
Type: corev1.SecretTypeBasicAuth,
Data: expectedSecretData,
},
opItem: map[string]string{
userKey: username,
passKey: password,
},
},
{ {
testName: "Custom secret type", testName: "Custom secret type",
customResource: &onepasswordv1.OnePasswordItem{ customResource: &onepasswordv1.OnePasswordItem{
@@ -260,7 +206,6 @@ var tests = []testReconcileItem{
Spec: onepasswordv1.OnePasswordItemSpec{ Spec: onepasswordv1.OnePasswordItemSpec{
ItemPath: itemPath, ItemPath: itemPath,
}, },
Type: "custom",
}, },
existingSecret: nil, existingSecret: nil,
expectedError: nil, expectedError: nil,
@@ -272,51 +217,6 @@ 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{

View File

@@ -7,17 +7,13 @@ 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"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"reflect"
kubeValidate "k8s.io/apimachinery/pkg/util/validation" kubeValidate "k8s.io/apimachinery/pkg/util/validation"
kubernetesClient "sigs.k8s.io/controller-runtime/pkg/client" kubernetesClient "sigs.k8s.io/controller-runtime/pkg/client"
@@ -31,11 +27,9 @@ 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, labels map[string]string, secretType string, secretAnnotations map[string]string, ownerRef *metav1.OwnerReference) error { func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretName, namespace string, item *onepassword.Item, autoRestart string, labels map[string]string, secretAnnotations map[string]string) error {
itemVersion := fmt.Sprint(item.Version) itemVersion := fmt.Sprint(item.Version)
@@ -55,9 +49,7 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa
} }
secretAnnotations[RestartDeploymentsAnnotation] = autoRestart secretAnnotations[RestartDeploymentsAnnotation] = autoRestart
} }
secret := BuildKubernetesSecretFromOnePasswordItem(secretName, namespace, secretAnnotations, labels, *item)
// "Opaque" and "" secret types are treated the same by Kubernetes.
secret := BuildKubernetesSecretFromOnePasswordItem(secretName, namespace, secretAnnotations, labels, secretType, *item, ownerRef)
currentSecret := &corev1.Secret{} 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)
@@ -68,14 +60,7 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa
return err return err
} }
currentAnnotations := currentSecret.Annotations if ! reflect.DeepEqual(currentSecret.Annotations, secretAnnotations) || ! reflect.DeepEqual(currentSecret.Labels, labels) {
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 = secretAnnotations currentSecret.ObjectMeta.Annotations = secretAnnotations
currentSecret.ObjectMeta.Labels = labels currentSecret.ObjectMeta.Labels = labels
@@ -87,26 +72,19 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa
return nil return nil
} }
func BuildKubernetesSecretFromOnePasswordItem(name, namespace string, annotations map[string]string, labels map[string]string, secretType string, item onepassword.Item, ownerRef *metav1.OwnerReference) *corev1.Secret { func BuildKubernetesSecretFromOnePasswordItem(name, namespace string, annotations map[string]string, labels map[string]string, item onepassword.Item) *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, Labels: labels,
OwnerReferences: ownerRefs,
}, },
Data: BuildKubernetesSecretData(item.Fields, item.Files), Data: BuildKubernetesSecretData(item.Fields),
Type: corev1.SecretType(secretType),
} }
} }
func BuildKubernetesSecretData(fields []*onepassword.ItemField, files []*onepassword.File) map[string][]byte { func BuildKubernetesSecretData(fields []*onepassword.ItemField) 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 != "" {
@@ -114,23 +92,6 @@ func BuildKubernetesSecretData(fields []*onepassword.ItemField, files []*onepass
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
} }

View File

@@ -8,7 +8,6 @@ import (
"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" kubeValidate "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
@@ -36,9 +35,7 @@ func TestCreateKubernetesSecretFromOnePasswordItem(t *testing.T) {
secretAnnotations := map[string]string{ secretAnnotations := map[string]string{
"testAnnotation": "exists", "testAnnotation": "exists",
} }
secretType := "" err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretAnnotations)
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, secretAnnotations, nil)
if err != nil { if err != nil {
t.Errorf("Unexpected error: %v", err) t.Errorf("Unexpected error: %v", err)
} }
@@ -56,54 +53,6 @@ func TestCreateKubernetesSecretFromOnePasswordItem(t *testing.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{}
secretAnnotations := map[string]string{
"testAnnotation": "exists",
}
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, secretAnnotations, 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"
@@ -117,10 +66,7 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) {
kubeClient := fake.NewFakeClient() kubeClient := fake.NewFakeClient()
secretLabels := map[string]string{} secretLabels := map[string]string{}
secretAnnotations := map[string]string{} secretAnnotations := map[string]string{}
secretType := "" err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretAnnotations)
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, secretAnnotations, nil)
if err != nil { if err != nil {
t.Errorf("Unexpected error: %v", err) t.Errorf("Unexpected error: %v", err)
} }
@@ -131,7 +77,7 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) {
newItem.Version = 456 newItem.Version = 456
newItem.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda" newItem.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda"
newItem.ID = "h46bb3jddvay7nxopfhvlwg35q" newItem.ID = "h46bb3jddvay7nxopfhvlwg35q"
err = CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &newItem, restartDeploymentAnnotation, secretLabels, secretType, secretAnnotations, nil) err = CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &newItem, restartDeploymentAnnotation, secretLabels, secretAnnotations)
if err != nil { if err != nil {
t.Errorf("Unexpected error: %v", err) t.Errorf("Unexpected error: %v", err)
} }
@@ -147,7 +93,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, nil) secretData := BuildKubernetesSecretData(fields)
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))
} }
@@ -165,9 +111,8 @@ func TestBuildKubernetesSecretFromOnePasswordItem(t *testing.T) {
item := onepassword.Item{} item := onepassword.Item{}
item.Fields = generateFields(5) item.Fields = generateFields(5)
labels := map[string]string{} labels := map[string]string{}
secretType := ""
kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels, secretType, item, nil) kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels, item)
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)
} }
@@ -189,7 +134,6 @@ func TestBuildKubernetesSecretFixesInvalidLabels(t *testing.T) {
} }
labels := map[string]string{} labels := map[string]string{}
item := onepassword.Item{} item := onepassword.Item{}
secretType := ""
item.Fields = []*onepassword.ItemField{ item.Fields = []*onepassword.ItemField{
{ {
@@ -202,7 +146,7 @@ func TestBuildKubernetesSecretFixesInvalidLabels(t *testing.T) {
}, },
} }
kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels, secretType, item, nil) kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels, item)
// Assert Secret's meta.name was fixed // Assert Secret's meta.name was fixed
if kubeSecret.Name != expectedName { if kubeSecret.Name != expectedName {
@@ -220,39 +164,6 @@ 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{}
secretAnnotations := map[string]string{
"testAnnotation": "exists",
}
secretType := "kubernetes.io/tls"
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, secretAnnotations, 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 {

View File

@@ -7,7 +7,6 @@ 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)
@@ -15,14 +14,11 @@ 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)
@@ -30,8 +26,6 @@ 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
@@ -43,10 +37,6 @@ 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)
} }
@@ -74,11 +64,3 @@ 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)
}

View File

@@ -7,7 +7,10 @@ import (
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++ {
envVariables := containers[i].Env envVariables := containers[i].Env
envVariableNames := map[string]struct{}{}
for j := 0; j < len(envVariables); j++ { for j := 0; j < len(envVariables); j++ {
envVariableNames[envVariables[j].Name] = struct{}{}
if envVariables[j].ValueFrom != nil && envVariables[j].ValueFrom.SecretKeyRef != nil { if envVariables[j].ValueFrom != nil && envVariables[j].ValueFrom.SecretKeyRef != nil {
_, ok := secrets[envVariables[j].ValueFrom.SecretKeyRef.Name] _, ok := secrets[envVariables[j].ValueFrom.SecretKeyRef.Name]
if ok { if ok {
@@ -18,6 +21,10 @@ func AreContainersUsingSecrets(containers []corev1.Container, secrets map[string
envFromVariables := containers[i].EnvFrom envFromVariables := containers[i].EnvFrom
for j := 0; j < len(envFromVariables); j++ { for j := 0; j < len(envFromVariables); j++ {
if envFromVariables[j].SecretRef != nil { if envFromVariables[j].SecretRef != nil {
// Skip env variables that will be overwritten by Env
if _, ok := envVariableNames[envFromVariables[i].SecretRef.Name]; ok {
continue;
}
_, ok := secrets[envFromVariables[j].SecretRef.Name] _, ok := secrets[envFromVariables[j].SecretRef.Name]
if ok { if ok {
return true return true
@@ -31,7 +38,10 @@ func AreContainersUsingSecrets(containers []corev1.Container, secrets map[string
func AppendUpdatedContainerSecrets(containers []corev1.Container, secrets map[string]*corev1.Secret, updatedDeploymentSecrets map[string]*corev1.Secret) map[string]*corev1.Secret { func AppendUpdatedContainerSecrets(containers []corev1.Container, secrets map[string]*corev1.Secret, updatedDeploymentSecrets map[string]*corev1.Secret) map[string]*corev1.Secret {
for i := 0; i < len(containers); i++ { for i := 0; i < len(containers); i++ {
envVariables := containers[i].Env envVariables := containers[i].Env
envVariableNames := map[string]struct{}{}
for j := 0; j < len(envVariables); j++ { for j := 0; j < len(envVariables); j++ {
envVariableNames[envVariables[j].Name] = struct{}{}
if envVariables[j].ValueFrom != nil && envVariables[j].ValueFrom.SecretKeyRef != nil { if envVariables[j].ValueFrom != nil && envVariables[j].ValueFrom.SecretKeyRef != nil {
secret, ok := secrets[envVariables[j].ValueFrom.SecretKeyRef.Name] secret, ok := secrets[envVariables[j].ValueFrom.SecretKeyRef.Name]
if ok { if ok {
@@ -42,6 +52,10 @@ func AppendUpdatedContainerSecrets(containers []corev1.Container, secrets map[st
envFromVariables := containers[i].EnvFrom envFromVariables := containers[i].EnvFrom
for j := 0; j < len(envFromVariables); j++ { for j := 0; j < len(envFromVariables); j++ {
if envFromVariables[j].SecretRef != nil { if envFromVariables[j].SecretRef != nil {
// Skip env variables that will be overwritten by Env
if _, ok := envVariableNames[envFromVariables[i].SecretRef.Name]; ok {
continue;
}
secret, ok := secrets[envFromVariables[j].SecretRef.LocalObjectReference.Name] secret, ok := secrets[envFromVariables[j].SecretRef.LocalObjectReference.Name]
if ok { if ok {
updatedDeploymentSecrets[secret.Name] = secret updatedDeploymentSecrets[secret.Name] = secret

View File

@@ -30,14 +30,6 @@ 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
} }

View File

@@ -5,8 +5,6 @@ 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"
@@ -118,30 +116,22 @@ func (h *SecretUpdateHandler) updateKubernetesSecrets() (map[string]map[string]*
continue continue
} }
OnePasswordItemPath := h.getPathFromOnePasswordItem(secret) item, err := GetOnePasswordItemByPath(h.opConnectClient, secret.Annotations[ItemPathAnnotation])
item, err := GetOnePasswordItemByPath(h.opConnectClient, OnePasswordItemPath)
if err != nil { if err != nil {
log.Error(err, "failed to retrieve 1Password item at path \"%s\" for secret \"%s\"", secret.Annotations[ItemPathAnnotation], secret.Name) return nil, fmt.Errorf("Failed to retrieve item: %v", err)
continue
} }
itemVersion := fmt.Sprint(item.Version) itemVersion := fmt.Sprint(item.Version)
itemPathString := fmt.Sprintf("vaults/%v/items/%v", item.Vault.ID, item.ID) if currentVersion != itemVersion {
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
secret.Annotations[ItemPathAnnotation] = itemPathString
h.client.Update(context.Background(), &secret) h.client.Update(context.Background(), &secret)
continue continue
} }
log.Info(fmt.Sprintf("Updating kubernetes secret '%v'", secret.GetName())) log.Info(fmt.Sprintf("Updating kubernetes secret '%v'", secret.GetName()))
secret.Annotations[VersionAnnotation] = itemVersion secret.Annotations[VersionAnnotation] = itemVersion
secret.Annotations[ItemPathAnnotation] = itemPathString updatedSecret := kubeSecrets.BuildKubernetesSecretFromOnePasswordItem(secret.Name, secret.Namespace, secret.Annotations, secret.Labels, *item)
updatedSecret := kubeSecrets.BuildKubernetesSecretFromOnePasswordItem(secret.Name, secret.Namespace, secret.Annotations, secret.Labels, string(secret.Type), *item, nil)
log.Info(fmt.Sprintf("New secret path: %v and version: %v", updatedSecret.Annotations[ItemPathAnnotation], updatedSecret.Annotations[VersionAnnotation]))
h.client.Update(context.Background(), updatedSecret) h.client.Update(context.Background(), updatedSecret)
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)
@@ -186,22 +176,6 @@ 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

View File

@@ -1,21 +0,0 @@
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.

View File

@@ -3,7 +3,6 @@ package connect
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@@ -11,12 +10,11 @@ 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 (
@@ -26,7 +24,6 @@ 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)
@@ -35,8 +32,6 @@ 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 {
@@ -117,41 +112,23 @@ func (rs *restClient) GetVaults() ([]onepassword.Vault, error) {
return nil, err return nil, err
} }
var vaults []onepassword.Vault if response.StatusCode != http.StatusOK {
if err := parseResponse(response, http.StatusOK, &vaults); err != nil { return nil, fmt.Errorf("Unable to retrieve vaults. Receieved %q for %q", response.Status, vaultURL)
}
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
vaults := []onepassword.Vault{}
if err := json.Unmarshal(body, &vaults); err != nil {
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()
@@ -168,8 +145,17 @@ func (rs *restClient) GetVaultsByTitle(title string) ([]onepassword.Vault, error
return nil, err return nil, err
} }
var vaults []onepassword.Vault if response.StatusCode != http.StatusOK {
if err := parseResponse(response, http.StatusOK, &vaults); err != nil { return nil, fmt.Errorf("Unable to retrieve vaults. Receieved %q for %q", response.Status, itemURL)
}
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
vaults := []onepassword.Vault{}
if err := json.Unmarshal(body, &vaults); err != nil {
return nil, err return nil, err
} }
@@ -191,8 +177,18 @@ 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 err := parseResponse(response, http.StatusOK, &item); err != nil { if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Unable to retrieve item. Receieved %q for %q", response.Status, itemURL)
}
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
item := onepassword.Item{}
if err := json.Unmarshal(body, &item); err != nil {
return nil, err return nil, err
} }
@@ -230,8 +226,17 @@ func (rs *restClient) GetItemsByTitle(title string, vaultUUID string) ([]onepass
return nil, err return nil, err
} }
var items []onepassword.Item if response.StatusCode != http.StatusOK {
if err := parseResponse(response, http.StatusOK, &items); 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
}
items := []onepassword.Item{}
if err := json.Unmarshal(body, &items); err != nil {
return nil, err return nil, err
} }
@@ -253,8 +258,17 @@ func (rs *restClient) GetItems(vaultUUID string) ([]onepassword.Item, error) {
return nil, err return nil, err
} }
var items []onepassword.Item if response.StatusCode != http.StatusOK {
if err := parseResponse(response, http.StatusOK, &items); err != nil { return nil, fmt.Errorf("Unable to retrieve items. Receieved %q for %q", response.Status, itemURL)
}
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
items := []onepassword.Item{}
if err := json.Unmarshal(body, &items); err != nil {
return nil, err return nil, err
} }
@@ -282,8 +296,17 @@ func (rs *restClient) CreateItem(item *onepassword.Item, vaultUUID string) (*one
return nil, err return nil, err
} }
var newItem onepassword.Item if response.StatusCode != http.StatusOK {
if err := parseResponse(response, http.StatusOK, &newItem); err != nil { return nil, fmt.Errorf("Unable to create item. Receieved %q for %q", response.Status, itemURL)
}
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
newItem := onepassword.Item{}
if err := json.Unmarshal(body, &newItem); err != nil {
return nil, err return nil, err
} }
@@ -311,8 +334,17 @@ func (rs *restClient) UpdateItem(item *onepassword.Item, vaultUUID string) (*one
return nil, err return nil, err
} }
var newItem onepassword.Item if response.StatusCode != http.StatusOK {
if err := parseResponse(response, http.StatusOK, &newItem); err != nil { return nil, fmt.Errorf("Unable to update item. Receieved %q for %q", response.Status, itemURL)
}
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
newItem := onepassword.Item{}
if err := json.Unmarshal(body, &newItem); err != nil {
return nil, err return nil, err
} }
@@ -335,73 +367,13 @@ func (rs *restClient) DeleteItem(item *onepassword.Item, vaultUUID string) error
return err return err
} }
if err := parseResponse(response, http.StatusNoContent, nil); err != nil { if response.StatusCode != http.StatusNoContent {
return err return fmt.Errorf("Unable to retrieve item. Receieved %q for %q", response.Status, itemURL)
} }
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)
@@ -422,32 +394,3 @@ 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
}

View File

@@ -1,104 +1,5 @@
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.2.0" const SDKVersion = "1.0.1"
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
}

View File

@@ -1,21 +0,0 @@
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
}

View File

@@ -1,49 +0,0 @@
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
}

View File

@@ -28,7 +28,6 @@ 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"
) )
@@ -40,7 +39,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, ApiCredential: Document, EmailAccount, SocialSecurityNumber:
*ic = category *ic = category
default: default:
*ic = Custom *ic = Custom
@@ -65,7 +64,6 @@ 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"`

View File

@@ -13,42 +13,12 @@ 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, ok := obj1.(int) intobj1 := obj1.(int)
if !ok { intobj2 := obj2.(int)
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
} }
@@ -61,14 +31,8 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
} }
case reflect.Int8: case reflect.Int8:
{ {
int8obj1, ok := obj1.(int8) int8obj1 := obj1.(int8)
if !ok { int8obj2 := obj2.(int8)
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
} }
@@ -81,14 +45,8 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
} }
case reflect.Int16: case reflect.Int16:
{ {
int16obj1, ok := obj1.(int16) int16obj1 := obj1.(int16)
if !ok { int16obj2 := obj2.(int16)
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
} }
@@ -101,14 +59,8 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
} }
case reflect.Int32: case reflect.Int32:
{ {
int32obj1, ok := obj1.(int32) int32obj1 := obj1.(int32)
if !ok { int32obj2 := obj2.(int32)
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
} }
@@ -121,14 +73,8 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
} }
case reflect.Int64: case reflect.Int64:
{ {
int64obj1, ok := obj1.(int64) int64obj1 := obj1.(int64)
if !ok { int64obj2 := obj2.(int64)
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
} }
@@ -141,14 +87,8 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
} }
case reflect.Uint: case reflect.Uint:
{ {
uintobj1, ok := obj1.(uint) uintobj1 := obj1.(uint)
if !ok { uintobj2 := obj2.(uint)
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
} }
@@ -161,14 +101,8 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
} }
case reflect.Uint8: case reflect.Uint8:
{ {
uint8obj1, ok := obj1.(uint8) uint8obj1 := obj1.(uint8)
if !ok { uint8obj2 := obj2.(uint8)
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
} }
@@ -181,14 +115,8 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
} }
case reflect.Uint16: case reflect.Uint16:
{ {
uint16obj1, ok := obj1.(uint16) uint16obj1 := obj1.(uint16)
if !ok { uint16obj2 := obj2.(uint16)
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
} }
@@ -201,14 +129,8 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
} }
case reflect.Uint32: case reflect.Uint32:
{ {
uint32obj1, ok := obj1.(uint32) uint32obj1 := obj1.(uint32)
if !ok { uint32obj2 := obj2.(uint32)
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
} }
@@ -221,14 +143,8 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
} }
case reflect.Uint64: case reflect.Uint64:
{ {
uint64obj1, ok := obj1.(uint64) uint64obj1 := obj1.(uint64)
if !ok { uint64obj2 := obj2.(uint64)
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
} }
@@ -241,14 +157,8 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
} }
case reflect.Float32: case reflect.Float32:
{ {
float32obj1, ok := obj1.(float32) float32obj1 := obj1.(float32)
if !ok { float32obj2 := obj2.(float32)
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
} }
@@ -261,14 +171,8 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
} }
case reflect.Float64: case reflect.Float64:
{ {
float64obj1, ok := obj1.(float64) float64obj1 := obj1.(float64)
if !ok { float64obj2 := obj2.(float64)
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
} }
@@ -281,14 +185,8 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
} }
case reflect.String: case reflect.String:
{ {
stringobj1, ok := obj1.(string) stringobj1 := obj1.(string)
if !ok { stringobj2 := obj2.(string)
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
} }
@@ -342,24 +240,6 @@ 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()

View File

@@ -114,24 +114,6 @@ 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.
// //
@@ -339,54 +321,6 @@ 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 {
@@ -441,17 +375,6 @@ 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.
// //
@@ -553,15 +476,6 @@ 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")
@@ -658,17 +572,6 @@ 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")

View File

@@ -204,42 +204,6 @@ 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()
@@ -667,102 +631,6 @@ 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 {
@@ -871,28 +739,6 @@ 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.
// //
@@ -1095,24 +941,6 @@ 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)
@@ -1305,28 +1133,6 @@ 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")

View File

@@ -1,81 +0,0 @@
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)
}

View File

@@ -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
} }
r, _ := utf8.DecodeRuneInString(name[len(prefix):]) rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
return !unicode.IsLower(r) return !unicode.IsLower(rune)
} }
func messageFromMsgAndArgs(msgAndArgs ...interface{}) string { func messageFromMsgAndArgs(msgAndArgs ...interface{}) string {
@@ -1622,7 +1622,6 @@ var spewConfig = spew.ConfigState{
DisableCapacities: true, DisableCapacities: true,
SortKeys: true, SortKeys: true,
DisableMethods: true, DisableMethods: true,
MaxDepth: 10,
} }
type tHelper interface { type tHelper interface {
@@ -1694,81 +1693,3 @@ 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
View File

@@ -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.2.0 # github.com/1Password/connect-sdk-go v1.0.1
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.7.0 # github.com/stretchr/testify v1.6.1
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