CLOVER🍀

That was when it all began.

Sonatype Nexus 3/2で、REST API+Groovyスクリプトを使ってリポジトリを作る

これは、なにをしたくて書いたもの?

  • Sonatype Nexusを使ってリポジトリを作るのに、Web Consoleにログインして作るのが面倒だなーと思って他の方法は?と
  • できれば、Sonatype Nexus 3と2、それぞれで知りたい
  • REST APIと、スクリプトが使えるらしい
  • 試してみよう

今回は、MavenリポジトリとnpmリポジトリをWeb Consoleを使うことなく作成してみたいと思います。

環境

Sonatype Nexusは、今回はDockerイメージを使用しました。

Sonatype Nexus 3。バージョンは、3.13.0-01です。

sonatype/nexus3

起動。
※タグは3.13.0ですが、中身は3.13.0-01です

$ docker container run -it --rm --name nexus3 sonatype/nexus3:3.13.0

Sonatype Nexus 2。バージョンは、2.14.9-01です。

sonatype/nexus

起動。

$ docker container run -it --rm --name nexus sonatype/nexus:2.14.9-01

Sonatype Nexus 3

Sonatype Nexus 3では、REST APIスクリプトを使用します。

REST and Integration API

例えば、リポジトリの一覧は、Repositories APIで確認することができます。

Repositories API

起動直後だと、こんな感じです。

