Compare commits

...

68 Commits

Author SHA1 Message Date
Volodymyr Zotov
0f56cab693 Merge pull request #220 from 1Password/vzt/pr-template
Add pull request template
2025-09-09 14:16:03 -05:00
Volodymyr Zotov
a1ab24f244 Add 'Resolves' section 2025-09-09 14:08:15 -05:00
Volodymyr Zotov
13e4b16846 Merge pull request #219 from 1Password/vzt/test-improvements
Add e2e tests
2025-09-09 13:59:59 -05:00
Volodymyr Zotov
94602ddd72 Fix lint errors 2025-09-05 11:34:18 -05:00
Volodymyr Zotov
292c6f0e93 Update testing doc to mention integration and unit tests under single command 2025-09-05 11:24:02 -05:00
Volodymyr Zotov
0f1293ca95 Update testing doc to merge integration and unit tests under single command 2025-09-05 11:20:08 -05:00
Volodymyr Zotov
706ebdd8b8 Copy manager.yaml from test/e2e when starting e2e tests 2025-09-05 10:40:46 -05:00
Volodymyr Zotov
bd963bcd1d Revert config/manager.yaml 2025-09-05 10:40:45 -05:00
Volodymyr Zotov
bf6cac81cb Add capabilities for ["CHOWN", "FOWNER"] to make it more striker 2025-09-04 11:17:38 -05:00
Volodymyr Zotov
9c4849ec2e Ignore these files across entire project 2025-09-04 10:43:55 -05:00
Volodymyr Zotov
c2788770fd Add comment about installing 1p cli in test workflow 2025-09-04 10:41:25 -05:00
Volodymyr Zotov
6baef1b9cf Fix lint error 2025-08-28 13:24:55 -05:00
Volodymyr Zotov
7e08158d2f Use op.ReadItemField command 2025-08-28 13:22:11 -05:00
Volodymyr Zotov
976909c438 Update OpReadField method to be able to read different item fields 2025-08-28 13:20:45 -05:00
Volodymyr Zotov
e61ba49018 Add namespace package 2025-08-28 13:17:27 -05:00
Volodymyr Zotov
6492b3cf34 Remove operator package, as make commands can be run directly using system.Run 2025-08-28 13:12:48 -05:00
Volodymyr Zotov
9d08bcc864 Update e2e local testing steps 2025-08-28 11:25:57 -05:00
Volodymyr Zotov
f7f5462133 Pass CRDs on createion of kube instance 2025-08-27 11:19:17 -05:00
Volodymyr Zotov
128954cd80 Add pull request template 2025-08-26 17:05:52 -05:00
Volodymyr Zotov
a1cbd40f9e Refer to testing.md from contributing.md 2025-08-26 16:58:32 -05:00
Volodymyr Zotov
d75a33d524 Add testing.md doc to describe where specific tests should be added 2025-08-26 16:57:42 -05:00
Volodymyr Zotov
b1b6c97a88 Remove redundant if statement 2025-08-26 16:33:49 -05:00
Volodymyr Zotov
0c3caf88b6 Provide default inerval and timeout via config 2025-08-26 16:31:07 -05:00
Volodymyr Zotov
24edff22d4 Do not run e2e tests when moving from draft to ready and vise versa 2025-08-26 16:25:23 -05:00
Volodymyr Zotov
8c893270f4 Update CONTRIBUTING.md with instructions on how to run e2e tests locally 2025-08-26 15:51:59 -05:00
Volodymyr Zotov
d5f1044571 Do not install kubectl cli in pipeline as we use golang library to interact with cluster 2025-08-26 15:12:16 -05:00
Volodymyr Zotov
b40f27b052 Refactor kube package to use controller-runtime golang client to interact with cluster 2025-08-26 15:11:04 -05:00
Volodymyr Zotov
cd03a651ad Refactor kube package to use controller-runtime client instead of using kubectl CLI.
This allows to runt the tests faster
2025-08-25 22:52:17 -05:00
Volodymyr Zotov
9aac824066 Add test case for ignore-secret tag 2025-08-22 11:22:39 -05:00
Volodymyr Zotov
05ad484bd6 Fix lint error 2025-08-22 10:25:45 -05:00
Volodymyr Zotov
71b29d5fe6 Install op-cli into github action job 2025-08-22 10:25:04 -05:00
Volodymyr Zotov
c082f9562e Add tests case to check that kubernetes secret is updated after item is updated in 1Password 2025-08-22 10:15:33 -05:00
Volodymyr Zotov
57478247cf Update secret to point to operator-acceptance-tests vault 2025-08-22 10:13:58 -05:00
Volodymyr Zotov
4836140f66 Add CheckSecretPasswordWasUpdated function to the kube package 2025-08-22 10:13:58 -05:00
Volodymyr Zotov
2b36f16940 Introduce op package to handle op-cli commands 2025-08-22 09:38:21 -05:00
Volodymyr Zotov
bb97134e10 Add comments on each test helper function 2025-08-22 08:30:35 -05:00
Volodymyr Zotov
904d269e7b Roll back changes to customization yaml 2025-08-21 17:01:10 -05:00
Volodymyr Zotov
cf9b267eaf Remove commented code 2025-08-21 16:02:04 -05:00
Volodymyr Zotov
4d64beab86 Exclude e2e tests from make test command 2025-08-21 15:52:38 -05:00
Volodymyr Zotov
ca051a08cf Move testhelper package to pkg so it can be installed as dependency in secrets injector repo 2025-08-21 15:52:06 -05:00
Volodymyr Zotov
22a7c8f586 Create 1password-credentials.json from env var 2025-08-21 14:57:24 -05:00
Volodymyr Zotov
2003d13788 Fix lint issues and CheckSecretExists function 2025-08-21 10:38:19 -05:00
Volodymyr Zotov
7187f41ef1 Checking that all secrets are created before running tests 2025-08-21 10:17:19 -05:00
Volodymyr Zotov
d0b11c70f0 Roll back Connect test 2025-08-21 10:11:06 -05:00
Volodymyr Zotov
9825cb57c9 Test with service account 2025-08-21 10:02:13 -05:00
Volodymyr Zotov
6bb6088353 Use GetProjectRoot to create secret 2025-08-21 09:56:37 -05:00
Volodymyr Zotov
5a56fd3330 Wait for Connect pod is running 2025-08-20 15:51:50 -05:00
Volodymyr Zotov
dcd7eefac0 Increase timeout to 1 minute 2025-08-20 15:39:44 -05:00
Volodymyr Zotov
29b7ed7899 Run correct make command that starts e2e tests 2025-08-20 15:33:08 -05:00
Volodymyr Zotov
331e8d7bfb Add e2e tests workflow 2025-08-20 15:29:58 -05:00
Volodymyr Zotov
c144bd3d01 Remove PatchOperatorManageConnect as manifest has MANAGE_CONNECT: true set already 2025-08-20 15:02:21 -05:00
Volodymyr Zotov
299689fe13 Extract setting context namespace to standalone function SetContextNamespace 2025-08-20 14:57:47 -05:00
Volodymyr Zotov
882d8e951d Never pull the image, but use local when deploying the operator. Deploy along with Connect 2025-08-20 14:56:32 -05:00
Volodymyr Zotov
7885ba649b Set to namespace to default 2025-08-20 14:42:08 -05:00
Volodymyr Zotov
600adf2670 Move cmd package to testhelper and rename to be system 2025-08-20 14:27:12 -05:00
Volodymyr Zotov
88b2dfbf67 Use GetProjectRoot in Run 2025-08-20 14:15:26 -05:00
Volodymyr Zotov
e167db2357 Remove secret from previous step 2025-08-20 10:30:28 -05:00
Volodymyr Zotov
91a9bb6d63 Create op-credentials secret to use operator with Connect 2025-08-20 10:24:16 -05:00
Volodymyr Zotov
116c8c92a7 Update item path to point to test secret 2025-08-20 10:23:44 -05:00
Volodymyr Zotov
4307e9d713 Add 1password-credentials.json and op-session to git ignore
Add 1password-credentials.json to git ignore
2025-08-20 10:23:44 -05:00
Volodymyr Zotov
1759055edd Update sqlite-permissions to run as root, so it can start Connect in e2e tests 2025-08-20 09:10:32 -05:00
Volodymyr Zotov
c1e9934088 Fix typo 2025-08-19 14:51:32 -05:00
Volodymyr Zotov
19b629f2ee Move BuildOperatorImage function to testhelper.operator package 2025-08-19 14:50:39 -05:00
Volodymyr Zotov
174f952691 Split testing flow for Connect and Service Accounts 2025-08-19 12:05:29 -05:00
Volodymyr Zotov
f8704223c8 Move all helpers to testhelper package 2025-08-19 12:04:56 -05:00
Volodymyr Zotov
5630d788a2 Create kube package that abstracts interactions with kubernetes cluster 2025-08-19 11:52:28 -05:00
Volodymyr Zotov
d504e5ef35 Add e2e tests using Service Accounts 2025-08-19 10:51:43 -05:00
Volodymyr Zotov
7d2596a4aa Create e2e tests package 2025-08-15 13:28:39 -05:00
23 changed files with 1219 additions and 3 deletions

