Compare commits
1 Commits
v4
...
vzt/update
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
551f03ee9c |
116
.github/workflows/acceptance-test.yml
vendored
Normal file
116
.github/workflows/acceptance-test.yml
vendored
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
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@<version>
|
||||||
|
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@<version>
|
||||||
|
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 }}
|
||||||
|
- 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 }}
|
||||||
|
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@<version>
|
||||||
|
with:
|
||||||
|
unset-previous: true
|
||||||
|
- name: Assert removed secrets [exported env]
|
||||||
|
if: ${{ inputs.export-env }}
|
||||||
|
run: ./tests/assert-env-unset.sh
|
||||||
246
.github/workflows/e2e-tests.yml
vendored
246
.github/workflows/e2e-tests.yml
vendored
@@ -1,246 +0,0 @@
|
|||||||
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:
|
|
||||||
inputs:
|
|
||||||
ref:
|
|
||||||
description: "Git ref to checkout"
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
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: true
|
|
||||||
max-parallel: 4
|
|
||||||
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
|
|
||||||
ref: ${{ inputs.ref }}
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: 24
|
|
||||||
cache: npm
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: npm ci
|
|
||||||
|
|
||||||
- name: Build actions
|
|
||||||
run: npm run build:all
|
|
||||||
|
|
||||||
- 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
|
|
||||||
echo "FILE_WEBSITE=op://${{ secrets.VAULT }}/test-secret/website" >> tests/.env.tpl
|
|
||||||
echo "FILE_TEST_SSH_KEY=op://${{ secrets.VAULT }}/test-ssh-key/private key" >> tests/.env.tpl
|
|
||||||
echo "FILE_TEST_SSH_KEY_OPENSSH=op://${{ secrets.VAULT }}/test-ssh-key/private key?ssh-format=openssh" >> 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
|
|
||||||
WEBSITE: op://${{ secrets.VAULT }}/test-secret/website
|
|
||||||
TEST_SSH_KEY: op://${{ secrets.VAULT }}/test-ssh-key/private key
|
|
||||||
TEST_SSH_KEY_OPENSSH: "op://${{ secrets.VAULT }}/test-ssh-key/private key?ssh-format=openssh"
|
|
||||||
OP_ENV_FILE: ./tests/.env.tpl
|
|
||||||
|
|
||||||
- name: Assert test secret values [step output]
|
|
||||||
if: ${{ !matrix.export-env }}
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
ASSERT_WEBSITE: "true"
|
|
||||||
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 }}
|
|
||||||
WEBSITE: ${{ steps.load_secrets.outputs.WEBSITE }}
|
|
||||||
FILE_WEBSITE: ${{ steps.load_secrets.outputs.FILE_WEBSITE }}
|
|
||||||
TEST_SSH_KEY: ${{ steps.load_secrets.outputs.TEST_SSH_KEY }}
|
|
||||||
FILE_TEST_SSH_KEY: ${{ steps.load_secrets.outputs.FILE_TEST_SSH_KEY }}
|
|
||||||
TEST_SSH_KEY_OPENSSH: ${{ steps.load_secrets.outputs.TEST_SSH_KEY_OPENSSH }}
|
|
||||||
FILE_TEST_SSH_KEY_OPENSSH: ${{ steps.load_secrets.outputs.FILE_TEST_SSH_KEY_OPENSSH }}
|
|
||||||
run: ./tests/assert-env-set.sh
|
|
||||||
|
|
||||||
- name: Assert SSH key env vars [step output]
|
|
||||||
if: ${{ !matrix.export-env }}
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
TEST_SSH_KEY: ${{ steps.load_secrets.outputs.TEST_SSH_KEY }}
|
|
||||||
FILE_TEST_SSH_KEY: ${{ steps.load_secrets.outputs.FILE_TEST_SSH_KEY }}
|
|
||||||
TEST_SSH_KEY_OPENSSH: ${{ steps.load_secrets.outputs.TEST_SSH_KEY_OPENSSH }}
|
|
||||||
FILE_TEST_SSH_KEY_OPENSSH: ${{ steps.load_secrets.outputs.FILE_TEST_SSH_KEY_OPENSSH }}
|
|
||||||
run: ./tests/assert-ssh-keys-set.sh
|
|
||||||
|
|
||||||
- name: Assert test secret values [exported env]
|
|
||||||
if: ${{ matrix.export-env }}
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
ASSERT_WEBSITE: "true"
|
|
||||||
run: ./tests/assert-env-set.sh
|
|
||||||
|
|
||||||
- name: Assert SSH key env vars [exported env]
|
|
||||||
if: ${{ matrix.export-env }}
|
|
||||||
shell: bash
|
|
||||||
run: ./tests/assert-ssh-keys-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: true
|
|
||||||
matrix:
|
|
||||||
version: [latest, 2.30.0]
|
|
||||||
export-env: [true, false]
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
ref: ${{ inputs.ref }}
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: 24
|
|
||||||
cache: npm
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: npm ci
|
|
||||||
|
|
||||||
- name: Build actions
|
|
||||||
run: npm run build:all
|
|
||||||
|
|
||||||
- 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
|
|
||||||
echo "FILE_TEST_SSH_KEY=op://${{ secrets.VAULT }}/test-ssh-key/private key" >> tests/.env.tpl
|
|
||||||
echo "FILE_TEST_SSH_KEY_OPENSSH=op://${{ secrets.VAULT }}/test-ssh-key/private key?ssh-format=openssh" >> 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
|
|
||||||
timeout 60 bash -c 'until curl -sf http://localhost:8080/health >/dev/null 2>&1; do sleep 2; done'
|
|
||||||
|
|
||||||
- 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
|
|
||||||
TEST_SSH_KEY: op://${{ secrets.VAULT }}/test-ssh-key/private key
|
|
||||||
TEST_SSH_KEY_OPENSSH: "op://${{ secrets.VAULT }}/test-ssh-key/private key?ssh-format=openssh"
|
|
||||||
OP_ENV_FILE: ./tests/.env.tpl
|
|
||||||
|
|
||||||
- name: Assert test secret values [step output]
|
|
||||||
if: ${{ !matrix.export-env }}
|
|
||||||
env:
|
|
||||||
ASSERT_WEBSITE: "false"
|
|
||||||
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 }}
|
|
||||||
TEST_SSH_KEY: ${{ steps.load_secrets.outputs.TEST_SSH_KEY }}
|
|
||||||
FILE_TEST_SSH_KEY: ${{ steps.load_secrets.outputs.FILE_TEST_SSH_KEY }}
|
|
||||||
TEST_SSH_KEY_OPENSSH: ${{ steps.load_secrets.outputs.TEST_SSH_KEY_OPENSSH }}
|
|
||||||
FILE_TEST_SSH_KEY_OPENSSH: ${{ steps.load_secrets.outputs.FILE_TEST_SSH_KEY_OPENSSH }}
|
|
||||||
run: ./tests/assert-env-set.sh
|
|
||||||
|
|
||||||
- name: Assert SSH key env vars [step output]
|
|
||||||
if: ${{ !matrix.export-env }}
|
|
||||||
env:
|
|
||||||
TEST_SSH_KEY: ${{ steps.load_secrets.outputs.TEST_SSH_KEY }}
|
|
||||||
FILE_TEST_SSH_KEY: ${{ steps.load_secrets.outputs.FILE_TEST_SSH_KEY }}
|
|
||||||
TEST_SSH_KEY_OPENSSH: ${{ steps.load_secrets.outputs.TEST_SSH_KEY_OPENSSH }}
|
|
||||||
FILE_TEST_SSH_KEY_OPENSSH: ${{ steps.load_secrets.outputs.FILE_TEST_SSH_KEY_OPENSSH }}
|
|
||||||
run: ./tests/assert-ssh-keys-set.sh
|
|
||||||
|
|
||||||
- name: Assert test secret values [exported env]
|
|
||||||
if: ${{ matrix.export-env }}
|
|
||||||
env:
|
|
||||||
ASSERT_WEBSITE: "false"
|
|
||||||
run: ./tests/assert-env-set.sh
|
|
||||||
|
|
||||||
- name: Assert SSH key env vars [exported env]
|
|
||||||
if: ${{ matrix.export-env }}
|
|
||||||
run: ./tests/assert-ssh-keys-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
|
|
||||||
@@ -1,36 +1,29 @@
|
|||||||
name: Lint and Test
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
pull_request:
|
pull_request:
|
||||||
|
name: Lint
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint-and-test:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Run ShellCheck
|
- name: Run ShellCheck
|
||||||
uses: ludeeus/action-shellcheck@2.0.0
|
uses: ludeeus/action-shellcheck@2.0.0
|
||||||
with:
|
with:
|
||||||
ignore_paths: >-
|
ignore_paths: >-
|
||||||
.husky
|
.husky
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
id: setup-node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 20
|
||||||
cache: npm
|
cache: npm
|
||||||
|
- name: Install Dependencies
|
||||||
- name: Install dependencies
|
id: install
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: Check formatting
|
- name: Check formatting
|
||||||
run: npm run format:check
|
run: npm run format:check
|
||||||
|
|
||||||
- name: Check lint
|
- name: Check lint
|
||||||
run: npm run lint
|
run: npm run lint
|
||||||
|
|
||||||
- name: Run unit tests
|
|
||||||
run: npm test
|
|
||||||
4
.github/workflows/ok-to-test.yml
vendored
4
.github/workflows/ok-to-test.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
# Write comments "/ok-to-test sha=<hash>" on a pull request. This will emit a repository_dispatch event.
|
# If someone with write access comments "/ok-to-test" on a pull request, emit a repository_dispatch event
|
||||||
name: Ok To Test
|
name: Ok To Test
|
||||||
|
|
||||||
on:
|
on:
|
||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
if: ${{ github.event.issue.pull_request }}
|
if: ${{ github.event.issue.pull_request }}
|
||||||
steps:
|
steps:
|
||||||
- name: Slash Command Dispatch
|
- name: Slash Command Dispatch
|
||||||
uses: peter-evans/slash-command-dispatch@v5
|
uses: peter-evans/slash-command-dispatch@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
reaction-token: ${{ secrets.GITHUB_TOKEN }}
|
reaction-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
120
.github/workflows/test-e2e.yml
vendored
120
.github/workflows/test-e2e.yml
vendored
@@ -1,120 +0,0 @@
|
|||||||
name: E2E Tests
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
paths-ignore: &ignore_paths
|
|
||||||
- "docs/**"
|
|
||||||
- "config/**"
|
|
||||||
- "*.md"
|
|
||||||
- ".gitignore"
|
|
||||||
- "LICENSE"
|
|
||||||
pull_request:
|
|
||||||
paths-ignore: *ignore_paths
|
|
||||||
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 }}
|
|
||||||
ref: ${{ steps.check.outputs.ref }}
|
|
||||||
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)"
|
|
||||||
echo "ref=${{ github.event.pull_request.head.sha }}" >> $GITHUB_OUTPUT
|
|
||||||
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)"
|
|
||||||
echo "ref=$PR_HEAD_SHA" >> $GITHUB_OUTPUT
|
|
||||||
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)"
|
|
||||||
echo "ref=${{ github.sha }}" >> $GITHUB_OUTPUT
|
|
||||||
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
|
|
||||||
with:
|
|
||||||
ref: ${{ needs.check-external-pr.outputs.ref }}
|
|
||||||
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@v5
|
|
||||||
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 }}
|
|
||||||
92
.github/workflows/test-fork.yml
vendored
Normal file
92
.github/workflows/test-fork.yml
vendored
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
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;
|
||||||
100
.github/workflows/test.yml
vendored
Normal file
100
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
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
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,4 +1,2 @@
|
|||||||
coverage/
|
coverage/
|
||||||
node_modules/
|
node_modules/
|
||||||
.idea/
|
|
||||||
1password-credentials.json
|
|
||||||
|
|||||||
21
README.md
21
README.md
@@ -35,11 +35,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Load secret
|
- name: Load secret
|
||||||
id: load_secrets
|
id: load_secrets
|
||||||
uses: 1password/load-secrets-action@v4
|
uses: 1password/load-secrets-action@v3
|
||||||
env:
|
env:
|
||||||
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
||||||
SECRET: op://app-cicd/hello-world/secret
|
SECRET: op://app-cicd/hello-world/secret
|
||||||
OP_ENV_FILE: "./path/to/.env.tpl" # see tests/.env.tpl for example
|
|
||||||
|
|
||||||
- name: Print masked secret
|
- name: Print masked secret
|
||||||
run: 'echo "Secret: ${{ steps.load_secrets.outputs.SECRET }}"'
|
run: 'echo "Secret: ${{ steps.load_secrets.outputs.SECRET }}"'
|
||||||
@@ -57,35 +56,19 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Load secret
|
- name: Load secret
|
||||||
uses: 1password/load-secrets-action@v4
|
uses: 1password/load-secrets-action@v3
|
||||||
with:
|
with:
|
||||||
# Export loaded secrets as environment variables
|
# Export loaded secrets as environment variables
|
||||||
export-env: true
|
export-env: true
|
||||||
env:
|
env:
|
||||||
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
||||||
SECRET: op://app-cicd/hello-world/secret
|
SECRET: op://app-cicd/hello-world/secret
|
||||||
OP_ENV_FILE: "./path/to/.env.tpl" # see tests/.env.tpl for example
|
|
||||||
|
|
||||||
- name: Print masked secret
|
- name: Print masked secret
|
||||||
run: 'echo "Secret: $SECRET"'
|
run: 'echo "Secret: $SECRET"'
|
||||||
# Prints: Secret: ***
|
# Prints: Secret: ***
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🔑 SSH Key Format
|
|
||||||
|
|
||||||
When loading SSH keys, you can specify the format using the `ssh-format` query parameter. This is useful when you need the private key in a specific format like OpenSSH.
|
|
||||||
|
|
||||||
```yml
|
|
||||||
- name: Load SSH key
|
|
||||||
uses: 1password/load-secrets-action@v4
|
|
||||||
env:
|
|
||||||
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
|
||||||
# Load SSH private key in OpenSSH format
|
|
||||||
SSH_PRIVATE_KEY: op://vault/item/private key?ssh-format=openssh
|
|
||||||
```
|
|
||||||
|
|
||||||
For more details on secret reference syntax, see the [1Password CLI documentation](https://developer.1password.com/docs/cli/secret-reference-syntax/#ssh-format-parameter).
|
|
||||||
|
|
||||||
## 💙 Community & Support
|
## 💙 Community & Support
|
||||||
|
|
||||||
- File an [issue](https://github.com/1Password/load-secrets-action/issues) for bugs and feature requests.
|
- File an [issue](https://github.com/1Password/load-secrets-action/issues) for bugs and feature requests.
|
||||||
|
|||||||
@@ -15,5 +15,5 @@ inputs:
|
|||||||
description: Specify which 1Password CLI version to install. Defaults to "latest".
|
description: Specify which 1Password CLI version to install. Defaults to "latest".
|
||||||
default: "latest"
|
default: "latest"
|
||||||
runs:
|
runs:
|
||||||
using: "node24"
|
using: "node20"
|
||||||
main: "dist/index.js"
|
main: "dist/index.js"
|
||||||
|
|||||||
@@ -10,11 +10,6 @@ const jestConfig = {
|
|||||||
rootDir: "../src/",
|
rootDir: "../src/",
|
||||||
testEnvironment: "node",
|
testEnvironment: "node",
|
||||||
testRegex: "(/__tests__/.*|(\\.|/)test)\\.ts",
|
testRegex: "(/__tests__/.*|(\\.|/)test)\\.ts",
|
||||||
moduleNameMapper: {
|
|
||||||
"^@actions/core$": "<rootDir>/__mocks__/actions-core.ts",
|
|
||||||
"^@actions/tool-cache$": "<rootDir>/__mocks__/actions-tool-cache.ts",
|
|
||||||
"^@actions/exec$": "<rootDir>/__mocks__/actions-exec.ts",
|
|
||||||
},
|
|
||||||
transform: {
|
transform: {
|
||||||
".ts": [
|
".ts": [
|
||||||
"ts-jest",
|
"ts-jest",
|
||||||
@@ -30,4 +25,4 @@ const jestConfig = {
|
|||||||
verbose: true,
|
verbose: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = jestConfig;
|
export default jestConfig;
|
||||||
|
|||||||
@@ -9,5 +9,5 @@ inputs:
|
|||||||
service-account-token:
|
service-account-token:
|
||||||
description: Your 1Password service account token
|
description: Your 1Password service account token
|
||||||
runs:
|
runs:
|
||||||
using: "node24"
|
using: "node20"
|
||||||
main: "dist/index.js"
|
main: "dist/index.js"
|
||||||
|
|||||||
32812
configure/dist/index.js
vendored
32812
configure/dist/index.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
|
|||||||
import * as core from "@actions/core";
|
const core = require("@actions/core");
|
||||||
|
|
||||||
const configure = () => {
|
const configure = () => {
|
||||||
const OP_CONNECT_HOST =
|
const OP_CONNECT_HOST =
|
||||||
|
|||||||
41048
dist/index.js
vendored
41048
dist/index.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,32 +0,0 @@
|
|||||||
# Fork PR Testing Guide
|
|
||||||
|
|
||||||
This document explains how testing works for external pull requests from forks.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The testing system consists of two main workflows:
|
|
||||||
|
|
||||||
1. **E2E Tests** (`test-e2e.yml`) - Runs automatically for internal PRs, need manual trigger on external PRs.
|
|
||||||
2. **Ok To Test** (`ok-to-test.yml`) - Dispatches `repository_dispatch` event when maintainer puts the `/ok-to-test sha=<commit hash>` comment in the forked PR thread.
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
### 1. PR is created by maintainer:
|
|
||||||
|
|
||||||
For the PR created by maintainer `E2E Test` workflow starts automatically. The PR check will reflect the status of the job.
|
|
||||||
|
|
||||||
### 2. PR is created by external contributor:
|
|
||||||
|
|
||||||
For the PR created by external contributor `E2E Test` workflow **won't** start automatically.
|
|
||||||
Maintainer should make a sanity check of the changes and run it manually by:
|
|
||||||
|
|
||||||
1. Putting a comment `/ok-to-test sha=<latest commit hash>` in the PR thread.
|
|
||||||
2. `E2E Test` workflow starts.
|
|
||||||
3. After `E2E Test` workflow finishes, a comment with a link to the workflow, along with its status will be posted in the PR.
|
|
||||||
4. Maintainer can merge PR or request the changes based on the `E2E Test` results.
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
- Only users with **write** permissions can trigger the `/ok-to-test` command.
|
|
||||||
- External PRs are automatically detected and prevented from running e2e tests automatically.
|
|
||||||
- Running e2e test on the external PR is optional. Maintainer can merge PR without running it. Maintainer decides whether it's needed to run an E2E test.
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
# Local Testing Guide
|
|
||||||
|
|
||||||
This document explains how to run e2e tests locally using `act`.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
1. **Docker** installed and running
|
|
||||||
2. **act** installed ([install guide](https://github.com/nektos/act#installation))
|
|
||||||
```bash
|
|
||||||
brew install act # macOS
|
|
||||||
```
|
|
||||||
3. **1Password credentials** (see [Required Secrets](#required-secrets))
|
|
||||||
4. Build action
|
|
||||||
|
|
||||||
## Required env variables
|
|
||||||
|
|
||||||
| Secret | Description |
|
|
||||||
| -------------------------- | --------------------- |
|
|
||||||
| `OP_SERVICE_ACCOUNT_TOKEN` | Service Account token |
|
|
||||||
| `VAULT` | Vault name or UUID |
|
|
||||||
|
|
||||||
## Building Before Testing
|
|
||||||
|
|
||||||
If you've modified TypeScript code, rebuild before running E2E tests:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
### Run E2E tests using Service Account
|
|
||||||
|
|
||||||
```bash
|
|
||||||
act push -W .github/workflows/e2e-tests.yml \
|
|
||||||
-s OP_SERVICE_ACCOUNT_TOKEN="$OP_SERVICE_ACCOUNT_TOKEN" \
|
|
||||||
-s VAULT="$VAULT" \
|
|
||||||
-j test-service-account \
|
|
||||||
--matrix os:ubuntu-latest
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run unit tests
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm test
|
|
||||||
```
|
|
||||||
241
package-lock.json
generated
241
package-lock.json
generated
@@ -1,19 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "load-secrets-action",
|
"name": "load-secrets-action",
|
||||||
"version": "4.0.0",
|
"version": "3.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "load-secrets-action",
|
"name": "load-secrets-action",
|
||||||
"version": "4.0.0",
|
"version": "3.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@1password/op-js": "^0.1.11",
|
"@1password/op-js": "^0.1.11",
|
||||||
"@actions/core": "^3.0.0",
|
"@actions/core": "^1.10.1",
|
||||||
"@actions/exec": "^3.0.0",
|
"@actions/exec": "^1.1.1",
|
||||||
"@actions/tool-cache": "^4.0.0",
|
"op-cli-installer": "github:1Password/op-cli-installer#e6c1c758bc3339e5fe9b06255728039f688f73fa"
|
||||||
"dotenv": "^17.2.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@1password/eslint-config": "^4.3.1",
|
"@1password/eslint-config": "^4.3.1",
|
||||||
@@ -73,49 +72,58 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/core": {
|
"node_modules/@actions/core": {
|
||||||
"version": "3.0.0",
|
"version": "1.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz",
|
||||||
"integrity": "sha512-zYt6cz+ivnTmiT/ksRVriMBOiuoUpDCJJlZ5KPl2/FRdvwU3f7MPh9qftvbkXJThragzUZieit2nyHUyw53Seg==",
|
"integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/exec": "^3.0.0",
|
"@actions/exec": "^1.1.1",
|
||||||
"@actions/http-client": "^4.0.0"
|
"@actions/http-client": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/exec": {
|
"node_modules/@actions/exec": {
|
||||||
"version": "3.0.0",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
|
||||||
"integrity": "sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw==",
|
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/io": "^3.0.2"
|
"@actions/io": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/http-client": {
|
"node_modules/@actions/http-client": {
|
||||||
"version": "4.0.0",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz",
|
||||||
"integrity": "sha512-QuwPsgVMsD6qaPD57GLZi9sqzAZCtiJT8kVBCDpLtxhL5MydQ4gS+DrejtZZPdIYyB1e95uCK9Luyds7ybHI3g==",
|
"integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tunnel": "^0.0.6",
|
"tunnel": "^0.0.6",
|
||||||
"undici": "^6.23.0"
|
"undici": "^5.25.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/io": {
|
"node_modules/@actions/io": {
|
||||||
"version": "3.0.2",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
|
||||||
"integrity": "sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw=="
|
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@actions/tool-cache": {
|
"node_modules/@actions/tool-cache": {
|
||||||
"version": "4.0.0",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-2.0.2.tgz",
|
||||||
"integrity": "sha512-L8P9HbXvpvqjZDveb/fdsa55IVC0trfPgQ4ZwGo6r5af6YDVdM9vMGPZ7rgY2fAT9gGj4PSYd6bYlg3p3jD78A==",
|
"integrity": "sha512-fBhNNOWxuoLxztQebpOaWu6WeVmuwa77Z+DxIZ1B+OYvGkGQon6kTVg6Z32Cb13WCuw0szqonK+hh03mJV7Z6w==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^3.0.0",
|
"@actions/core": "^1.11.1",
|
||||||
"@actions/exec": "^3.0.0",
|
"@actions/exec": "^1.0.0",
|
||||||
"@actions/http-client": "^4.0.0",
|
"@actions/http-client": "^2.0.1",
|
||||||
"@actions/io": "^3.0.0",
|
"@actions/io": "^1.1.1",
|
||||||
"semver": "^7.7.3"
|
"semver": "^6.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@actions/tool-cache/node_modules/semver": {
|
||||||
|
"version": "6.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||||
|
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ampproject/remapping": {
|
"node_modules/@ampproject/remapping": {
|
||||||
@@ -726,9 +734,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/eslintrc/node_modules/js-yaml": {
|
"node_modules/@eslint/eslintrc/node_modules/js-yaml": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
@@ -750,6 +758,15 @@
|
|||||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fastify/busboy": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@humanwhocodes/config-array": {
|
"node_modules/@humanwhocodes/config-array": {
|
||||||
"version": "0.13.0",
|
"version": "0.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
|
||||||
@@ -1556,6 +1573,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"balanced-match": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
|
||||||
|
"version": "9.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
|
||||||
|
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16 || 14 >=14.17"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "6.21.0",
|
"version": "6.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz",
|
||||||
@@ -1644,10 +1687,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ajv": {
|
"node_modules/ajv": {
|
||||||
"version": "6.14.0",
|
"version": "6.12.6",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
|
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
@@ -2089,13 +2133,14 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "2.0.2",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0"
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/braces": {
|
"node_modules/braces": {
|
||||||
@@ -2475,6 +2520,13 @@
|
|||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/concat-map": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
|
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/convert-source-map": {
|
"node_modules/convert-source-map": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||||
@@ -2714,18 +2766,6 @@
|
|||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dotenv": {
|
|
||||||
"version": "17.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz",
|
|
||||||
"integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==",
|
|
||||||
"license": "BSD-2-Clause",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://dotenvx.com"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/dunder-proto": {
|
"node_modules/dunder-proto": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
@@ -3417,9 +3457,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint/node_modules/js-yaml": {
|
"node_modules/eslint/node_modules/js-yaml": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
@@ -3698,6 +3738,29 @@
|
|||||||
"minimatch": "^5.0.1"
|
"minimatch": "^5.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/filelist/node_modules/brace-expansion": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"balanced-match": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/filelist/node_modules/minimatch": {
|
||||||
|
"version": "5.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
||||||
|
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fill-range": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
@@ -3742,10 +3805,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/flatted": {
|
"node_modules/flatted": {
|
||||||
"version": "3.4.2",
|
"version": "3.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz",
|
||||||
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
|
"integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/for-each": {
|
"node_modules/for-each": {
|
||||||
@@ -5462,9 +5526,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/js-yaml": {
|
"node_modules/js-yaml": {
|
||||||
"version": "3.14.2",
|
"version": "3.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||||
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
|
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -6119,19 +6183,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "9.0.9",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
|
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^2.0.2"
|
"brace-expansion": "^1.1.7"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16 || 14 >=14.17"
|
"node": "*"
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/minimist": {
|
"node_modules/minimist": {
|
||||||
@@ -6325,6 +6386,28 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/op-cli-installer": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "git+ssh://git@github.com/1Password/op-cli-installer.git#e6c1c758bc3339e5fe9b06255728039f688f73fa",
|
||||||
|
"integrity": "sha512-ueyYQAgtbIHP2QWx9iCiztoPov01GdxPZNZ4+Qg3IaAyC4khMIk4/vYPagRhRKta+HI6fWV6jO3/ajBj27KBZA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@actions/core": "^1.11.1",
|
||||||
|
"@actions/tool-cache": "^2.0.2",
|
||||||
|
"semver": "^7.7.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/op-cli-installer/node_modules/semver": {
|
||||||
|
"version": "7.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||||
|
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/optionator": {
|
"node_modules/optionator": {
|
||||||
"version": "0.9.4",
|
"version": "0.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||||
@@ -6949,9 +7032,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.7.4",
|
"version": "7.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
@@ -7818,11 +7901,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici": {
|
"node_modules/undici": {
|
||||||
"version": "6.24.1",
|
"version": "5.29.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz",
|
"resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
|
||||||
"integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==",
|
"integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@fastify/busboy": "^2.0.0"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.17"
|
"node": ">=14.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
|
|||||||
12
package.json
12
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "load-secrets-action",
|
"name": "load-secrets-action",
|
||||||
"version": "4.0.0",
|
"version": "3.0.0",
|
||||||
"description": "Load Secrets from 1Password",
|
"description": "Load Secrets from 1Password",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"directories": {
|
"directories": {
|
||||||
@@ -41,13 +41,9 @@
|
|||||||
"homepage": "https://github.com/1Password/load-secrets-action#readme",
|
"homepage": "https://github.com/1Password/load-secrets-action#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@1password/op-js": "^0.1.11",
|
"@1password/op-js": "^0.1.11",
|
||||||
"@actions/core": "^3.0.0",
|
"@actions/core": "^1.10.1",
|
||||||
"@actions/exec": "^3.0.0",
|
"@actions/exec": "^1.1.1",
|
||||||
"@actions/tool-cache": "^4.0.0",
|
"op-cli-installer": "github:1Password/op-cli-installer#e6c1c758bc3339e5fe9b06255728039f688f73fa"
|
||||||
"dotenv": "^17.2.2"
|
|
||||||
},
|
|
||||||
"overrides": {
|
|
||||||
"minimatch": "^9.0.7"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@1password/eslint-config": "^4.3.1",
|
"@1password/eslint-config": "^4.3.1",
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
getInput: jest.fn(() => ""),
|
|
||||||
getBooleanInput: jest.fn(() => false),
|
|
||||||
setOutput: jest.fn(),
|
|
||||||
setSecret: jest.fn(),
|
|
||||||
exportVariable: jest.fn(),
|
|
||||||
setFailed: jest.fn(),
|
|
||||||
info: jest.fn(),
|
|
||||||
warning: jest.fn(),
|
|
||||||
error: jest.fn(),
|
|
||||||
debug: jest.fn(),
|
|
||||||
addPath: jest.fn(),
|
|
||||||
isDebug: jest.fn(() => false),
|
|
||||||
};
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
getExecOutput: jest.fn(() => ({
|
|
||||||
stdout: "MOCK_SECRET",
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
downloadTool: jest.fn(),
|
|
||||||
extractTar: jest.fn(),
|
|
||||||
extractZip: jest.fn(),
|
|
||||||
cacheDir: jest.fn<Promise<string>, [string]>(async (dir) => {
|
|
||||||
await Promise.resolve();
|
|
||||||
return dir;
|
|
||||||
}),
|
|
||||||
find: jest.fn<string, [string, string?, string?]>(() => ""),
|
|
||||||
};
|
|
||||||
@@ -2,6 +2,5 @@ export const envConnectHost = "OP_CONNECT_HOST";
|
|||||||
export const envConnectToken = "OP_CONNECT_TOKEN";
|
export const envConnectToken = "OP_CONNECT_TOKEN";
|
||||||
export const envServiceAccountToken = "OP_SERVICE_ACCOUNT_TOKEN";
|
export const envServiceAccountToken = "OP_SERVICE_ACCOUNT_TOKEN";
|
||||||
export const envManagedVariables = "OP_MANAGED_VARIABLES";
|
export const envManagedVariables = "OP_MANAGED_VARIABLES";
|
||||||
export const envFilePath = "OP_ENV_FILE";
|
|
||||||
|
|
||||||
export const authErr = `Authentication error with environment variables: you must set either 1) ${envServiceAccountToken}, or 2) both ${envConnectHost} and ${envConnectToken}.`;
|
export const authErr = `Authentication error with environment variables: you must set either 1) ${envServiceAccountToken}, or 2) both ${envConnectHost} and ${envConnectToken}.`;
|
||||||
|
|||||||
11
src/index.ts
11
src/index.ts
@@ -1,9 +1,7 @@
|
|||||||
import dotenv from "dotenv";
|
|
||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
import { validateCli } from "@1password/op-js";
|
import { validateCli } from "@1password/op-js";
|
||||||
import { installCliOnGithubActionRunner } from "./op-cli-installer";
|
import { installCliOnGithubActionRunner } from "op-cli-installer";
|
||||||
import { loadSecrets, unsetPrevious, validateAuth } from "./utils";
|
import { loadSecrets, unsetPrevious, validateAuth } from "./utils";
|
||||||
import { envFilePath } from "./constants";
|
|
||||||
|
|
||||||
const loadSecretsAction = async () => {
|
const loadSecretsAction = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -19,13 +17,6 @@ const loadSecretsAction = async () => {
|
|||||||
// Validate that a proper authentication configuration is set for the CLI
|
// Validate that a proper authentication configuration is set for the CLI
|
||||||
validateAuth();
|
validateAuth();
|
||||||
|
|
||||||
// Set environment variables from OP_ENV_FILE
|
|
||||||
const file = process.env[envFilePath];
|
|
||||||
if (file) {
|
|
||||||
core.info(`Loading environment variables from file: ${file}`);
|
|
||||||
dotenv.config({ path: file });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download and install the CLI
|
// Download and install the CLI
|
||||||
await installCLI();
|
await installCLI();
|
||||||
|
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
import os from "os";
|
|
||||||
|
|
||||||
import * as core from "@actions/core";
|
|
||||||
import * as tc from "@actions/tool-cache";
|
|
||||||
|
|
||||||
export type SupportedPlatform = Extract<
|
|
||||||
NodeJS.Platform,
|
|
||||||
"linux" | "darwin" | "win32"
|
|
||||||
>;
|
|
||||||
|
|
||||||
// maps OS architecture names to 1Password CLI installer architecture names
|
|
||||||
export const archMap: Record<string, string> = {
|
|
||||||
ia32: "386",
|
|
||||||
x64: "amd64",
|
|
||||||
arm: "arm",
|
|
||||||
arm64: "arm64",
|
|
||||||
};
|
|
||||||
|
|
||||||
// Builds the download URL for the 1Password CLI based on the platform and version.
|
|
||||||
export const cliUrlBuilder: Record<
|
|
||||||
SupportedPlatform,
|
|
||||||
(version: string, arch?: string) => string
|
|
||||||
> = {
|
|
||||||
linux: (version, arch) =>
|
|
||||||
`https://cache.agilebits.com/dist/1P/op2/pkg/${version}/op_linux_${arch}_${version}.zip`,
|
|
||||||
darwin: (version) =>
|
|
||||||
`https://cache.agilebits.com/dist/1P/op2/pkg/${version}/op_apple_universal_${version}.pkg`,
|
|
||||||
win32: (version, arch) =>
|
|
||||||
`https://cache.agilebits.com/dist/1P/op2/pkg/${version}/op_windows_${arch}_${version}.zip`,
|
|
||||||
};
|
|
||||||
|
|
||||||
export class CliInstaller {
|
|
||||||
public readonly version: string;
|
|
||||||
public readonly arch: string;
|
|
||||||
|
|
||||||
public constructor(version: string) {
|
|
||||||
this.version = version;
|
|
||||||
this.arch = this.getArch();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async install(url: string): Promise<void> {
|
|
||||||
console.info(`Downloading 1Password CLI from: ${url}`);
|
|
||||||
const downloadPath = await tc.downloadTool(url);
|
|
||||||
console.info("Installing 1Password CLI");
|
|
||||||
const extractedPath = await tc.extractZip(downloadPath);
|
|
||||||
core.addPath(extractedPath);
|
|
||||||
core.info("1Password CLI installed");
|
|
||||||
}
|
|
||||||
|
|
||||||
private getArch(): string {
|
|
||||||
const arch = archMap[os.arch()];
|
|
||||||
if (!arch) {
|
|
||||||
throw new Error("Unsupported architecture");
|
|
||||||
}
|
|
||||||
|
|
||||||
return arch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { type Installer, newCliInstaller } from "./installer";
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import os from "os";
|
|
||||||
|
|
||||||
import { newCliInstaller } from "./installer";
|
|
||||||
import { LinuxInstaller } from "./linux";
|
|
||||||
import { MacOsInstaller } from "./macos";
|
|
||||||
import { WindowsInstaller } from "./windows";
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.restoreAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("newCliInstaller", () => {
|
|
||||||
const version = "1.0.0";
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.resetAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return LinuxInstaller for linux platform", () => {
|
|
||||||
jest.spyOn(os, "platform").mockReturnValue("linux");
|
|
||||||
const installer = newCliInstaller(version);
|
|
||||||
expect(installer).toBeInstanceOf(LinuxInstaller);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return MacOsInstaller for darwin platform", () => {
|
|
||||||
jest.spyOn(os, "platform").mockReturnValue("darwin");
|
|
||||||
const installer = newCliInstaller(version);
|
|
||||||
expect(installer).toBeInstanceOf(MacOsInstaller);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return WindowsInstaller for win32 platform", () => {
|
|
||||||
jest.spyOn(os, "platform").mockReturnValue("win32");
|
|
||||||
const installer = newCliInstaller(version);
|
|
||||||
expect(installer).toBeInstanceOf(WindowsInstaller);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should throw error for unsupported platform", () => {
|
|
||||||
jest.spyOn(os, "platform").mockReturnValue("sunos");
|
|
||||||
expect(() => newCliInstaller(version)).toThrow(
|
|
||||||
"Unsupported platform: sunos",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import os from "os";
|
|
||||||
|
|
||||||
import { LinuxInstaller } from "./linux";
|
|
||||||
import { MacOsInstaller } from "./macos";
|
|
||||||
import { WindowsInstaller } from "./windows";
|
|
||||||
|
|
||||||
export interface Installer {
|
|
||||||
installCli(): Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const newCliInstaller = (version: string): Installer => {
|
|
||||||
const platform = os.platform();
|
|
||||||
switch (platform) {
|
|
||||||
case "linux":
|
|
||||||
return new LinuxInstaller(version);
|
|
||||||
case "darwin":
|
|
||||||
return new MacOsInstaller(version);
|
|
||||||
case "win32":
|
|
||||||
return new WindowsInstaller(version);
|
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported platform: ${platform}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import os from "os";
|
|
||||||
|
|
||||||
import {
|
|
||||||
archMap,
|
|
||||||
CliInstaller,
|
|
||||||
cliUrlBuilder,
|
|
||||||
type SupportedPlatform,
|
|
||||||
} from "./cli-installer";
|
|
||||||
import { LinuxInstaller } from "./linux";
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.restoreAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("LinuxInstaller", () => {
|
|
||||||
const version = "1.2.3";
|
|
||||||
const arch: NodeJS.Architecture = "arm64";
|
|
||||||
|
|
||||||
it("should construct with given version and architecture", () => {
|
|
||||||
jest.spyOn(os, "arch").mockReturnValue(arch);
|
|
||||||
const installer = new LinuxInstaller(version);
|
|
||||||
expect(installer.version).toEqual(version);
|
|
||||||
expect(installer.arch).toEqual(archMap[arch]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call install with correct URL", async () => {
|
|
||||||
const installer = new LinuxInstaller(version);
|
|
||||||
const installMock = jest
|
|
||||||
.spyOn(CliInstaller.prototype, "install")
|
|
||||||
.mockResolvedValue();
|
|
||||||
|
|
||||||
await installer.installCli();
|
|
||||||
|
|
||||||
const builder = cliUrlBuilder["linux" as SupportedPlatform];
|
|
||||||
const url = builder(version, installer.arch);
|
|
||||||
expect(installMock).toHaveBeenCalledWith(url);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import {
|
|
||||||
CliInstaller,
|
|
||||||
cliUrlBuilder,
|
|
||||||
type SupportedPlatform,
|
|
||||||
} from "./cli-installer";
|
|
||||||
import type { Installer } from "./installer";
|
|
||||||
|
|
||||||
export class LinuxInstaller extends CliInstaller implements Installer {
|
|
||||||
private readonly platform: SupportedPlatform = "linux"; // Node.js platform identifier for Linux
|
|
||||||
|
|
||||||
public constructor(version: string) {
|
|
||||||
super(version);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async installCli(): Promise<void> {
|
|
||||||
const urlBuilder = cliUrlBuilder[this.platform];
|
|
||||||
await super.install(urlBuilder(this.version, this.arch));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import os from "os";
|
|
||||||
|
|
||||||
import {
|
|
||||||
archMap,
|
|
||||||
cliUrlBuilder,
|
|
||||||
type SupportedPlatform,
|
|
||||||
} from "./cli-installer";
|
|
||||||
import { MacOsInstaller } from "./macos";
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.restoreAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("MacOsInstaller", () => {
|
|
||||||
const version = "1.2.3";
|
|
||||||
const arch: NodeJS.Architecture = "x64";
|
|
||||||
|
|
||||||
it("should construct with given version and architecture", () => {
|
|
||||||
jest.spyOn(os, "arch").mockReturnValue(arch);
|
|
||||||
const installer = new MacOsInstaller(version);
|
|
||||||
expect(installer.version).toEqual(version);
|
|
||||||
expect(installer.arch).toEqual(archMap[arch]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call install with correct URL", async () => {
|
|
||||||
const installer = new MacOsInstaller(version);
|
|
||||||
const installMock = jest.spyOn(installer, "install").mockResolvedValue();
|
|
||||||
|
|
||||||
await installer.installCli();
|
|
||||||
|
|
||||||
const builder = cliUrlBuilder["darwin" as SupportedPlatform];
|
|
||||||
const url = builder(version, installer.arch);
|
|
||||||
expect(installMock).toHaveBeenCalledWith(url);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import { execFile } from "child_process";
|
|
||||||
import * as fs from "fs";
|
|
||||||
import * as path from "path";
|
|
||||||
import { promisify } from "util";
|
|
||||||
|
|
||||||
import * as core from "@actions/core";
|
|
||||||
import * as tc from "@actions/tool-cache";
|
|
||||||
|
|
||||||
import {
|
|
||||||
CliInstaller,
|
|
||||||
cliUrlBuilder,
|
|
||||||
type SupportedPlatform,
|
|
||||||
} from "./cli-installer";
|
|
||||||
import { type Installer } from "./installer";
|
|
||||||
|
|
||||||
const execFileAsync = promisify(execFile);
|
|
||||||
|
|
||||||
export class MacOsInstaller extends CliInstaller implements Installer {
|
|
||||||
private readonly platform: SupportedPlatform = "darwin"; // Node.js platform identifier for macOS
|
|
||||||
|
|
||||||
public constructor(version: string) {
|
|
||||||
super(version);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async installCli(): Promise<void> {
|
|
||||||
const urlBuilder = cliUrlBuilder[this.platform];
|
|
||||||
await this.install(urlBuilder(this.version));
|
|
||||||
}
|
|
||||||
|
|
||||||
// @actions/tool-cache package does not support .pkg files, so we need to handle the installation manually
|
|
||||||
public override async install(downloadUrl: string): Promise<void> {
|
|
||||||
console.info(`Downloading 1Password CLI from: ${downloadUrl}`);
|
|
||||||
const pkgPath = await tc.downloadTool(downloadUrl);
|
|
||||||
const pkgWithExtension = `${pkgPath}.pkg`;
|
|
||||||
fs.renameSync(pkgPath, pkgWithExtension);
|
|
||||||
|
|
||||||
const expandDir = "temp-pkg";
|
|
||||||
await execFileAsync("pkgutil", ["--expand", pkgWithExtension, expandDir]);
|
|
||||||
const payloadPath = path.join(expandDir, "op.pkg", "Payload");
|
|
||||||
console.info("Installing 1Password CLI");
|
|
||||||
const cliPath = await tc.extractTar(payloadPath);
|
|
||||||
core.addPath(cliPath);
|
|
||||||
|
|
||||||
fs.rmSync(expandDir, { recursive: true, force: true });
|
|
||||||
fs.rmSync(pkgPath, { force: true });
|
|
||||||
|
|
||||||
core.info("1Password CLI installed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import os from "os";
|
|
||||||
|
|
||||||
import {
|
|
||||||
archMap,
|
|
||||||
CliInstaller,
|
|
||||||
cliUrlBuilder,
|
|
||||||
type SupportedPlatform,
|
|
||||||
} from "./cli-installer";
|
|
||||||
import { WindowsInstaller } from "./windows";
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.restoreAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("WindowsInstaller", () => {
|
|
||||||
const version = "1.2.3";
|
|
||||||
const arch: NodeJS.Architecture = "x64";
|
|
||||||
|
|
||||||
it("should construct with given version and architecture", () => {
|
|
||||||
jest.spyOn(os, "arch").mockReturnValue(arch);
|
|
||||||
const installer = new WindowsInstaller(version);
|
|
||||||
expect(installer.version).toEqual(version);
|
|
||||||
expect(installer.arch).toEqual(archMap[arch]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call install with correct URL", async () => {
|
|
||||||
const installer = new WindowsInstaller(version);
|
|
||||||
const installMock = jest
|
|
||||||
.spyOn(CliInstaller.prototype, "install")
|
|
||||||
.mockResolvedValue();
|
|
||||||
|
|
||||||
await installer.installCli();
|
|
||||||
|
|
||||||
const builder = cliUrlBuilder["win32" as SupportedPlatform];
|
|
||||||
const url = builder(version, installer.arch);
|
|
||||||
expect(installMock).toHaveBeenCalledWith(url);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import {
|
|
||||||
CliInstaller,
|
|
||||||
cliUrlBuilder,
|
|
||||||
type SupportedPlatform,
|
|
||||||
} from "./cli-installer";
|
|
||||||
import type { Installer } from "./installer";
|
|
||||||
|
|
||||||
export class WindowsInstaller extends CliInstaller implements Installer {
|
|
||||||
private readonly platform: SupportedPlatform = "win32"; // Node.js platform identifier for Windows
|
|
||||||
|
|
||||||
public constructor(version: string) {
|
|
||||||
super(version);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async installCli(): Promise<void> {
|
|
||||||
const urlBuilder = cliUrlBuilder[this.platform];
|
|
||||||
await super.install(urlBuilder(this.version, this.arch));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import * as core from "@actions/core";
|
|
||||||
|
|
||||||
import { ReleaseChannel, VersionResolver } from "../version";
|
|
||||||
|
|
||||||
import { newCliInstaller } from "./cli-installer";
|
|
||||||
|
|
||||||
// Installs the 1Password CLI on a GitHub Action runner.
|
|
||||||
export const installCliOnGithubActionRunner = async (
|
|
||||||
version?: string,
|
|
||||||
): Promise<void> => {
|
|
||||||
// Get the version from parameter, if not passed - from the job input. Defaults to latest if no version is provided
|
|
||||||
const providedVersion =
|
|
||||||
version || core.getInput("version") || ReleaseChannel.latest;
|
|
||||||
const versionResolver = new VersionResolver(providedVersion);
|
|
||||||
await versionResolver.resolve();
|
|
||||||
const installer = newCliInstaller(versionResolver.get());
|
|
||||||
await installer.installCli();
|
|
||||||
};
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
import * as core from "@actions/core";
|
|
||||||
|
|
||||||
import { newCliInstaller } from "./github-action/cli-installer";
|
|
||||||
import {
|
|
||||||
installCliOnGithubActionRunner,
|
|
||||||
ReleaseChannel,
|
|
||||||
VersionResolver,
|
|
||||||
} from "./index";
|
|
||||||
|
|
||||||
jest.mock("./github-action/cli-installer", () => ({
|
|
||||||
newCliInstaller: jest.fn().mockImplementation((_resolved: string) => ({
|
|
||||||
installCli: jest.fn(),
|
|
||||||
})),
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.restoreAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("installCliOnGithubActionRunner", () => {
|
|
||||||
it("should defaults to `latest` when nothing is passed", async () => {
|
|
||||||
jest.spyOn(core, "getInput").mockReturnValue("");
|
|
||||||
jest.spyOn(VersionResolver.prototype, "resolve").mockResolvedValue();
|
|
||||||
jest
|
|
||||||
.spyOn(VersionResolver.prototype, "get")
|
|
||||||
.mockReturnValue(ReleaseChannel.latest);
|
|
||||||
|
|
||||||
await installCliOnGithubActionRunner();
|
|
||||||
|
|
||||||
expect(newCliInstaller).toHaveBeenCalledWith(ReleaseChannel.latest);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should defaults to `latest` when undefined is passed", async () => {
|
|
||||||
jest.spyOn(core, "getInput").mockReturnValue("");
|
|
||||||
jest.spyOn(VersionResolver.prototype, "resolve").mockResolvedValue();
|
|
||||||
jest
|
|
||||||
.spyOn(VersionResolver.prototype, "get")
|
|
||||||
.mockReturnValue(ReleaseChannel.latest);
|
|
||||||
|
|
||||||
await installCliOnGithubActionRunner(undefined);
|
|
||||||
|
|
||||||
expect(newCliInstaller).toHaveBeenCalledWith(ReleaseChannel.latest);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should set provided explicit version", async () => {
|
|
||||||
const providedVersion = "1.2.3";
|
|
||||||
jest.spyOn(core, "getInput").mockReturnValue("");
|
|
||||||
jest.spyOn(VersionResolver.prototype, "resolve").mockResolvedValue();
|
|
||||||
jest
|
|
||||||
.spyOn(VersionResolver.prototype, "get")
|
|
||||||
.mockReturnValue(providedVersion);
|
|
||||||
|
|
||||||
await installCliOnGithubActionRunner(providedVersion);
|
|
||||||
|
|
||||||
expect(newCliInstaller).toHaveBeenCalledWith(providedVersion);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should set version provided as job input", async () => {
|
|
||||||
const providedVersion = "3.0.0";
|
|
||||||
jest.spyOn(core, "getInput").mockReturnValue(providedVersion);
|
|
||||||
jest.spyOn(VersionResolver.prototype, "resolve").mockResolvedValue();
|
|
||||||
jest
|
|
||||||
.spyOn(VersionResolver.prototype, "get")
|
|
||||||
.mockReturnValue(providedVersion);
|
|
||||||
|
|
||||||
await installCliOnGithubActionRunner();
|
|
||||||
|
|
||||||
expect(newCliInstaller).toHaveBeenCalledWith(providedVersion);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should throw error for invalid version", async () => {
|
|
||||||
const providedVersion = "invalid";
|
|
||||||
jest.spyOn(core, "getInput").mockReturnValue(providedVersion);
|
|
||||||
jest.spyOn(VersionResolver.prototype, "resolve").mockResolvedValue();
|
|
||||||
jest
|
|
||||||
.spyOn(VersionResolver.prototype, "get")
|
|
||||||
.mockReturnValue(providedVersion);
|
|
||||||
|
|
||||||
await expect(installCliOnGithubActionRunner()).rejects.toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export { installCliOnGithubActionRunner } from "./github-action";
|
|
||||||
export { ReleaseChannel, VersionResolver } from "./version";
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
export enum ReleaseChannel {
|
|
||||||
latest = "latest",
|
|
||||||
latestBeta = "latest-beta",
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VersionResponse {
|
|
||||||
// eslint disabled next line as CLI2 is expected in getting CLI versions response
|
|
||||||
/* eslint-disable-next-line @typescript-eslint/naming-convention */
|
|
||||||
CLI2: {
|
|
||||||
release: { version: string };
|
|
||||||
beta: { version: string };
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
import { ReleaseChannel } from "./constants";
|
|
||||||
import { getLatestVersion } from "./helper";
|
|
||||||
|
|
||||||
describe("getLatestVersion", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.restoreAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return latest stable version", async () => {
|
|
||||||
const mockResponse = {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
CLI2: {
|
|
||||||
release: { version: "2.31.0" },
|
|
||||||
beta: { version: "2.32.0-beta.01" },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
jest.spyOn(global, "fetch").mockResolvedValueOnce({
|
|
||||||
// eslint-disable-next-line @typescript-eslint/require-await
|
|
||||||
json: async () => mockResponse,
|
|
||||||
} as Response);
|
|
||||||
|
|
||||||
const version = await getLatestVersion(ReleaseChannel.latest);
|
|
||||||
expect(version).toBe("2.31.0");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return latest beta version", async () => {
|
|
||||||
const mockResponse = {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
CLI2: {
|
|
||||||
release: { version: "2.31.0" },
|
|
||||||
beta: { version: "2.32.0-beta.01" },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
jest.spyOn(global, "fetch").mockResolvedValueOnce({
|
|
||||||
// eslint-disable-next-line @typescript-eslint/require-await
|
|
||||||
json: async () => mockResponse,
|
|
||||||
} as Response);
|
|
||||||
|
|
||||||
const version = await getLatestVersion(ReleaseChannel.latestBeta);
|
|
||||||
expect(version).toBe("2.32.0-beta.01");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should throw if no CLI2 field", async () => {
|
|
||||||
jest.spyOn(global, "fetch").mockResolvedValueOnce({
|
|
||||||
// eslint-disable-next-line @typescript-eslint/require-await
|
|
||||||
json: async () => ({}),
|
|
||||||
} as Response);
|
|
||||||
|
|
||||||
await expect(getLatestVersion(ReleaseChannel.latest)).rejects.toThrow(
|
|
||||||
`No ${ReleaseChannel.latest} versions found`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should throw if no stable version found", async () => {
|
|
||||||
const mockResponse = {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
CLI2: {
|
|
||||||
beta: { version: "2.32.0-beta.01" },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
jest.spyOn(global, "fetch").mockResolvedValueOnce({
|
|
||||||
// eslint-disable-next-line @typescript-eslint/require-await
|
|
||||||
json: async () => mockResponse,
|
|
||||||
} as Response);
|
|
||||||
|
|
||||||
await expect(getLatestVersion(ReleaseChannel.latest)).rejects.toThrow(
|
|
||||||
`No ${ReleaseChannel.latest} versions found`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should throw if no beta version found", async () => {
|
|
||||||
const mockResponse = {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
CLI2: {
|
|
||||||
release: { version: "2.32.0" },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
jest.spyOn(global, "fetch").mockResolvedValueOnce({
|
|
||||||
// eslint-disable-next-line @typescript-eslint/require-await
|
|
||||||
json: async () => mockResponse,
|
|
||||||
} as Response);
|
|
||||||
|
|
||||||
await expect(getLatestVersion(ReleaseChannel.latestBeta)).rejects.toThrow(
|
|
||||||
`No ${ReleaseChannel.latestBeta} versions found`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import * as core from "@actions/core";
|
|
||||||
|
|
||||||
import { ReleaseChannel, type VersionResponse } from "./constants";
|
|
||||||
|
|
||||||
// Returns the latest version of the 1Password CLI based on the specified channel.
|
|
||||||
export const getLatestVersion = async (
|
|
||||||
channel: ReleaseChannel,
|
|
||||||
): Promise<string> => {
|
|
||||||
core.info(`Getting ${channel} version number`);
|
|
||||||
const res = await fetch("https://app-updates.agilebits.com/latest");
|
|
||||||
const json = (await res.json()) as VersionResponse;
|
|
||||||
const latestStable = json?.CLI2?.release?.version;
|
|
||||||
const latestBeta = json?.CLI2?.beta?.version;
|
|
||||||
const version =
|
|
||||||
channel === ReleaseChannel.latestBeta ? latestBeta : latestStable;
|
|
||||||
|
|
||||||
if (!version) {
|
|
||||||
core.error(`No ${channel} versions found`);
|
|
||||||
throw new Error(`No ${channel} versions found`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return version;
|
|
||||||
};
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export { VersionResolver } from "./version-resolver";
|
|
||||||
export { ReleaseChannel } from "./constants";
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import { describe, expect, it } from "@jest/globals";
|
|
||||||
|
|
||||||
import { validateVersion } from "./validate";
|
|
||||||
|
|
||||||
describe("validateVersion", () => {
|
|
||||||
it('should not throw for "latest"', () => {
|
|
||||||
expect(() => validateVersion("latest")).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not throw for "latest-beta"', () => {
|
|
||||||
expect(() => validateVersion("latest-beta")).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not throw for valid semver version "2.18.0"', () => {
|
|
||||||
expect(() => validateVersion("2.18.0")).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw for partial version "2"', () => {
|
|
||||||
expect(() => validateVersion("2")).toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw for partial version "2.1"', () => {
|
|
||||||
expect(() => validateVersion("2.1")).toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not throw for valid beta "2.19.0-beta.01"', () => {
|
|
||||||
expect(() => validateVersion("2.19.0-beta.01")).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not throw for valid beta "2.19.3-beta.12"', () => {
|
|
||||||
expect(() => validateVersion("2.19.3-beta.12")).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not throw for coerced version "v2.19.0"', () => {
|
|
||||||
expect(() => validateVersion("v2.19.0")).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw for invalid version "latest-abc"', () => {
|
|
||||||
expect(() => validateVersion("latest-abc")).toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should throw for empty string", () => {
|
|
||||||
expect(() => validateVersion("")).toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import semver from "semver";
|
|
||||||
|
|
||||||
import { ReleaseChannel } from "./constants";
|
|
||||||
|
|
||||||
// Validates if the provided version type is a valid enum value or a valid semver version.
|
|
||||||
export const validateVersion = (input: string): void => {
|
|
||||||
if (Object.values(ReleaseChannel).includes(input as ReleaseChannel)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1Password beta releases (aka 2.19.0-beta.01) are not semver compliant.
|
|
||||||
// According to semver, it should be "2.19.0-beta.1".
|
|
||||||
// That's why we need to normalize them before validating.
|
|
||||||
// Accepts valid semver versions like "2.18.0" or beta-releases like "2.19.0-beta.01"
|
|
||||||
// or versions with 'v' prefix like "v2.19.0"
|
|
||||||
const normalized = input.replace(/-beta\.0*(\d+)/, "-beta.$1");
|
|
||||||
const normInput = new semver.SemVer(normalized);
|
|
||||||
if (semver.valid(normInput)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Invalid version input: ${input}`);
|
|
||||||
};
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
import { expect } from "@jest/globals";
|
|
||||||
|
|
||||||
import { ReleaseChannel } from "./constants";
|
|
||||||
import { VersionResolver } from "./version-resolver";
|
|
||||||
|
|
||||||
describe("VersionResolver", () => {
|
|
||||||
test("should throw error when invalid version provided", () => {
|
|
||||||
expect(() => new VersionResolver("vv")).toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should throw error when version is empty", () => {
|
|
||||||
expect(() => new VersionResolver("")).toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should throw error for major version only", () => {
|
|
||||||
expect(() => new VersionResolver("1")).toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should throw error for major and minor version only", () => {
|
|
||||||
expect(() => new VersionResolver("1.0")).toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should resolve latest stable version", async () => {
|
|
||||||
const versionResolver = new VersionResolver(ReleaseChannel.latest);
|
|
||||||
await versionResolver.resolve();
|
|
||||||
expect(versionResolver.get()).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should resolve latest beta version", async () => {
|
|
||||||
const versionResolver = new VersionResolver(ReleaseChannel.latestBeta);
|
|
||||||
await versionResolver.resolve();
|
|
||||||
expect(versionResolver.get()).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should resolve version without 'v' prefix", async () => {
|
|
||||||
const versionResolver = new VersionResolver("1.0.0");
|
|
||||||
await versionResolver.resolve();
|
|
||||||
expect(versionResolver.get()).toBe("v1.0.0");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should resolve version with 'v' prefix", async () => {
|
|
||||||
const versionResolver = new VersionResolver("v1.0.0");
|
|
||||||
await versionResolver.resolve();
|
|
||||||
expect(versionResolver.get()).toBe("v1.0.0");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should resolve beta version without 'v' prefix", async () => {
|
|
||||||
const versionResolver = new VersionResolver("2.19.0-beta.01");
|
|
||||||
await versionResolver.resolve();
|
|
||||||
expect(versionResolver.get()).toBe("v2.19.0-beta.01");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should resolve beta version with 'v' prefix", async () => {
|
|
||||||
const versionResolver = new VersionResolver("v2.19.0-beta.01");
|
|
||||||
await versionResolver.resolve();
|
|
||||||
expect(versionResolver.get()).toBe("v2.19.0-beta.01");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import * as core from "@actions/core";
|
|
||||||
|
|
||||||
import { ReleaseChannel } from "./constants";
|
|
||||||
import { getLatestVersion } from "./helper";
|
|
||||||
import { validateVersion } from "./validate";
|
|
||||||
|
|
||||||
export class VersionResolver {
|
|
||||||
private version: string;
|
|
||||||
|
|
||||||
public constructor(version: string) {
|
|
||||||
this.validate(version);
|
|
||||||
this.version = version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get(): string {
|
|
||||||
return this.version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async resolve(): Promise<void> {
|
|
||||||
core.info(`Resolving version: ${this.version}`);
|
|
||||||
if (!this.version) {
|
|
||||||
core.error("Version is not provided");
|
|
||||||
throw new Error("Version is not provided");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isReleaseChannel(this.version)) {
|
|
||||||
this.version = await getLatestVersion(this.version);
|
|
||||||
}
|
|
||||||
|
|
||||||
// add `v` prefix if not already present
|
|
||||||
this.version = this.version.startsWith("v")
|
|
||||||
? this.version
|
|
||||||
: `v${this.version}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private validate(version: string) {
|
|
||||||
core.info(`Validating version number: '${version}'`);
|
|
||||||
validateVersion(version);
|
|
||||||
core.info(`Version number '${version}' is valid`);
|
|
||||||
}
|
|
||||||
|
|
||||||
private isReleaseChannel(value: string): value is ReleaseChannel {
|
|
||||||
return Object.values(ReleaseChannel).includes(value as ReleaseChannel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,6 +15,12 @@ import {
|
|||||||
envServiceAccountToken,
|
envServiceAccountToken,
|
||||||
} from "./constants";
|
} from "./constants";
|
||||||
|
|
||||||
|
jest.mock("@actions/core");
|
||||||
|
jest.mock("@actions/exec", () => ({
|
||||||
|
getExecOutput: jest.fn(() => ({
|
||||||
|
stdout: "MOCK_SECRET",
|
||||||
|
})),
|
||||||
|
}));
|
||||||
jest.mock("@1password/op-js");
|
jest.mock("@1password/op-js");
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -100,41 +106,6 @@ describe("extractSecret", () => {
|
|||||||
);
|
);
|
||||||
expect(core.setSecret).toHaveBeenCalledWith(testSecretValue);
|
expect(core.setSecret).toHaveBeenCalledWith(testSecretValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when secret value is empty string", () => {
|
|
||||||
const emptySecretValue = "";
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
(read.parse as jest.Mock).mockReturnValue(emptySecretValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
(read.parse as jest.Mock).mockReturnValue(testSecretValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should set empty string as step output", () => {
|
|
||||||
extractSecret(envTestSecretEnv, false);
|
|
||||||
expect(core.setOutput).toHaveBeenCalledWith(
|
|
||||||
envTestSecretEnv,
|
|
||||||
emptySecretValue,
|
|
||||||
);
|
|
||||||
expect(core.exportVariable).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should set empty string as environment variable", () => {
|
|
||||||
extractSecret(envTestSecretEnv, true);
|
|
||||||
expect(core.exportVariable).toHaveBeenCalledWith(
|
|
||||||
envTestSecretEnv,
|
|
||||||
emptySecretValue,
|
|
||||||
);
|
|
||||||
expect(core.setOutput).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not call setSecret for empty string", () => {
|
|
||||||
extractSecret(envTestSecretEnv, false);
|
|
||||||
expect(core.setSecret).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("loadSecrets", () => {
|
describe("loadSecrets", () => {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export const extractSecret = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const secretValue = read.parse(ref);
|
const secretValue = read.parse(ref);
|
||||||
if (secretValue === null || secretValue === undefined) {
|
if (!secretValue) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,11 +50,7 @@ export const extractSecret = (
|
|||||||
} else {
|
} else {
|
||||||
core.setOutput(envName, secretValue);
|
core.setOutput(envName, secretValue);
|
||||||
}
|
}
|
||||||
// Skip setSecret for empty strings to avoid the warning:
|
core.setSecret(secretValue);
|
||||||
// "Can't add secret mask for empty string in ##[add-mask] command."
|
|
||||||
if (secretValue) {
|
|
||||||
core.setSecret(secretValue);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loadSecrets = async (shouldExportEnv: boolean): Promise<void> => {
|
export const loadSecrets = async (shouldExportEnv: boolean): Promise<void> => {
|
||||||
|
|||||||
@@ -9,8 +9,11 @@ assert_env_equals() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly SECRET="RGVhciBzZWN1cml0eSByZXNlYXJjaGVyLCB0aGlzIGlzIGp1c3QgYSBkdW1teSBzZWNyZXQuIFBsZWFzZSBkb24ndCByZXBvcnQgaXQu"
|
assert_env_equals "SECRET" "RGVhciBzZWN1cml0eSByZXNlYXJjaGVyLCB0aGlzIGlzIGp1c3QgYSBkdW1teSBzZWNyZXQuIFBsZWFzZSBkb24ndCByZXBvcnQgaXQu"
|
||||||
MULTILINE_SECRET="$(cat << EOF
|
|
||||||
|
assert_env_equals "SECRET_IN_SECTION" "RGVhciBzZWN1cml0eSByZXNlYXJjaGVyLCB0aGlzIGlzIGp1c3QgYSBkdW1teSBzZWNyZXQuIFBsZWFzZSBkb24ndCByZXBvcnQgaXQu"
|
||||||
|
|
||||||
|
assert_env_equals "MULTILINE_SECRET" "$(cat << EOF
|
||||||
-----BEGIN PRIVATE KEY-----
|
-----BEGIN PRIVATE KEY-----
|
||||||
RGVhciBzZWN1cml0eSByZXNlYXJjaGVyLApXaGls
|
RGVhciBzZWN1cml0eSByZXNlYXJjaGVyLApXaGls
|
||||||
ZSB3ZSBkZWVwbHkgYXBwcmVjaWF0ZSB5b3VyIHZp
|
ZSB3ZSBkZWVwbHkgYXBwcmVjaWF0ZSB5b3VyIHZp
|
||||||
@@ -25,20 +28,3 @@ IApTbyBwbGVhc2UgZG9uJ3QgcmVwb3J0IGl0IQo=
|
|||||||
-----END PRIVATE KEY-----
|
-----END PRIVATE KEY-----
|
||||||
EOF
|
EOF
|
||||||
)"
|
)"
|
||||||
readonly MULTILINE_SECRET
|
|
||||||
readonly WEBSITE="www.test.com"
|
|
||||||
|
|
||||||
assert_env_equals "SECRET" "${SECRET}"
|
|
||||||
assert_env_equals "FILE_SECRET" "${SECRET}"
|
|
||||||
|
|
||||||
assert_env_equals "SECRET_IN_SECTION" "${SECRET}"
|
|
||||||
assert_env_equals "FILE_SECRET_IN_SECTION" "${SECRET}"
|
|
||||||
|
|
||||||
assert_env_equals "MULTILINE_SECRET" "${MULTILINE_SECRET}"
|
|
||||||
assert_env_equals "FILE_MULTILINE_SECRET" "${MULTILINE_SECRET}"
|
|
||||||
|
|
||||||
# WEBSITE/FILE_WEBSITE: required when ASSERT_WEBSITE=true (Service Account), skipped when false (Connect)
|
|
||||||
if [ "${ASSERT_WEBSITE:-false}" = "true" ]; then
|
|
||||||
assert_env_equals "WEBSITE" "${WEBSITE}"
|
|
||||||
assert_env_equals "FILE_WEBSITE" "${WEBSITE}"
|
|
||||||
fi
|
|
||||||
|
|||||||
@@ -10,18 +10,5 @@ assert_env_unset() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert_env_unset "SECRET"
|
assert_env_unset "SECRET"
|
||||||
assert_env_unset "FILE_SECRET"
|
|
||||||
|
|
||||||
assert_env_unset "SECRET_IN_SECTION"
|
assert_env_unset "SECRET_IN_SECTION"
|
||||||
assert_env_unset "FILE_SECRET_IN_SECTION"
|
|
||||||
|
|
||||||
assert_env_unset "MULTILINE_SECRET"
|
assert_env_unset "MULTILINE_SECRET"
|
||||||
assert_env_unset "FILE_MULTILINE_SECRET"
|
|
||||||
|
|
||||||
assert_env_unset "WEBSITE"
|
|
||||||
assert_env_unset "FILE_WEBSITE"
|
|
||||||
|
|
||||||
assert_env_unset "TEST_SSH_KEY"
|
|
||||||
assert_env_unset "FILE_TEST_SSH_KEY"
|
|
||||||
assert_env_unset "TEST_SSH_KEY_OPENSSH"
|
|
||||||
assert_env_unset "FILE_TEST_SSH_KEY_OPENSSH"
|
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
assert_ssh_key_set() {
|
|
||||||
local var="$1"
|
|
||||||
local val
|
|
||||||
val="$(printenv "$var" || true)"
|
|
||||||
if [ -z "$val" ]; then
|
|
||||||
echo "Expected $var to be set"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
[ "$val" = "***" ] && return 0
|
|
||||||
local line
|
|
||||||
line="$(echo "$val" | head -1)"
|
|
||||||
if echo "$var" | grep -q "OPENSSH"; then
|
|
||||||
echo "$line" | grep -q "OPENSSH" || { echo "Expected $var to start with -----BEGIN OPENSSH PRIVATE KEY-----"; exit 1; }
|
|
||||||
else
|
|
||||||
echo "$line" | grep -q "BEGIN.*PRIVATE KEY" || { echo "Expected $var to be a private key"; exit 1; }
|
|
||||||
fi
|
|
||||||
echo "$var OK"
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_ssh_key_set "TEST_SSH_KEY"
|
|
||||||
assert_ssh_key_set "TEST_SSH_KEY_OPENSSH"
|
|
||||||
assert_ssh_key_set "FILE_TEST_SSH_KEY"
|
|
||||||
assert_ssh_key_set "FILE_TEST_SSH_KEY_OPENSSH"
|
|
||||||
Reference in New Issue
Block a user