Artifact Registry で private npm パッケージを管理する

こんにちは、CTO室の丸山と申します。普段は某CTOからの無茶振りをさばいたりしています。

今回は、いま開発に携わっているプロダクトにおいて、フロントエンド用のライブラリの管理方法について少し調査したことを共有したいと思います。

概要

最近のアプリケーション開発においては、様々なOSSを活用して開発を進めることが当たり前になっています。 一方で、上記と並行して社内で開発したライブラリを社内限りで使わせたいというニーズもあります。

過去に携わったプロジェクトでは、 jFrogSonatype Nexus Repository を利用してこれを解決したことがありますが、コスト面/管理面に課題があって今回のニーズには合わなそうでした。

  • jFrogは有償版を利用する必要がある
  • Sonatype Nexus Repository を利用する場合は、自前でサーバーを構築する必要がある

これらを回避しつつ上記ニーズを解決できないかを探ったところ、npm2系から取り入れられた スコープ の概念と Artifact Registry を組み合わせればあっさりと実現できることがわかりました。

そこで、ライブラリ開発者目線/ライブラリ利用者目線でArtifact Registryを利用したprivate npmパッケージの管理方法を簡単に紹介します。

全体イメージ

  • Artifact Registry で private な npm パッケージを管理するためのリポジトリを作成する
  • 公開範囲を絞りたいライブラリ群は、上記privateリポジトリに公開する
  • ライブラリを利用するアプリケーションは、利用するライブラリに応じてnpmリポジトリを使い分ける
    • 公開範囲を絞ったライブラリは、上記privateリポジトリからインストールする
    • 上記以外のOSSは通常通りnpmjs.com からインストールする

イメージ

private npm リポジトリを作成する

プライベート npm パッケージの公開先として、Artifacts Registry を使用します。 gcloud コマンドを使用して以下のようにリポジトリを作成します。

# 設定値は全て架空の値を使用しています 
artifacts repositories create sot-private-npm-repo \
    --repository-format=npm \
    --location=us-central1 \
    --description="Private repository for Node.js packages for SOT dev team" \
    [--project=sot-private-project]

なお、以降のサンプルでは以下の前提で進めますが、適用する開発プロジェクトに合わせて適宜読み替えてください。

  • GCP のプロジェクトとして sot-private-project という架空の GCP プロジェクトを作成
  • Artifact Registry はアイオワリージョン(us-central1)に作成
  • npm リポジトリの名前は sot-private-npm-repo として作成

ライブラリの開発者目線

モチベーション

社内で開発したライブラリを社内限りで共有したい

ライブラリの作成から公開までの流れ

まずは、公開範囲を社内に絞るという意図のもと、 @sot-private スコープの npm パッケージを作成します。

mkdir awesome_lib && cd awesome_lib
npm init --scope=@sot-private

初期化作業を完了すると、以下のような package.json が作成されます。