17
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,17 @@
### ✨ Summary
<!-- What does this change do? -->
<!-- What issue does it resolve? -->
### 🔗 Resolves:
### ✅ Checklist
- [ ] 🖊️ Commits are signed
- [ ] 🧪 Tests added/updated: _(See the [Testing Guide](docs/testing.md) for when to use each type and how to run them)_
- [ ] 🔹 Unit
- [ ] 🔸 Integration
- [ ] 🌐 E2E (Connect)
- [ ] 🔑 E2E (Service Account)
- [ ] 📚 Docs updated (if behavior changed)
### 🕵️ Review Notes & ⚠️ Risks
<!-- Notes for reviewers, flags, feature gates, rollout considerations, etc. -->

48
.github/workflows/test-e2e.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
name: Test E2E
on:
pull_request:
types: [opened, synchronize, reopened]
branches: ['**'] # run for PRs targeting any branch (main and others)
concurrency:
group: e2e-${{ github.event.pull_request.head.ref }}
cancel-in-progress: true # cancel previous job runs for the same branch
jobs:
e2e-test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Install dependencies
run: go mod tidy
- name: Create kind cluster
uses: helm/kind-action@v1
with:
cluster_name: onepassword-operator-test-e2e
# install cli to interact with item in 1Password to update/read using `testhelper/op` package
- name: Install 1Password CLI
uses: 1password/install-cli-action@v2
with:
version: 2.32.0
- name: Create '1password-credentials.json' file
env:
OP_CONNECT_CREDENTIALS: ${{ secrets.OP_CONNECT_CREDENTIALS }}
run: |
echo "$OP_CONNECT_CREDENTIALS" > 1password-credentials.json
- name: Run E2E tests
run: make test-e2e
env:
OP_CONNECT_TOKEN: ${{ secrets.OP_CONNECT_TOKEN }}
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}

3
.gitignore vendored
View File

@@ -25,3 +25,6 @@ go.work
*.swp
*.swo
*~
**/1password-credentials.json
**/op-session

View File

@@ -4,7 +4,17 @@ Thank you for your interest in contributing to the 1Password Kubernetes Operator
## Testing
- For functional testing, run the local version of the operator. From the project root:
All contributions must include tests where applicable.
- **Unit tests** for pure Go logic.
- **Integration tests** for controller/reconciler logic using envtest.
- **E2E tests** for full cluster behavior with kind.
👉 See the [Testing Guide](docs/testing.md) for details on when to use each, how to run them locally, and how they are run in CI.
----
For functional testing, run the local version of the operator. From the project root:
```sh
# Go to the K8s environment (e.g. minikube)
@@ -24,6 +34,8 @@ Thank you for your interest in contributing to the 1Password Kubernetes Operator
1. Rebuild the Docker image by running `make docker-build`
2. Restart deployment `make restart`
----
- For testing the changes made to the `OnePasswordItem` Custom Resource Definition (CRD), you need to re-generate the object:
```sh
make manifests

