Skip to content

Commit

Permalink
Scoped registry (#9)
Browse files Browse the repository at this point in the history
* Add custom scope

* Lint README

* Fix up workflow and docker compose

* Lint

* Quiet docker output

* proper quiet for pull

* Fix BATS

* Lint

* Add tests

* Update README.md

* Change scope URL

* Add always-auth for legacy managers

* Protocol-Relative URL info

* Bump version

---------

Co-authored-by: Sam Chung <[email protected]>
  • Loading branch information
lantrix and samchungy authored Nov 8, 2024
1 parent ef8c8c3 commit 4256a4d
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 33 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
run: docker compose run --quiet-pull tests
58 changes: 37 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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"
```
Expand All @@ -27,77 +26,94 @@ 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
> is unstable and has a tedency to return an empty string in the context of this plugin.

### `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))
5 changes: 2 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
version: '3.4'
services:
tests:
image: buildkite/plugin-tester
volumes:
- ".:/plugin"
lint:
image: buildkite/plugin-linter
command: ['--name', 'seek-oss/private-npm']
command: ["--name", "seek-oss/private-npm"]
volumes:
- ".:/plugin:ro"
- ".:/plugin:ro"
11 changes: 10 additions & 1 deletion hooks/pre-command
Original file line number Diff line number Diff line change
Expand Up @@ -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:-''}
Expand All @@ -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
Expand All @@ -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
3 changes: 2 additions & 1 deletion plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ configuration:
properties:
registry:
type: string
scope:
type: string
token:
type: string
env:
Expand All @@ -16,4 +18,3 @@ configuration:
type: string
output-path:
type: string

42 changes: 38 additions & 4 deletions tests/pre-command.bats
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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" {
Expand Down Expand Up @@ -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/'
Expand All @@ -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'
Expand Down Expand Up @@ -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' ]
}
}

0 comments on commit 4256a4d

Please sign in to comment.