CLOVER🍀

That was when it all began.

Sonatype Nexus 3をTerraformで操作する

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

TerraformのProviderを見ていると、Sonatype Nexus用のProviderがあったのでちょっと試してみたいなと。

Nexus Provider

Nexus Provider

Sonatype NexusのTerraform Providerです。

Nexus Provider

リポジトリーの定義などをスクリプトなどでできるといいなと思っていたのですが、REST APIかGroovyスクリプトしか方法を
知りませんでした。

REST and Integration API

Terraformで定義できるのなら、宣言的に書けて良さそうですね、ということで。

今回は、Mavenリポジトリーを作成してみたいと思います。

環境

今回の環境は、こちら。

$ terraform version
Terraform v1.4.6
on linux_amd64

Sonatype Nexusは、Dockerイメージを使うことにします。

sonatype/nexus3

起動。

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

Nexusコンテナにアクセスする際のIPアドレスは、172.17.0.2とします。

Sonatype NexusMavenリポジトリーをTerraformで定義する

では、Sonatype NexusMavenリポジトリーをNexus Providerを使って、Terraformで定義してみましょう。

今回はHostedリポジトリー、Proxyリポジトリー、そしてその2つを含んだGroupリポジトリーを作成してみます。

結果はこちら。

main.tf

terraform {
  required_version = "v1.4.6"

  required_providers {
    nexus = {
      source  = "datadrivers/nexus"
      version = "1.21.2"
    }
  }
}

provider "nexus" {
  username = "admin"
  # 初期パスワードは /nexus-data/admin.password に記載
  password = "admin123"
  url      = "http://172.17.0.2:8081"
  insecure = true
}

resource "nexus_repository_maven_hosted" "my_maven_hosted_repo" {
  name   = "my-maven-hosted-repo"
  online = true

  maven {
    layout_policy       = "STRICT"
    version_policy      = "MIXED"
    content_disposition = "INLINE"
  }

  storage {
    blob_store_name                = "default"
    strict_content_type_validation = true
    write_policy                   = "ALLOW"
  }
}

resource "nexus_repository_maven_proxy" "my_maven_proxy_repo" {
  name   = "my-maven-proxy-repo"
  online = true

  proxy {
    remote_url = "https://repo1.maven.org/maven2/"
  }

  maven {
    layout_policy       = "STRICT"
    version_policy      = "RELEASE"
    content_disposition = "INLINE"
  }

  storage {
    blob_store_name                = "default"
    strict_content_type_validation = true
  }

  http_client {
    auto_block = true
    blocked = false
  }

  negative_cache {
    enabled = true
    ttl = 1440
  }
}

resource "nexus_repository_maven_group" "my_maven_group_repo" {
  name   = "my-maven-group-repo"
  online = true

  group {
    member_names = [
      nexus_repository_maven_hosted.my_maven_hosted_repo.name,
      nexus_repository_maven_proxy.my_maven_proxy_repo.name
    ]
  }

  storage {
    blob_store_name                = "default"
    strict_content_type_validation = true
  }
}

providerにSonatype Nexusへの接続情報を定義。

provider "nexus" {
  username = "admin"
  # 初期パスワードは /nexus-data/admin.password に記載
  password = "admin123"
  url      = "http://172.17.0.2:8081"
  insecure = true
}

あとは、ドキュメントを見つつリポジトリーを定義。上から、Hostedリポジトリー、Proxyリポジトリー、Groupリポジトリーです。

resource "nexus_repository_maven_hosted" "my_maven_hosted_repo" {
  name   = "my-maven-hosted-repo"
  online = true

  ## 省略
}

resource "nexus_repository_maven_proxy" "my_maven_proxy_repo" {
  name   = "my-maven-proxy-repo"
  online = true

  ## 省略
}

resource "nexus_repository_maven_group" "my_maven_group_repo" {
  name   = "my-maven-group-repo"
  online = true

  ## 省略
}

設定値が不安な時は、Sonatype NexusのWeb UIでリポジトリーを作成する時の情報を見ると良いかもですね。

terraform initして

$ terraform init

apply

$ terraform apply

リポジトリーが作成できました。

REST APIで確認する場合は、こちら。

$ curl 172.17.0.2:8081/service/rest/v1/repositories

デプロイしてみる

作成したMavenリポジトリーに、アーティファクトをデプロイして動作確認してみましょう。

サンプルを作成。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.littlewings</groupId>
    <artifactId>sample-app</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <repositories>
        <repository>
            <id>my-maven-group-repo</id>
            <name>My Maven Group Repository</name>
            <url>http://172.17.0.2:8081/repository/my-maven-group-repo/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>my-maven-group-repo</id>
            <name>My Maven Group Repository</name>
            <url>http://172.17.0.2:8081/repository/my-maven-group-repo/</url>
        </pluginRepository>
    </pluginRepositories>

    <distributionManagement>
        <repository>
            <id>my-maven-hosted-repo</id>
            <name>My Maven Hosted Repository</name>
            <url>http://172.17.0.2:8081/repository/my-maven-hosted-repo/</url>
        </repository>
    </distributionManagement>
</project>

ソースコード

src/main/java/org/littlewings/sample/Greeting.java