View File

@@ -117,7 +117,7 @@ vet: ## Run go vet against code.
.PHONY: test
test: manifests generate fmt vet setup-envtest ## Run tests.
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./... -coverprofile cover.out
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $(shell go list ./... | grep -v /test/e2e) -coverprofile cover.out
# TODO(user): To use a different vendor for e2e tests, modify the setup under 'tests/e2e'.
# The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally.

View File

@@ -14,6 +14,8 @@ spec:
spec:
securityContext:
runAsNonRoot: true
fsGroup: 999
fsGroupChangePolicy: OnRootMismatch
volumes:
- name: shared-data
emptyDir: {}
@@ -31,10 +33,20 @@ spec:
volumeMounts:
- mountPath: /home/opuser/.op/data
name: shared-data
securityContext:
runAsUser: 0
runAsNonRoot: false
allowPrivilegeEscalation: false
capabilities:
drop: [ "ALL" ]
add: ["CHOWN", "FOWNER"]
containers:
- name: connect-api
image: 1password/connect-api:latest
securityContext:
runAsNonRoot: true
runAsUser: 999
runAsGroup: 999
allowPrivilegeEscalation: false
resources:
limits:
@@ -55,6 +67,9 @@ spec:
- name: connect-sync
image: 1password/connect-sync:latest
securityContext:
runAsNonRoot: true
runAsUser: 999
runAsGroup: 999
allowPrivilegeEscalation: false
resources:
limits:

22
docs/testing.md Normal file
View File

