Compare commits

..

1 Commits

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

View File

@@ -1 +1 @@
1.5.0
1.1.0

View File

@@ -12,136 +12,68 @@
---
[//]: # (START/v1.5.0)
# v1.5.0
## Features
* `OnePasswordItem` now contains a `status` which contains the status of creating the kubernetes secret for a OnePasswordItem. {#52}
## Fixes
* The operator no longer logs an error about changing the secret type if the secret type is not actually being changed.
* Annotations on a deployment are no longer removed when the operator triggers a restart. {#112}
---
[//]: # "START/v1.4.1"
# v1.4.1
## Fixes
- OwnerReferences on secrets are now persisted after an item is updated. {#101}
- Annotations from a Deployment or OnePasswordItem are no longer applied to Secrets that are created for it. {#102}
---
[//]: # "START/v1.4.0"
# v1.4.0
## Features
- The operator now declares the an OwnerReference for the secrets it manages. This should stop secrets from getting pruned by tools like Argo CD. {#51,#84,#96}
---
[//]: # "START/v1.3.0"
# v1.3.0
## Features
- Added support for loading secrets from files stored in 1Password. {#47}
---
[//]: # "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"
[//]: # (START/v1.1.0)
# v1.1.0
## Fixes
- Fix normalization for keys in a Secret's `data` section to allow upper- and lower-case alphanumeric characters. {#66}
* Fix normalization for keys in a Secret's `data` section to allow upper- and lower-case alphanumeric characters. {#66}
---
[//]: # "START/v1.0.2"
[//]: # (START/v1.0.2)
# v1.0.2
## Fixes
- Name normalizer added to handle non-conforming item names.
* Name normalizer added to handle non-conforming item names.
---
[//]: # "START/v1.0.1"
[//]: # (START/v1.0.1)
# v1.0.1
## Features
- This release also contains an arm64 Docker image. {#20}
- Docker images are also pushed to the :latest and :<major>.<minor> tags.
* This release also contains an arm64 Docker image. {#20}
* Docker images are also pushed to the :latest and :<major>.<minor> tags.
---
[//]: # "START/v1.0.0"
[//]: # (START/v1.0.0)
# v1.0.0
## Features:
- Option to automatically deploy 1Password Connect via the operator
- Ignore restart annotation when looking for 1Password annotations
- Release Automation
- Upgrading apiextensions.k8s.io/v1beta apiversion from the operator custom resource
- Adding configuration for auto rolling restart on deployments
- Configure Auto Restarts for a OnePasswordItem Custom Resource
- Update Connect Dependencies to latest
- Add Github action for building and testing operator
* Option to automatically deploy 1Password Connect via the operator
* Ignore restart annotation when looking for 1Password annotations
* Release Automation
* Upgrading apiextensions.k8s.io/v1beta apiversion from the operator custom resource
* Adding configuration for auto rolling restart on deployments
* Configure Auto Restarts for a OnePasswordItem Custom Resource
* Update Connect Dependencies to latest
* Add Github action for building and testing operator
## Fixes:
- Fix spec field example for OnePasswordItem in readme
- Casing of annotations are now consistent
* Fix spec field example for OnePasswordItem in readme
* Casing of annotations are now consistent
---
[//]: # "START/v0.0.2"
[//]: # (START/v0.0.2)
# v0.0.2
## Features:
- Items can now be accessed by either `vaults/<vault_id>/items/<item_id>` or `vaults/<vault_title>/items/<item_title>`
* Items can now be accessed by either `vaults/<vault_id>/items/<item_id>` or `vaults/<vault_title>/items/<item_title>`
---
[//]: # "START/v0.0.1"
[//]: # (START/v0.0.1)
# v0.0.1
Initial 1Password Operator release
## Features
- watches for deployment creations with `onepassword` annotations and creates an associated kubernetes secret
- watches for `onepasswordsecret` crd creations and creates an associated kubernetes secrets
- watches for changes to 1Password secrets associated with kubernetes secrets and updates the kubernetes secret with changes
- restart pods when secret has been updated
- cleanup of kubernetes secrets when deployment or `onepasswordsecret` is deleted
* watches for deployment creations with `onepassword` annotations and creates an associated kubernetes secret
* watches for `onepasswordsecret` crd creations and creates an associated kubernetes secrets
* watches for changes to 1Password secrets associated with kubernetes secrets and updates the kubernetes secret with changes
* restart pods when secret has been updated
* cleanup of kubernetes secrets when deployment or `onepasswordsecret` is deleted
---

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:
```bash
cat 1password-credentials.json | base64 | \
$ cat 1password-credentials.json | base64 | \
tr '/+' '_-' | tr -d '=' | tr -d '\n' > op-session
```
Create a Kubernetes secret from the op-session file:
```bash
kubectl create secret generic op-credentials --from-file=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`:
@@ -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:
```bash
kubectl create secret generic onepassword-token --from-literal=token=<OP_CONNECT_TOKEN>"
$ kubectl create secret generic onepassword-token --from-literal=token=<OP_CONNECT_TOKEN>"
```
If you do not have a token for the operator, you can generate a token and save it to kubernetes with the following command:
```bash
kubectl create secret generic onepassword-token --from-literal=token=$(op create connect token <server> op-k8s-operator --vault <vault>)
$ kubectl create secret generic onepassword-token --from-literal=token=$(op create connect token <server> op-k8s-operator --vault <vault>)
```
[More information on generating a token can be found here](https://support.1password.com/secrets-automation/#appendix-issue-additional-access-tokens)
@@ -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.
```bash
kubectl apply -f deploy/permissions.yaml
$ kubectl apply -f deploy/permissions.yaml
```
**Create Custom One Password Secret Resource**
```bash
kubectl apply -f deploy/crds/onepassword.com_onepassworditems_crd.yaml
$ kubectl apply -f deploy/crds/onepassword.com_onepassworditems_crd.yaml
```
**Deploying the Operator**
@@ -111,13 +112,13 @@ spec:
Deploy the OnePasswordItem to Kubernetes:
```bash
kubectl apply -f <your_item>.yaml
$ kubectl apply -f <your_item>.yaml
```
To test that the Kubernetes Secret check that the following command returns a secret:
```bash
kubectl get secret <secret_name>
$ kubectl get secret <secret_name>
```
Note: Deleting the `OnePasswordItem` that you've created will automatically delete the created Kubernetes Secret.
@@ -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.
The contents of the Kubernetes secret will be key-value pairs in which the keys are the fields of the 1Password item and the values are the corresponding values stored in 1Password.
In case of fields that store files, the file's contents will be used as the value.
Within an item, if both a field storing a file and a field of another type have the same name, the file field will be ignored and the other field will take precedence.
Note: Deleting the Deployment that you've created will automatically delete the created Kubernetes Secret only if the deployment is still annotated with `operator.1password.io/item-path` and `operator.1password.io/item-name` and no other deployment is using the secret.
If a 1Password Item that is linked to a Kubernetes Secret is updated within the POLLING_INTERVAL the associated Kubernetes Secret will be updated. However, if you do not want a specific secret to be updated you can add the tag `operator.1password.io:ignore-secret` to the item stored in 1Password. While this tag is in place, any updates made to an item will not trigger an update to the associated secret in Kubernetes.
@@ -175,7 +171,7 @@ metadata:
annotations:
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**
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:
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**
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:
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

View File

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

View File

@@ -12,6 +12,8 @@ spec:
scope: Namespaced
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
description: OnePasswordItem is the Schema for the onepassworditems API
@@ -36,41 +38,5 @@ spec:
type: object
status:
description: OnePasswordItemStatus defines the observed state of OnePasswordItem
properties:
conditions:
description: 'Important: Run "operator-sdk generate k8s" to regenerate
code after modifying this file Add custom validation using kubebuilder
tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html'
items:
properties:
lastTransitionTime:
description: Last time the condition transit from one status
to another.
format: date-time
type: string
message:
description: Human-readable message indicating details about
last transition.
type: string
status:
description: Status of the condition, one of True, False, Unknown.
type: string
type:
description: Type of job condition, Completed.
type: string
required:
- status
- type
type: object
type: array
required:
- conditions
type: object
type:
description: 'Kubernetes secret type. More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types'
type: string
type: object
served: true
storage: true
subresources:
status: {}

4
go.mod
View File

@@ -3,11 +3,11 @@ module github.com/1Password/onepassword-operator
go 1.13
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/prometheus/common v0.14.0 // indirect
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/apimachinery v0.18.2
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=
github.com/1Password/connect-sdk-go v1.0.1 h1:BOeMIxVk6/ISmLNWUkSxEbVI7tNr5+aNXIobMM0/I0U=
github.com/1Password/connect-sdk-go v1.0.1/go.mod h1:br2BWk2sqgJFnOFK5WSDfBBmwQ6E7hV9LoPqrtHGRNY=
github.com/1Password/connect-sdk-go v1.2.0 h1:WbIvmbDUpA89nyH0l3LF2iRSFJAv86d2D7IjVNjw6iw=
github.com/1Password/connect-sdk-go v1.2.0/go.mod h1:qK2bF/GweAq812xj+HGfbauaE6cKX1MXfKhpAvoHEq8=
github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=
github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc=
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
@@ -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.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/thanos-io/thanos v0.11.0/go.mod h1:N/Yes7J68KqvmY+xM6J5CJqEvWIvKSR5sqGtmuD6wDc=
github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=

View File

@@ -11,31 +11,11 @@ type OnePasswordItemSpec struct {
ItemPath string `json:"itemPath,omitempty"`
}
type OnePasswordItemConditionType string
const (
// OnePasswordItemReady means the Kubernetes secret is ready for use.
OnePasswordItemReady OnePasswordItemConditionType = "Ready"
)
type OnePasswordItemCondition struct {
// Type of job condition, Completed.
Type OnePasswordItemConditionType `json:"type"`
// Status of the condition, one of True, False, Unknown.
Status metav1.ConditionStatus `json:"status"`
// Last time the condition transit from one status to another.
// +optional
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
// Human-readable message indicating details about last transition.
// +optional
Message string `json:"message,omitempty"`
}
// OnePasswordItemStatus defines the observed state of OnePasswordItem
type OnePasswordItemStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file
// Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html
Conditions []OnePasswordItemCondition `json:"conditions"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@@ -47,9 +27,6 @@ type OnePasswordItem struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// Kubernetes secret type. More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types
Type string `json:"type,omitempty"`
Spec OnePasswordItemSpec `json:"spec,omitempty"`
Status OnePasswordItemStatus `json:"status,omitempty"`
}

View File

@@ -1,4 +1,3 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
// Code generated by operator-sdk. DO NOT EDIT.
@@ -15,7 +14,7 @@ func (in *OnePasswordItem) DeepCopyInto(out *OnePasswordItem) {
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
in.Status.DeepCopyInto(&out.Status)
out.Status = in.Status
return
}
@@ -37,23 +36,6 @@ func (in *OnePasswordItem) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OnePasswordItemCondition) DeepCopyInto(out *OnePasswordItemCondition) {
*out = *in
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OnePasswordItemCondition.
func (in *OnePasswordItemCondition) DeepCopy() *OnePasswordItemCondition {
if in == nil {
return nil
}
out := new(OnePasswordItemCondition)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OnePasswordItemList) DeepCopyInto(out *OnePasswordItemList) {
*out = *in
@@ -106,13 +88,6 @@ func (in *OnePasswordItemSpec) DeepCopy() *OnePasswordItemSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OnePasswordItemStatus) DeepCopyInto(out *OnePasswordItemStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]OnePasswordItemCondition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}

View File

@@ -14,11 +14,9 @@ import (
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
logf "sigs.k8s.io/controller-runtime/pkg/log"
@@ -116,7 +114,7 @@ func (r *ReconcileDeployment) Reconcile(request reconcile.Request) (reconcile.Re
}
}
// 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{}, nil
@@ -189,13 +187,11 @@ func (r *ReconcileDeployment) removeOnePasswordFinalizerFromDeployment(deploymen
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)
secretName := annotations[op.NameAnnotation]
secretLabels := map[string]string(nil)
secretType := ""
if len(secretName) == 0 {
reqLog.Info("No 'item-name' annotation set. 'item-path' and 'item-name' must be set as annotations to add new secret.")
return nil
@@ -206,17 +202,5 @@ func (r *ReconcileDeployment) HandleApplyingDeployment(deployment *appsv1.Deploy
return fmt.Errorf("Failed to retrieve item: %v", err)
}
// Create owner reference.
gvk, err := apiutil.GVKForObject(deployment, r.scheme)
if err != nil {
return fmt.Errorf("could not to retrieve group version kind: %v", err)
}
ownerRef := &metav1.OwnerReference{
APIVersion: gvk.GroupVersion().String(),
Kind: gvk.Kind,
Name: deployment.GetName(),
UID: deployment.GetUID(),
}
return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, namespace, item, annotations[op.RestartDeploymentsAnnotation], secretLabels, secretType, ownerRef)
return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, namespace, item, annotations[op.RestartDeploymentsAnnotation], secretLabels, annotations)
}

View File

@@ -281,6 +281,7 @@ var tests = []testReconcileItem{
Annotations: map[string]string{
op.VersionAnnotation: fmt.Sprint(version),
op.ItemPathAnnotation: itemPath,
op.NameAnnotation: name,
},
},
Data: expectedSecretData,
@@ -293,6 +294,7 @@ var tests = []testReconcileItem{
Annotations: map[string]string{
op.VersionAnnotation: fmt.Sprint(version),
op.ItemPathAnnotation: itemPath,
op.NameAnnotation: name,
},
Labels: map[string]string(nil),
},
@@ -327,7 +329,6 @@ var tests = []testReconcileItem{
op.VersionAnnotation: "456",
},
},
Type: corev1.SecretType(""),
Data: expectedSecretData,
},
expectedError: nil,
@@ -339,7 +340,6 @@ var tests = []testReconcileItem{
op.VersionAnnotation: fmt.Sprint(version),
},
},
Type: corev1.SecretType(""),
Data: expectedSecretData,
},
opItem: map[string]string{
@@ -373,7 +373,6 @@ var tests = []testReconcileItem{
op.VersionAnnotation: fmt.Sprint(version),
},
},
Type: corev1.SecretType(""),
Data: expectedSecretData,
},
opItem: map[string]string{
@@ -383,7 +382,7 @@ var tests = []testReconcileItem{
},
}
func TestReconcileDeployment(t *testing.T) {
func TestReconcileDepoyment(t *testing.T) {
for _, testData := range tests {
t.Run(testData.testName, func(t *testing.T) {

View File

@@ -3,7 +3,6 @@ package onepassworditem
import (
"context"
"fmt"
onepasswordv1 "github.com/1Password/onepassword-operator/pkg/apis/onepassword/v1"
kubeSecrets "github.com/1Password/onepassword-operator/pkg/kubernetessecrets"
"github.com/1Password/onepassword-operator/pkg/onepassword"
@@ -14,11 +13,9 @@ import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
kubeClient "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
logf "sigs.k8s.io/controller-runtime/pkg/log"
@@ -96,12 +93,11 @@ func (r *ReconcileOnePasswordItem) Reconcile(request reconcile.Request) (reconci
}
// Handles creation or updating secrets for deployment if needed
err := r.HandleOnePasswordItem(onepassworditem, request)
if updateStatusErr := r.updateStatus(onepassworditem, err); updateStatusErr != nil {
return reconcile.Result{}, fmt.Errorf("cannot update status: %s", updateStatusErr)
}
if err := r.HandleOnePasswordItem(onepassworditem, request); err != nil {
return reconcile.Result{}, err
}
return reconcile.Result{}, nil
}
// If one password finalizer exists then we must cleanup associated secrets
if utils.ContainsString(onepassworditem.ObjectMeta.Finalizers, finalizer) {
@@ -148,56 +144,13 @@ func (r *ReconcileOnePasswordItem) removeOnePasswordFinalizerFromOnePasswordItem
func (r *ReconcileOnePasswordItem) HandleOnePasswordItem(resource *onepasswordv1.OnePasswordItem, request reconcile.Request) error {
secretName := resource.GetName()
labels := resource.Labels
secretType := resource.Type
autoRestart := resource.Annotations[op.RestartDeploymentsAnnotation]
annotations := resource.Annotations
autoRestart := annotations[op.RestartDeploymentsAnnotation]
item, err := onepassword.GetOnePasswordItemByPath(r.opConnectClient, resource.Spec.ItemPath)
if err != nil {
return fmt.Errorf("Failed to retrieve item: %v", err)
}
// Create owner reference.
gvk, err := apiutil.GVKForObject(resource, r.scheme)
if err != nil {
return fmt.Errorf("could not to retrieve group version kind: %v", err)
}
ownerRef := &metav1.OwnerReference{
APIVersion: gvk.GroupVersion().String(),
Kind: gvk.Kind,
Name: resource.GetName(),
UID: resource.GetUID(),
}
return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, resource.Namespace, item, autoRestart, labels, secretType, ownerRef)
}
func (r *ReconcileOnePasswordItem) updateStatus(resource *onepasswordv1.OnePasswordItem, err error) error {
existingCondition := findCondition(resource.Status.Conditions, onepasswordv1.OnePasswordItemReady)
updatedCondition := existingCondition
if err != nil {
updatedCondition.Message = err.Error()
updatedCondition.Status = metav1.ConditionFalse
} else {
updatedCondition.Message = ""
updatedCondition.Status = metav1.ConditionTrue
}
if existingCondition.Status != updatedCondition.Status {
updatedCondition.LastTransitionTime = metav1.Now()
}
resource.Status.Conditions = []onepasswordv1.OnePasswordItemCondition{updatedCondition}
return r.kubeClient.Status().Update(context.Background(), resource)
}
func findCondition(conditions []onepasswordv1.OnePasswordItemCondition, t onepasswordv1.OnePasswordItemConditionType) onepasswordv1.OnePasswordItemCondition {
for _, c := range conditions {
if c.Type == t {
return c
}
}
return onepasswordv1.OnePasswordItemCondition{
Type: t,
Status: metav1.ConditionUnknown,
}
return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, resource.Namespace, item, autoRestart, labels, annotations)
}

View File

@@ -5,7 +5,6 @@ import (
"fmt"
"testing"
"github.com/1Password/onepassword-operator/pkg/kubernetessecrets"
"github.com/1Password/onepassword-operator/pkg/mocks"
op "github.com/1Password/onepassword-operator/pkg/onepassword"
@@ -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{
TypeMeta: metav1.TypeMeta{
Kind: onePasswordItemKind,
@@ -193,59 +192,6 @@ var tests = []testReconcileItem{
passKey: password,
},
},
{
testName: "Test Updating Type of Existing Kubernetes Secret using OnePasswordItem",
customResource: &onepasswordv1.OnePasswordItem{
TypeMeta: metav1.TypeMeta{
Kind: onePasswordItemKind,
APIVersion: onePasswordItemAPIVersion,
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Annotations: map[string]string{
op.VersionAnnotation: fmt.Sprint(version),
op.ItemPathAnnotation: itemPath,
},
Labels: map[string]string{},
},
Spec: onepasswordv1.OnePasswordItemSpec{
ItemPath: itemPath,
},
Type: string(corev1.SecretTypeBasicAuth),
},
existingSecret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Annotations: map[string]string{
op.VersionAnnotation: fmt.Sprint(version),
op.ItemPathAnnotation: itemPath,
},
Labels: map[string]string{},
},
Type: corev1.SecretTypeBasicAuth,
Data: expectedSecretData,
},
expectedError: nil,
expectedResultSecret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Annotations: map[string]string{
op.VersionAnnotation: fmt.Sprint(version),
op.ItemPathAnnotation: itemPath,
},
Labels: map[string]string{},
},
Type: corev1.SecretTypeBasicAuth,
Data: expectedSecretData,
},
opItem: map[string]string{
userKey: username,
passKey: password,
},
},
{
testName: "Custom secret type",
customResource: &onepasswordv1.OnePasswordItem{
@@ -260,7 +206,6 @@ var tests = []testReconcileItem{
Spec: onepasswordv1.OnePasswordItemSpec{
ItemPath: itemPath,
},
Type: "custom",
},
existingSecret: nil,
expectedError: nil,
@@ -272,51 +217,6 @@ var tests = []testReconcileItem{
op.VersionAnnotation: fmt.Sprint(version),
},
},
Type: corev1.SecretType("custom"),
Data: expectedSecretData,
},
opItem: map[string]string{
userKey: username,
passKey: password,
},
},
{
testName: "Error if secret type is changed",
customResource: &onepasswordv1.OnePasswordItem{
TypeMeta: metav1.TypeMeta{
Kind: onePasswordItemKind,
APIVersion: onePasswordItemAPIVersion,
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: onepasswordv1.OnePasswordItemSpec{
ItemPath: itemPath,
},
Type: "custom",
},
existingSecret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Annotations: map[string]string{
op.VersionAnnotation: fmt.Sprint(version),
},
},
Type: corev1.SecretTypeOpaque,
Data: expectedSecretData,
},
expectedError: kubernetessecrets.ErrCannotUpdateSecretType,
expectedResultSecret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Annotations: map[string]string{
op.VersionAnnotation: fmt.Sprint(version),
},
},
Type: corev1.SecretTypeOpaque,
Data: expectedSecretData,
},
opItem: map[string]string{

View File

@@ -7,17 +7,13 @@ import (
"regexp"
"strings"
"reflect"
errs "errors"
"github.com/1Password/connect-sdk-go/onepassword"
"github.com/1Password/onepassword-operator/pkg/utils"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"reflect"
kubeValidate "k8s.io/apimachinery/pkg/util/validation"
kubernetesClient "sigs.k8s.io/controller-runtime/pkg/client"
@@ -27,30 +23,33 @@ import (
const OnepasswordPrefix = "operator.1password.io"
const NameAnnotation = OnepasswordPrefix + "/item-name"
const VersionAnnotation = OnepasswordPrefix + "/item-version"
const restartAnnotation = OnepasswordPrefix + "/last-restarted"
const ItemPathAnnotation = OnepasswordPrefix + "/item-path"
const RestartDeploymentsAnnotation = OnepasswordPrefix + "/auto-restart"
var ErrCannotUpdateSecretType = errs.New("Cannot change secret type. Secret type is immutable")
var log = logf.Log
func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretName, namespace string, item *onepassword.Item, autoRestart string, labels map[string]string, secretType 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)
secretAnnotations := map[string]string{
VersionAnnotation: itemVersion,
ItemPathAnnotation: fmt.Sprintf("vaults/%v/items/%v", item.Vault.ID, item.ID),
// If secretAnnotations is nil we create an empty map so we can later assign values for the OP Annotations in the map
if secretAnnotations == nil {
secretAnnotations = map[string]string{}
}
secretAnnotations[VersionAnnotation] = itemVersion
secretAnnotations[ItemPathAnnotation] = fmt.Sprintf("vaults/%v/items/%v", item.Vault.ID, item.ID)
if autoRestart != "" {
_, err := utils.StringToBool(autoRestart)
if err != nil {
return fmt.Errorf("Error parsing %v annotation on Secret %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, secretName)
log.Error(err, "Error parsing %v annotation on Secret %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, secretName)
return err
}
secretAnnotations[RestartDeploymentsAnnotation] = autoRestart
}
// "Opaque" and "" secret types are treated the same by Kubernetes.
secret := BuildKubernetesSecretFromOnePasswordItem(secretName, namespace, secretAnnotations, labels, secretType, *item, ownerRef)
secret := BuildKubernetesSecretFromOnePasswordItem(secretName, namespace, secretAnnotations, labels, *item)
currentSecret := &corev1.Secret{}
err := kubeClient.Get(context.Background(), types.NamespacedName{Name: secret.Name, Namespace: secret.Namespace}, currentSecret)
@@ -61,57 +60,31 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa
return err
}
// Check if the secret types are being changed on the update.
// Avoid Opaque and "" are treated as different on check.
wantSecretType := secretType
if wantSecretType == "" {
wantSecretType = string(corev1.SecretTypeOpaque)
}
currentSecretType := string(currentSecret.Type)
if currentSecretType == "" {
currentSecretType = string(corev1.SecretTypeOpaque)
}
if currentSecretType != wantSecretType {
return ErrCannotUpdateSecretType
}
currentAnnotations := currentSecret.Annotations
currentLabels := currentSecret.Labels
if !reflect.DeepEqual(currentAnnotations, secretAnnotations) || !reflect.DeepEqual(currentLabels, labels) {
if ! reflect.DeepEqual(currentSecret.Annotations, secretAnnotations) || ! reflect.DeepEqual(currentSecret.Labels, labels) {
log.Info(fmt.Sprintf("Updating Secret %v at namespace '%v'", secret.Name, secret.Namespace))
currentSecret.ObjectMeta.Annotations = secretAnnotations
currentSecret.ObjectMeta.Labels = labels
currentSecret.Data = secret.Data
if err := kubeClient.Update(context.Background(), currentSecret); err != nil {
return fmt.Errorf("Kubernetes secret update failed: %w", err)
}
return nil
return kubeClient.Update(context.Background(), currentSecret)
}
log.Info(fmt.Sprintf("Secret with name %v and version %v already exists", secret.Name, secret.Annotations[VersionAnnotation]))
return nil
}
func BuildKubernetesSecretFromOnePasswordItem(name, namespace string, annotations map[string]string, labels map[string]string, secretType string, item onepassword.Item, ownerRef *metav1.OwnerReference) *corev1.Secret {
var ownerRefs []metav1.OwnerReference
if ownerRef != nil {
ownerRefs = []metav1.OwnerReference{*ownerRef}
}
func BuildKubernetesSecretFromOnePasswordItem(name, namespace string, annotations map[string]string, labels map[string]string, item onepassword.Item) *corev1.Secret {
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: formatSecretName(name),
Namespace: namespace,
Annotations: annotations,
Labels: labels,
OwnerReferences: ownerRefs,
},
Data: BuildKubernetesSecretData(item.Fields, item.Files),
Type: corev1.SecretType(secretType),
Data: BuildKubernetesSecretData(item.Fields),
}
}
func BuildKubernetesSecretData(fields []*onepassword.ItemField, files []*onepassword.File) map[string][]byte {
func BuildKubernetesSecretData(fields []*onepassword.ItemField) map[string][]byte {
secretData := map[string][]byte{}
for i := 0; i < len(fields); i++ {
if fields[i].Value != "" {
@@ -119,23 +92,6 @@ func BuildKubernetesSecretData(fields []*onepassword.ItemField, files []*onepass
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
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/1Password/connect-sdk-go/onepassword"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
kubeValidate "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/client-go/kubernetes"
@@ -33,9 +32,10 @@ func TestCreateKubernetesSecretFromOnePasswordItem(t *testing.T) {
kubeClient := fake.NewFakeClient()
secretLabels := map[string]string{}
secretType := ""
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil)
secretAnnotations := map[string]string{
"testAnnotation": "exists",
}
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretAnnotations)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
@@ -47,50 +47,9 @@ func TestCreateKubernetesSecretFromOnePasswordItem(t *testing.T) {
}
compareFields(item.Fields, createdSecret.Data, t)
compareAnnotationsToItem(createdSecret.Annotations, item, t)
}
func TestKubernetesSecretFromOnePasswordItemOwnerReferences(t *testing.T) {
secretName := "test-secret-name"
namespace := "test"
item := onepassword.Item{}
item.Fields = generateFields(5)
item.Version = 123
item.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda"
item.ID = "h46bb3jddvay7nxopfhvlwg35q"
kubeClient := fake.NewFakeClient()
secretLabels := map[string]string{}
secretType := ""
ownerRef := &metav1.OwnerReference{
Kind: "Deployment",
APIVersion: "apps/v1",
Name: "test-deployment",
UID: types.UID("test-uid"),
}
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, ownerRef)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
createdSecret := &corev1.Secret{}
err = kubeClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: namespace}, createdSecret)
// Check owner references.
gotOwnerRefs := createdSecret.ObjectMeta.OwnerReferences
if len(gotOwnerRefs) != 1 {
t.Errorf("Expected owner references length: 1 but got: %d", len(gotOwnerRefs))
}
expOwnerRef := metav1.OwnerReference{
Kind: "Deployment",
APIVersion: "apps/v1",
Name: "test-deployment",
UID: types.UID("test-uid"),
}
gotOwnerRef := gotOwnerRefs[0]
if gotOwnerRef != expOwnerRef {
t.Errorf("Expected owner reference value: %v but got: %v", expOwnerRef, gotOwnerRef)
if createdSecret.Annotations["testAnnotation"] != "exists" {
t.Errorf("Expected testAnnotation to be merged with existing annotations, but wasn't.")
}
}
@@ -106,10 +65,8 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) {
kubeClient := fake.NewFakeClient()
secretLabels := map[string]string{}
secretType := ""
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil)
secretAnnotations := map[string]string{}
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretAnnotations)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
@@ -120,7 +77,7 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) {
newItem.Version = 456
newItem.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda"
newItem.ID = "h46bb3jddvay7nxopfhvlwg35q"
err = CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &newItem, restartDeploymentAnnotation, secretLabels, secretType, nil)
err = CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &newItem, restartDeploymentAnnotation, secretLabels, secretAnnotations)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
@@ -136,7 +93,7 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) {
func TestBuildKubernetesSecretData(t *testing.T) {
fields := generateFields(5)
secretData := BuildKubernetesSecretData(fields, nil)
secretData := BuildKubernetesSecretData(fields)
if len(secretData) != len(fields) {
t.Errorf("Unexpected number of secret fields returned. Expected 3, got %v", len(secretData))
}
@@ -154,9 +111,8 @@ func TestBuildKubernetesSecretFromOnePasswordItem(t *testing.T) {
item := onepassword.Item{}
item.Fields = generateFields(5)
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) {
t.Errorf("Expected name value: %v but got: %v", name, kubeSecret.Name)
}
@@ -178,7 +134,6 @@ func TestBuildKubernetesSecretFixesInvalidLabels(t *testing.T) {
}
labels := map[string]string{}
item := onepassword.Item{}
secretType := ""
item.Fields = []*onepassword.ItemField{
{
@@ -191,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
if kubeSecret.Name != expectedName {
@@ -209,36 +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{}
secretType := "kubernetes.io/tls"
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
createdSecret := &corev1.Secret{}
err = kubeClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: namespace}, createdSecret)
if err != nil {
t.Errorf("Secret was not created: %v", err)
}
if createdSecret.Type != corev1.SecretTypeTLS {
t.Errorf("Expected secretType to be of tyype corev1.SecretTypeTLS, got %s", string(createdSecret.Type))
}
}
func compareAnnotationsToItem(annotations map[string]string, item onepassword.Item, t *testing.T) {
actualVaultId, actualItemId, err := ParseVaultIdAndItemIdFromPath(annotations[ItemPathAnnotation])
if err != nil {

View File

@@ -7,7 +7,6 @@ import (
type TestClient struct {
GetVaultsFunc func() ([]onepassword.Vault, error)
GetVaultsByTitleFunc func(title string) ([]onepassword.Vault, error)
GetVaultFunc func(uuid string) (*onepassword.Vault, error)
GetItemFunc func(uuid string, vaultUUID string) (*onepassword.Item, error)
GetItemsFunc func(vaultUUID string) ([]onepassword.Item, error)
GetItemsByTitleFunc func(title string, vaultUUID string) ([]onepassword.Item, error)
@@ -15,14 +14,11 @@ type TestClient struct {
CreateItemFunc func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error)
UpdateItemFunc func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error)
DeleteItemFunc func(item *onepassword.Item, vaultUUID string) error
GetFileFunc func(uuid string, itemUUID string, vaultUUID string) (*onepassword.File, error)
GetFileContentFunc func(file *onepassword.File) ([]byte, error)
}
var (
GetGetVaultsFunc func() ([]onepassword.Vault, error)
DoGetVaultsByTitleFunc func(title string) ([]onepassword.Vault, error)
DoGetVaultFunc func(uuid string) (*onepassword.Vault, error)
GetGetItemFunc func(uuid string, vaultUUID string) (*onepassword.Item, error)
DoGetItemsByTitleFunc func(title string, vaultUUID string) ([]onepassword.Item, error)
DoGetItemByTitleFunc func(title string, vaultUUID string) (*onepassword.Item, error)
@@ -30,8 +26,6 @@ var (
DoDeleteItemFunc func(item *onepassword.Item, vaultUUID string) error
DoGetItemsFunc func(vaultUUID string) ([]onepassword.Item, error)
DoUpdateItemFunc func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error)
DoGetFileFunc func(uuid string, itemUUID string, vaultUUID string) (*onepassword.File, error)
DoGetFileContentFunc func(file *onepassword.File) ([]byte, error)
)
// Do is the mock client's `Do` func
@@ -43,10 +37,6 @@ func (m *TestClient) GetVaultsByTitle(title string) ([]onepassword.Vault, error)
return DoGetVaultsByTitleFunc(title)
}
func (m *TestClient) GetVault(uuid string) (*onepassword.Vault, error) {
return DoGetVaultFunc(uuid)
}
func (m *TestClient) GetItem(uuid string, vaultUUID string) (*onepassword.Item, error) {
return GetGetItemFunc(uuid, vaultUUID)
}
@@ -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) {
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 {
for i := 0; i < len(containers); i++ {
envVariables := containers[i].Env
envVariableNames := map[string]struct{}{}
for j := 0; j < len(envVariables); j++ {
envVariableNames[envVariables[j].Name] = struct{}{}
if envVariables[j].ValueFrom != nil && envVariables[j].ValueFrom.SecretKeyRef != nil {
_, ok := secrets[envVariables[j].ValueFrom.SecretKeyRef.Name]
if ok {
@@ -18,6 +21,10 @@ func AreContainersUsingSecrets(containers []corev1.Container, secrets map[string
envFromVariables := containers[i].EnvFrom
for j := 0; j < len(envFromVariables); j++ {
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]
if ok {
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 {
for i := 0; i < len(containers); i++ {
envVariables := containers[i].Env
envVariableNames := map[string]struct{}{}
for j := 0; j < len(envVariables); j++ {
envVariableNames[envVariables[j].Name] = struct{}{}
if envVariables[j].ValueFrom != nil && envVariables[j].ValueFrom.SecretKeyRef != nil {
secret, ok := secrets[envVariables[j].ValueFrom.SecretKeyRef.Name]
if ok {
@@ -42,6 +52,10 @@ func AppendUpdatedContainerSecrets(containers []corev1.Container, secrets map[st
envFromVariables := containers[i].EnvFrom
for j := 0; j < len(envFromVariables); j++ {
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]
if ok {
updatedDeploymentSecrets[secret.Name] = secret

View File

@@ -30,14 +30,6 @@ func GetOnePasswordItemByPath(opConnectClient connect.Client, path string) (*one
if err != nil {
return nil, err
}
for _, file := range item.Files {
_, err := opConnectClient.GetFileContent(file)
if err != nil {
return nil, err
}
}
return item, nil
}

View File

@@ -5,8 +5,6 @@ import (
"fmt"
"time"
v1 "github.com/1Password/onepassword-operator/pkg/apis/onepassword/v1"
kubeSecrets "github.com/1Password/onepassword-operator/pkg/kubernetessecrets"
"github.com/1Password/onepassword-operator/pkg/utils"
@@ -91,10 +89,9 @@ func (h *SecretUpdateHandler) restartDeploymentsWithUpdatedSecrets(updatedSecret
func (h *SecretUpdateHandler) restartDeployment(deployment *appsv1.Deployment) {
log.Info(fmt.Sprintf("Deployment %q at namespace %q references an updated secret. Restarting", deployment.GetName(), deployment.Namespace))
if deployment.Spec.Template.Annotations == nil {
deployment.Spec.Template.Annotations = map[string]string{}
deployment.Spec.Template.Annotations = map[string]string{
RestartAnnotation: time.Now().String(),
}
deployment.Spec.Template.Annotations[RestartAnnotation] = time.Now().String()
err := h.client.Update(context.Background(), deployment)
if err != nil {
log.Error(err, "Problem restarting deployment")
@@ -119,37 +116,23 @@ func (h *SecretUpdateHandler) updateKubernetesSecrets() (map[string]map[string]*
continue
}
OnePasswordItemPath := h.getPathFromOnePasswordItem(secret)
item, err := GetOnePasswordItemByPath(h.opConnectClient, OnePasswordItemPath)
item, err := GetOnePasswordItemByPath(h.opConnectClient, secret.Annotations[ItemPathAnnotation])
if err != nil {
log.Error(err, "failed to retrieve 1Password item at path \"%s\" for secret \"%s\"", secret.Annotations[ItemPathAnnotation], secret.Name)
continue
return nil, fmt.Errorf("Failed to retrieve item: %v", err)
}
itemVersion := fmt.Sprint(item.Version)
itemPathString := fmt.Sprintf("vaults/%v/items/%v", item.Vault.ID, item.ID)
if currentVersion != itemVersion || secret.Annotations[ItemPathAnnotation] != itemPathString {
if currentVersion != itemVersion {
if isItemLockedForForcedRestarts(item) {
log.Info(fmt.Sprintf("Secret '%v' has been updated in 1Password but is set to be ignored. Updates to an ignored secret will not trigger an update to a kubernetes secret or a rolling restart.", secret.GetName()))
secret.Annotations[VersionAnnotation] = itemVersion
secret.Annotations[ItemPathAnnotation] = itemPathString
if err := h.client.Update(context.Background(), &secret); err != nil {
log.Error(err, "failed to update secret %s annotations to version %d: %s", secret.Name, itemVersion, err)
continue
}
h.client.Update(context.Background(), &secret)
continue
}
log.Info(fmt.Sprintf("Updating kubernetes secret '%v'", secret.GetName()))
secret.Annotations[VersionAnnotation] = itemVersion
secret.Annotations[ItemPathAnnotation] = itemPathString
secret.Data = kubeSecrets.BuildKubernetesSecretData(item.Fields, item.Files)
log.Info(fmt.Sprintf("New secret path: %v and version: %v", secret.Annotations[ItemPathAnnotation], secret.Annotations[VersionAnnotation]))
if err := h.client.Update(context.Background(), &secret); err != nil {
log.Error(err, "failed to update secret %s to version %d: %s", secret.Name, itemVersion, err)
continue
}
updatedSecret := kubeSecrets.BuildKubernetesSecretFromOnePasswordItem(secret.Name, secret.Namespace, secret.Annotations, secret.Labels, *item)
h.client.Update(context.Background(), updatedSecret)
if updatedSecrets[secret.Namespace] == nil {
updatedSecrets[secret.Namespace] = make(map[string]*corev1.Secret)
}
@@ -193,22 +176,6 @@ func (h *SecretUpdateHandler) getIsSetForAutoRestartByNamespaceMap() (map[string
return namespacesMap, nil
}
func (h *SecretUpdateHandler) getPathFromOnePasswordItem(secret corev1.Secret) string {
onePasswordItem := &v1.OnePasswordItem{}
// Search for our original OnePasswordItem if it exists
err := h.client.Get(context.TODO(), client.ObjectKey{
Namespace: secret.Namespace,
Name: secret.Name}, onePasswordItem)
if err == nil {
return onePasswordItem.Spec.ItemPath
}
// If we can't find the OnePassword Item we'll just return the annotation from the secret item.
return secret.Annotations[ItemPathAnnotation]
}
func isSecretSetForAutoRestart(secret *corev1.Secret, deployment *appsv1.Deployment, setForAutoRestartByNamespace map[string]bool) bool {
restartDeployment := secret.Annotations[RestartDeploymentsAnnotation]
//If annotation for auto restarts for deployment is not set. Check for the annotation on its namepsace

View File

@@ -122,9 +122,6 @@ var tests = []testUpdateSecretTask{
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"external-annotation": "some-value"},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
@@ -238,9 +235,6 @@ var tests = []testUpdateSecretTask{
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"external-annotation": "some-value"},
},
Spec: corev1.PodSpec{
Volumes: []corev1.Volume{
{
@@ -348,9 +342,6 @@ var tests = []testUpdateSecretTask{
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"external-annotation": "some-value"},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
@@ -420,9 +411,6 @@ var tests = []testUpdateSecretTask{
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"external-annotation": "some-value"},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
@@ -494,9 +482,6 @@ var tests = []testUpdateSecretTask{
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"external-annotation": "some-value"},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
@@ -568,9 +553,6 @@ var tests = []testUpdateSecretTask{
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"external-annotation": "some-value"},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
@@ -648,9 +630,6 @@ var tests = []testUpdateSecretTask{
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"external-annotation": "some-value"},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
@@ -724,9 +703,6 @@ var tests = []testUpdateSecretTask{
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"external-annotation": "some-value"},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
@@ -853,16 +829,6 @@ func TestUpdateSecretHandler(t *testing.T) {
} else {
assert.False(t, testData.expectedRestart, "Deployment was restarted but should not have been.")
}
oldPodTemplateAnnotations := testData.existingDeployment.Spec.Template.ObjectMeta.Annotations
newPodTemplateAnnotations := deployment.Spec.Template.Annotations
for name, expected := range oldPodTemplateAnnotations {
actual, ok := newPodTemplateAnnotations[name]
if assert.Truef(t, ok, "Annotation %s was present in original pod template but was dropped after update", name) {
assert.Equalf(t, expected, actual, "Annotation value for %s original pod template has changed", name)
continue
}
}
})
}
}

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 (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
@@ -11,12 +10,11 @@ import (
"net/url"
"os"
"github.com/1Password/connect-sdk-go/onepassword"
opentracing "github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
jaegerClientConfig "github.com/uber/jaeger-client-go/config"
"github.com/uber/jaeger-client-go/zipkin"
"github.com/1Password/connect-sdk-go/onepassword"
)
const (
@@ -26,7 +24,6 @@ const (
// Client Represents an available 1Password Connect API to connect to
type Client interface {
GetVaults() ([]onepassword.Vault, error)
GetVault(uuid string) (*onepassword.Vault, error)
GetVaultsByTitle(uuid string) ([]onepassword.Vault, error)
GetItem(uuid string, vaultUUID string) (*onepassword.Item, error)
GetItems(vaultUUID string) ([]onepassword.Item, error)
@@ -35,8 +32,6 @@ type Client interface {
CreateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error)
UpdateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error)
DeleteItem(item *onepassword.Item, vaultUUID string) error
GetFile(fileUUID string, itemUUID string, vaultUUID string) (*onepassword.File, error)
GetFileContent(file *onepassword.File) ([]byte, error)
}
type httpClient interface {
@@ -117,41 +112,23 @@ func (rs *restClient) GetVaults() ([]onepassword.Vault, error) {
return nil, err
}
var vaults []onepassword.Vault
if err := parseResponse(response, http.StatusOK, &vaults); err != nil {
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Unable to retrieve vaults. Receieved %q for %q", response.Status, vaultURL)
}
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
vaults := []onepassword.Vault{}
if err := json.Unmarshal(body, &vaults); err != nil {
return nil, err
}
return vaults, nil
}
// GetVaults Get a list of all available vaults
func (rs *restClient) GetVault(uuid string) (*onepassword.Vault, error) {
if uuid == "" {
return nil, errors.New("no uuid provided")
}
span := rs.tracer.StartSpan("GetVault")
defer span.Finish()
vaultURL := fmt.Sprintf("/v1/vaults/%s", uuid)
request, err := rs.buildRequest(http.MethodGet, vaultURL, http.NoBody, span)
if err != nil {
return nil, err
}
response, err := rs.client.Do(request)
if err != nil {
return nil, err
}
var vault onepassword.Vault
if err := parseResponse(response, http.StatusOK, &vault); err != nil {
return nil, err
}
return &vault, nil
}
func (rs *restClient) GetVaultsByTitle(title string) ([]onepassword.Vault, error) {
span := rs.tracer.StartSpan("GetVaultsByTitle")
defer span.Finish()
@@ -168,8 +145,17 @@ func (rs *restClient) GetVaultsByTitle(title string) ([]onepassword.Vault, error
return nil, err
}
var vaults []onepassword.Vault
if err := parseResponse(response, http.StatusOK, &vaults); err != nil {
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Unable to retrieve vaults. Receieved %q for %q", response.Status, itemURL)
}
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
vaults := []onepassword.Vault{}
if err := json.Unmarshal(body, &vaults); err != nil {
return nil, err
}
@@ -191,8 +177,18 @@ func (rs *restClient) GetItem(uuid string, vaultUUID string) (*onepassword.Item,
if err != nil {
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
}
@@ -230,8 +226,17 @@ func (rs *restClient) GetItemsByTitle(title string, vaultUUID string) ([]onepass
return nil, err
}
var items []onepassword.Item
if err := parseResponse(response, http.StatusOK, &items); 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
}
items := []onepassword.Item{}
if err := json.Unmarshal(body, &items); err != nil {
return nil, err
}
@@ -253,8 +258,17 @@ func (rs *restClient) GetItems(vaultUUID string) ([]onepassword.Item, error) {
return nil, err
}
var items []onepassword.Item
if err := parseResponse(response, http.StatusOK, &items); err != nil {
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Unable to retrieve items. Receieved %q for %q", response.Status, itemURL)
}
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
items := []onepassword.Item{}
if err := json.Unmarshal(body, &items); err != nil {
return nil, err
}
@@ -282,8 +296,17 @@ func (rs *restClient) CreateItem(item *onepassword.Item, vaultUUID string) (*one
return nil, err
}
var newItem onepassword.Item
if err := parseResponse(response, http.StatusOK, &newItem); err != nil {
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Unable to create item. Receieved %q for %q", response.Status, itemURL)
}
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
newItem := onepassword.Item{}
if err := json.Unmarshal(body, &newItem); err != nil {
return nil, err
}
@@ -311,8 +334,17 @@ func (rs *restClient) UpdateItem(item *onepassword.Item, vaultUUID string) (*one
return nil, err
}
var newItem onepassword.Item
if err := parseResponse(response, http.StatusOK, &newItem); err != nil {
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Unable to update item. Receieved %q for %q", response.Status, itemURL)
}
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
newItem := onepassword.Item{}
if err := json.Unmarshal(body, &newItem); err != nil {
return nil, err
}
@@ -335,73 +367,13 @@ func (rs *restClient) DeleteItem(item *onepassword.Item, vaultUUID string) error
return err
}
if err := parseResponse(response, http.StatusNoContent, nil); err != nil {
return err
if response.StatusCode != http.StatusNoContent {
return fmt.Errorf("Unable to retrieve item. Receieved %q for %q", response.Status, itemURL)
}
return nil
}
// GetFile Get a specific File in a specified item.
// This does not include the file contents. Call GetFileContent() to load the file's content.
func (rs *restClient) GetFile(uuid string, itemUUID string, vaultUUID string) (*onepassword.File, error) {
span := rs.tracer.StartSpan("GetFile")
defer span.Finish()
itemURL := fmt.Sprintf("/v1/vaults/%s/items/%s/files/%s", vaultUUID, itemUUID, uuid)
request, err := rs.buildRequest(http.MethodGet, itemURL, http.NoBody, span)
if err != nil {
return nil, err
}
response, err := rs.client.Do(request)
if err != nil {
return nil, err
}
if err := expectMinimumConnectVersion(response, version{1, 3, 0}); err != nil {
return nil, err
}
var file onepassword.File
if err := parseResponse(response, http.StatusOK, &file); err != nil {
return nil, err
}
return &file, nil
}
// GetFileContent retrieves the file's content.
// If the file's content have previously been fetched, those contents are returned without making another request.
func (rs *restClient) GetFileContent(file *onepassword.File) ([]byte, error) {
if content, err := file.Content(); err == nil {
return content, nil
}
span := rs.tracer.StartSpan("GetFileContent")
defer span.Finish()
request, err := rs.buildRequest(http.MethodGet, file.ContentPath, http.NoBody, span)
if err != nil {
return nil, err
}
response, err := rs.client.Do(request)
if err != nil {
return nil, err
}
if err := expectMinimumConnectVersion(response, version{1, 3, 0}); err != nil {
return nil, err
}
content, err := readResponseBody(response, http.StatusOK)
if err != nil {
return nil, err
}
file.SetContent(content)
return content, nil
}
func (rs *restClient) buildRequest(method string, path string, body io.Reader, span opentracing.Span) (*http.Request, error) {
url := fmt.Sprintf("%s%s", rs.URL, path)
@@ -422,32 +394,3 @@ func (rs *restClient) buildRequest(method string, path string, body io.Reader, s
return request, nil
}
func parseResponse(resp *http.Response, expectedStatusCode int, result interface{}) error {
body, err := readResponseBody(resp, expectedStatusCode)
if err != nil {
return err
}
if result != nil {
if err := json.Unmarshal(body, result); err != nil {
return fmt.Errorf("decoding response: %s", err)
}
}
return nil
}
func readResponseBody(resp *http.Response, expectedStatusCode int) ([]byte, error) {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != expectedStatusCode {
var errResp *onepassword.Error
if err := json.Unmarshal(body, &errResp); err != nil {
return nil, fmt.Errorf("decoding error response: %s", err)
}
return nil, errResp
}
return body, nil
}

View File

@@ -1,104 +1,5 @@
package connect
import (
"errors"
"fmt"
"net/http"
"strconv"
"strings"
)
// SDKVersion is the latest Semantic Version of the library
// Do not rename this variable without changing the regex in the Makefile
const SDKVersion = "1.2.0"
const VersionHeaderKey = "1Password-Connect-Version"
// expectMinimumConnectVersion returns an error if the provided minimum version for Connect is lower than the version
// reported in the response from Connect.
func expectMinimumConnectVersion(resp *http.Response, minimumVersion version) error {
serverVersion, err := getServerVersion(resp)
if err != nil {
// Return gracefully if server version cannot be determined reliably
return nil
}
if !serverVersion.IsGreaterOrEqualThan(minimumVersion) {
return fmt.Errorf("need at least version %s of Connect for this function, detected version %s. Please update your Connect server", minimumVersion, serverVersion)
}
return nil
}
func getServerVersion(resp *http.Response) (serverVersion, error) {
versionHeader := resp.Header.Get(VersionHeaderKey)
if versionHeader == "" {
// The last version without the version header was v1.2.0
return serverVersion{
version: version{1, 2, 0},
orEarlier: true,
}, nil
}
return parseServerVersion(versionHeader)
}
type version struct {
major int
minor int
patch int
}
// serverVersion describes the version reported by the server.
type serverVersion struct {
version
// orEarlier is true if the version is derived from the lack of a version header from the server.
orEarlier bool
}
func (v version) String() string {
return fmt.Sprintf("%d.%d.%d", v.major, v.minor, v.patch)
}
func (v serverVersion) String() string {
if v.orEarlier {
return v.version.String() + " (or earlier)"
}
return v.version.String()
}
// IsGreaterOrEqualThan returns true if the lefthand-side version is equal to or or a higher version than the provided
// minimum according to the semantic versioning rules.
func (v version) IsGreaterOrEqualThan(min version) bool {
if v.major != min.major {
// Different major version
return v.major > min.major
}
if v.minor != min.minor {
// Same major, but different minor version
return v.minor > min.minor
}
// Same major and minor version
return v.patch >= min.patch
}
func parseServerVersion(v string) (serverVersion, error) {
spl := strings.Split(v, ".")
if len(spl) != 3 {
return serverVersion{}, errors.New("wrong length")
}
var res [3]int
for i := range res {
tmp, err := strconv.Atoi(spl[i])
if err != nil {
return serverVersion{}, err
}
res[i] = tmp
}
return serverVersion{
version: version{
major: res[0],
minor: res[1],
patch: res[2],
},
}, nil
}
const SDKVersion = "1.0.1"

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"
EmailAccount ItemCategory = "EMAIL_ACCOUNT"
SocialSecurityNumber ItemCategory = "SOCIAL_SECURITY_NUMBER"
ApiCredential ItemCategory = "API_CREDENTIAL"
Custom ItemCategory = "CUSTOM"
)
@@ -40,7 +39,7 @@ func (ic *ItemCategory) UnmarshalJSON(b []byte) error {
switch category {
case Login, Password, Server, Database, CreditCard, Membership, Passport, SoftwareLicense,
OutdoorLicense, SecureNote, WirelessRouter, BankAccount, DriverLicense, Identity, RewardProgram,
Document, EmailAccount, SocialSecurityNumber, ApiCredential:
Document, EmailAccount, SocialSecurityNumber:
*ic = category
default:
*ic = Custom
@@ -65,7 +64,6 @@ type Item struct {
Sections []*ItemSection `json:"sections,omitempty"`
Fields []*ItemField `json:"fields,omitempty"`
Files []*File `json:"files,omitempty"`
LastEditedBy string `json:"lastEditedBy,omitempty"`
CreatedAt time.Time `json:"createdAt,omitempty"`

View File

@@ -13,42 +13,12 @@ const (
compareGreater
)
var (
intType = reflect.TypeOf(int(1))
int8Type = reflect.TypeOf(int8(1))
int16Type = reflect.TypeOf(int16(1))
int32Type = reflect.TypeOf(int32(1))
int64Type = reflect.TypeOf(int64(1))
uintType = reflect.TypeOf(uint(1))
uint8Type = reflect.TypeOf(uint8(1))
uint16Type = reflect.TypeOf(uint16(1))
uint32Type = reflect.TypeOf(uint32(1))
uint64Type = reflect.TypeOf(uint64(1))
float32Type = reflect.TypeOf(float32(1))
float64Type = reflect.TypeOf(float64(1))
stringType = reflect.TypeOf("")
)
func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
obj1Value := reflect.ValueOf(obj1)
obj2Value := reflect.ValueOf(obj2)
// throughout this switch we try and avoid calling .Convert() if possible,
// as this has a pretty big performance impact
switch kind {
case reflect.Int:
{
intobj1, ok := obj1.(int)
if !ok {
intobj1 = obj1Value.Convert(intType).Interface().(int)
}
intobj2, ok := obj2.(int)
if !ok {
intobj2 = obj2Value.Convert(intType).Interface().(int)
}
intobj1 := obj1.(int)
intobj2 := obj2.(int)
if intobj1 > intobj2 {
return compareGreater, true
}
@@ -61,14 +31,8 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
}
case reflect.Int8:
{
int8obj1, ok := obj1.(int8)
if !ok {
int8obj1 = obj1Value.Convert(int8Type).Interface().(int8)
}
int8obj2, ok := obj2.(int8)
if !ok {
int8obj2 = obj2Value.Convert(int8Type).Interface().(int8)
}
int8obj1 := obj1.(int8)
int8obj2 := obj2.(int8)
if int8obj1 > int8obj2 {
return compareGreater, true
}
@@ -81,14 +45,8 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
}
case reflect.Int16:
{
int16obj1, ok := obj1.(int16)
if !ok {
int16obj1 = obj1Value.Convert(int16Type).Interface().(int16)
}
int16obj2, ok := obj2.(int16)
if !ok {
int16obj2 = obj2Value.Convert(int16Type).Interface().(int16)
}
int16obj1 := obj1.(int16)
int16obj2 := obj2.(int16)
if int16obj1 > int16obj2 {
return compareGreater, true
}
@@ -101,14 +59,8 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
}
case reflect.Int32:
{
int32obj1, ok := obj1.(int32)
if !ok {
int32obj1 = obj1Value.Convert(int32Type).Interface().(int32)
}
int32obj2, ok := obj2.(int32)
if !ok {
int32obj2 = obj2Value.Convert(int32Type).Interface().(int32)
}
int32obj1 := obj1.(int32)
int32obj2 := obj2.(int32)
if int32obj1 > int32obj2 {
return compareGreater, true
}
@@ -121,14 +73,8 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
}
case reflect.Int64:
{
int64obj1, ok := obj1.(int64)
if !ok {
int64obj1 = obj1Value.Convert(int64Type).Interface().(int64)
}
int64obj2, ok := obj2.(int64)
if !ok {
int64obj2 = obj2Value.Convert(int64Type).Interface().(int64)
}
int64obj1 := obj1.(int64)
int64obj2 := obj2.(int64)
if int64obj1 > int64obj2 {
return compareGreater, true
}
@@ -141,14 +87,8 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
}
case reflect.Uint:
{
uintobj1, ok := obj1.(uint)
if !ok {
uintobj1 = obj1Value.Convert(uintType).Interface().(uint)
}
uintobj2, ok := obj2.(uint)
if !ok {
uintobj2 = obj2Value.Convert(uintType).Interface().(uint)
}
uintobj1 := obj1.(uint)
uintobj2 := obj2.(uint)
if uintobj1 > uintobj2 {
return compareGreater, true
}
@@ -161,14 +101,8 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
}
case reflect.Uint8:
{
uint8obj1, ok := obj1.(uint8)
if !ok {
uint8obj1 = obj1Value.Convert(uint8Type).Interface().(uint8)
}
uint8obj2, ok := obj2.(uint8)
if !ok {
uint8obj2 = obj2Value.Convert(uint8Type).Interface().(uint8)
}
uint8obj1 := obj1.(uint8)
uint8obj2 := obj2.(uint8)
if uint8obj1 > uint8obj2 {
return compareGreater, true
}
@@ -181,14 +115,8 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
}
case reflect.Uint16:
{
uint16obj1, ok := obj1.(uint16)
if !ok {
uint16obj1 = obj1Value.Convert(uint16Type).Interface().(uint16)
}
uint16obj2, ok := obj2.(uint16)
if !ok {
uint16obj2 = obj2Value.Convert(uint16Type).Interface().(uint16)
}
uint16obj1 := obj1.(uint16)
uint16obj2 := obj2.(uint16)
if uint16obj1 > uint16obj2 {
return compareGreater, true
}
@@ -201,14 +129,8 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
}
case reflect.Uint32:
{
uint32obj1, ok := obj1.(uint32)
if !ok {
uint32obj1 = obj1Value.Convert(uint32Type).Interface().(uint32)
}
uint32obj2, ok := obj2.(uint32)
if !ok {
uint32obj2 = obj2Value.Convert(uint32Type).Interface().(uint32)
}
uint32obj1 := obj1.(uint32)
uint32obj2 := obj2.(uint32)
if uint32obj1 > uint32obj2 {
return compareGreater, true
}
@@ -221,14 +143,8 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
}
case reflect.Uint64:
{
uint64obj1, ok := obj1.(uint64)
if !ok {
uint64obj1 = obj1Value.Convert(uint64Type).Interface().(uint64)
}
uint64obj2, ok := obj2.(uint64)
if !ok {
uint64obj2 = obj2Value.Convert(uint64Type).Interface().(uint64)
}
uint64obj1 := obj1.(uint64)
uint64obj2 := obj2.(uint64)
if uint64obj1 > uint64obj2 {
return compareGreater, true
}
@@ -241,14 +157,8 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
}
case reflect.Float32:
{
float32obj1, ok := obj1.(float32)
if !ok {
float32obj1 = obj1Value.Convert(float32Type).Interface().(float32)
}
float32obj2, ok := obj2.(float32)
if !ok {
float32obj2 = obj2Value.Convert(float32Type).Interface().(float32)
}
float32obj1 := obj1.(float32)
float32obj2 := obj2.(float32)
if float32obj1 > float32obj2 {
return compareGreater, true
}
@@ -261,14 +171,8 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
}
case reflect.Float64:
{
float64obj1, ok := obj1.(float64)
if !ok {
float64obj1 = obj1Value.Convert(float64Type).Interface().(float64)
}
float64obj2, ok := obj2.(float64)
if !ok {
float64obj2 = obj2Value.Convert(float64Type).Interface().(float64)
}
float64obj1 := obj1.(float64)
float64obj2 := obj2.(float64)
if float64obj1 > float64obj2 {
return compareGreater, true
}
@@ -281,14 +185,8 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
}
case reflect.String:
{
stringobj1, ok := obj1.(string)
if !ok {
stringobj1 = obj1Value.Convert(stringType).Interface().(string)
}
stringobj2, ok := obj2.(string)
if !ok {
stringobj2 = obj2Value.Convert(stringType).Interface().(string)
}
stringobj1 := obj1.(string)
stringobj2 := obj2.(string)
if stringobj1 > stringobj2 {
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)
}
// Positive asserts that the specified element is positive
//
// assert.Positive(t, 1)
// assert.Positive(t, 1.23)
func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
zero := reflect.Zero(reflect.TypeOf(e))
return compareTwoValues(t, e, zero.Interface(), []CompareType{compareGreater}, "\"%v\" is not positive", msgAndArgs)
}
// Negative asserts that the specified element is negative
//
// assert.Negative(t, -1)
// assert.Negative(t, -1.23)
func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
zero := reflect.Zero(reflect.TypeOf(e))
return compareTwoValues(t, e, zero.Interface(), []CompareType{compareLess}, "\"%v\" is not negative", msgAndArgs)
}
func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()

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...)...)
}
// ErrorAsf asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value.
// This is a wrapper for errors.As.
func ErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return ErrorAs(t, err, target, append([]interface{}{msg}, args...)...)
}
// ErrorIsf asserts that at least one of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return ErrorIs(t, err, target, append([]interface{}{msg}, args...)...)
}
// Eventuallyf asserts that given condition will be met in waitFor time,
// periodically checking target function each tick.
//
@@ -339,54 +321,6 @@ func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsil
return InEpsilonSlice(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
}
// IsDecreasingf asserts that the collection is decreasing
//
// assert.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted")
// assert.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted")
// assert.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted")
func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return IsDecreasing(t, object, append([]interface{}{msg}, args...)...)
}
// IsIncreasingf asserts that the collection is increasing
//
// assert.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted")
// assert.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted")
// assert.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted")
func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return IsIncreasing(t, object, append([]interface{}{msg}, args...)...)
}
// IsNonDecreasingf asserts that the collection is not decreasing
//
// assert.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted")
// assert.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted")
// assert.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted")
func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return IsNonDecreasing(t, object, append([]interface{}{msg}, args...)...)
}
// IsNonIncreasingf asserts that the collection is not increasing
//
// assert.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted")
// assert.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted")
// assert.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted")
func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return IsNonIncreasing(t, object, append([]interface{}{msg}, args...)...)
}
// IsTypef asserts that the specified objects are of the same type.
func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
@@ -441,17 +375,6 @@ func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args .
return LessOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...)
}
// Negativef asserts that the specified element is negative
//
// assert.Negativef(t, -1, "error message %s", "formatted")
// assert.Negativef(t, -1.23, "error message %s", "formatted")
func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Negative(t, e, append([]interface{}{msg}, args...)...)
}
// Neverf asserts that the given condition doesn't satisfy in waitFor time,
// periodically checking the target function each tick.
//
@@ -553,15 +476,6 @@ func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg s
return NotEqualValues(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// NotErrorIsf asserts that at none of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotErrorIs(t, err, target, append([]interface{}{msg}, args...)...)
}
// NotNilf asserts that the specified object is not nil.
//
// assert.NotNilf(t, err, "error message %s", "formatted")
@@ -658,17 +572,6 @@ func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg str
return PanicsWithValue(t, expected, f, append([]interface{}{msg}, args...)...)
}
// Positivef asserts that the specified element is positive
//
// assert.Positivef(t, 1, "error message %s", "formatted")
// assert.Positivef(t, 1.23, "error message %s", "formatted")
func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Positive(t, e, append([]interface{}{msg}, args...)...)
}
// Regexpf asserts that a specified regexp matches a string.
//
// assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted")

View File

@@ -204,42 +204,6 @@ func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool {
return Error(a.t, err, msgAndArgs...)
}
// ErrorAs asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value.
// This is a wrapper for errors.As.
func (a *Assertions) ErrorAs(err error, target interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return ErrorAs(a.t, err, target, msgAndArgs...)
}
// ErrorAsf asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value.
// This is a wrapper for errors.As.
func (a *Assertions) ErrorAsf(err error, target interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return ErrorAsf(a.t, err, target, msg, args...)
}
// ErrorIs asserts that at least one of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func (a *Assertions) ErrorIs(err error, target error, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return ErrorIs(a.t, err, target, msgAndArgs...)
}
// ErrorIsf asserts that at least one of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func (a *Assertions) ErrorIsf(err error, target error, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return ErrorIsf(a.t, err, target, msg, args...)
}
// Errorf asserts that a function returned an error (i.e. not `nil`).
//
// actualObj, err := SomeFunction()
@@ -667,102 +631,6 @@ func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilo
return InEpsilonf(a.t, expected, actual, epsilon, msg, args...)
}
// IsDecreasing asserts that the collection is decreasing
//
// a.IsDecreasing([]int{2, 1, 0})
// a.IsDecreasing([]float{2, 1})
// a.IsDecreasing([]string{"b", "a"})
func (a *Assertions) IsDecreasing(object interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return IsDecreasing(a.t, object, msgAndArgs...)
}
// IsDecreasingf asserts that the collection is decreasing
//
// a.IsDecreasingf([]int{2, 1, 0}, "error message %s", "formatted")
// a.IsDecreasingf([]float{2, 1}, "error message %s", "formatted")
// a.IsDecreasingf([]string{"b", "a"}, "error message %s", "formatted")
func (a *Assertions) IsDecreasingf(object interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return IsDecreasingf(a.t, object, msg, args...)
}
// IsIncreasing asserts that the collection is increasing
//
// a.IsIncreasing([]int{1, 2, 3})
// a.IsIncreasing([]float{1, 2})
// a.IsIncreasing([]string{"a", "b"})
func (a *Assertions) IsIncreasing(object interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return IsIncreasing(a.t, object, msgAndArgs...)
}
// IsIncreasingf asserts that the collection is increasing
//
// a.IsIncreasingf([]int{1, 2, 3}, "error message %s", "formatted")
// a.IsIncreasingf([]float{1, 2}, "error message %s", "formatted")
// a.IsIncreasingf([]string{"a", "b"}, "error message %s", "formatted")
func (a *Assertions) IsIncreasingf(object interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return IsIncreasingf(a.t, object, msg, args...)
}
// IsNonDecreasing asserts that the collection is not decreasing
//
// a.IsNonDecreasing([]int{1, 1, 2})
// a.IsNonDecreasing([]float{1, 2})
// a.IsNonDecreasing([]string{"a", "b"})
func (a *Assertions) IsNonDecreasing(object interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return IsNonDecreasing(a.t, object, msgAndArgs...)
}
// IsNonDecreasingf asserts that the collection is not decreasing
//
// a.IsNonDecreasingf([]int{1, 1, 2}, "error message %s", "formatted")
// a.IsNonDecreasingf([]float{1, 2}, "error message %s", "formatted")
// a.IsNonDecreasingf([]string{"a", "b"}, "error message %s", "formatted")
func (a *Assertions) IsNonDecreasingf(object interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return IsNonDecreasingf(a.t, object, msg, args...)
}
// IsNonIncreasing asserts that the collection is not increasing
//
// a.IsNonIncreasing([]int{2, 1, 1})
// a.IsNonIncreasing([]float{2, 1})
// a.IsNonIncreasing([]string{"b", "a"})
func (a *Assertions) IsNonIncreasing(object interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return IsNonIncreasing(a.t, object, msgAndArgs...)
}
// IsNonIncreasingf asserts that the collection is not increasing
//
// a.IsNonIncreasingf([]int{2, 1, 1}, "error message %s", "formatted")
// a.IsNonIncreasingf([]float{2, 1}, "error message %s", "formatted")
// a.IsNonIncreasingf([]string{"b", "a"}, "error message %s", "formatted")
func (a *Assertions) IsNonIncreasingf(object interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return IsNonIncreasingf(a.t, object, msg, args...)
}
// IsType asserts that the specified objects are of the same type.
func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
@@ -871,28 +739,6 @@ func (a *Assertions) Lessf(e1 interface{}, e2 interface{}, msg string, args ...i
return Lessf(a.t, e1, e2, msg, args...)
}
// Negative asserts that the specified element is negative
//
// a.Negative(-1)
// a.Negative(-1.23)
func (a *Assertions) Negative(e interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Negative(a.t, e, msgAndArgs...)
}
// Negativef asserts that the specified element is negative
//
// a.Negativef(-1, "error message %s", "formatted")
// a.Negativef(-1.23, "error message %s", "formatted")
func (a *Assertions) Negativef(e interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Negativef(a.t, e, msg, args...)
}
// Never asserts that the given condition doesn't satisfy in waitFor time,
// periodically checking the target function each tick.
//
@@ -1095,24 +941,6 @@ func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg str
return NotEqualf(a.t, expected, actual, msg, args...)
}
// NotErrorIs asserts that at none of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func (a *Assertions) NotErrorIs(err error, target error, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return NotErrorIs(a.t, err, target, msgAndArgs...)
}
// NotErrorIsf asserts that at none of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func (a *Assertions) NotErrorIsf(err error, target error, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return NotErrorIsf(a.t, err, target, msg, args...)
}
// NotNil asserts that the specified object is not nil.
//
// a.NotNil(err)
@@ -1305,28 +1133,6 @@ func (a *Assertions) Panicsf(f PanicTestFunc, msg string, args ...interface{}) b
return Panicsf(a.t, f, msg, args...)
}
// Positive asserts that the specified element is positive
//
// a.Positive(1)
// a.Positive(1.23)
func (a *Assertions) Positive(e interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Positive(a.t, e, msgAndArgs...)
}
// Positivef asserts that the specified element is positive
//
// a.Positivef(1, "error message %s", "formatted")
// a.Positivef(1.23, "error message %s", "formatted")
func (a *Assertions) Positivef(e interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Positivef(a.t, e, msg, args...)
}
// Regexp asserts that a specified regexp matches a string.
//
// a.Regexp(regexp.MustCompile("start"), "it's starting")

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
return true
}
r, _ := utf8.DecodeRuneInString(name[len(prefix):])
return !unicode.IsLower(r)
rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
return !unicode.IsLower(rune)
}
func messageFromMsgAndArgs(msgAndArgs ...interface{}) string {
@@ -1622,7 +1622,6 @@ var spewConfig = spew.ConfigState{
DisableCapacities: true,
SortKeys: true,
DisableMethods: true,
MaxDepth: 10,
}
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/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/onepassword
# github.com/Azure/go-autorest/autorest v0.9.3-0.20191028180845-3492b2aff503
@@ -110,7 +110,7 @@ github.com/prometheus/procfs/internal/fs
github.com/prometheus/procfs/internal/util
# github.com/spf13/pflag v1.0.5
github.com/spf13/pflag
# github.com/stretchr/testify v1.7.0
# github.com/stretchr/testify v1.6.1
github.com/stretchr/testify/assert
# github.com/uber/jaeger-client-go v2.25.0+incompatible
github.com/uber/jaeger-client-go