$ curl -XGET -u admin:admin123 http://172.17.0.2:8081/service/rest/v1/repositories
[ {
  "name" : "nuget-group",
  "format" : "nuget",
  "type" : "group",
  "url" : "http://172.17.0.2:8081/repository/nuget-group"
}, {
  "name" : "maven-snapshots",
  "format" : "maven2",
  "type" : "hosted",
  "url" : "http://172.17.0.2:8081/repository/maven-snapshots"
}, {
  "name" : "maven-central",
  "format" : "maven2",
  "type" : "proxy",
  "url" : "http://172.17.0.2:8081/repository/maven-central"
}, {
  "name" : "nuget.org-proxy",
  "format" : "nuget",
  "type" : "proxy",
  "url" : "http://172.17.0.2:8081/repository/nuget.org-proxy"
}, {
  "name" : "maven-releases",
  "format" : "maven2",
  "type" : "hosted",
  "url" : "http://172.17.0.2:8081/repository/maven-releases"
}, {
  "name" : "nuget-hosted",
  "format" : "nuget",
  "type" : "hosted",
  "url" : "http://172.17.0.2:8081/repository/nuget-hosted"
}, {
  "name" : "maven-public",
  "format" : "maven2",
  "type" : "group",
  "url" : "http://172.17.0.2:8081/repository/maven-public"
}

これらのAPIは、NexusのWeb Consoleからも確認することができます。

f:id:Kazuhira:20180909002349p:plain

Mavenリポジトリを作成する

それでは、まずはMavenリポジトリを作成してみます。

スクリプトやリクエストのサンプルは、こちらにあります。

https://github.com/sonatype/nexus-book-examples/tree/nexus-3.x/scripting/simple-shell-example

Script API / Examples

スクリプトとして登録する、JSONファイルを作成します。 create-maven.json

{
    "name": "create-maven",
    "type": "groovy",
    "content": "repository.createMavenHosted('my-maven-hosted-repo'); repository.createMavenProxy('my-maven-proxy-repo', 'https://repo.maven.apache.org/maven2'); repository.createMavenGroup('my-maven-group-repo', ['my-maven-hosted-repo', 'my-maven-proxy-repo'])"
}

nameがスクリプトの名前で、typeがスクリプトの言語です。スクリプトに使える言語はGroovyのみなようなので、
「groovy」と指定します。

Writing Scripts

The scripting language used on the repository manager is Groovy. Any editor can be used to author the scripts.

contentは、実行するスクリプトです。セミコロンで区切ってリポジトリを3つ作っているので、ちょっと見づらいですが…。

今回は、以下の3つを作成します。

  • Hosted Repository
  • Proxy Repository(Maven Central RepositoryのProxy)
  • Group Repository

スクリプト内でいくと、Hosted Repository。第1引数は、リポジトリ名です。

repository.createMavenHosted('my-maven-hosted-repo');

Proxy Repository。第2引数は、プロキシ先のURLですね。

repository.createMavenProxy('my-maven-proxy-repo', 'https://repo.maven.apache.org/maven2');

そして、これらをまとめたGroup Repository。

repository.createMavenGroup('my-maven-group-repo', ['my-maven-hosted-repo', 'my-maven-proxy-repo'])

記述はGroovyなので、今回のように複数の記述をしていなければ、セミコロンはなくてもOKです。

repository.createMavenHosted('my-maven-hosted-repo')

で、このスクリプトを登録します。要認証。

$ curl -u admin:admin123 -i -H "Content-Type: application/json" 'http://172.17.0.2:8081/service/rest/v1/script/' -d @create-maven.json 
HTTP/1.1 204 No Content
Date: Sat, 08 Sep 2018 14:20:01 GMT
Server: Nexus/3.13.0-01 (OSS)
X-Content-Type-Options: nosniff

「http://ホスト名:ポート/service/rest/v1/script/」が、エンドポイントです。

実行には、この登録したスクリプトを呼び出すことで行います。

$ curl -u admin:admin123 -i -XPOST -H "Content-Type: text/plain" 'http://172.17.0.2:8081/service/rest/v1/script/create-maven/run'
HTTP/1.1 200 OK
Date: Sat, 08 Sep 2018 14:27:41 GMT
Server: Nexus/3.13.0-01 (OSS)
X-Content-Type-Options: nosniff
Content-Type: application/json
Content-Length: 142

{
  "name" : "create-maven",
  "result" : "RepositoryImpl$$EnhancerByGuice$$c5f0822b{type=group, format=maven2, name='my-maven-group-repo'}"

「http://ホスト名:ポート/service/rest/v1/script/[登録したスクリプトのname]]/run」が、エンドポイントになります。

これで、リポジトリが作成されました。

確認。

$ curl -XGET -u admin:admin123 http://172.17.0.2:8081/service/rest/v1/repositories

追加分です。

, {
  "name" : "my-maven-proxy-repo",
  "format" : "maven2",
  "type" : "proxy",
  "url" : "http://172.17.0.2:8081/repository/my-maven-proxy-repo"
}, {
  "name" : "my-maven-hosted-repo",
  "format" : "maven2",
  "type" : "hosted",
  "url" : "http://172.17.0.2:8081/repository/my-maven-hosted-repo"
}, {
  "name" : "my-maven-group-repo",
  "format" : "maven2",
  "type" : "group",
  "url" : "http://172.17.0.2:8081/repository/my-maven-group-repo"
}

不要になったスクリプトは、削除。

$ curl -u admin:admin123 -i -XDELETE 'http://172.17.0.2:8081/service/rest/v1/script/create-maven'
HTTP/1.1 204 No Content
Date: Sat, 08 Sep 2018 15:30:32 GMT
Server: Nexus/3.13.0-01 (OSS)
X-Content-Type-Options: nosniff

更新はPUTで行うなどしますが、スクリプトに関するREST APIはこちらで。

Script API

ところで、このGroovyスクリプトでどういうことが書けるのか、よくわかりませんよね?

暗黙的に、以下の変数が使えるようになっているようです。

  • core
  • repository
  • blobStore
  • security

Writing Scripts

しかし、JavadocはJAR形態での提供のみです。

Writing Scripts

Development environments such as IntelliJ IDEA or Eclipse IDE can download the relevant JavaDoc and Sources JAR files to ease your development. Typically you would create your scripts in src/main/groovy or src/main/scripts.

オンラインでAPI見れないんですかぃ…。

あとは、ソースコードを見る感じですね。今回のRepositoryに関する部分であれば、こちらのようです。
Groovyのデフォルト引数を使っていくつか省略しているものがあるのですが、完全な定義、指摘できる引数を
確認したければ見ておくとよいでしょう。

https://github.com/sonatype/nexus-public/blob/release-3.13.0-01/plugins/nexus-script-plugin/src/main/java/org/sonatype/nexus/script/plugin/RepositoryApi.java

https://github.com/sonatype/nexus-public/blob/release-3.13.0-01/plugins/nexus-script-plugin/src/main/java/org/sonatype/nexus/script/plugin/internal/provisioning/RepositoryApiImpl.groovy

その他のオブジェクトは、こちらのようで。

https://github.com/sonatype/nexus-public/blob/release-3.13.0-01/components/nexus-core/src/main/java/org/sonatype/nexus/CoreApi.java

https://github.com/sonatype/nexus-public/blob/release-3.13.0-01/components/nexus-core/src/main/java/org/sonatype/nexus/BlobStoreApi.java

https://github.com/sonatype/nexus-public/blob/release-3.13.0-01/components/nexus-security/src/main/java/org/sonatype/nexus/security/SecurityApi.java

npmリポジトリを作成する

続いて、npmリポジトリ

Mavenリポジトリとほぼ同じなので、いろいろ省略。

Nexusに登録する、スクリプトの定義は、こちら。 create-npm.json

{
    "name": "create-npm",
    "type": "groovy",
    "content": "repository.createNpmHosted('my-npm-hosted-repo'); repository.createNpmProxy('my-npm-proxy-repo','https://registry.npmjs.org'); repository.createNpmGroup('my-npm-group-repo', ['my-npm-hosted-repo', 'my-npm-proxy-repo'])"
}

こちらも、以下の3つを作成します。

  • Hosted Repository
  • Proxy Repository(npm RepositoryのProxy)
  • Group Repository

スクリプトの部分を抜粋すると、こうですね。

repository.createNpmHosted('my-npm-hosted-repo')
repository.createNpmProxy('my-npm-proxy-repo','https://registry.npmjs.org')
repository.createNpmGroup('my-npm-group-repo', ['my-npm-hosted-repo', 'my-npm-proxy-repo'])

登録。

$ curl -u admin:admin123 -i -H "Content-Type: application/json" 'http://172.17.0.2:8081/service/rest/v1/script/' -d @create-npm.json 
HTTP/1.1 204 No Content
Date: Sat, 08 Sep 2018 16:02:06 GMT
Server: Nexus/3.13.0-01 (OSS)
X-Content-Type-Options: nosniff

スクリプト実行。

$ curl -u admin:admin123 -i -XPOST -H "Content-Type: text/plain" 'http://172.17.0.2:8081/service/rest/v1/script/create-npm/run'
HTTP/1.1 200 OK
Date: Sat, 08 Sep 2018 16:03:29 GMT
Server: Nexus/3.13.0-01 (OSS)
X-Content-Type-Options: nosniff
Content-Type: application/json
Content-Length: 135

{
  "name" : "create-npm",
  "result" : "RepositoryImpl$$EnhancerByGuice$$c5f0822b{type=group, format=npm, name='my-npm-group-repo'}"

確認。

$ curl -XGET -u admin:admin123 http://172.17.0.2:8081/service/rest/v1/repositories

抜粋結果は、こちら。

{
  "name" : "my-npm-proxy-repo",
  "format" : "npm",
  "type" : "proxy",
  "url" : "http://172.17.0.2:8081/repository/my-npm-proxy-repo"
}, {
  "name" : "my-npm-hosted-repo",
  "format" : "npm",
  "type" : "hosted",
  "url" : "http://172.17.0.2:8081/repository/my-npm-hosted-repo"
}, {
  "name" : "my-npm-group-repo",
  "format" : "npm",
  "type" : "group",
  "url" : "http://172.17.0.2:8081/repository/my-npm-group-repo"
}

npmリポジトリは、これで作成完了です。

Sonatype Nexus 2

Sonatype Nexus 2の場合は、REST APIを使用します。

Plugins and the REST API

オンラインのドキュメントはこれ以上ないので、Web Consoleにログイン後、「Administration」→「Plugin Console」を選び、
Nexus Core API (Restlet 1.x Plugin)」を選択すると、詳細部分に「Documentation」が表示されるので、こちらを参照します。 f:id:Kazuhira:20180909193646p:plain

こんな感じです。 f:id:Kazuhira:20180909193650p:plain

これで各APIのエンドポイントやサポートしているHTTPメソッドはわかるのですが、パラメーターの詳細が全然わからないので、
既存のリポジトリの情報をREST APIで参照したり、Web Consoleで1度作成してREST APIで参照したりするとよいでしょう。
XML Schemaを見ることはできるのですが、それだけだと参考にならない&パラメーター足りなかったり…

Hosted、Proxy Repositoryの確認に使うAPIは、こちら。

リポジトリの一覧。
HTTP GET / http://[ホスト]:[ポート]/nexus/service/local/repositories

リポジトリの詳細。
HTTP GET / http://[ホスト]:[ポート]/nexus/service/local/repositories/[リポジトリのID]

Group Repositoryは、エンドポイントが変わります。

リポジトリの一覧。
HTTP GET / http://[ホスト]:[ポート]/nexus/service/local/repo_groups

リポジトリの詳細。
HTTP GET / http://[ホスト]:[ポート]/nexus/service/local/repo_groups/[リポジトリのID]

例えば、Sonatype Nexus 2のインストール直後のHosted、Proxy Repositoryの一覧は、このようになります。
※長いので、いくつか省略しました
※アクセスは、要認証

$ curl -u admin:admin123 http://172.17.0.2:8081/nexus/service/local/repositories
<repositories>
  <data>
    <repositories-item>
      <resourceURI>http://172.17.0.2:8081/nexus/service/local/repositories/central</resourceURI>
      <contentResourceURI>http://172.17.0.2:8081/nexus/content/repositories/central</contentResourceURI>
      <id>central</id>
      <name>Central</name>
      <repoType>proxy</repoType>
      <repoPolicy>RELEASE</repoPolicy>
      <provider>maven2</provider>
      <providerRole>org.sonatype.nexus.proxy.repository.Repository</providerRole>
      <format>maven2</format>
      <userManaged>true</userManaged>
      <exposed>true</exposed>
      <effectiveLocalStorageUrl>file:/sonatype-work/storage/central</effectiveLocalStorageUrl>
      <remoteUri>https://repo.maven.apache.org/maven2</remoteUri>
    </repositories-item>

   ...

    <repositories-item>
      <resourceURI>http://172.17.0.2:8081/nexus/service/local/repositories/central-m1</resourceURI>
      <contentResourceURI>http://172.17.0.2:8081/nexus/content/shadows/central-m1</contentResourceURI>
      <id>central-m1</id>
      <name>Central M1 shadow</name>
      <repoType>virtual</repoType>
      <repoPolicy>RELEASE</repoPolicy>
      <provider>m2-m1-shadow</provider>
      <providerRole>org.sonatype.nexus.proxy.repository.ShadowRepository</providerRole>
      <format>maven1</format>
      <userManaged>true</userManaged>
      <exposed>true</exposed>
      <effectiveLocalStorageUrl>file:/sonatype-work/storage/central-m1</effectiveLocalStorageUrl>
    </repositories-item>

   ...

    <repositories-item>
      <resourceURI>http://172.17.0.2:8081/nexus/service/local/repositories/releases</resourceURI>
      <contentResourceURI>http://172.17.0.2:8081/nexus/content/repositories/releases</contentResourceURI>
      <id>releases</id>
      <name>Releases</name>
      <repoType>hosted</repoType>
      <repoPolicy>RELEASE</repoPolicy>
      <provider>maven2</provider>
      <providerRole>org.sonatype.nexus.proxy.repository.Repository</providerRole>
      <format>maven2</format>
      <userManaged>true</userManaged>
      <exposed>true</exposed>
      <effectiveLocalStorageUrl>file:/sonatype-work/storage/releases</effectiveLocalStorageUrl>
    </repositories-item>
  </data>
</repositories>

デフォルトで作成されている、MavenのHosted Repositoryの情報。

$ curl -u admin:admin123 http://172.17.0.2:8081/nexus/service/local/repositories/releases
<repository>
  <data>
    <contentResourceURI>http://172.17.0.2:8081/nexus/content/repositories/releases</contentResourceURI>
    <id>releases</id>
    <name>Releases</name>
    <provider>maven2</provider>
    <providerRole>org.sonatype.nexus.proxy.repository.Repository</providerRole>
    <format>maven2</format>
    <repoType>hosted</repoType>
    <exposed>true</exposed>
    <writePolicy>ALLOW_WRITE_ONCE</writePolicy>
    <browseable>true</browseable>
    <indexable>true</indexable>
    <notFoundCacheTTL>1440</notFoundCacheTTL>
    <repoPolicy>RELEASE</repoPolicy>
    <downloadRemoteIndexes>false</downloadRemoteIndexes>
    <defaultLocalStorageUrl>file:/sonatype-work/storage/releases</defaultLocalStorageUrl>
  </data>
</repository>

こんな感じです。

XMLの内容は、Web Consoleで確認することができる情報ほぼそのままなので、こちらを参考にリクエストとなるXMLを作成して
POSTします。

Mavenリポジトリを作成する

では、まずはMavenリポジトリから作成します。

作成するリポジトリは、Nexus 3の時と同じように、

  • Hosted Repository
  • Proxy Repository
  • Group Repository

それぞれひとつずつ作成していきます。

Hosted Repository。 create-maven2-hosted.xml

<?xml version="1.0" encoding="UTF-8"?>
<repository>
  <data>
    <id>my-maven-hosted-repo</id>
    <name>My Maven Hosted Repo</name>
    <repoType>hosted</repoType>
    <repoPolicy>RELEASE</repoPolicy>
    <provider>maven2</provider>
    <providerRole>org.sonatype.nexus.proxy.repository.Repository</providerRole>
    <format>maven2</format>
    <exposed>true</exposed>
    <browseable>true</browseable>
    <indexable>true</indexable>
    <notFoundCacheTTL>1440</notFoundCacheTTL>
  </data>
</repository>

URIやストレージのファイルパスの指定がありませんが、これはNexus側が設定してくれます。

idはURLのキーになったりしますし、providerやformatでMaven Repositoryであることを指定するなど、
けっこういろいろ設定する必要があります。はしょるとデフォルト値となるのですが、これがWeb Consoleで
デフォルト状態で作成した場合とは同じにならない…。

作成。

$ curl -u admin:admin123 -i -XPOST -H 'Content-Type: application/xml' http://172.17.0.2:8081/nexus/service/local/repositories -d @create-maven2-hosted.xml

結果。

HTTP/1.1 201 Created
Date: Sun, 09 Sep 2018 09:12:29 GMT
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Server: Nexus/2.14.9-01 Noelios-Restlet-Engine/1.1.6-SONATYPE-5348-V8
Content-Type: application/xml; charset=UTF-8
Content-Length: 789

<repository>
  <data>
    <contentResourceURI>http://172.17.0.2:8081/nexus/content/repositories/my-maven-hosted-repo</contentResourceURI>
    <id>my-maven-hosted-repo</id>
    <name>My Maven Hosted Repo</name>
    <provider>maven2</provider>
    <providerRole>org.sonatype.nexus.proxy.repository.Repository</providerRole>
    <format>maven2</format>
    <repoType>hosted</repoType>
    <exposed>true</exposed>
    <writePolicy>ALLOW_WRITE_ONCE</writePolicy>
    <browseable>true</browseable>
    <indexable>true</indexable>
    <notFoundCacheTTL>1440</notFoundCacheTTL>
    <repoPolicy>RELEASE</repoPolicy>
    <downloadRemoteIndexes>false</downloadRemoteIndexes>
    <defaultLocalStorageUrl>file:/sonatype-work/storage/my-maven-hosted-repo</defaultLocalStorageUrl>
  </data>
</repository>

続いて、Proxy Repositoryを作成します。

create-maven2-proxy.xml

<?xml version="1.0" encoding="UTF-8"?>
<repository>
  <data>
    <id>my-maven-proxy-repo</id>
    <name>My Maven Proxy Repo</name>
    <provider>maven2</provider>
    <providerRole>org.sonatype.nexus.proxy.repository.Repository</providerRole>
    <format>maven2</format>
    <repoType>proxy</repoType>
    <exposed>true</exposed>
    <writePolicy>READ_ONLY</writePolicy>
    <browseable>true</browseable>
    <indexable>true</indexable>
    <notFoundCacheTTL>1440</notFoundCacheTTL>
    <repoPolicy>RELEASE</repoPolicy>
    <checksumPolicy>WARN</checksumPolicy>
    <downloadRemoteIndexes>false</downloadRemoteIndexes>
    <remoteStorage>
      <remoteStorageUrl>https://repo.maven.apache.org/maven2</remoteStorageUrl>
    </remoteStorage>
    <fileTypeValidation>true</fileTypeValidation>
    <artifactMaxAge>-1</artifactMaxAge>
    <metadataMaxAge>1440</metadataMaxAge>
    <itemMaxAge>1440</itemMaxAge>
    <autoBlockActive>true</autoBlockActive>
  </data>
</repository>

こちらは、repoTypeがproxyとなり、remoteStorageおよびremoteStrageUrlを設定する必要があります。

作成。

$ curl -u admin:admin123 -i -XPOST -H 'Content-Type: application/xml' http://172.17.0.2:8081/nexus/service/local/repositories -d @create-maven2-proxy.xml

結果。

HTTP/1.1 201 Created
Date: Sun, 09 Sep 2018 09:13:01 GMT
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Server: Nexus/2.14.9-01 Noelios-Restlet-Engine/1.1.6-SONATYPE-5348-V8
Content-Type: application/xml; charset=UTF-8
Content-Length: 1145

<repository>
  <data>
    <contentResourceURI>http://172.17.0.2:8081/nexus/content/repositories/my-maven-proxy-repo</contentResourceURI>
    <id>my-maven-proxy-repo</id>
    <name>My Maven Proxy Repo</name>
    <provider>maven2</provider>
    <providerRole>org.sonatype.nexus.proxy.repository.Repository</providerRole>
    <format>maven2</format>
    <repoType>proxy</repoType>
    <exposed>true</exposed>
    <writePolicy>READ_ONLY</writePolicy>
    <browseable>true</browseable>
    <indexable>true</indexable>
    <notFoundCacheTTL>1440</notFoundCacheTTL>
    <repoPolicy>RELEASE</repoPolicy>
    <checksumPolicy>WARN</checksumPolicy>
    <downloadRemoteIndexes>false</downloadRemoteIndexes>
    <defaultLocalStorageUrl>file:/sonatype-work/storage/my-maven-proxy-repo</defaultLocalStorageUrl>
    <remoteStorage>
      <remoteStorageUrl>https://repo.maven.apache.org/maven2</remoteStorageUrl>
    </remoteStorage>
    <fileTypeValidation>true</fileTypeValidation>
    <artifactMaxAge>-1</artifactMaxAge>
    <metadataMaxAge>1440</metadataMaxAge>
    <itemMaxAge>1440</itemMaxAge>
    <autoBlockActive>true</autoBlockActive>
  </data>
</repository>

最後、Group Repositoryです。

こちらは、タグの構造がだいぶ変わります。 create-maven2-group.xml

<?xml version="1.0" encoding="UTF-8"?>
<repo-group>
  <data>
    <id>my-maven-group-repo</id>
    <name>My Maven Group Repo</name>
    <provider>maven2</provider>
    <format>maven2</format>
    <repoType>group</repoType>
    <exposed>true</exposed>
    <repositories>
      <repo-group-member>
        <id>my-maven-hosted-repo</id>
      </repo-group-member>
      <repo-group-member>
        <id>my-maven-proxy-repo</id>
      </repo-group-member>
    </repositories>
  </data>
</repo-group>

repositories / repo-group-member内に、追加するリポジトリのidを指定すればOKです。nameなどは、Nexus側が設定してくれます。

エンドポイントは、HostedやProxy Repositoryと異なるので、注意。

$ curl -u admin:admin123 -i -XPOST -H 'Content-Type: application/xml' http://172.17.0.2:8081/nexus/service/local/repo_groups -d @create-maven2-group.xml

結果。

HTTP/1.1 201 Created
Date: Sun, 09 Sep 2018 09:13:26 GMT
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Server: Nexus/2.14.9-01 Noelios-Restlet-Engine/1.1.6-SONATYPE-5348-V8
Content-Type: application/xml; charset=UTF-8
Content-Length: 867

<repo-group>
  <data>
    <contentResourceURI>http://172.17.0.2:8081/nexus/content/groups/my-maven-group-repo</contentResourceURI>
    <id>my-maven-group-repo</id>
    <name>My Maven Group Repo</name>
    <provider>maven2</provider>
    <format>maven2</format>
    <repoType>group</repoType>
    <exposed>true</exposed>
    <repositories>
      <repo-group-member>
        <id>my-maven-hosted-repo</id>
        <name>My Maven Hosted Repo</name>
        <resourceURI>http://172.17.0.2:8081/nexus/service/local/repo_groups/my-maven-hosted-repo</resourceURI>
      </repo-group-member>
      <repo-group-member>
        <id>my-maven-proxy-repo</id>
        <name>My Maven Proxy Repo</name>
        <resourceURI>http://172.17.0.2:8081/nexus/service/local/repo_groups/my-maven-proxy-repo</resourceURI>
      </repo-group-member>
    </repositories>
  </data>
</repo-group>

これで、MavenリポジトリREST APIで作れるようになりました、と。

npmリポジトリを作成する

最後は、npmリポジトリを作成します。こちらも、3種類のリポジトリを作成しましょう。

npm Hosted Repositoryから。

create-npm-hosted.xml

<?xml version="1.0" encoding="UTF-8"?>
<repository>
  <data>
    <id>my-npm-hosted-repo</id>
    <name>My npm Hosted Repo</name>
    <repoType>hosted</repoType>
    <provider>npm-hosted</provider>
    <providerRole>org.sonatype.nexus.proxy.repository.Repository</providerRole>
    <format>npm</format>
    <exposed>true</exposed>
    <writePolicy>ALLOW_WRITE_ONCE</writePolicy>
    <browseable>true</browseable>
    <indexable>false</indexable>
    <notFoundCacheTTL>1440</notFoundCacheTTL>
    <repoPolicy>MIXED</repoPolicy>
    <checksumPolicy>IGNORE</checksumPolicy>
    <downloadRemoteIndexes>false</downloadRemoteIndexes>
  </data>
</repository>

providerが、「npm-hosted」となっているところがポイントです。Mavenの時は、Hosted、Proxy、Groupに関わらず「maven2」でした。
npmの場合は、Hosted、Proxy、Groupそれぞれで異なります。

あと、repoPolicyやchecksumPolicy、indexableなどもMavenリポジトリの時とは異なります。
1度Web Consoleからリポジトリを作成して、内容をREST APIで確認してみるとよいでしょう。

作成。

$ curl -u admin:admin123 -i -XPOST -H 'Content-Type: application/xml' http://172.17.0.2:8081/nexus/service/local/repositories -d @create-npm-hosted.xml

結果。

HTTP/1.1 201 Created
Date: Sun, 09 Sep 2018 11:00:38 GMT
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Server: Nexus/2.14.9-01 Noelios-Restlet-Engine/1.1.6-SONATYPE-5348-V8
Content-Type: application/xml; charset=UTF-8
Content-Length: 825

<repository>
  <data>
    <contentResourceURI>http://172.17.0.2:8081/nexus/content/repositories/my-npm-hosted-repo</contentResourceURI>
    <id>my-npm-hosted-repo</id>
    <name>My npm Hosted Repo</name>
    <provider>npm-hosted</provider>
    <providerRole>org.sonatype.nexus.proxy.repository.Repository</providerRole>
    <format>npm</format>
    <repoType>hosted</repoType>
    <exposed>true</exposed>
    <writePolicy>ALLOW_WRITE_ONCE</writePolicy>
    <browseable>true</browseable>
    <indexable>false</indexable>
    <notFoundCacheTTL>1440</notFoundCacheTTL>
    <repoPolicy>MIXED</repoPolicy>
    <checksumPolicy>IGNORE</checksumPolicy>
    <downloadRemoteIndexes>false</downloadRemoteIndexes>
    <defaultLocalStorageUrl>file:/sonatype-work/storage/my-npm-hosted-repo</defaultLocalStorageUrl>
  </data>
</repository>

Proxy Repository。

create-npm-proxy.xml

<?xml version="1.0" encoding="UTF-8"?>
<repository>
  <data>
    <id>my-npm-proxy-repo</id>
    <name>My npm Proxy Repo</name>
    <repoType>proxy</repoType>
    <repoPolicy>MIXED</repoPolicy>
    <provider>npm-proxy</provider>
    <providerRole>org.sonatype.nexus.proxy.repository.Repository</providerRole>
    <format>npm</format>
    <exposed>true</exposed>
    <writePolicy>READ_ONLY</writePolicy>
    <browseable>true</browseable>
    <indexable>false</indexable>
    <notFoundCacheTTL>1440</notFoundCacheTTL>
    <checksumPolicy>IGNORE</checksumPolicy>
    <downloadRemoteIndexes>false</downloadRemoteIndexes>
    <remoteStorage>
      <remoteStorageUrl>https://registry.npmjs.org</remoteStorageUrl>
    </remoteStorage>
    <fileTypeValidation>true</fileTypeValidation>
    <artifactMaxAge>0</artifactMaxAge>
    <metadataMaxAge>0</metadataMaxAge>
    <itemMaxAge>1440</itemMaxAge>
    <autoBlockActive>true</autoBlockActive>
  </data>
</repository>

こちらは、providerが「npm-proxy」となります。

作成。

$ curl -u admin:admin123 -i -XPOST -H 'Content-Type: application/xml' http://172.17.0.2:8081/nexus/service/local/repositories -d @create-npm-proxy.xml

結果。

HTTP/1.1 201 Created
Date: Sun, 09 Sep 2018 11:02:41 GMT
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Server: Nexus/2.14.9-01 Noelios-Restlet-Engine/1.1.6-SONATYPE-5348-V8
Content-Type: application/xml; charset=UTF-8
Content-Length: 1129

<repository>
  <data>
    <contentResourceURI>http://172.17.0.2:8081/nexus/content/repositories/my-npm-proxy-repo</contentResourceURI>
    <id>my-npm-proxy-repo</id>
    <name>My npm Proxy Repo</name>
    <provider>npm-proxy</provider>
    <providerRole>org.sonatype.nexus.proxy.repository.Repository</providerRole>
    <format>npm</format>
    <repoType>proxy</repoType>
    <exposed>true</exposed>
    <writePolicy>READ_ONLY</writePolicy>
    <browseable>true</browseable>
    <indexable>false</indexable>
    <notFoundCacheTTL>1440</notFoundCacheTTL>
    <repoPolicy>MIXED</repoPolicy>
    <checksumPolicy>IGNORE</checksumPolicy>
    <downloadRemoteIndexes>false</downloadRemoteIndexes>
    <defaultLocalStorageUrl>file:/sonatype-work/storage/my-npm-proxy-repo</defaultLocalStorageUrl>
    <remoteStorage>
      <remoteStorageUrl>https://registry.npmjs.org</remoteStorageUrl>
    </remoteStorage>
    <fileTypeValidation>true</fileTypeValidation>
    <artifactMaxAge>0</artifactMaxAge>
    <metadataMaxAge>0</metadataMaxAge>
    <itemMaxAge>1440</itemMaxAge>
    <autoBlockActive>true</autoBlockActive>
  </data>
</repository>

Group Repository。

create-npm-group.xml

<?xml version="1.0" encoding="UTF-8"?>
<repo-group>
  <data>
    <id>my-npm-group-repo</id>
    <name>My npm Group Repo</name>
    <provider>npm-group</provider>
    <format>npm</format>
    <repoType>group</repoType>
    <exposed>true</exposed>
    <repositories>
      <repo-group-member>
        <id>my-npm-hosted-repo</id>
      </repo-group-member>
      <repo-group-member>
        <id>my-npm-proxy-repo</id>
      </repo-group-member>
    </repositories>
  </data>
</repo-group>

こちらは、providerが「npm-group」。

作成。

$ curl -u admin:admin123 -i -XPOST -H 'Content-Type: application/xml' http://172.17.0.2:8081/nexus/service/local/repo_groups -d @create-npm-group.xml

結果。

HTTP/1.1 201 Created
Date: Sun, 09 Sep 2018 11:04:25 GMT
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Server: Nexus/2.14.9-01 Noelios-Restlet-Engine/1.1.6-SONATYPE-5348-V8
Content-Type: application/xml; charset=UTF-8
Content-Length: 849

<repo-group>
  <data>
    <contentResourceURI>http://172.17.0.2:8081/nexus/content/groups/my-npm-group-repo</contentResourceURI>
    <id>my-npm-group-repo</id>
    <name>My npm Group Repo</name>
    <provider>npm-group</provider>
    <format>npm</format>
    <repoType>group</repoType>
    <exposed>true</exposed>
    <repositories>
      <repo-group-member>
        <id>my-npm-hosted-repo</id>
        <name>My npm Hosted Repo</name>
        <resourceURI>http://172.17.0.2:8081/nexus/service/local/repo_groups/my-npm-hosted-repo</resourceURI>
      </repo-group-member>
      <repo-group-member>
        <id>my-npm-proxy-repo</id>
        <name>My npm Proxy Repo</name>
        <resourceURI>http://172.17.0.2:8081/nexus/service/local/repo_groups/my-npm-proxy-repo</resourceURI>
      </repo-group-member>
    </repositories>
  </data>
</repo-group>

これで、npmリポジトリも作成できました。

だいぶ長くなりましたが、Sonatype Nexus 3と2それぞれで、Web Consoleを使うことなくリポジトリを作成することができそうです。

OKD/MinishiftでConfigMapとSecretsを試す

これは、なにをしたくて書いたもの?

  • OpenShift(というかKubernetes)での、ConfigMapとSecretsのお試しに
  • アプリケーションの環境変数に組み込んで試してみたい

今回は、ConigMapとSecretsを使って環境変数から値を読む、簡単なNode.jsアプリケーションを書いてみます。

ConfigMapとSecretsの説明自体は、実際に作成する時に書いていきます。

環境

今回の環境は、こちら。

$ minishift version
minishift v1.23.0+91235ee


$ oc version
oc v3.10.0+dd10d17
kubernetes v1.10.0+b81c8f8
features: Basic-Auth GSSAPI Kerberos SPNEGO

Server https://192.168.42.238:8443
openshift v3.10.0+756ae6e-40
kubernetes v1.10.0+b81c8f8

アプリケーションを書く

最初に、Node.jsでアプリケーションを書いていきます。

Expressのインストール。

$ npm i --save express

Expressのバージョンは、こちら。

  "dependencies": {
    "express": "^4.16.3"
  }

アプリケーションは、「/config」でConfigMapから設定した環境変数を、「/secrets」でSecretsから設定した環境変数を 参照するように作成します。 src/server.js

const express = require('express');
const app = express();

app.get('/config', (req, res) => {
    const username = process.env.CONFIG_USERNAME;
    const password = process.env.CONFIG_PASSWORD;

    res.send({ config_username: username, config_password: password });
});

app.get('/secrets', (req, res) => {
    const username = process.env.SECRET_USERNAME;
    const password = process.env.SECRET_PASSWORD;

    res.send({ secret_username: username, secret_password: password });
});

app.listen(8080);

で、このスクリプトを「npm start」で起動するように設定。 package.json

  "scripts": {
    "start": "node src/server.js"
  },

ConfigMapとSecretsを作る

続いて、このアプリケーションに設定するConfigMapとSecretsを作成します。

まずは、ConfigMapから。

ConfigMaps | Developer Guide | OKD 3.10

ConfigMapとは、文字通り設定情報ですが、設定を稼働するコンテナに含めるのではなく、デプロイ時にコンテナに提供する仕組みの ことを言うそうです。

コンテナは、ConfigMapを環境変数やコンテナの起動引数、ファイルとして参照することができるそうな。

Consuming in Environment Variables

Setting Command-line Arguments

作り方はYAMLで、ファイルから、リテラルからといくつかあるようですが、今回はリテラルから作ってみます。

Creating from Literal Values

参考)

Creating from Directories

Creating from Files

Consuming in Volumes

ConfigMapの作成。

$ oc create configmap app-config --from-literal=config-username=cuser --from-literal=config-password=cpassword

確認。

$ oc get cm/app-config -o yaml
apiVersion: v1
data:
  config-password: cpassword
  config-username: cuser
kind: ConfigMap
metadata:
  creationTimestamp: 2018-09-08T07:33:30Z
  name: app-config
  namespace: myproject
  resourceVersion: "27888"
  selfLink: /api/v1/namespaces/myproject/configmaps/app-config
  uid: 7bf7b343-b339-11e8-9de6-52540089d814

Secretsを作る

今度は、Secretsを作成します。

Secrets | Developer Guide | OKD 3.10

Secretsは、パスワード、OKDクライアントの設定ファイル、dockercfg、プライベートなソースリポジトリのクレデンシャルなどを 管理する仕組みのことだそうな。

Base64エンコードされ、データはtmpfs上に置かれて参照されます。Node上には置かれません、と。また、namespace内で共有することが できます。

Properties of Secrets

作成できるSecretsの種類はいくつかあり、Service Account Tokenやdockercfg、TLSに関するものなどがあります。

Types of Secrets

もちろん、汎用的な設定情報としても作成することができます(type=Opaque)。

作成したSecretsは、ボリュームとしてマウントしたり、コンテナの起動引数としたり、環境変数として参照できたりします。

Examples

ConfigMapと同じですね。

Secretsの作成は、ファイルやリテラルから行います。

今回は、リテラルから作成してみます。

$ oc create secret generic app-secrets --from-literal=secret-username=suser --from-literal=secret-password=spassword

確認。

$ oc get secrets/app-secrets -o yaml
apiVersion: v1
data:
  secret-password: c3Bhc3N3b3Jk
  secret-username: c3VzZXI=
kind: Secret
metadata:
  creationTimestamp: 2018-09-08T07:33:35Z
  name: app-secrets
  namespace: myproject
  resourceVersion: "27896"
  selfLink: /api/v1/namespaces/myproject/secrets/app-secrets
  uid: 7eec487a-b339-11e8-9de6-52540089d814
type: Opaque

確かに、Base64エンコードされて保存されています。

$ echo -n 'c3Bhc3N3b3Jk' | base64 -d
spassword

参考)

Kubernetes勉強会第1回 〜Secrets、StatefulSet、DaemonSet、API server への接続方法〜 - 世界中の羊をかき集めて

KubernetesのSecrets機能を試してみた

Kubernetes Secrets の紹介 – データベースのパスワードやその他秘密情報をどこに保存するか? – ゆびてく

確認

それでは、ここまで作成してきたアプリケーションとConfigMap、Secretsを使って動作確認してみます。

アプリケーションのデプロイ。

$ oc new-app [GitリポジトリのURL]

Routeの作成。

$ oc expose svc/node-config-and-secrets
route "node-config-and-secrets" exposed

$ oc get routes/node-config-and-secrets
NAME                      HOST/PORT                                                PATH      SERVICES                  PORT       TERMINATION   WILDCARD
node-config-and-secrets   node-config-and-secrets-myproject.192.168.42.24.nip.io             node-config-and-secrets   8080-tcp                 None

アプリケーションに、環境変数としてConfigMapとSecretsを取り込みます。

$ oc set env --from=configmap/app-config dc/node-config-and-secrets

$ oc set env --from=secrets/app-secrets dc/node-config-and-secrets

確認。

$ curl node-config-and-secrets-myproject.192.168.42.24.nip.io/config
{"config_username":"cuser","config_password":"cpassword"}

$ curl node-config-and-secrets-myproject.192.168.42.24.nip.io/secrets
{"secret_username":"suser","secret_password":"spassword"}

アプリケーションから、環境変数として参照できていますね。

変更してみる

ConfigMapやSecretsの変更は、今回は「oc edit」でYAMLを変更。

$ oc edit cm/app-config

$ oc edit secrets/app-secrets

変更したら、DeploymentConfigをrollout。

$ oc rollout latest dc/node-config-and-secrets

これで、変更がコンテナ側にも反映されました、と。

YAMLで書こう

最後に、ここまでのことをYAMLで書き直してみます。

ConfigMap。 config-resources.yml

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  config-password: cpassword
  config-username: cuser

Secrets。 secrets-resources.yml

apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
type: Opaque
data:
  secret-password: c3Bhc3N3b3Jk
  secret-username: c3VzZXI=

stringDataを使えば、Base64エンコードなしで書くこともできるようで。 secrets-resources.yml

apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
type: Opaque
stringData:
  secret-username: secret-username
  secret-password: secret-password

アプリケーション。 application-resources.yml

---
## ImageStream
apiVersion: v1
kind: ImageStream
metadata:
  labels:
    app: node-config-and-secrets
  name: node-config-and-secrets

---
## BuildConfig
apiVersion: v1
kind: BuildConfig
metadata:
  labels:
    app: node-config-and-secrets
  name: node-config-and-secrets
spec:
  triggers:
    - type: ConfigChange
    - imageChange: {}
      type: ImageChange
  source:
    git:
      uri: [GitリポジトリのURL]
      ref: master
    type: Git
  strategy:
    type: Source
    sourceStrategy:
      from:
        kind: ImageStreamTag
        name: nodejs:8
        namespace: openshift
  output:
    to:
      kind: ImageStreamTag
      name: node-config-and-secrets:latest

---
## DeploymentConfig
apiVersion: v1
kind: DeploymentConfig
metadata:
  labels:
    app: node-config-and-secrets
  name: node-config-and-secrets
spec:
  replicas: 1
  selector:
    app: node-config-and-secrets
    deploymentconfig: node-config-and-secrets
  template:
    metadata:
      labels:
        app: node-config-and-secrets
        deploymentconfig: node-config-and-secrets
    spec:
      containers:
      - name: node-config-and-secrets
        image: node-config-and-secrets:latest
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
          protocol: TCP
        env:
          - name: CONFIG_USERNAME
            valueFrom:
              configMapKeyRef:
                name: app-config
                key: config-username
          - name: CONFIG_PASSWORD
            valueFrom:
              configMapKeyRef:
                name: app-config
                key: config-password
          - name: SECRET_USERNAME
            valueFrom:
              secretKeyRef:
                name: app-secrets
                key: secret-username
          - name: SECRET_PASSWORD
            valueFrom:
              secretKeyRef:
                name: app-secrets
                key: secret-password
      restartPolicy: Always
  triggers:
  - type: ConfigChange
  - type: ImageChange
    imageChangeParams:
      automatic: true
      containerNames:
      - node-config-and-secrets
      from:
        kind: ImageStreamTag
        name: node-config-and-secrets:latest

---
## Service
apiVersion: v1
kind: Service
metadata:
  labels:
    app: node-config-and-secrets
  name: node-config-and-secrets
spec:
  ports:
  - name: 8080-tcp
    port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    app: node-config-and-secrets
    deploymentconfig: node-config-and-secrets

---
## Route
apiVersion: v1
kind: Route
metadata:
  labels:
    app: node-config-and-secrets
  name: node-config-and-secrets
spec:
  port:
    targetPort: 8080-tcp
  to:
    kind: Service
    name: node-config-and-secrets

なお、今回のConfigMapおよびSecretsのキー名だと、そのまま環境変数として使うのは微妙ですが、コンテナにConfigMapやSecretsの 内容を環境変数として一括で取り込むには、こんな感じで。

spec:
  ...
  template:
    metadata:
      ...
    spec:
      containers:
      - name: node-config-and-secrets
        image: node-config-and-secrets:latest
        ...
        envFrom:
        - configMapRef:
            name: app-config
        - secretRef:
            name: app-secrets