@@ -0,0 +1,22 @@
# Testing
## Unit & Integration tests
**When**: Unit (pure Go) and integration (controller-runtime envtest).
**Where**: `internal/...`, `pkg/...`
**Add files in**: `*_test.go` next to the code.
**Run**: `make test`
## E2E tests (kind)
**When**: Full cluster behavior (CRDs, operator image, Connect/SA flows).
**Where**: `test/e2e/...`
**Add files in**: `*_test.go` next to the code.
**Framework**: Ginkgo + `pkg/testhelper`.
**Local prep**:
1. [Install `kind`](https://kind.sigs.k8s.io/docs/user/quick-start/#installing-with-a-package-manager) to spin up local Kubernetes cluster.
2. `export OP_CONNECT_TOKEN=<token>`
3. `export OP_SERVICE_ACCOUNT_TOKEN=<token>`
4. `make test-e2e`
5. Put `1password-credentials.json` into project root.
**Run**: `make test-e2e`

2
go.mod
View File

@@ -12,6 +12,7 @@ require (
github.com/onsi/gomega v1.36.1
github.com/stretchr/testify v1.10.0
k8s.io/api v0.33.0
k8s.io/apiextensions-apiserver v0.33.0
k8s.io/apimachinery v0.33.0
k8s.io/client-go v0.33.0
k8s.io/kubectl v0.29.0
@@ -102,7 +103,6 @@ require (
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.33.0 // indirect
k8s.io/apiserver v0.33.0 // indirect
k8s.io/component-base v0.33.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect

View File

@@ -0,0 +1,8 @@
package defaults
import "time"
const (
E2EInterval = 1 * time.Second
E2ETimeout = 1 * time.Minute
)

View File

@@ -0,0 +1,23 @@
package kind
import (
"os"
//nolint:staticcheck // ST1001
. "github.com/onsi/ginkgo/v2"
//nolint:staticcheck // ST1001
. "github.com/onsi/gomega"
"github.com/1Password/onepassword-operator/pkg/testhelper/system"
)
// LoadImageToKind loads a local docker image to the Kind cluster
func LoadImageToKind(imageName string) {
By("Loading the operator image on Kind")
clusterName := "kind"
if value, ok := os.LookupEnv("KIND_CLUSTER"); ok {
clusterName = value
}
_, err := system.Run("kind", "load", "docker-image", imageName, "--name", clusterName)
Expect(err).NotTo(HaveOccurred())
}

View File

@@ -0,0 +1,125 @@
package kube
import (
"context"
"time"
//nolint:staticcheck // ST1001
. "github.com/onsi/ginkgo/v2"
//nolint:staticcheck // ST1001
. "github.com/onsi/gomega"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
type Deployment struct {
client client.Client
config *Config
name string
}
func (d *Deployment) Get(ctx context.Context) *appsv1.Deployment {
// Derive a short-lived context so this API call won't hang indefinitely.
c, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
deployment := &appsv1.Deployment{}
err := d.client.Get(c, client.ObjectKey{Name: d.name, Namespace: d.config.Namespace}, deployment)
Expect(err).ToNot(HaveOccurred())
return deployment
}
func (d *Deployment) ReadEnvVar(ctx context.Context, envVarName string) string {
By("Reading " + envVarName + " value from deployment/" + d.name)
deployment := d.Get(ctx)
// Search env across all containers
found := ""
for _, container := range deployment.Spec.Template.Spec.Containers {
for _, env := range container.Env {
if env.Name == envVarName && env.Value != "" {
found = env.Value
break
}
}
}
Expect(found).NotTo(BeEmpty())
return found
}
func (d *Deployment) PatchEnvVars(ctx context.Context, upsert []corev1.EnvVar, remove []string) {
By("Patching env variables for deployment/" + d.name)
deployment := d.Get(ctx)
deploymentCopy := deployment.DeepCopy()
container := &deployment.Spec.Template.Spec.Containers[0]
// Build removal set for quick lookup
toRemove := make(map[string]struct{}, len(remove))
for _, n := range remove {
toRemove[n] = struct{}{}
}
// Build upsert map for quick lookup
upserts := make(map[string]corev1.EnvVar, len(upsert))
for _, e := range upsert {
upserts[e.Name] = e
}
// Filter existing envs: keep if not in remove and not being upserted
filtered := make([]corev1.EnvVar, 0, len(container.Env))
for _, e := range container.Env {
if _, ok := toRemove[e.Name]; ok {
continue
}
if newE, ok := upserts[e.Name]; ok {
filtered = append(filtered, newE) // replace existing
delete(upserts, e.Name) // delete from map to not use once again
} else {
filtered = append(filtered, e)
}
}
// Append any new envs that werent already in the container
for _, e := range upserts {
filtered = append(filtered, e)
}
container.Env = filtered
// Derive a short-lived context so this API call won't hang indefinitely.
c, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
err := d.client.Patch(c, deployment, client.MergeFrom(deploymentCopy))
Expect(err).ToNot(HaveOccurred())
// wait for new deployment to roll out
d.WaitDeploymentRolledOut(ctx)
}
// WaitDeploymentRolledOut waits for deployment to finish a rollout.
func (d *Deployment) WaitDeploymentRolledOut(ctx context.Context) {
By("Waiting for deployment/" + d.name + " to roll out")
deployment := d.Get(ctx)
targetGen := deployment.Generation
Eventually(func(g Gomega) error {
newDeployment := d.Get(ctx)
g.Expect(newDeployment.Status.ObservedGeneration).To(BeNumerically(">=", targetGen))
desired := int32(1)
if newDeployment.Spec.Replicas != nil {
desired = *newDeployment.Spec.Replicas
}
g.Expect(newDeployment.Status.UpdatedReplicas).To(Equal(desired))
g.Expect(newDeployment.Status.AvailableReplicas).To(Equal(desired))
g.Expect(newDeployment.Status.Replicas).To(Equal(desired))
return nil
}, d.config.TestConfig.Timeout, d.config.TestConfig.Interval).Should(Succeed())
}

224
pkg/testhelper/kube/kube.go Normal file
View File

@@ -0,0 +1,224 @@
package kube
import (
"context"
"fmt"
"os"
"path/filepath"
"time"
//nolint:staticcheck // ST1001
. "github.com/onsi/ginkgo/v2"
//nolint:staticcheck // ST1001
. "github.com/onsi/gomega"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apix "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
apiv1 "github.com/1Password/onepassword-operator/api/v1"
"github.com/1Password/onepassword-operator/pkg/testhelper/defaults"
)
type TestConfig struct {
Timeout time.Duration
Interval time.Duration
}
type Config struct {
Namespace string
ManifestsDir string
TestConfig *TestConfig
CRDs []string
}
type Kube struct {
Config *Config
Client client.Client
Mapper meta.RESTMapper
}
func NewKubeClient(config *Config) *Kube {
By("Creating a kubernetes client")
kubeconfig := os.Getenv("KUBECONFIG")
if kubeconfig == "" {
home, _ := os.UserHomeDir()
kubeconfig = filepath.Join(home, ".kube", "config")
}
restConfig, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
Expect(err).NotTo(HaveOccurred())
// Install CRDs first (so discovery sees them)
installCRDs(context.Background(), restConfig, config.CRDs)
// Build an http.Client from restConfig
httpClient, err := rest.HTTPClientFor(restConfig)
Expect(err).NotTo(HaveOccurred())
// Create a Dynamic RESTMapper that uses restConfig
rm, err := apiutil.NewDynamicRESTMapper(restConfig, httpClient)
Expect(err).NotTo(HaveOccurred())
scheme := runtime.NewScheme()
utilruntime.Must(corev1.AddToScheme(scheme))
utilruntime.Must(appsv1.AddToScheme(scheme))
utilruntime.Must(apiv1.AddToScheme(scheme)) // add OnePasswordItem to scheme
kubernetesClient, err := client.New(restConfig, client.Options{
Scheme: scheme,
Mapper: rm,
})
Expect(err).NotTo(HaveOccurred())
// update the current contexts namespace in kubeconfig
pathOpts := clientcmd.NewDefaultPathOptions()
cfg, err := pathOpts.GetStartingConfig()
Expect(err).NotTo(HaveOccurred())
currentContext := cfg.CurrentContext
Expect(currentContext).NotTo(BeEmpty(), "no current kube context is set in kubeconfig")
ctx, ok := cfg.Contexts[currentContext]
Expect(ok).To(BeTrue(), fmt.Sprintf("current context %q not found in kubeconfig", currentContext))
ctx.Namespace = config.Namespace
err = clientcmd.ModifyConfig(pathOpts, *cfg, true)
Expect(err).NotTo(HaveOccurred())
return &Kube{
Config: config,
Client: kubernetesClient,
Mapper: rm,
}
}
func (k *Kube) Secret(name string) *Secret {
return &Secret{
client: k.Client,
config: k.Config,
name: name,
}
}
func (k *Kube) Deployment(name string) *Deployment {
return &Deployment{
client: k.Client,
config: k.Config,
name: name,
}
}
func (k *Kube) Pod(selector map[string]string) *Pod {
return &Pod{
client: k.Client,
config: k.Config,
selector: selector,
}
}
func (k *Kube) Namespace(name string) *Namespace {
return &Namespace{
client: k.Client,
config: k.Config,
name: name,
}
}
// ApplyOnePasswordItem applies a OnePasswordItem manifest.
func (k *Kube) ApplyOnePasswordItem(ctx context.Context, fileName string) {
By("Applying " + fileName)
// Derive a short-lived context so this API call won't hang indefinitely.
c, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
data, err := os.ReadFile(k.Config.ManifestsDir + "/" + fileName)
Expect(err).NotTo(HaveOccurred())
// Decode YAML -> JSON -> unstructured.Unstructured
jsonBytes, err := yaml.ToJSON(data)
Expect(err).NotTo(HaveOccurred())
var obj unstructured.Unstructured
Expect(obj.UnmarshalJSON(jsonBytes)).To(Succeed())
// Default namespace for namespaced resources if not set in YAML
if obj.GetNamespace() == "" && k.Config.Namespace != "" {
gvk := obj.GroupVersionKind()
mapping, mapErr := k.Mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if mapErr == nil && mapping.Scope.Name() == meta.RESTScopeNameNamespace {
obj.SetNamespace(k.Config.Namespace)
}
}
// Server-Side Apply (create or update)
patchOpts := []client.PatchOption{
client.FieldOwner("onepassword-e2e"),
client.ForceOwnership, // to force-take conflicting fields
}
Expect(k.Client.Patch(c, &obj, client.Apply, patchOpts...)).To(Succeed())
}
func installCRDs(ctx context.Context, restConfig *rest.Config, crdFiles []string) {
apixClient, err := apix.NewForConfig(restConfig)
Expect(err).NotTo(HaveOccurred())
for _, f := range crdFiles {
By("Installing CRD " + f)
b, err := os.ReadFile(filepath.Clean(f))
Expect(err).NotTo(HaveOccurred())
var crd apiextv1.CustomResourceDefinition
err = yaml.Unmarshal(b, &crd)
Expect(err).NotTo(HaveOccurred())
// Create or Update
_, err = apixClient.ApiextensionsV1().CustomResourceDefinitions().Create(ctx, &crd, metav1.CreateOptions{})
if apierrors.IsAlreadyExists(err) {
existing, getErr := apixClient.ApiextensionsV1().CustomResourceDefinitions().Get(ctx, crd.Name, metav1.GetOptions{})
Expect(getErr).NotTo(HaveOccurred())
crd.ResourceVersion = existing.ResourceVersion
_, err = apixClient.ApiextensionsV1().CustomResourceDefinitions().Update(ctx, &crd, metav1.UpdateOptions{})
}
Expect(err).NotTo(HaveOccurred())
waitCRDEstablished(ctx, apixClient, crd.Name)
}
}
// waitCRDEstablished Wait until the CRD reaches Established=True, retrying until the suite timeout.
func waitCRDEstablished(ctx context.Context, apixClient *apix.Clientset, name string) {
By("Waiting for CRD " + name + " to be Established")
Eventually(func(g Gomega) {
// Short per-attempt timeout so a single Get can't hang the whole Eventually loop.
attemptCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
crd, err := apixClient.ApiextensionsV1().
CustomResourceDefinitions().
Get(attemptCtx, name, metav1.GetOptions{})
g.Expect(err).NotTo(HaveOccurred())
established := false
for _, c := range crd.Status.Conditions {
if c.Type == apiextv1.Established && c.Status == apiextv1.ConditionTrue {
established = true
break
}
}
g.Expect(established).To(BeTrue(), "CRD %q is not Established yet", name)
}, defaults.E2ETimeout, defaults.E2EInterval).Should(Succeed())
}

View File

@@ -0,0 +1,41 @@
package kube
import (
"context"
//nolint:staticcheck // ST1001
. "github.com/onsi/ginkgo/v2"
//nolint:staticcheck // ST1001
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"sigs.k8s.io/controller-runtime/pkg/client"
)
type Namespace struct {
client client.Client
config *Config
name string
}
// LabelNamespace applies the given labels to the specified namespace
func (n *Namespace) LabelNamespace(ctx context.Context, labelsMap map[string]string) {
if len(labelsMap) == 0 {
return
}
By("Setting labelsMap " + labels.Set(labelsMap).String() + " to namespace/" + n.name)
ns := &corev1.Namespace{}
err := n.client.Get(ctx, client.ObjectKey{Name: n.name}, ns)
Expect(err).NotTo(HaveOccurred())
if ns.Labels == nil {
ns.Labels = map[string]string{}
}
for k, v := range labelsMap {
ns.Labels[k] = v
}
err = n.client.Update(ctx, ns)
Expect(err).NotTo(HaveOccurred())
}

View File

@@ -0,0 +1,47 @@
package kube
import (
"context"
"time"
//nolint:staticcheck // ST1001
. "github.com/onsi/ginkgo/v2"
//nolint:staticcheck // ST1001
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"sigs.k8s.io/controller-runtime/pkg/client"
)
type Pod struct {
client client.Client
config *Config
selector map[string]string
}
func (p *Pod) WaitingForRunningPod(ctx context.Context) {
By("Waiting for the pod " + labels.Set(p.selector).String() + " to be 'Running'")
Eventually(func(g Gomega) {
// short per-attempt timeout to avoid hanging calls while Eventually polls
attemptCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
var pods corev1.PodList
listOpts := []client.ListOption{
client.InNamespace(p.config.Namespace),
client.MatchingLabels(p.selector),
}
g.Expect(p.client.List(attemptCtx, &pods, listOpts...)).To(Succeed())
g.Expect(pods.Items).NotTo(BeEmpty(), "no pods found with selector %q", labels.Set(p.selector).String())
foundRunning := false
for _, p := range pods.Items {
if p.Status.Phase == corev1.PodRunning {
foundRunning = true
break
}
}
g.Expect(foundRunning).To(BeTrue(), "pod not Running yet")
}, p.config.TestConfig.Timeout, p.config.TestConfig.Interval).Should(Succeed())
}

View File

@@ -0,0 +1,141 @@
package kube
import (
"context"
"encoding/base64"
"os"
"path/filepath"
"time"
//nolint:staticcheck // ST1001
. "github.com/onsi/ginkgo/v2"
//nolint:staticcheck // ST1001
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/1Password/onepassword-operator/pkg/testhelper/system"
)
type Secret struct {
client client.Client
config *Config
name string
}
// CreateFromEnvVar creates a kubernetes secret from an environment variable
func (s *Secret) CreateFromEnvVar(ctx context.Context, envVar string) *corev1.Secret {
By("Creating '" + s.name + "' secret from environment variable")
// Derive a short-lived context so this API call won't hang indefinitely.
c, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
value, ok := os.LookupEnv(envVar)
Expect(ok).To(BeTrue())
Expect(value).NotTo(BeEmpty())
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: s.name,
Namespace: s.config.Namespace,
},
StringData: map[string]string{
"token": value,
},
}
err := s.client.Create(c, secret)
Expect(err).NotTo(HaveOccurred())
return secret
}
// CreateFromFile creates a kubernetes secret from a file
func (s *Secret) CreateFromFile(ctx context.Context, fileName string, content []byte) *corev1.Secret {
By("Creating '" + s.name + "' secret from file " + fileName)
// Derive a short-lived context so this API call won't hang indefinitely.
c, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: s.name,
Namespace: s.config.Namespace,
},
Data: map[string][]byte{
filepath.Base(fileName): content,
},
}
err := s.client.Create(c, secret)
Expect(err).NotTo(HaveOccurred())
return secret
}
// CreateOpCredentials creates a kubernetes secret from 1password-credentials.json file in the project root
// encodes it in base64 and saves it to op-session file
func (s *Secret) CreateOpCredentials(ctx context.Context) *corev1.Secret {
rootDir, err := system.GetProjectRoot()
Expect(err).NotTo(HaveOccurred())
credentialsFilePath := filepath.Join(rootDir, "1password-credentials.json")
data, err := os.ReadFile(credentialsFilePath)
Expect(err).NotTo(HaveOccurred())
encoded := base64.RawURLEncoding.EncodeToString(data)
return s.CreateFromFile(ctx, "op-session", []byte(encoded))
}
// Get retrieves a kubernetes secret
func (s *Secret) Get(ctx context.Context) *corev1.Secret {
By("Getting '" + s.name + "' secret")
// Derive a short-lived context so this API call won't hang indefinitely.
c, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
secret := &corev1.Secret{}
err := s.client.Get(c, client.ObjectKey{Name: s.name, Namespace: s.config.Namespace}, secret)
Expect(err).NotTo(HaveOccurred())
return secret
}
// Delete deletes a kubernetes secret
func (s *Secret) Delete(ctx context.Context) {
By("Deleting '" + s.name + "' secret")
// Derive a short-lived context so this API call won't hang indefinitely.
c, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: s.name,
Namespace: s.config.Namespace,
},
}
err := s.client.Delete(c, secret)
Expect(err).NotTo(HaveOccurred())
}
// CheckIfExists repeatedly attempts to retrieve the given Secret
// from the cluster until it is found or the test's timeout expires.
func (s *Secret) CheckIfExists(ctx context.Context) {
By("Checking '" + s.name + "' secret")
Eventually(func(g Gomega) {
// Derive a short-lived context so this API call won't hang indefinitely.
attemptCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
secret := &corev1.Secret{}
err := s.client.Get(attemptCtx, client.ObjectKey{Name: s.name, Namespace: s.config.Namespace}, secret)
g.Expect(err).NotTo(HaveOccurred())
}, s.config.TestConfig.Timeout, s.config.TestConfig.Interval).Should(Succeed())
}