{
  "name": "@sot-private/awsome_lib",
  "version": "1.0.0",
  "description": "Awesome library for SO Technologies dev team.",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

次に、 @sot-private スコープで修飾した npm パッケージの公開先として、さきほどArtifacts Registry内に構築した sot-private-npm-repo を指定します。

# 設定値は全て架空の値を使用しています 
gcloud artifacts print-settings npm --scope=@sot-private \
    --repository=sot-private-npm-repo \
    --location=us-central1 \
    [--project=sot-private-project]

コマンドを実行した結果、以下のような内容で認証情報が表示されるので、これをライブラリプロジェクト直下の .npmrc ファイルとして保存します。この設定により、@sot-private スコープのライブラリの公開先としてArtifact Registory に作成された private リポジトリが関連付けられます。

# 設定値は全て架空の値を使用しています 
# Insert the following snippet into your project .npmrc

@sot-private:registry=https://us-central1-npm.pkg.dev/sot-private-project/sot-private-npm-repo/
//us-central1-npm.pkg.dev/sot-private-project/sot-private-npm-repo/:always-auth=true

Artifacts Registry へのアクセスには GCP の認証情報が必要になります。 Googleが提供する google-artifactregistry-auth ライブラリを使用することで認証処理の複雑さを隠蔽することができます。

先程作成した package.json に以下の情報を追記し、

    "scripts": {
        "artifactregistry-login": "npx google-artifactregistry-auth",
    }

以下のコマンドを実行することで、Artifact Registryに認証することができます(認証情報の期限は1時間)。

npm run artifactregistry-login

この方法で認証すると、認証後にユーザーのホームディレクトリに.npmrcが作成され、そこにArtifactRegistryにアクセスするためのアクセストークンが保存されます。

プロジェクト配下に作成した .npmrc からは認証情報を分離することができるので、プロジェクト配下の .npmrc はそのままバージョン管理に含めても問題ありません。

# Macの場合、ユーザーのホームディレクトリ配下に.npmrcが作成される
% cat ~/.npmrc

//us-central1-npm.pkg.dev/sot-private-project/sot-private-npm-repo/:_authToken=XXXXXXXXXXXXXXXXX

開発したライブラリは、以下のように公開することができます。

# 設定値は全て架空の値を使用しています 
npm publish \
    --scope=@sot-private \
    --registry=https://us-central1-npm.pkg.dev/sot-private-project/sot-private-npm-repo/

publishが成功すると、privateリポジトリ配下に以下のようにライブラリがアップロードされていることが確認できます。

arifact registry

ライブラリの利用者目線

モチベーション

社内で開発したライブラリを利用しつつ、他のライブラリは通常通りnpmjsからインストールしたい

アプリケーションでライブラリを利用するまでの流れ

さきほど公開した公開範囲を絞ったライブラリを使用して、アプリケーションを作成します。 まずは、アプリケーションのプロジェクトを作成します。

mkdir awesome_apps & cd awesome_apps
npm init

初期化作業を完了すると、以下のような package.json が作成されます。

{
  "name": "awesome_apps",
  "version": "1.0.0",
  "description": "Awsome apps which uses awesome library.",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

次に、必要なライブラリを追加していきます。例えば emoji-regex を使いたかったら、以下のように追加することで、npmjs.comからインストールすることができます。

npm install emoji-regex

一方で、@sot-privateスコープのライブラリを利用するには、ライブラリの取得先として先程のprivateリポジトリを指定する必要があります。 先程と同様にプロジェクト直下に .npmrc ファイルを作成し、以下の情報を書き込みます。

@sot-private:registry=https://us-central1-npm.pkg.dev/sot-private-project/sot-private-npm-repo/
//us-central1-npm.pkg.dev/sot-private-project/sot-private-npm-repo/:always-auth=true

また、Artifacts Registry へのアクセスには GCP の認証情報が必要になるため、先程と同様に google-artifactregistry-auth を使用して認証します。

先程作成した awsome_apppackage.json に以下の情報を追記し、

    "scripts": {
        "artifactregistry-login": "npx google-artifactregistry-auth",
    }

以下のコマンドを実行することで、Artifact Registryに認証することができます(認証情報の期限は1時間)。

npm run artifactregistry-login

認証が完了していれば、先程作成した @sot-privateスコープのライブラリを private な npm リポジトリからインストールすることができます。

npm install @sot-private/awesome_lib

なお、予め package.jsondependencies に書いておけば、npm install コマンドで一括してインストールすることもできます。

{
  "name": "awesome_apps",
  "version": "1.0.0",
  "description": "Awsome apps which uses awesome library.",
  "main": "index.js",
  "scripts": {
    "artifactregistry-login": "npx google-artifactregistry-auth",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@sot-private/awsome_lib": "^1.0.0",
    "emoji-regex": "^10.2.1"
  }
}

インストールされたライブラリは node_modules 配下に以下のように配置されます。 特に、スコープを指定されているライブラリは、@sot-private配下に配置されます。

.
├── node_modules
│   ├── @sot-private
│   │   └── awsome_lib
│   └── emoji-regex
├── package-lock.json
└── package.json

また、スコープを指定されたライブラリを利用するには、以下のようにインポートします。

// 架空のライブラリなので実際には動きません
const awesome_lib = require('@sot-private/awesome_lib');

まとめ

思っていたよりもあっさりとArtifact Registryでprivate npmパッケージを管理することができました。

今回は時間の関係で調査しきれませんでしたが、以下の観点でもう少し調査をすると実際の開発プロジェクトにも安心して適用できそうです。

  • CI/CD環境などの google-artifactregistry-auth ライブラリが使えない環境でビルドをしたいときに、どのようにArtifact Registryからnpmパッケージを取得するか
  • 利用者目線のユーザーにはArtifact Registryへの参照権限だけを与えることはできそうか
    • npm install は許容するが、 npm publish は許容しないといったことはできるか

少しでも参考になれば幸いです。