package org.littlewings.sample;

public class Greeting {
    public String hello(String message) {
        return String.format("Hello %s!!", message);
    }
}

settings.xml

<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                      https://maven.apache.org/xsd/settings-1.0.0.xsd">
    <servers>
        <server>
            <id>my-maven-hosted-repo</id>
            <username>admin</username>
            <password>admin123</password>
        </server>
    </servers>
</settings>

デプロイ。

$ mvn package deploy -s settings.xml

確認。

OKですね。

ハマったこと

最初、必須項目だけ定義していたらProxyリポジトリーの作成でハマりました。

Providerがクラッシュするんですよね…。

nexus_repository_maven_proxy.my_maven_proxy_repo: Creating...
╷
│ Error: Plugin did not respond
│
│   with nexus_repository_maven_proxy.my_maven_proxy_repo,
│   on main.tf line 37, in resource "nexus_repository_maven_proxy" "my_maven_proxy_repo":
│   37: resource "nexus_repository_maven_proxy" "my_maven_proxy_repo" {
│
│ The plugin encountered an error, and failed to respond to the plugin.(*GRPCProvider).ApplyResourceChange call. The plugin logs may contain more details.
╵

Stack trace from the terraform-provider-nexus_v1.21.2 plugin:

panic: runtime error: index out of range [0] with length 0

goroutine 40 [running]:
github.com/datadrivers/terraform-provider-nexus/internal/services/repository.getMavenProxyRepositoryFromResourceData(0xde39e0?)
        github.com/datadrivers/terraform-provider-nexus/internal/services/repository/resource_repository_maven_proxy.go:45 +0xffe
github.com/datadrivers/terraform-provider-nexus/internal/services/repository.resourceMavenProxyRepositoryCreate(0x0?, {0xb7b2e0?, 0xc0003e3140})
        github.com/datadrivers/terraform-provider-nexus/internal/services/repository/resource_repository_maven_proxy.go:168 +0x66
github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema.(*Resource).create(0xde39e0?, {0xde39e0?, 0xc0003ce330?}, 0xd?, {0xb7b2e0?, 0xc0003e3140?})
        github.com/hashicorp/terraform-plugin-sdk/v2@v2.24.0/helper/schema/resource.go:695 +0x178
github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema.(*Resource).Apply(0xc000436620, {0xde39e0, 0xc0003ce330}, 0xc0002a5a00, 0xc0003e1800, {0xb7b2e0, 0xc0003e3140})
        github.com/hashicorp/terraform-plugin-sdk/v2@v2.24.0/helper/schema/resource.go:837 +0xa7a
github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema.(*GRPCProviderServer).ApplyResourceChange(0xc0003051e8, {0xde39e0?, 0xc0003ce210?}, 0xc0004669b0)
        github.com/hashicorp/terraform-plugin-sdk/v2@v2.24.0/helper/schema/grpc_provider.go:1021 +0xe3c
github.com/hashicorp/terraform-plugin-go/tfprotov5/tf5server.(*server).ApplyResourceChange(0xc000366460, {0xde39e0?, 0xc0002f79e0?}, 0xc0003dc380)
        github.com/hashicorp/terraform-plugin-go@v0.14.0/tfprotov5/tf5server/server.go:818 +0x574
github.com/hashicorp/terraform-plugin-go/tfprotov5/internal/tfplugin5._Provider_ApplyResourceChange_Handler({0xc840a0?, 0xc000366460}, {0xde39e0, 0xc0002f79e0}, 0xc0003dc310, 0x0)
        github.com/hashicorp/terraform-plugin-go@v0.14.0/tfprotov5/internal/tfplugin5/tfplugin5_grpc.pb.go:385 +0x170
google.golang.org/grpc.(*Server).processUnaryRPC(0xc0003f2000, {0xde6540, 0xc00048a1a0}, 0xc000354ea0, 0xc000432de0, 0x129b980, 0x0)
        google.golang.org/grpc@v1.50.1/server.go:1340 +0xd13
google.golang.org/grpc.(*Server).handleStream(0xc0003f2000, {0xde6540, 0xc00048a1a0}, 0xc000354ea0, 0x0)
        google.golang.org/grpc@v1.50.1/server.go:1713 +0xa1b
google.golang.org/grpc.(*Server).serveStreams.func1.2()
        google.golang.org/grpc@v1.50.1/server.go:965 +0x98
created by google.golang.org/grpc.(*Server).serveStreams.func1
        google.golang.org/grpc@v1.50.1/server.go:963 +0x28a

Error: The terraform-provider-nexus_v1.21.2 plugin crashed!

スタックトレースからソースコードを見ると、negative_cacheは設定上はオプションで、実際には必須なようなのでこちらを加えると
動作しました。

https://github.com/datadrivers/terraform-provider-nexus/blob/v1.21.2/internal/services/repository/resource_repository_maven_proxy.go#L45

まとめ

Nexus Providerを使って、Sonatype NexusをTerraformで操作してみました。

若干ハマるところがありましたが、REST APIで頑張るよりはTerraformで扱えた方が個人的にはわかりやすいですね。

使えるところでは、使っていきましょう。