mirror of
https://github.com/1Password/onepassword-operator.git
synced 2025-10-22 15:38:06 +00:00
Compare commits
32 Commits
6baef1b9cf
...
vzt/fix-ok
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d403a29fdc | ||
![]() |
d30ed0b9d7 | ||
![]() |
db2053fde4 | ||
![]() |
316f605680 | ||
![]() |
7135f439a5 | ||
![]() |
625297c1fb | ||
![]() |
e967f7ce1e | ||
![]() |
5199e9f350 | ||
![]() |
878b38deac | ||
![]() |
1a40b4fb95 | ||
![]() |
4b5d3c5f1a | ||
![]() |
8d4908fb72 | ||
![]() |
6cf52a7bfc | ||
![]() |
7ef9ec6de7 | ||
![]() |
3f52bb2840 | ||
![]() |
63e3f29be9 | ||
![]() |
49bc9cb329 | ||
![]() |
de62e07bcf | ||
![]() |
3ebc536dd7 | ||
![]() |
6769e25a98 | ||
![]() |
d8734c9ae3 | ||
![]() |
460742869b | ||
![]() |
35e476230c | ||
![]() |
3a9691576a | ||
![]() |
94602ddd72 | ||
![]() |
292c6f0e93 | ||
![]() |
0f1293ca95 | ||
![]() |
706ebdd8b8 | ||
![]() |
bd963bcd1d | ||
![]() |
bf6cac81cb | ||
![]() |
9c4849ec2e | ||
![]() |
c2788770fd |
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -11,10 +11,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clone the code
|
- name: Clone the code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
|
|
||||||
|
52
.github/workflows/e2e-tests.yml
vendored
Normal file
52
.github/workflows/e2e-tests.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
name: E2E Tests [reusable]
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
secrets:
|
||||||
|
OP_CONNECT_CREDENTIALS:
|
||||||
|
description: '1Password Connect credentials'
|
||||||
|
required: true
|
||||||
|
OP_CONNECT_TOKEN:
|
||||||
|
description: '1Password Connect token'
|
||||||
|
required: true
|
||||||
|
OP_SERVICE_ACCOUNT_TOKEN:
|
||||||
|
description: '1Password service account token'
|
||||||
|
required: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
e2e-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v6
|
||||||
|
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 }}
|
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
@@ -11,10 +11,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clone the code
|
- name: Clone the code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
|
|
||||||
|
25
.github/workflows/ok-to-test.yml
vendored
Normal file
25
.github/workflows/ok-to-test.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Write comments "/ok-to-test sha=<hash>" on a pull request. This will emit a repository_dispatch event.
|
||||||
|
name: Ok To Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ok-to-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
pull-requests: write # For adding reactions to the pull request comments
|
||||||
|
contents: write # For executing the repository_dispatch event
|
||||||
|
# Only run for PRs, not issue comments
|
||||||
|
if: ${{ github.event.issue.pull_request }}
|
||||||
|
steps:
|
||||||
|
- name: Slash Command Dispatch
|
||||||
|
uses: volodymyrZotov/slash-command-dispatch@7c1b623a2b0eba93f684c34f689a441f0be84cf1 # TODO: use peter-evans/slash-command-dispatch when fix for team permissions is released https://github.com/peter-evans/slash-command-dispatch/pull/424
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
reaction-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
issue-type: pull-request
|
||||||
|
commands: ok-to-test
|
||||||
|
# The repository permission level required by the user to dispatch commands. Only allows 1Password collaborators to run this.
|
||||||
|
permission: write
|
2
.github/workflows/release-pr.yml
vendored
2
.github/workflows/release-pr.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
|||||||
name: Create Release Pull Request
|
name: Create Release Pull Request
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Parse release version
|
- name: Parse release version
|
||||||
id: get_version
|
id: get_version
|
||||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
DOCKER_CLI_EXPERIMENTAL: "enabled"
|
DOCKER_CLI_EXPERIMENTAL: "enabled"
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
67
.github/workflows/test-e2e-fork.yml
vendored
Normal file
67
.github/workflows/test-e2e-fork.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
name: E2E tests [fork]
|
||||||
|
|
||||||
|
on:
|
||||||
|
repository_dispatch:
|
||||||
|
types: [ ok-to-test-command ]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
checks: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: e2e-fork-${{ github.event.client_payload.pull_request.number || github.run_id }}
|
||||||
|
cancel-in-progress: true # cancel previous job runs for the same branch
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run-e2e-tests:
|
||||||
|
uses: ./.github/workflows/e2e-tests.yml
|
||||||
|
if: |
|
||||||
|
github.event_name == 'repository_dispatch' &&
|
||||||
|
github.event.client_payload.slash_command.args.named.sha != '' &&
|
||||||
|
contains(
|
||||||
|
github.event.client_payload.pull_request.head.sha,
|
||||||
|
github.event.client_payload.slash_command.args.named.sha
|
||||||
|
)
|
||||||
|
secrets:
|
||||||
|
OP_CONNECT_CREDENTIALS: ${{ secrets.OP_CONNECT_CREDENTIALS }}
|
||||||
|
OP_CONNECT_TOKEN: ${{ secrets.OP_CONNECT_TOKEN }}
|
||||||
|
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
||||||
|
|
||||||
|
update-check-status:
|
||||||
|
needs: run-e2e-tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: always() && github.event_name == 'repository_dispatch'
|
||||||
|
steps:
|
||||||
|
- uses: actions/github-script@v6
|
||||||
|
env:
|
||||||
|
ref: ${{ github.event.client_payload.pull_request.head.sha }}
|
||||||
|
conclusion: ${{ needs.run-e2e-tests.result }}
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
script: |
|
||||||
|
const { data: checks } = await github.rest.checks.listForRef({
|
||||||
|
...context.repo,
|
||||||
|
ref: process.env.ref
|
||||||
|
});
|
||||||
|
|
||||||
|
// update the 'check-external-pr' check run
|
||||||
|
const check = checks.check_runs.filter(c => c.name === 'check-external-pr');
|
||||||
|
|
||||||
|
const { data: result } = await github.rest.checks.update({
|
||||||
|
...context.repo,
|
||||||
|
check_run_id: check[0].id,
|
||||||
|
status: 'completed',
|
||||||
|
conclusion: process.env.conclusion
|
||||||
|
});
|
||||||
|
|
||||||
|
// update the 'run-e2e-tests' check run from 'test-e2e.yml' workflow
|
||||||
|
const check = checks.check_runs.filter(c => c.name === 'run-e2e-tests');
|
||||||
|
|
||||||
|
const { data: result } = await github.rest.checks.update({
|
||||||
|
...context.repo,
|
||||||
|
check_run_id: check[0].id,
|
||||||
|
status: 'completed',
|
||||||
|
conclusion: process.env.conclusion
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
62
.github/workflows/test-e2e.yml
vendored
62
.github/workflows/test-e2e.yml
vendored
@@ -1,47 +1,43 @@
|
|||||||
name: Test E2E
|
name: E2E Tests
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, synchronize, reopened]
|
types: [opened, synchronize, reopened]
|
||||||
branches: ['**'] # run for PRs targeting any branch (main and others)
|
branches: ['**'] # run for PRs targeting any branch (main and others)
|
||||||
|
paths-ignore: &ignore_paths
|
||||||
|
- 'docs/**'
|
||||||
|
- '*.md'
|
||||||
|
- '.golangci.yml'
|
||||||
|
- '.gitignore'
|
||||||
|
- '.dockerignore'
|
||||||
|
- 'LICENSE'
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths-ignore: *ignore_paths
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: e2e-${{ github.event.pull_request.head.ref }}
|
group: e2e-${{ github.event.pull_request.head.ref }}
|
||||||
cancel-in-progress: true # cancel previous job runs for the same branch
|
cancel-in-progress: true # cancel previous job runs for the same branch
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
e2e-test:
|
check-external-pr:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Check if PR is from external contributor
|
||||||
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
|
|
||||||
|
|
||||||
- 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: |
|
run: |
|
||||||
echo "$OP_CONNECT_CREDENTIALS" > 1password-credentials.json
|
if [ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]; then
|
||||||
|
echo "❌ External PR detected. This workflow requires approval from a maintainer."
|
||||||
|
echo "Please ask a maintainer to run '/ok-to-test' command to trigger the fork workflow."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✅ Internal PR detected. Proceeding with tests."
|
||||||
|
|
||||||
- name: Run E2E tests
|
run-e2e-tests:
|
||||||
run: make test-e2e
|
needs: check-external-pr
|
||||||
env:
|
if: always() && (needs.check-external-pr.result == 'success' || github.event_name != 'pull_request')
|
||||||
OP_CONNECT_TOKEN: ${{ secrets.OP_CONNECT_TOKEN }}
|
uses: ./.github/workflows/e2e-tests.yml
|
||||||
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
secrets:
|
||||||
|
OP_CONNECT_CREDENTIALS: ${{ secrets.OP_CONNECT_CREDENTIALS }}
|
||||||
|
OP_CONNECT_TOKEN: ${{ secrets.OP_CONNECT_TOKEN }}
|
||||||
|
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
||||||
|
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@@ -11,10 +11,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clone the code
|
- name: Clone the code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
|
|
||||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@@ -26,5 +26,5 @@ go.work
|
|||||||
*.swo
|
*.swo
|
||||||
*~
|
*~
|
||||||
|
|
||||||
1password-credentials.json
|
**/1password-credentials.json
|
||||||
op-session
|
**/op-session
|
||||||
|
@@ -39,6 +39,7 @@ spec:
|
|||||||
allowPrivilegeEscalation: false
|
allowPrivilegeEscalation: false
|
||||||
capabilities:
|
capabilities:
|
||||||
drop: [ "ALL" ]
|
drop: [ "ALL" ]
|
||||||
|
add: ["CHOWN", "FOWNER"]
|
||||||
containers:
|
containers:
|
||||||
- name: connect-api
|
- name: connect-api
|
||||||
image: 1password/connect-api:latest
|
image: 1password/connect-api:latest
|
||||||
|
@@ -74,7 +74,6 @@ spec:
|
|||||||
- --leader-elect
|
- --leader-elect
|
||||||
- --health-probe-bind-address=:8081
|
- --health-probe-bind-address=:8081
|
||||||
image: 1password/onepassword-operator:latest
|
image: 1password/onepassword-operator:latest
|
||||||
imagePullPolicy: Never
|
|
||||||
name: manager
|
name: manager
|
||||||
env:
|
env:
|
||||||
- name: OPERATOR_NAME
|
- name: OPERATOR_NAME
|
||||||
@@ -97,7 +96,7 @@ spec:
|
|||||||
name: onepassword-token
|
name: onepassword-token
|
||||||
key: token
|
key: token
|
||||||
- name: MANAGE_CONNECT
|
- name: MANAGE_CONNECT
|
||||||
value: "true"
|
value: "false"
|
||||||
# Uncomment the following lines to enable service account token and comment out the OP_CONNECT_TOKEN, OP_CONNECT_HOST and MANAGE_CONNECT env vars.
|
# Uncomment the following lines to enable service account token and comment out the OP_CONNECT_TOKEN, OP_CONNECT_HOST and MANAGE_CONNECT env vars.
|
||||||
# - name: OP_SERVICE_ACCOUNT_TOKEN
|
# - name: OP_SERVICE_ACCOUNT_TOKEN
|
||||||
# valueFrom:
|
# valueFrom:
|
||||||
|
109
docs/fork-pr-testing.md
Normal file
109
docs/fork-pr-testing.md
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# Fork PR Testing Guide
|
||||||
|
|
||||||
|
This document explains how to test external pull requests using the dispatch action workflow system.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The testing system consists of three main workflows:
|
||||||
|
|
||||||
|
1. **E2E Tests** (`test-e2e.yml`) - Runs automatically for internal PRs, requires approval for external PRs
|
||||||
|
2. **Ok To Test** (`ok-to-test.yml`) - Processes slash commands to trigger fork testing
|
||||||
|
3. **E2E tests [fork]** (`test-e2e-fork.yml`) - Triggered manually via slash commands for external PRs
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
### 1. Initial PR State
|
||||||
|
When a PR is created or updated:
|
||||||
|
- The `test-e2e.yml` workflow automatically runs
|
||||||
|
- For **external PRs**: The `check-external-pr` job detects it's from a fork and **fails intentionally**
|
||||||
|
- For **internal PRs**: The workflow proceeds normally with e2e tests
|
||||||
|
- External PRs show ❌ for the check-external-pr job with a message asking for maintainer approval
|
||||||
|
|
||||||
|
### 2. Manual Approval Required
|
||||||
|
**Important**: External PRs require manual approval before workflows can run.
|
||||||
|
|
||||||
|
**Steps for maintainers:**
|
||||||
|
1. Go to the PR page
|
||||||
|
2. Click **"Approve workflow to run"** button
|
||||||
|
3. The workflow will then execute
|
||||||
|
|
||||||
|
**Note**: The `test-e2e.yml` workflow will still fail for external PRs even after approval, as it's designed to prevent automatic execution. Need to use the slash command instead.
|
||||||
|
|
||||||
|
### 3. Testing External PRs
|
||||||
|
Once the initial checks have run (and failed), maintainers can test the PR using slash commands:
|
||||||
|
|
||||||
|
#### Step-by-Step Process:
|
||||||
|
|
||||||
|
1. **Navigate to the PR**
|
||||||
|
- Go to the pull request you want to test
|
||||||
|
|
||||||
|
2. **Add a comment with slash command**
|
||||||
|
```
|
||||||
|
/ok-to-test sha=<commit-sha>
|
||||||
|
```
|
||||||
|
Replace `<commit-sha>` with the actual commit SHA from the PR.
|
||||||
|
|
||||||
|
**Note**: Use the short SHA.
|
||||||
|
|
||||||
|
3. **Dispatch Action Triggers**
|
||||||
|
- The `ok-to-test.yml` workflow processes the slash command
|
||||||
|
- It triggers a `repository_dispatch` event with type `ok-to-test-command`
|
||||||
|
- The `test-e2e-fork.yml` workflow starts
|
||||||
|
|
||||||
|
4. **Workflow Execution**
|
||||||
|
The fork workflow runs two jobs:
|
||||||
|
|
||||||
|
**a) `run-e2e-tests` job** (conditional)
|
||||||
|
- Only runs if:
|
||||||
|
- Event is `repository_dispatch`
|
||||||
|
- SHA parameter is not empty
|
||||||
|
- PR head SHA contains the provided SHA
|
||||||
|
- Calls the reusable `run-e2e-tests.yml` workflow
|
||||||
|
- Runs the actual E2E tests if conditions are met
|
||||||
|
|
||||||
|
**b) `update-check-status` job** (conditional)
|
||||||
|
- Runs after `e2e-tests` completes
|
||||||
|
- Updates the existing check for job named "run-e2e-tests" from 'test-e2e.yml' workflow
|
||||||
|
- Sets the conclusion based on `run-e2e-tests` result:
|
||||||
|
- ✅ **Success** if tests pass
|
||||||
|
- ❌ **Failure** if tests fail
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Workflow Not Running
|
||||||
|
- **Check**: Ensure you've approved the workflow to run
|
||||||
|
- **Action**: Click "Approve workflow to run" in the PR checks tab
|
||||||
|
|
||||||
|
### SHA Not Found
|
||||||
|
- **Check**: Verify the SHA exists in the PR commits
|
||||||
|
- **Action**: Use `git log --oneline` to find valid commit SHAs
|
||||||
|
|
||||||
|
### Dispatch Not Triggering
|
||||||
|
- **Check**: Ensure the slash command format is correct
|
||||||
|
- **Action**: Use exact format: `/ok-to-test sha=<sha>`
|
||||||
|
|
||||||
|
### Check Runs Not Updating
|
||||||
|
- **Note**: The fork workflow updates the existing "E2E Tests [reusable]" check run
|
||||||
|
- **Behavior**: The same check run is updated with new results rather than creating a new one
|
||||||
|
|
||||||
|
## Security Notes
|
||||||
|
|
||||||
|
- Only users with **write** permissions can trigger dispatch commands
|
||||||
|
- The fork workflow runs in the main repository context, allowing it to update check runs
|
||||||
|
- Manual approval is required for external PR workflows
|
||||||
|
- External PRs are automatically detected and prevented from running tests automatically
|
||||||
|
|
||||||
|
## Workflow Files
|
||||||
|
|
||||||
|
- **`.github/workflows/ok-to-test.yml`** - Ok To Test - Slash command processor
|
||||||
|
- **`.github/workflows/test-e2e.yml`** - E2E Tests - Initial PR checks
|
||||||
|
- **`.github/workflows/test-e2e-fork.yml`** - E2E tests [fork] - Dispatch action handler
|
||||||
|
- **`.github/workflows/e2e-tests.yml`** - E2E Tests [reusable] - Reusable workflow for actual testing
|
||||||
|
|
||||||
|
## Testing Checklist
|
||||||
|
|
||||||
|
- [ ] PR created with failing check-external-pr check (for external PRs)
|
||||||
|
- [ ] Workflow approved to run (if required)
|
||||||
|
- [ ] Slash command posted with valid SHA
|
||||||
|
- [ ] Dispatch action triggered successfully
|
||||||
|
- [ ] `check-external-pr` check run updated with correct conclusion
|
@@ -1,20 +1,15 @@
|
|||||||
# Testing
|
# Testing
|
||||||
|
|
||||||
## Unit tests
|
## Unit & Integration tests
|
||||||
**When**: Pure Go logic, no Kubernetes apiserver or network.
|
**When**: Unit (pure Go) and integration (controller-runtime envtest).
|
||||||
**Where**: `internal/...`, `pkg/...`
|
**Where**: `internal/...`, `pkg/...`
|
||||||
**Add files in**: `*_test.go` next to the code.
|
**Add files in**: `*_test.go` next to the code.
|
||||||
**Run**: `make test`
|
**Run**: `make test`
|
||||||
|
|
||||||
## Integration tests (envtest)
|
|
||||||
**When**: Controller/reconciler behavior against a mocked kubernetes cluster.
|
|
||||||
**Where**: `internal/controller/...`
|
|
||||||
**Framework**: controller-runtime’s `envtest`.
|
|
||||||
**Run**: `make test`
|
|
||||||
|
|
||||||
## E2E tests (kind)
|
## E2E tests (kind)
|
||||||
**When**: Full cluster behavior (CRDs, operator image, Connect/SA flows).
|
**When**: Full cluster behavior (CRDs, operator image, Connect/SA flows).
|
||||||
**Where**: `test/e2e/...`
|
**Where**: `test/e2e/...`
|
||||||
|
**Add files in**: `*_test.go` next to the code.
|
||||||
**Framework**: Ginkgo + `pkg/testhelper`.
|
**Framework**: Ginkgo + `pkg/testhelper`.
|
||||||
|
|
||||||
**Local prep**:
|
**Local prep**:
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
package system
|
package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -51,3 +53,42 @@ func GetProjectRoot() (string, error) {
|
|||||||
dir = parent
|
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()
|
||||||
|
}
|
||||||
|
@@ -60,39 +60,42 @@ var _ = Describe("Onepassword Operator e2e", Ordered, func() {
|
|||||||
kubeClient.Secret("onepassword-service-account-token").CreateFromEnvVar(ctx, "OP_SERVICE_ACCOUNT_TOKEN")
|
kubeClient.Secret("onepassword-service-account-token").CreateFromEnvVar(ctx, "OP_SERVICE_ACCOUNT_TOKEN")
|
||||||
kubeClient.Secret("onepassword-service-account-token").CheckIfExists(ctx)
|
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")
|
_, err = system.Run("make", "deploy")
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
kubeClient.Pod(map[string]string{"name": "onepassword-connect-operator"}).WaitingForRunningPod(ctx)
|
kubeClient.Pod(map[string]string{"name": "onepassword-connect-operator"}).WaitingForRunningPod(ctx)
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("Use the operator with Connect", func() {
|
Context("Use the operator with Service Account", func() {
|
||||||
BeforeAll(func() {
|
|
||||||
kubeClient.Pod(map[string]string{"app": "onepassword-connect"}).WaitingForRunningPod(ctx)
|
|
||||||
})
|
|
||||||
|
|
||||||
runCommonTestCases(ctx)
|
runCommonTestCases(ctx)
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("Use the operator with Service Account", func() {
|
Context("Use the operator with Connect", func() {
|
||||||
BeforeAll(func() {
|
BeforeAll(func() {
|
||||||
kubeClient.Deployment("onepassword-connect-operator").PatchEnvVars(ctx, []corev1.EnvVar{
|
kubeClient.Deployment("onepassword-connect-operator").PatchEnvVars(ctx, []corev1.EnvVar{
|
||||||
{Name: "MANAGE_CONNECT", Value: "false"},
|
{Name: "MANAGE_CONNECT", Value: "true"},
|
||||||
|
{Name: "OP_CONNECT_HOST", Value: "http://onepassword-connect:8080"},
|
||||||
{
|
{
|
||||||
Name: "OP_SERVICE_ACCOUNT_TOKEN",
|
Name: "OP_CONNECT_TOKEN",
|
||||||
ValueFrom: &corev1.EnvVarSource{
|
ValueFrom: &corev1.EnvVarSource{
|
||||||
SecretKeyRef: &corev1.SecretKeySelector{
|
SecretKeyRef: &corev1.SecretKeySelector{
|
||||||
LocalObjectReference: corev1.LocalObjectReference{
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
Name: "onepassword-service-account-token",
|
Name: "onepassword-token",
|
||||||
},
|
},
|
||||||
Key: "token",
|
Key: "token",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, []string{"OP_CONNECT_HOST", "OP_CONNECT_TOKEN"})
|
}, []string{"OP_SERVICE_ACCOUNT_TOKEN"})
|
||||||
|
|
||||||
kubeClient.Secret("login").Delete(ctx) // remove secret crated in previous test
|
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-ignored").Delete(ctx) // remove secret crated in previous test
|
||||||
kubeClient.Secret("secret-for-update").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(ctx)
|
||||||
@@ -101,13 +104,13 @@ var _ = Describe("Onepassword Operator e2e", Ordered, func() {
|
|||||||
|
|
||||||
// runCommonTestCases contains test cases that are common to both Connect and Service Account authentication methods.
|
// runCommonTestCases contains test cases that are common to both Connect and Service Account authentication methods.
|
||||||
func runCommonTestCases(ctx context.Context) {
|
func runCommonTestCases(ctx context.Context) {
|
||||||
It("Should create secret from manifest file", func() {
|
It("Should create kubernetes secret from manifest file", func() {
|
||||||
By("Creating secret `login` from 1Password item")
|
By("Creating secret `login` from 1Password item")
|
||||||
kubeClient.ApplyOnePasswordItem(ctx, "secret.yaml")
|
kubeClient.ApplyOnePasswordItem(ctx, "secret.yaml")
|
||||||
kubeClient.Secret("login").CheckIfExists(ctx)
|
kubeClient.Secret("login").CheckIfExists(ctx)
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Secret is updated after POOLING_INTERVAL", func() {
|
It("Kubernetes secret is updated after POOLING_INTERVAL, when updating item in 1Password", func() {
|
||||||
itemName := "secret-for-update"
|
itemName := "secret-for-update"
|
||||||
secretName := itemName
|
secretName := itemName
|
||||||
|
|
||||||
@@ -139,7 +142,7 @@ func runCommonTestCases(ctx context.Context) {
|
|||||||
}, defaults.E2ETimeout, defaults.E2EInterval).Should(Succeed())
|
}, defaults.E2ETimeout, defaults.E2EInterval).Should(Succeed())
|
||||||
})
|
})
|
||||||
|
|
||||||
It("1Password item with `ignore-secret` doesn't pull updates to kubernetes secret", func() {
|
It("1Password item with `ignore-secret` tag doesn't pull updates to kubernetes secret", func() {
|
||||||
itemName := "secret-ignored"
|
itemName := "secret-ignored"
|
||||||
secretName := itemName
|
secretName := itemName
|
||||||
|
|
||||||
|
99
test/e2e/manifests/manager.yaml
Normal file
99
test/e2e/manifests/manager.yaml
Normal 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
|
Reference in New Issue
Block a user