diff --git a/.github/workflows/acceptance-test.yml b/.github/workflows/acceptance-test.yml deleted file mode 100644 index aaa4924..0000000 --- a/.github/workflows/acceptance-test.yml +++ /dev/null @@ -1,131 +0,0 @@ -name: Acceptance test - -on: - workflow_call: - inputs: - secret: - required: true - type: string - secret-in-section: - required: true - type: string - multiline-secret: - required: true - type: string - export-env: - required: true - type: boolean - version: - required: false - type: string - default: "latest" - os: - required: true - type: string - default: "ubuntu-latest" - auth: - required: true - type: string - -jobs: - acceptance-test: - runs-on: ${{ inputs.os }} - steps: - - name: Base checkout - uses: actions/checkout@v4 - if: | - github.event_name != 'repository_dispatch' && - ( - github.ref == 'refs/heads/main' || - ( - github.event_name == 'pull_request' && - github.event.pull_request.head.repo.full_name == github.repository - ) - ) - - - name: Fork based /ok-to-test checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.event.client_payload.pull_request.head.sha }} - 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 - ) - - - name: Launch 1Password Connect instance - if: ${{ inputs.auth == 'connect' }} - env: - OP_CONNECT_CREDENTIALS: ${{ secrets.OP_CONNECT_CREDENTIALS }} - run: | - echo "$OP_CONNECT_CREDENTIALS" > 1password-credentials.json - docker compose -f tests/fixtures/docker-compose.yml up -d && sleep 10 - - - name: Configure Service account - if: ${{ inputs.auth == 'service-account' }} - uses: ./configure - with: - service-account-token: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} - - - name: Verify Service Account env var is set - if: ${{ inputs.auth == 'service-account' }} - shell: bash - run: | - if [ -z "${OP_SERVICE_ACCOUNT_TOKEN}" ]; then - echo "OP_SERVICE_ACCOUNT_TOKEN environment variable is not set" >&2 - exit 1 - fi - - - name: Configure 1Password Connect - if: ${{ inputs.auth == 'connect' }} - uses: ./configure # 1password/load-secrets-action/configure@ - with: - connect-host: http://localhost:8080 - connect-token: ${{ secrets.OP_CONNECT_TOKEN }} - - - name: Verify Connect env vars are set - if: ${{ inputs.auth == 'connect' }} - run: | - if [ -z "$OP_CONNECT_HOST" ] || [ -z "$OP_CONNECT_TOKEN" ]; then - echo "OP_CONNECT_HOST or OP_CONNECT_TOKEN environment variables are not set" >&2 - exit 1 - fi - - - name: Load secrets - id: load_secrets - uses: ./ # 1password/load-secrets-action@ - with: - version: ${{ inputs.version }} - export-env: ${{ inputs.export-env }} - env: - SECRET: ${{ inputs.secret }} - SECRET_IN_SECTION: ${{ inputs.secret-in-section }} - MULTILINE_SECRET: ${{ inputs.multiline-secret }} - OP_ENV_FILE: ./tests/.env.tpl - - - name: Assert test secret values [step output] - if: ${{ !inputs.export-env }} - env: - SECRET: ${{ steps.load_secrets.outputs.SECRET }} - SECRET_IN_SECTION: ${{ steps.load_secrets.outputs.SECRET_IN_SECTION }} - MULTILINE_SECRET: ${{ steps.load_secrets.outputs.MULTILINE_SECRET }} - FILE_SECRET: ${{ steps.load_secrets.outputs.FILE_SECRET }} - FILE_SECRET_IN_SECTION: ${{ steps.load_secrets.outputs.FILE_SECRET_IN_SECTION }} - FILE_MULTILINE_SECRET: ${{ steps.load_secrets.outputs.FILE_MULTILINE_SECRET }} - run: ./tests/assert-env-set.sh - - - name: Assert test secret values [exported env] - if: ${{ inputs.export-env }} - run: ./tests/assert-env-set.sh - - - name: Remove secrets [exported env] - if: ${{ inputs.export-env }} - uses: ./ # 1password/load-secrets-action@ - with: - unset-previous: true - - - name: Assert removed secrets [exported env] - if: ${{ inputs.export-env }} - run: ./tests/assert-env-unset.sh diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml new file mode 100644 index 0000000..ee191e4 --- /dev/null +++ b/.github/workflows/e2e-tests.yml @@ -0,0 +1,159 @@ +name: E2E Tests + +on: + # For local testing with: act push -W .github/workflows/e2e-tests.yml + push: + branches-ignore: + - "**" # Never runs on GitHub, only locally with act + + # For test.yml to call this workflow + workflow_call: + secrets: + OP_CONNECT_CREDENTIALS: + required: true + OP_CONNECT_TOKEN: + required: true + OP_SERVICE_ACCOUNT_TOKEN: + required: true + VAULT: + description: "1Password vault name or UUID" + required: true + +jobs: + test-service-account: + name: Service Account (${{ matrix.os }}, ${{ matrix.version }}, export-env=${{ matrix.export-env }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + version: [latest, 2.30.0] + export-env: [true, false] + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Generate .env.tpl + shell: bash + run: | + echo "FILE_SECRET=op://${{ secrets.VAULT }}/test-secret/password" > tests/.env.tpl + echo "FILE_SECRET_IN_SECTION=op://${{ secrets.VAULT }}/test-secret/test-section/password" >> tests/.env.tpl + echo "FILE_MULTILINE_SECRET=op://${{ secrets.VAULT }}/multiline-secret/notesPlain" >> tests/.env.tpl + + - name: Configure Service account + uses: ./configure + with: + service-account-token: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + + - name: Load secrets + id: load_secrets + uses: ./ + with: + version: ${{ matrix.version }} + export-env: ${{ matrix.export-env }} + env: + SECRET: op://${{ secrets.VAULT }}/test-secret/password + SECRET_IN_SECTION: op://${{ secrets.VAULT }}/test-secret/test-section/password + MULTILINE_SECRET: op://${{ secrets.VAULT }}/multiline-secret/notesPlain + OP_ENV_FILE: ./tests/.env.tpl + + - name: Assert test secret values [step output] + if: ${{ !matrix.export-env }} + shell: bash + env: + SECRET: ${{ steps.load_secrets.outputs.SECRET }} + SECRET_IN_SECTION: ${{ steps.load_secrets.outputs.SECRET_IN_SECTION }} + MULTILINE_SECRET: ${{ steps.load_secrets.outputs.MULTILINE_SECRET }} + FILE_SECRET: ${{ steps.load_secrets.outputs.FILE_SECRET }} + FILE_SECRET_IN_SECTION: ${{ steps.load_secrets.outputs.FILE_SECRET_IN_SECTION }} + FILE_MULTILINE_SECRET: ${{ steps.load_secrets.outputs.FILE_MULTILINE_SECRET }} + run: ./tests/assert-env-set.sh + + - name: Assert test secret values [exported env] + if: ${{ matrix.export-env }} + shell: bash + run: ./tests/assert-env-set.sh + + - name: Remove secrets [exported env] + if: ${{ matrix.export-env }} + uses: ./ + with: + unset-previous: true + + - name: Assert removed secrets [exported env] + if: ${{ matrix.export-env }} + shell: bash + run: ./tests/assert-env-unset.sh + + test-connect: + name: Connect (ubuntu-latest, ${{ matrix.version }}, export-env=${{ matrix.export-env }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + version: [latest, 2.30.0] + export-env: [true, false] + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Generate .env.tpl + run: | + mkdir -p tests + echo "FILE_SECRET=op://${{ secrets.VAULT }}/test-secret/password" > tests/.env.tpl + echo "FILE_SECRET_IN_SECTION=op://${{ secrets.VAULT }}/test-secret/test-section/password" >> tests/.env.tpl + echo "FILE_MULTILINE_SECRET=op://${{ secrets.VAULT }}/multiline-secret/notesPlain" >> tests/.env.tpl + + - name: Launch 1Password Connect instance + env: + OP_CONNECT_CREDENTIALS: ${{ secrets.OP_CONNECT_CREDENTIALS }} + run: | + echo "$OP_CONNECT_CREDENTIALS" > 1password-credentials.json + docker compose -f tests/fixtures/docker-compose.yml up -d && sleep 10 + + - name: Configure 1Password Connect + uses: ./configure + with: + connect-host: http://localhost:8080 + connect-token: ${{ secrets.OP_CONNECT_TOKEN }} + + - name: Load secrets + id: load_secrets + uses: ./ + with: + version: ${{ matrix.version }} + export-env: ${{ matrix.export-env }} + env: + SECRET: op://${{ secrets.VAULT }}/test-secret/password + SECRET_IN_SECTION: op://${{ secrets.VAULT }}/test-secret/test-section/password + MULTILINE_SECRET: op://${{ secrets.VAULT }}/multiline-secret/notesPlain + OP_ENV_FILE: ./tests/.env.tpl + + - name: Assert test secret values [step output] + if: ${{ !matrix.export-env }} + env: + SECRET: ${{ steps.load_secrets.outputs.SECRET }} + SECRET_IN_SECTION: ${{ steps.load_secrets.outputs.SECRET_IN_SECTION }} + MULTILINE_SECRET: ${{ steps.load_secrets.outputs.MULTILINE_SECRET }} + FILE_SECRET: ${{ steps.load_secrets.outputs.FILE_SECRET }} + FILE_SECRET_IN_SECTION: ${{ steps.load_secrets.outputs.FILE_SECRET_IN_SECTION }} + FILE_MULTILINE_SECRET: ${{ steps.load_secrets.outputs.FILE_MULTILINE_SECRET }} + run: ./tests/assert-env-set.sh + + - name: Assert test secret values [exported env] + if: ${{ matrix.export-env }} + run: ./tests/assert-env-set.sh + + - name: Remove secrets [exported env] + if: ${{ matrix.export-env }} + uses: ./ + with: + unset-previous: true + + - name: Assert removed secrets [exported env] + if: ${{ matrix.export-env }} + run: ./tests/assert-env-unset.sh diff --git a/.github/workflows/ok-to-test.yml b/.github/workflows/ok-to-test.yml index e1d4060..e2f8584 100644 --- a/.github/workflows/ok-to-test.yml +++ b/.github/workflows/ok-to-test.yml @@ -1,4 +1,4 @@ -# If someone with write access comments "/ok-to-test" on a pull request, emit a repository_dispatch event +# Write comments "/ok-to-test sha=" on a pull request. This will emit a repository_dispatch event. name: Ok To Test on: @@ -15,7 +15,7 @@ jobs: if: ${{ github.event.issue.pull_request }} steps: - name: Slash Command Dispatch - uses: peter-evans/slash-command-dispatch@v3 + 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 }} diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml new file mode 100644 index 0000000..ce1a011 --- /dev/null +++ b/.github/workflows/test-e2e.yml @@ -0,0 +1,107 @@ +name: E2E Tests + +on: + push: + branches: [main] + pull_request: + repository_dispatch: + types: [ok-to-test-command] + +concurrency: + group: >- + ${{ github.event_name == 'pull_request' && + format('e2e-{0}', github.event.pull_request.head.ref) || + format('e2e-{0}', github.ref) }} + cancel-in-progress: true + +jobs: + check-external-pr: + runs-on: ubuntu-latest + outputs: + condition: ${{ steps.check.outputs.condition }} + steps: + - name: Check if PR is from external contributor + id: check + run: | + echo "Event name: ${{ github.event_name }}" + echo "Repository: ${{ github.repository }}" + + if [ "${{ github.event_name }}" == "pull_request" ]; then + # For pull_request events, check if PR is from external fork + echo "PR head repo: ${{ github.event.pull_request.head.repo.full_name }}" + if [ "${{ github.actor }}" == "dependabot[bot]" ]; then + echo "condition=skip" >> $GITHUB_OUTPUT + echo "Setting condition=skip (Dependabot PR)" + elif [ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]; then + echo "condition=skip" >> $GITHUB_OUTPUT + echo "Setting condition=skip (external fork PR creation)" + else + echo "condition=pr-creation-maintainer" >> $GITHUB_OUTPUT + echo "Setting condition=pr-creation-maintainer (internal PR creation)" + fi + elif [ "${{ github.event_name }}" == "repository_dispatch" ]; then + # For repository_dispatch events (ok-to-test), check if sha matches + SHA_PARAM="${{ github.event.client_payload.slash_command.args.named.sha }}" + PR_HEAD_SHA="${{ github.event.client_payload.pull_request.head.sha }}" + + echo "Checking dispatch event conditions..." + echo "SHA from command: $SHA_PARAM" + echo "PR head SHA: $PR_HEAD_SHA" + + if [ -n "$SHA_PARAM" ] && [[ "$PR_HEAD_SHA" == *"$SHA_PARAM"* ]]; then + echo "condition=dispatch-event" >> $GITHUB_OUTPUT + echo "Setting condition=dispatch-event (sha matches)" + else + echo "condition=skip" >> $GITHUB_OUTPUT + echo "Setting condition=skip (sha does not match or empty)" + fi + elif [ "${{ github.event_name }}" == "push" ] && [ "${{ github.ref_name }}" == "main" ]; then + echo "condition=push-to-main" >> $GITHUB_OUTPUT + echo "Setting condition=push-to-main (push to main)" + else + # Unknown event type + echo "condition=skip" >> $GITHUB_OUTPUT + echo "Setting condition=skip (unknown event type: ${{ github.event_name }})" + fi + + e2e: + needs: check-external-pr + if: | + (needs.check-external-pr.outputs.condition == 'pr-creation-maintainer') + || + (needs.check-external-pr.outputs.condition == 'dispatch-event') + || + needs.check-external-pr.outputs.condition == 'push-to-main' + uses: ./.github/workflows/e2e-tests.yml + secrets: + OP_CONNECT_CREDENTIALS: ${{ secrets.OP_CONNECT_CREDENTIALS }} + OP_CONNECT_TOKEN: ${{ secrets.OP_CONNECT_TOKEN }} + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + VAULT: ${{ secrets.VAULT }} + + # Post comment on fork PRs after /ok-to-test + comment-pr: + needs: [check-external-pr, e2e] + runs-on: ubuntu-latest + if: always() && needs.check-external-pr.outputs.condition == 'dispatch-event' + permissions: + pull-requests: write + steps: + - name: Create URL to the run output + id: vars + run: echo "run-url=https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" >> $GITHUB_OUTPUT + + - name: Create comment on PR + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.client_payload.pull_request.number }} + body: | + ${{ + needs.e2e.result == 'success' && '✅ E2E tests passed.' || + needs.e2e.result == 'failure' && '❌ E2E tests failed.' || + '⚠️ E2E tests completed.' + }} + + [View test run output][1] + + [1]: ${{ steps.vars.outputs.run-url }} diff --git a/.github/workflows/test-fork.yml b/.github/workflows/test-fork.yml deleted file mode 100644 index cdfa906..0000000 --- a/.github/workflows/test-fork.yml +++ /dev/null @@ -1,92 +0,0 @@ -on: - repository_dispatch: - types: [ok-to-test-command] -name: Run acceptance tests [fork] - -jobs: - test-with-output-secrets: - 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 - ) - uses: 1password/load-secrets-action/.github/workflows/acceptance-test.yml@main - secrets: inherit - with: - secret: op://acceptance-tests/test-secret/password - secret-in-section: op://acceptance-tests/test-secret/test-section/password - multiline-secret: op://acceptance-tests/multiline-secret/notesPlain - export-env: false - test-with-export-env: - 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 - ) - uses: 1password/load-secrets-action/.github/workflows/acceptance-test.yml@main - secrets: inherit - with: - secret: op://acceptance-tests/test-secret/password - secret-in-section: op://acceptance-tests/test-secret/test-section/password - multiline-secret: op://acceptance-tests/multiline-secret/notesPlain - export-env: true - test-references-with-ids: - 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 - ) - uses: 1password/load-secrets-action/.github/workflows/acceptance-test.yml@main - secrets: inherit - with: - secret: op://v5pz6venw4roosmkzdq2nhpv6u/hrgkzhrlvscomepxlgafb2m3ca/password - secret-in-section: op://v5pz6venw4roosmkzdq2nhpv6u/hrgkzhrlvscomepxlgafb2m3ca/Section_tco6nsqycj6jcbyx63h5isxcny/doxu3mhkozcznnk5vjrkpdqayy - multiline-secret: op://v5pz6venw4roosmkzdq2nhpv6u/ghtz3jvcc6dqmzc53d3r3eskge/notesPlain - export-env: false - update-checks: - # required permissions for updating the status of the pull request checks - permissions: - pull-requests: write - checks: write - runs-on: ubuntu-latest - if: ${{ always() }} - strategy: - matrix: - job-name: - [ - test-with-output-secrets, - test-with-export-env, - test-references-with-ids, - ] - needs: - [test-with-output-secrets, test-with-export-env, test-references-with-ids] - steps: - - uses: actions/github-script@v6 - env: - job: ${{ matrix.job-name }} - ref: ${{ github.event.client_payload.pull_request.head.sha }} - conclusion: ${{ needs[format('{0}', matrix.job-name )].result }} - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { data: checks } = await github.rest.checks.listForRef({ - ...context.repo, - ref: process.env.ref - }); - - const check = checks.check_runs.filter(c => c.name === process.env.job); - - const { data: result } = await github.rest.checks.update({ - ...context.repo, - check_run_id: check[0].id, - status: 'completed', - conclusion: process.env.conclusion - }); - - return result; diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 772ac00..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,102 +0,0 @@ -on: - push: - branches: [main] - pull_request: - types: [opened, synchronize, reopened] - branches: ["**"] # run for PRs targeting any branch (main and others) -name: Run acceptance tests - -jobs: - unit-tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 20 - - run: npm ci - - run: npm test - - test-with-output-secrets: - if: | - github.ref == 'refs/heads/main' || - ( - github.event_name == 'pull_request' && - github.event.pull_request.head.repo.full_name == github.repository - ) - uses: 1password/load-secrets-action/.github/workflows/acceptance-test.yml@main - secrets: inherit - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - version: [latest, latest-beta, 2.30.0, 2.30.0-beta.03] - auth: [connect, service-account] - exclude: - - os: macos-latest - auth: connect - - os: windows-latest - auth: connect - with: - os: ${{ matrix.os }} - version: ${{ matrix.version }} - auth: ${{ matrix.auth }} - secret: op://acceptance-tests/test-secret/password - secret-in-section: op://acceptance-tests/test-secret/test-section/password - multiline-secret: op://acceptance-tests/multiline-secret/notesPlain - export-env: false - - test-with-export-env: - if: | - github.ref == 'refs/heads/main' || - ( - github.event_name == 'pull_request' && - github.event.pull_request.head.repo.full_name == github.repository - ) - uses: 1password/load-secrets-action/.github/workflows/acceptance-test.yml@main - secrets: inherit - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - version: [latest, latest-beta, 2.30.0, 2.30.0-beta.03] - auth: [connect, service-account] - exclude: - - os: macos-latest - auth: connect - - os: windows-latest - auth: connect - with: - os: ${{ matrix.os }} - version: ${{ matrix.version }} - auth: ${{ matrix.auth }} - secret: op://acceptance-tests/test-secret/password - secret-in-section: op://acceptance-tests/test-secret/test-section/password - multiline-secret: op://acceptance-tests/multiline-secret/notesPlain - export-env: true - - test-references-with-ids: - if: | - github.ref == 'refs/heads/main' || - ( - github.event_name == 'pull_request' && - github.event.pull_request.head.repo.full_name == github.repository - ) - uses: 1password/load-secrets-action/.github/workflows/acceptance-test.yml@main - secrets: inherit - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - version: [latest, latest-beta, 2.30.0, 2.30.0-beta.03] - auth: [connect, service-account] - exclude: - - os: macos-latest - auth: connect - - os: windows-latest - auth: connect - with: - os: ${{ matrix.os }} - version: ${{ matrix.version }} - auth: ${{ matrix.auth }} - secret: op://v5pz6venw4roosmkzdq2nhpv6u/hrgkzhrlvscomepxlgafb2m3ca/password - secret-in-section: op://v5pz6venw4roosmkzdq2nhpv6u/hrgkzhrlvscomepxlgafb2m3ca/Section_tco6nsqycj6jcbyx63h5isxcny/doxu3mhkozcznnk5vjrkpdqayy - multiline-secret: op://v5pz6venw4roosmkzdq2nhpv6u/ghtz3jvcc6dqmzc53d3r3eskge/notesPlain - export-env: false