32
pkg/testhelper/op/op.go Normal file
View File

@@ -0,0 +1,32 @@
package op
import (
"fmt"
"github.com/1Password/onepassword-operator/pkg/testhelper/system"
)
type Field string
const (
FieldUsername = "username"
FieldPassword = "password"
)
// UpdateItemPassword updates the password of an item in 1Password
func UpdateItemPassword(item string) error {
_, err := system.Run("op", "item", "edit", item, "--generate-password=letters,digits,symbols,32")
if err != nil {
return err
}
return nil
}
// ReadItemField reads the password of an item in 1Password
func ReadItemField(item, vault string, field Field) (string, error) {
output, err := system.Run("op", "read", fmt.Sprintf("op://%s/%s/%s", vault, item, field))
if err != nil {
return "", err
}
return output, nil
}

View File

@@ -0,0 +1,94 @@
package system
import (
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
)
// Run executes the provided command within this context
func Run(name string, args ...string) (string, error) {
cmd := exec.Command(name, args...)
rootDir, err := GetProjectRoot()
if err != nil {
return "", err
}
// Command will run from project root
cmd.Dir = rootDir
command := strings.Join(cmd.Args, " ")
output, err := cmd.CombinedOutput()
if err != nil {
return string(output), fmt.Errorf("%s failed with error: (%v) %s", command, err, string(output))
}
return string(output), nil
}
func GetProjectRoot() (string, error) {
dir, err := os.Getwd()
if err != nil {
return "", err
}
for {
// check if go.mod exists in current dir
modFile := filepath.Join(dir, "go.mod")
if _, err := os.Stat(modFile); err == nil {
return dir, nil
}
// move one level up
parent := filepath.Dir(dir)
if parent == dir {
// reached filesystem root
return "", fmt.Errorf("project root not found (no go.mod)")
}
dir = parent
}
}
func ReplaceFile(src, dst string) error {
rootDir, err := GetProjectRoot()
if err != nil {
return err
}
// Open the source file
sourceFile, err := os.Open(filepath.Join(rootDir, src))
if err != nil {
return err
}
defer func(sourceFile *os.File) {
cerr := sourceFile.Close()
if err != nil {
err = errors.Join(err, cerr)
}
}(sourceFile)
// Create (or overwrite) the destination file
destFile, err := os.Create(filepath.Join(rootDir, dst))
if err != nil {
return err
}
defer func(destFile *os.File) {
cerr := destFile.Close()
if err != nil {
err = errors.Join(err, cerr)
}
}(destFile)
// Copy contents
if _, err = io.Copy(destFile, sourceFile); err != nil {
return err
}
// Ensure data is written to disk
return destFile.Sync()
}

