diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a5aef15..7b85d13 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,8 +4,8 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Lint - run: docker-compose run lint + run: docker compose run --quiet-pull lint - name: Tests - run: docker-compose run tests \ No newline at end of file + run: docker compose run --quiet-pull tests diff --git a/README.md b/README.md index 9258576..2675263 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ [![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fseek-oss%2Fprivate-npm-buildkite-plugin%2Fbadge&style=flat)](https://actions-badge.atrox.dev/seek-oss/private-npm-buildkite-plugin/goto) [![GitHub Release](https://img.shields.io/github/release/seek-oss/private-npm-buildkite-plugin.svg)](https://github.com/seek-oss/private-npm-buildkite-plugin/releases) - A [Buildkite plugin](https://buildkite.com/docs/agent/v3/plugins) to allow pipeline steps to easily install private packages from an [npm](https://www.npmjs.com) repository. @@ -17,7 +16,7 @@ To read the value from an environment variable named `MY_TOKEN` when the plugin steps: - command: yarn install plugins: - - seek-oss/private-npm#v1.2.0: + - seek-oss/private-npm#v1.3.0: env: "MY_TOKEN" ``` @@ -27,43 +26,56 @@ To read the value from a file named `my_token_file`, use the `file` field. steps: - command: yarn install plugins: - - seek-oss/private-npm#v1.2.0: + - seek-oss/private-npm#v1.3.0: file: "my_token_file" ``` -Alternatively you can read the token directly from any value exposed to your `pipeline.yml` file. However, this -approach is discouraged in favour of using with the `env` or `file` fields. This functionality remains in the interest - of backwards compatibility. +Alternatively you can read the token directly from any value exposed to your `pipeline.yml` file. However, this +approach is discouraged in favour of using with the `env` or `file` fields. This functionality remains in the interest +of backwards compatibility. ```yml steps: - command: yarn install plugins: - - seek-oss/private-npm#v1.2.0: + - seek-oss/private-npm#v1.3.0: token: ${MY_TOKEN} ``` - You can also specify a custom npm registry if you are using your own mirror. ```yml steps: - command: yarn install plugins: - - seek-oss/private-npm#v1.2.0: + - seek-oss/private-npm#v1.3.0: env: "MY_TOKEN" registry: //myprivatenpm.com/ ``` +If you set a registry, you can configure a specific scope to fetch packages from your custom registry. +In this case, use only Protocol-Relative URL for `registry` as `https://` will be prepended in the +scope in the `.npmrc` for you. + +```yml +steps: + - command: yarn install + plugins: + - seek-oss/private-npm#v1.3.0: + env: "MY_TOKEN" + registry: //myprivatenpm.com/ + scope: "@myprivatescope" +``` + ## Configuration -> **NOTE** Even thought `env`, `file` and `token` are described as optional, _at least one must be set_ or the plugin +> **NOTE** Even thought `env`, `file` and `token` are described as optional, _at least one must be set_ or the plugin > will fail. ### `env` (optional) -The value of the NPM token will be read from the agent environment when the plugin executes. This is useful in working -around cases where eager binding of variables in `pipeline.yml` means some variables are not present in the +The value of the NPM token will be read from the agent environment when the plugin executes. This is useful in working +around cases where eager binding of variables in `pipeline.yml` means some variables are not present in the environment when the configuration file is parsed. > **NOTE** : Beware of using `NPM_TOKEN` as the name for the environment variable. When using that name the variable @@ -71,33 +83,37 @@ environment when the configuration file is parsed. ### `file` (optional) -The value of the NPM token will be read from a file on the agent when the plugin executes. This is useful when working +The value of the NPM token will be read from a file on the agent when the plugin executes. This is useful when working with secret that are created as files on the filesystem when a build is initiated. ### `token` (optional) -The value of the NPM token will be read from a variable which is available to the Buildkite YAML parsing context. -This value is interpolated when the YAML configuration is parsed by the Buildgent agent and provided to the plugin "as +The value of the NPM token will be read from a variable which is available to the Buildkite YAML parsing context. +This value is interpolated when the YAML configuration is parsed by the Buildgent agent and provided to the plugin "as is". Example: `${MY_TOKEN}` -> **NOTE:** Don't put your tokens into source control. Don't use web interfaces you don't control to inject them into -> your environment either. Rather use a Secrets Manager. If you are an AWS user, perhaps consider the + +> **NOTE:** Don't put your tokens into source control. Don't use web interfaces you don't control to inject them into +> your environment either. Rather use a Secrets Manager. If you are an AWS user, perhaps consider the > [aws-sm-buildkite-plugin](https://github.com/seek-oss/aws-sm-buildkite-plugin) which works well with this plugin. -> **NOTE:** There is anecdotal evidence to suggest that using `NPM_TOKEN` as the variable name containing the -> token can intermittently cause the token to become empty. It is advised to use a different name as has been done in +> **NOTE:** There is anecdotal evidence to suggest that using `NPM_TOKEN` as the variable name containing the +> token can intermittently cause the token to become empty. It is advised to use a different name as has been done in > these docs. ### `registry` (optional) -The path to a private npm repository. Please ensure you supply the trailing `/`! + +The path to a private npm repository. Please ensure you supply the trailing `/`! Example: `//myprivatenpm.com/` ### `output-path` (optional) -The path to the .npmrc that will be created. Please ensure you supply the trailing `/`! + +The path to the .npmrc that will be created. Please ensure you supply the trailing `/`! Example: `./project/path/` ## License + MIT (see [LICENSE](./LICENSE)) diff --git a/docker-compose.yml b/docker-compose.yml index 8238dea..0e2ce6f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,3 @@ -version: '3.4' services: tests: image: buildkite/plugin-tester @@ -6,6 +5,6 @@ services: - ".:/plugin" lint: image: buildkite/plugin-linter - command: ['--name', 'seek-oss/private-npm'] + command: ["--name", "seek-oss/private-npm"] volumes: - - ".:/plugin:ro" \ No newline at end of file + - ".:/plugin:ro" diff --git a/hooks/pre-command b/hooks/pre-command index ca35d83..7c4ba18 100755 --- a/hooks/pre-command +++ b/hooks/pre-command @@ -3,6 +3,7 @@ set -euo pipefail IFS=$'\n\t' SEEK_OSS_PRIVATE_NPM_REGISTRY=${BUILDKITE_PLUGIN_PRIVATE_NPM_REGISTRY:-'//registry.npmjs.org/'} +SEEK_OSS_PRIVATE_NPM_SCOPE=${BUILDKITE_PLUGIN_PRIVATE_NPM_SCOPE:-''} SEEK_OSS_PRIVATE_NPM_TOKEN=${BUILDKITE_PLUGIN_PRIVATE_NPM_TOKEN:-''} SEEK_OSS_PRIVATE_NPM_FILE=${BUILDKITE_PLUGIN_PRIVATE_NPM_FILE:-''} SEEK_OSS_PRIVATE_NPM_ENV=${BUILDKITE_PLUGIN_PRIVATE_NPM_ENV:-''} @@ -18,13 +19,14 @@ fi if [[ -n "${SEEK_OSS_PRIVATE_NPM_FILE}" ]] then + SEEK_OSS_PRIVATE_NPM_TOKEN=$(cat "${SEEK_OSS_PRIVATE_NPM_FILE}") elif [[ -n "${SEEK_OSS_PRIVATE_NPM_ENV}" ]] then SEEK_OSS_PRIVATE_NPM_TOKEN="${!SEEK_OSS_PRIVATE_NPM_ENV}" fi -if [[ -z $SEEK_OSS_PRIVATE_NPM_TOKEN ]] +if [[ -z $SEEK_OSS_PRIVATE_NPM_TOKEN ]] then echo ':no_entry_sign: :npm: :package: Failed! A valid NPM_TOKEN could not be determined' exit 1 @@ -38,3 +40,10 @@ mkdir -p "${OUTPUT_FILE%/*}" && cat > $OUTPUT_FILE << EOF ${SEEK_OSS_PRIVATE_NPM_REGISTRY}:_authToken=${SEEK_OSS_PRIVATE_NPM_TOKEN} save-exact=true EOF +if [[ -n "${SEEK_OSS_PRIVATE_NPM_SCOPE}" ]] +then +cat >> $OUTPUT_FILE << EOF +always-auth=true +${SEEK_OSS_PRIVATE_NPM_SCOPE}:registry=https:${SEEK_OSS_PRIVATE_NPM_REGISTRY} +EOF +fi diff --git a/plugin.yml b/plugin.yml index 36798f2..8389b5a 100644 --- a/plugin.yml +++ b/plugin.yml @@ -8,6 +8,8 @@ configuration: properties: registry: type: string + scope: + type: string token: type: string env: @@ -16,4 +18,3 @@ configuration: type: string output-path: type: string - diff --git a/tests/pre-command.bats b/tests/pre-command.bats index 57c6460..30060d5 100644 --- a/tests/pre-command.bats +++ b/tests/pre-command.bats @@ -1,6 +1,12 @@ #!/usr/bin/env bats -load "$BATS_PATH/load.bash" +setup() { + load "$BATS_PLUGIN_PATH/load.bash" + + # Uncomment to enable stub debugging + # export GIT_STUB_DEBUG=/dev/tty +} + teardown() { rm -f .npmrc rm -f ./tests/path/to/project/.npmrc @@ -24,6 +30,7 @@ teardown() { assert_equal "$(head -n1 .npmrc)" '//registry.npmjs.org/:_authToken=abc123' } + @test "reads the token from a file if the file parameter is used" { export BUILDKITE_PLUGIN_PRIVATE_NPM_FILE='my_token_file' echo 'abc123' > my_token_file @@ -32,7 +39,7 @@ teardown() { assert_success assert [ -e '.npmrc' ] - assert_equal "$(head -n1 .npmrc)" '//registry.npmjs.org/:_authToken=abc123' + assert_equal "$(head -n1 .npmrc)" '//registry.npmjs.org/:_authToken=abc123' } @test "fails if the file parameter is used but no file exists" { @@ -117,6 +124,33 @@ teardown() { assert_equal "$(head -n1 .npmrc)" '//myprivateregistry.org/:_authToken=abc123' } +@test "creates a npmrc file with supplied scoped registry path and env" { + export BUILDKITE_PLUGIN_PRIVATE_NPM_ENV='MY_ENV_VAR' + export MY_ENV_VAR='abc123' + export BUILDKITE_PLUGIN_PRIVATE_NPM_REGISTRY='//myprivateregistry.org/' + export BUILDKITE_PLUGIN_PRIVATE_NPM_SCOPE='@myprivatescope' + + run $PWD/hooks/pre-command + + assert_success + assert [ -e '.npmrc' ] + assert_equal "$(head -n1 .npmrc)" '//myprivateregistry.org/:_authToken=abc123' + assert_equal "$(tail -n1 .npmrc)" '@myprivatescope:registry=https://myprivateregistry.org/' +} + +@test "creates a npmrc file with supplied scoped and default registry path and env" { + export BUILDKITE_PLUGIN_PRIVATE_NPM_ENV='MY_ENV_VAR' + export MY_ENV_VAR='abc123' + export BUILDKITE_PLUGIN_PRIVATE_NPM_SCOPE='@myprivatescope' + + run $PWD/hooks/pre-command + + assert_success + assert [ -e '.npmrc' ] + assert_equal "$(head -n1 .npmrc)" '//registry.npmjs.org/:_authToken=abc123' + assert_equal "$(tail -n1 .npmrc)" '@myprivatescope:registry=https://registry.npmjs.org/' +} + @test "creates a npmrc file with supplied output path and token" { export BUILDKITE_PLUGIN_PRIVATE_NPM_TOKEN='abc123' export BUILDKITE_PLUGIN_PRIVATE_NPM_OUTPUT_PATH='./tests/path/to/project/' @@ -135,7 +169,7 @@ teardown() { refute [ -e '.npmrc' ] } -# There is an exclusive relationship between file, env, and token. These tests ensure only value is set and fail with +# There is an exclusive relationship between file, env, and token. These tests ensure only value is set and fail with # a meaninful message otherwise @test "fails if env and file are both set" { export BUILDKITE_PLUGIN_PRIVATE_NPM_FILE='my_token_file' @@ -180,4 +214,4 @@ teardown() { assert_failure assert_output ':no_entry_sign: :npm: :package: Failed! Only one of file, env or token parameters may be set' refute [ -e '.npmrc' ] -} \ No newline at end of file +}