View File

@@ -0,0 +1,14 @@
package e2e
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
// Run e2e tests using the Ginkgo runner.
func TestE2E(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "onepassword-operator e2e suite")
}

233
test/e2e/e2e_test.go Normal file
View File

@@ -0,0 +1,233 @@
package e2e
import (
"context"
"path/filepath"
"strconv"
"time"
//nolint:staticcheck // ST1001
. "github.com/onsi/ginkgo/v2"
//nolint:staticcheck // ST1001
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
"github.com/1Password/onepassword-operator/pkg/testhelper/defaults"
"github.com/1Password/onepassword-operator/pkg/testhelper/kind"
"github.com/1Password/onepassword-operator/pkg/testhelper/kube"
"github.com/1Password/onepassword-operator/pkg/testhelper/op"
"github.com/1Password/onepassword-operator/pkg/testhelper/system"
)
const (
operatorImageName = "1password/onepassword-operator:latest"
vaultName = "operator-acceptance-tests"
)
var kubeClient *kube.Kube
var _ = Describe("Onepassword Operator e2e", Ordered, func() {
ctx := context.Background()
BeforeAll(func() {
rootDir, err := system.GetProjectRoot()
Expect(err).NotTo(HaveOccurred())
kubeClient = kube.NewKubeClient(&kube.Config{
Namespace: "default",
ManifestsDir: filepath.Join("manifests"),
TestConfig: &kube.TestConfig{
Timeout: defaults.E2ETimeout,
Interval: defaults.E2EInterval,
},
CRDs: []string{
filepath.Join(rootDir, "config", "crd", "bases", "onepassword.com_onepassworditems.yaml"),
},
})
By("Building the Operator image")
_, err = system.Run("make", "docker-build")
Expect(err).NotTo(HaveOccurred())
kind.LoadImageToKind(operatorImageName)
kubeClient.Secret("op-credentials").CreateOpCredentials(ctx)
kubeClient.Secret("op-credentials").CheckIfExists(ctx)
kubeClient.Secret("onepassword-token").CreateFromEnvVar(ctx, "OP_CONNECT_TOKEN")
kubeClient.Secret("onepassword-token").CheckIfExists(ctx)
kubeClient.Secret("onepassword-service-account-token").CreateFromEnvVar(ctx, "OP_SERVICE_ACCOUNT_TOKEN")
kubeClient.Secret("onepassword-service-account-token").CheckIfExists(ctx)
By("Replace manager.yaml")
err = system.ReplaceFile("test/e2e/manifests/manager.yaml", "config/manager/manager.yaml")
Expect(err).NotTo(HaveOccurred())
_, err = system.Run("make", "deploy")
Expect(err).NotTo(HaveOccurred())
kubeClient.Pod(map[string]string{"name": "onepassword-connect-operator"}).WaitingForRunningPod(ctx)
})
Context("Use the operator with Service Account", func() {
runCommonTestCases(ctx)
})
Context("Use the operator with Connect", func() {
BeforeAll(func() {
kubeClient.Deployment("onepassword-connect-operator").PatchEnvVars(ctx, []corev1.EnvVar{
{Name: "MANAGE_CONNECT", Value: "true"},
{Name: "OP_CONNECT_HOST", Value: "http://onepassword-connect:8080"},
{
Name: "OP_CONNECT_TOKEN",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "onepassword-token",
},
Key: "token",
},
},
},
}, []string{"OP_SERVICE_ACCOUNT_TOKEN"})
kubeClient.Secret("login").Delete(ctx) // remove secret crated in previous test
kubeClient.Secret("secret-ignored").Delete(ctx) // remove secret crated in previous test
kubeClient.Secret("secret-for-update").Delete(ctx) // remove secret crated in previous test
kubeClient.Pod(map[string]string{"app": "onepassword-connect"}).WaitingForRunningPod(ctx)
})
runCommonTestCases(ctx)
})
})
// runCommonTestCases contains test cases that are common to both Connect and Service Account authentication methods.
func runCommonTestCases(ctx context.Context) {
It("Should create kubernetes secret from manifest file", func() {
By("Creating secret `login` from 1Password item")
kubeClient.ApplyOnePasswordItem(ctx, "secret.yaml")
kubeClient.Secret("login").CheckIfExists(ctx)
})
It("Kubernetes secret is updated after POOLING_INTERVAL, when updating item in 1Password", func() {
itemName := "secret-for-update"
secretName := itemName
By("Creating secret `" + secretName + "` from 1Password item")
kubeClient.ApplyOnePasswordItem(ctx, secretName+".yaml")
kubeClient.Secret(secretName).CheckIfExists(ctx)
By("Reading old password")
secret := kubeClient.Secret(secretName).Get(ctx)
oldPassword, ok := secret.Data["password"]
Expect(ok).To(BeTrue())
By("Updating `" + secretName + "` 1Password item")
err := op.UpdateItemPassword(itemName)
Expect(err).NotTo(HaveOccurred())
// checking that password was updated
Eventually(func(g Gomega) {
// Derive a short-lived context so this API call won't hang indefinitely.
attemptCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
secret = kubeClient.Secret(secretName).Get(attemptCtx)
g.Expect(err).NotTo(HaveOccurred())
newPassword, ok := secret.Data["password"]
g.Expect(ok).To(BeTrue())
g.Expect(newPassword).NotTo(Equal(oldPassword))
}, defaults.E2ETimeout, defaults.E2EInterval).Should(Succeed())
})
It("1Password item with `ignore-secret` tag doesn't pull updates to kubernetes secret", func() {
itemName := "secret-ignored"
secretName := itemName
By("Creating secret `" + secretName + "` from 1Password item")
kubeClient.ApplyOnePasswordItem(ctx, secretName+".yaml")
kubeClient.Secret(secretName).CheckIfExists(ctx)
By("Reading old password")
secret := kubeClient.Secret(secretName).Get(ctx)
oldPassword, ok := secret.Data["password"]
Expect(ok).To(BeTrue())
By("Updating `" + secretName + "` 1Password item")
err := op.UpdateItemPassword(itemName)
Expect(err).NotTo(HaveOccurred())
newPassword, err := op.ReadItemField(itemName, vaultName, op.FieldPassword)
Expect(err).NotTo(HaveOccurred())
Expect(newPassword).NotTo(BeEquivalentTo(oldPassword))
// checking that password was NOT updated
Eventually(func(g Gomega) {
// Derive a short-lived context so this API call won't hang indefinitely.
attemptCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
intervalStr := kubeClient.Deployment("onepassword-connect-operator").ReadEnvVar(attemptCtx, "POLLING_INTERVAL")
Expect(intervalStr).NotTo(BeEmpty())
i, err := strconv.Atoi(intervalStr)
Expect(err).NotTo(HaveOccurred())
// convert to duration in seconds
interval := time.Duration(i) * time.Second
// wait for one polling interval + 2 seconds to make sure updated secret is pulled
time.Sleep(interval + 2*time.Second)
secret = kubeClient.Secret(secretName).Get(attemptCtx)
g.Expect(err).NotTo(HaveOccurred())
currentPassword, ok := secret.Data["password"]
Expect(ok).To(BeTrue())
Expect(currentPassword).To(BeEquivalentTo(oldPassword))
Expect(currentPassword).NotTo(BeEquivalentTo(newPassword))
}, defaults.E2ETimeout, defaults.E2EInterval).Should(Succeed())
})
It("AUTO_RESTART restarts deployments using 1Password secrets after item update", func() {
By("Enabling AUTO_RESTART")
kubeClient.Deployment("onepassword-connect-operator").PatchEnvVars(ctx, []corev1.EnvVar{
{Name: "AUTO_RESTART", Value: "true"},
}, nil)
DeferCleanup(func() {
By("Disabling AUTO_RESTART")
// disable AUTO_RESTART after test
kubeClient.Deployment("onepassword-connect-operator").PatchEnvVars(ctx, []corev1.EnvVar{
{Name: "AUTO_RESTART", Value: "false"},
}, nil)
})
// Ensure the secret exists (created in earlier test), but apply again safely just in case
kubeClient.ApplyOnePasswordItem(ctx, "secret-for-update.yaml")
kubeClient.Secret("secret-for-update").CheckIfExists(ctx)
// add custom secret to the operator
kubeClient.Deployment("onepassword-connect-operator").PatchEnvVars(ctx, []corev1.EnvVar{
{
Name: "CUSTOM_SECRET",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "secret-for-update",
},
Key: "password",
},
},
},
}, nil)
By("Updating `secret-for-update` 1Password item")
err := op.UpdateItemPassword("secret-for-update")
Expect(err).NotTo(HaveOccurred())
By("Checking the operator is restarted")
kubeClient.Deployment("onepassword-connect-operator").WaitDeploymentRolledOut(ctx)
})
}

View File

@@ -0,0 +1,99 @@
# This manager file is used for e2e tests.
# It will be copied to `config/manager` and be used when deploying the operator in e2e tests
# The purpose of it is to increase e2e tests speed and do not introduce additional changes in original `manager.yaml`
apiVersion: v1
kind: Namespace
metadata:
labels:
control-plane: onepassword-connect-operator
app.kubernetes.io/name: namespace
app.kubernetes.io/instance: system
app.kubernetes.io/component: manager
app.kubernetes.io/created-by: onepassword-connect-operator
app.kubernetes.io/part-of: onepassword-connect-operator
app.kubernetes.io/managed-by: kustomize
name: system
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: onepassword-connect-operator
namespace: system
labels:
control-plane: controller-manager
app.kubernetes.io/name: deployment
app.kubernetes.io/instance: controller-manager
app.kubernetes.io/component: manager
app.kubernetes.io/created-by: onepassword-connect-operator
app.kubernetes.io/part-of: onepassword-connect-operator
app.kubernetes.io/managed-by: kustomize
spec:
selector:
matchLabels:
name: onepassword-connect-operator
control-plane: onepassword-connect-operator
replicas: 1
template:
metadata:
annotations:
kubectl.kubernetes.io/default-container: manager
labels:
name: onepassword-connect-operator
control-plane: onepassword-connect-operator
spec:
securityContext:
runAsNonRoot: true
containers:
- command:
- /manager
args:
- --leader-elect
- --health-probe-bind-address=:8081
image: 1password/onepassword-operator:latest
imagePullPolicy: Never
name: manager
env:
- name: OPERATOR_NAME
value: "onepassword-connect-operator"
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: WATCH_NAMESPACE
value: "default"
- name: POLLING_INTERVAL
value: "10"
- name: AUTO_RESTART
value: "false"
- name: OP_SERVICE_ACCOUNT_TOKEN
valueFrom:
secretKeyRef:
name: onepassword-service-account-token
key: token
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- "ALL"
livenessProbe:
httpGet:
path: /healthz
port: 8081
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /readyz
port: 8081
initialDelaySeconds: 5
periodSeconds: 10
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
serviceAccountName: onepassword-connect-operator
terminationGracePeriodSeconds: 10

View File

@@ -0,0 +1,6 @@
apiVersion: onepassword.com/v1
kind: OnePasswordItem
metadata:
name: secret-for-update
spec:
itemPath: "vaults/operator-acceptance-tests/items/secret-for-update"

View File

@@ -0,0 +1,6 @@
apiVersion: onepassword.com/v1
kind: OnePasswordItem
metadata:
name: secret-ignored
spec:
itemPath: "vaults/operator-acceptance-tests/items/secret-ignored"

View File

@@ -0,0 +1,6 @@
apiVersion: onepassword.com/v1
kind: OnePasswordItem
metadata:
name: login
spec:
itemPath: "vaults/operator-acceptance-tests/items/test-login"