Rename to the vault-secrets-netbox provider convention

Align the provider with unkin/terraform-provider-vault-secrets-netbox: the
Go module becomes terraform-provider-vault-secrets-ghp, the provider source
address vault-secrets-ghp, and the resources ghp_secret_backend /
ghp_secret_role (TypeName ghp).

- main.go: module import, providerserver Address, package doc comment.
- provider.go: TypeName ghp (resources ghp_secret_backend, ghp_secret_role).
- Makefile: BINARY + INSTALL_DIR under vault-secrets-ghp; drop the e2e target
  (netbox has none).
- .woodpecker/release: PUT the zip to the artifactapi vault-secrets-ghp path.
- Restructure examples to netbox's layout (combined examples/main.tf + per
  resource) and rewrite README for the new names; drop the Docker e2e harness
  to mirror netbox exactly.

Engine schema mapping is unchanged. gofmt, go vet, go build ./... and
go test -race ./... all pass.
This commit is contained in:
2026-08-15 20:18:01 +10:00
parent 986aecd28f
commit afa53a3362
14 changed files with 85 additions and 248 deletions
+1 -5
View File
@@ -1,4 +1,4 @@
/terraform-provider-ghpvaultsecret
/terraform-provider-vault-secrets-ghp
*.zip
*.out
*.test
@@ -8,7 +8,3 @@ dist/
*.tfstate
*.tfstate.backup
.env
# e2e artifacts
test/plugins/
test/dev.tfrc
+2 -2
View File
@@ -22,9 +22,9 @@ steps:
commands:
- |
VERSION=$$(echo ${CI_COMMIT_TAG} | sed 's/^v//')
FILE="terraform-provider-ghpvaultsecret_$${VERSION}_linux_amd64.zip"
FILE="terraform-provider-vault-secrets-ghp_$${VERSION}_linux_amd64.zip"
curl -f -X PUT \
"https://artifactapi.k8s.syd1.au.unkin.net/api/v2/remotes/terraform-unkin/files/unkin/ghpvaultsecret/$${FILE}" \
"https://artifactapi.k8s.syd1.au.unkin.net/api/v2/remotes/terraform-unkin/files/unkin/vault-secrets-ghp/$${FILE}" \
-H "Content-Type: application/zip" \
--data-binary @"$${FILE}"
depends_on: [package]
+3 -7
View File
@@ -1,10 +1,10 @@
.PHONY: build install test lint fmt clean tidy package e2e patch minor major check-go
.PHONY: build install test lint fmt clean tidy package patch minor major check-go
BINARY := terraform-provider-ghpvaultsecret
BINARY := terraform-provider-vault-secrets-ghp
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo "0.0.0-dev")
OS_ARCH := linux_amd64
INSTALL_VERSION := $(shell echo $(VERSION) | sed 's/^v//')
INSTALL_DIR := ~/.terraform.d/plugins/git.unkin.net/unkin/ghpvaultsecret/$(INSTALL_VERSION)/$(OS_ARCH)
INSTALL_DIR := ~/.terraform.d/plugins/git.unkin.net/unkin/vault-secrets-ghp/$(INSTALL_VERSION)/$(OS_ARCH)
ZIP := $(BINARY)_$(INSTALL_VERSION)_$(OS_ARCH).zip
GO_VERSION_REQUIRED := 1.25
@@ -36,10 +36,6 @@ package: build
python3 -c "import zipfile,sys; z=zipfile.ZipFile(sys.argv[1],'w',zipfile.ZIP_DEFLATED); z.write(sys.argv[2]); z.close()" $(ZIP) $(BINARY)_v$(INSTALL_VERSION)
rm $(BINARY)_v$(INSTALL_VERSION)
# End-to-end: boots Vault + mock ghp + the plugin and applies real terraform.
e2e:
./scripts/e2e.sh
clean:
rm -f $(BINARY) *.zip
+17 -18
View File
@@ -1,43 +1,43 @@
# terraform-provider-ghpvaultsecret
# terraform-provider-vault-secrets-ghp
A Terraform provider that manages the **ghp token secrets engine**
A Terraform/OpenTofu provider that manages the **ghp token secrets engine**
([`vault-plugin-secrets-ghp`](https://git.unkin.net/unkin/vault-plugin-secrets-ghp))
on HashiCorp Vault or OpenBao, so the engine's mount, config, and roles can be
driven declaratively (e.g. from `terraform-vault`).
Source address: `git.unkin.net/unkin/ghpvaultsecret` (declare it under the local
name `ghpvaultsecret`, so its resources are `ghpvaultsecret_*`).
Source address: `git.unkin.net/unkin/vault-secrets-ghp` (declare it under the
local name `ghp`, so its resources are `ghp_*`).
## Resources
| Resource | Manages |
|----------|---------|
| `ghpvaultsecret_secret_backend` | Mounts the engine at a path and writes its `config` (ghp base URL, TLS, seeded service token). |
| `ghpvaultsecret_secret_role` | A role: `token_type`, `installation_id`, `app_record_id`, `repositories`, `scopes`, `session_prefix`, `ttl`, `max_ttl`. |
| `ghp_secret_backend` | Mounts the engine at a path and writes its `config` (ghp base URL, TLS, seeded service token). |
| `ghp_secret_role` | A role: `token_type`, `installation_id`, `app_record_id`, `repositories`, `scopes`, `session_prefix`, `ttl`, `max_ttl`. |
## Usage
```hcl
terraform {
required_providers {
ghpvaultsecret = {
source = "git.unkin.net/unkin/ghpvaultsecret"
ghp = {
source = "git.unkin.net/unkin/vault-secrets-ghp"
}
}
}
provider "ghpvaultsecret" {
provider "ghp" {
# address / token fall back to VAULT_ADDR / VAULT_TOKEN.
}
resource "ghpvaultsecret_secret_backend" "ghp" {
resource "ghp_secret_backend" "ghp" {
path = "ghp"
base_url = "https://ghp.unkin.net"
admin_token = var.ghp_admin_token
}
resource "ghpvaultsecret_secret_role" "ci" {
backend = ghpvaultsecret_secret_backend.ghp.path
resource "ghp_secret_role" "ci" {
backend = ghp_secret_backend.ghp.path
name = "ci"
token_type = "agent"
installation_id = 12345
@@ -64,8 +64,8 @@ resource "ghpvaultsecret_secret_role" "ci" {
## Import
```sh
terraform import ghpvaultsecret_secret_backend.ghp ghp
terraform import ghpvaultsecret_secret_role.ci ghp/roles/ci
terraform import ghp_secret_backend.ghp ghp
terraform import ghp_secret_role.ci ghp/roles/ci
```
## Development
@@ -74,12 +74,11 @@ terraform import ghpvaultsecret_secret_role.ci ghp/roles/ci
make build # build the provider binary
make install # install into ~/.terraform.d/plugins for local use
make test # unit tests (race)
make e2e # apply real terraform against Vault + a mock ghp (Docker)
make package # build the release zip
```
Releases are tag-driven (`make patch|minor|major`): a Woodpecker pipeline builds
`terraform-provider-ghpvaultsecret_<version>_linux_amd64.zip` and PUTs it to the
artifactapi terraform registry
(`.../api/v2/remotes/terraform-unkin/files/unkin/ghpvaultsecret/<file>`), which
`terraform-provider-vault-secrets-ghp_<version>_linux_amd64.zip` and PUTs it to
the artifactapi terraform registry
(`.../api/v2/remotes/terraform-unkin/files/unkin/vault-secrets-ghp/<file>`), which
signs it server-side. Install it via the bare `source` address above.
+45
View File
@@ -0,0 +1,45 @@
terraform {
required_providers {
ghp = {
source = "git.unkin.net/unkin/vault-secrets-ghp"
version = "0.0.1"
}
}
}
provider "ghp" {
# address defaults to $VAULT_ADDR, token to $VAULT_TOKEN
}
variable "ghp_admin_token" {
type = string
sensitive = true
}
resource "ghp_secret_backend" "ghp" {
path = "ghp"
base_url = "https://ghp.unkin.net"
admin_token = var.ghp_admin_token
}
# Agent role scoped to a ghp App installation for CI.
resource "ghp_secret_role" "ci" {
backend = ghp_secret_backend.ghp.path
name = "ci"
token_type = "agent"
installation_id = 12345
repositories = ["unkin/prodenv"]
scopes = ["contents:read", "pull_requests:write"]
ttl = 3600
max_ttl = 28800
}
# Proxy (OAuth-backed) role, no installation binding.
resource "ghp_secret_role" "proxy" {
backend = ghp_secret_backend.ghp.path
name = "proxy"
token_type = "proxy"
scopes = ["contents:read"]
ttl = 1800
max_ttl = 14400
}
@@ -1,17 +1,17 @@
terraform {
required_providers {
ghpvaultsecret = {
source = "git.unkin.net/unkin/ghpvaultsecret"
ghp = {
source = "git.unkin.net/unkin/vault-secrets-ghp"
}
}
}
provider "ghpvaultsecret" {
provider "ghp" {
# address / token fall back to VAULT_ADDR / VAULT_TOKEN.
}
# Mount the ghp secrets engine and seed it with a service token.
resource "ghpvaultsecret_secret_backend" "ghp" {
resource "ghp_secret_backend" "ghp" {
path = "ghp"
base_url = "https://ghp.unkin.net"
ca_cert = file("${path.module}/ghp-ca.pem")
@@ -1,8 +1,8 @@
# A role that mints short-lived agent tokens bound to a ghp App installation,
# scoped to contents and pull requests. Reading ghp/creds/ci returns a
# lease-bound token that ghp revokes when the lease ends.
resource "ghpvaultsecret_secret_role" "ci" {
backend = ghpvaultsecret_secret_backend.ghp.path
resource "ghp_secret_role" "ci" {
backend = ghp_secret_backend.ghp.path
name = "ci"
token_type = "agent"
installation_id = 12345
+1 -1
View File
@@ -1,4 +1,4 @@
module git.unkin.net/unkin/terraform-provider-ghpvaultsecret
module git.unkin.net/unkin/terraform-provider-vault-secrets-ghp
go 1.25
+4 -4
View File
@@ -29,10 +29,10 @@ func New(version string) func() provider.Provider {
}
func (p *ghpProvider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) {
// Source address is git.unkin.net/unkin/ghpvaultsecret; resources are
// prefixed "ghpvaultsecret_" (declare it under the local name
// "ghpvaultsecret" in required_providers).
resp.TypeName = "ghpvaultsecret"
// The provider's source address is git.unkin.net/unkin/vault-secrets-ghp,
// but its resources are prefixed "ghp_" (declare it in required_providers
// under the local name "ghp"), mirroring how google-beta ships google_*.
resp.TypeName = "ghp"
resp.Version = p.version
}
+6 -2
View File
@@ -1,3 +1,7 @@
// Command terraform-provider-vault-secrets-ghp is the Terraform/OpenTofu
// provider for the vault-plugin-secrets-ghp secrets engine: it manages the
// engine's mount + connection config and its token-minting roles on HashiCorp
// Vault or OpenBao.
package main
import (
@@ -7,7 +11,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"git.unkin.net/unkin/terraform-provider-ghpvaultsecret/internal/provider"
"git.unkin.net/unkin/terraform-provider-vault-secrets-ghp/internal/provider"
)
var version = "0.0.1"
@@ -18,7 +22,7 @@ func main() {
flag.Parse()
opts := providerserver.ServeOpts{
Address: "git.unkin.net/unkin/ghpvaultsecret",
Address: "git.unkin.net/unkin/vault-secrets-ghp",
Debug: debug,
}
-118
View File
@@ -1,118 +0,0 @@
#!/usr/bin/env bash
#
# End-to-end test for terraform-provider-ghpvaultsecret.
#
# Builds the sibling ghp plugin and this provider, boots Vault + a mock ghp
# REST API in Docker, then runs a real `terraform apply` through the provider to
# mount the engine, seed the service token, and create a role. It asserts a
# token can be minted from the role, then `terraform destroy` and verifies the
# mount is gone.
#
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PLUGIN_REPO="${PLUGIN_REPO:-${ROOT_DIR}/../vault-plugin-secrets-ghp}"
COMPOSE_FILE="${ROOT_DIR}/test/docker-compose.yml"
COMPOSE="docker compose -f ${COMPOSE_FILE}"
TF="${TF:-terraform}"
E2E_DIR="${ROOT_DIR}/test/e2e"
PLUGIN_BIN="vault-plugin-secrets-ghp"
PROVIDER_BIN="terraform-provider-ghpvaultsecret"
GHP_ADDR="http://127.0.0.1:3000"
export VAULT_ADDR="http://127.0.0.1:8200"
export VAULT_TOKEN="root"
export PLUGIN_SRC="${PLUGIN_REPO}"
red() { printf '\033[31m%s\033[0m\n' "$*"; }
green() { printf '\033[32m%s\033[0m\n' "$*"; }
blue() { printf '\033[34m==> %s\033[0m\n' "$*"; }
fail() { red "FAIL: $*"; exit 1; }
cleanup() {
blue "Cleaning up"
if [ -d "${E2E_DIR}" ]; then
(cd "${E2E_DIR}" && TF_CLI_CONFIG_FILE="${ROOT_DIR}/test/dev.tfrc" "${TF}" destroy -auto-approve >/dev/null 2>&1 || true)
rm -f "${E2E_DIR}"/terraform.tfstate* "${E2E_DIR}"/.terraform.lock.hcl
rm -rf "${E2E_DIR}/.terraform"
fi
${COMPOSE} down -v >/dev/null 2>&1 || true
}
trap cleanup EXIT
wait_for() {
local desc="$1"; shift
local retries="${WAIT_RETRIES:-90}" i=0
until "$@" >/dev/null 2>&1; do
i=$((i + 1))
[ "$i" -ge "$retries" ] && fail "timed out waiting for ${desc}"
sleep 2
done
green "ready: ${desc}"
}
jq_field() { python3 -c "import sys,json;print(json.load(sys.stdin)$1)"; }
# ---------------------------------------------------------------------------
blue "Building ghp plugin from ${PLUGIN_REPO}"
[ -d "${PLUGIN_REPO}" ] || fail "plugin repo not found at ${PLUGIN_REPO} (set PLUGIN_REPO)"
mkdir -p "${ROOT_DIR}/test/plugins"
( cd "${PLUGIN_REPO}" && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" \
-o "${ROOT_DIR}/test/plugins/${PLUGIN_BIN}" ./cmd/vault-plugin-secrets-ghp )
blue "Building the provider"
( cd "${ROOT_DIR}" && go build -o "${PROVIDER_BIN}" . )
blue "Writing terraform dev_overrides config"
cat > "${ROOT_DIR}/test/dev.tfrc" <<EOF
provider_installation {
dev_overrides {
"git.unkin.net/unkin/ghpvaultsecret" = "${ROOT_DIR}"
}
direct {}
}
EOF
export TF_CLI_CONFIG_FILE="${ROOT_DIR}/test/dev.tfrc"
blue "Starting Docker stack"
${COMPOSE} up -d --build
wait_for "ghp" curl -fsS "${GHP_ADDR}/healthz"
wait_for "vault" ${COMPOSE} exec -T vault vault status -address=http://127.0.0.1:8200
blue "Registering the ghp plugin in Vault"
SHA="$(sha256sum "${ROOT_DIR}/test/plugins/${PLUGIN_BIN}" | awk '{print $1}')"
${COMPOSE} exec -T vault vault plugin register -sha256="${SHA}" secret "${PLUGIN_BIN}" >/dev/null
# ---------------------------------------------------------------------------
blue "terraform apply (mount engine + config + role via the provider)"
( cd "${E2E_DIR}" && "${TF}" apply -auto-approve )
green "apply succeeded"
blue "Verifying the mount and role exist"
${COMPOSE} exec -T vault vault secrets list 2>/dev/null | grep -q '^ghp/' \
|| fail "ghp mount not found after apply"
${COMPOSE} exec -T vault vault read ghp/roles/ci >/dev/null \
|| fail "role ci not found after apply"
green "mount + role present"
blue "Minting a token from the terraform-managed role"
JSON="$(${COMPOSE} exec -T vault vault read -format=json ghp/creds/ci)"
TOKEN="$(printf '%s' "${JSON}" | jq_field '["data"]["token"]')"
TYPE="$(printf '%s' "${JSON}" | jq_field '["data"]["token_type"]')"
LEASE="$(printf '%s' "${JSON}" | jq_field '["lease_id"]')"
[ -n "${TOKEN}" ] || fail "no token minted"
[ "${TYPE}" = "agent" ] || fail "unexpected token_type ${TYPE}"
green "minted token ${TOKEN:0:10}... (type ${TYPE}, lease ${LEASE})"
blue "Revoking the lease"
${COMPOSE} exec -T vault vault lease revoke "${LEASE}" >/dev/null
green "lease revoked"
# ---------------------------------------------------------------------------
blue "terraform destroy (unmount engine)"
( cd "${E2E_DIR}" && "${TF}" destroy -auto-approve )
${COMPOSE} exec -T vault vault secrets list 2>/dev/null | grep -q '^ghp/' \
&& fail "ghp mount still present after destroy" || true
green "mount removed by destroy"
green "ALL PROVIDER END-TO-END CHECKS PASSED"
-47
View File
@@ -1,47 +0,0 @@
# E2E stack for the provider: a mock ghp REST API (run from the sibling plugin
# repo) + a Vault dev server with the ghp plugin mounted. Bind mounts use ":z"
# for SELinux (Fedora/RHEL). MOCKGHP_ADMIN_TOKEN is an ephemeral test fixture,
# not a real credential.
services:
ghp:
image: golang:1.25-alpine
working_dir: /src
environment:
MOCKGHP_ADDR: ":3000"
MOCKGHP_ADMIN_TOKEN: "ghpsvc_e2e_fixture"
GOFLAGS: "-mod=mod"
# PLUGIN_SRC is the sibling vault-plugin-secrets-ghp checkout, which ships
# the mock ghp server under test/mockghp.
command: ["go", "run", "./test/mockghp"]
volumes:
- ${PLUGIN_SRC:-../vault-plugin-secrets-ghp}:/src:ro,z
ports:
- "3000:3000"
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/healthz"]
interval: 3s
timeout: 3s
retries: 40
vault:
image: hashicorp/vault:1.18
depends_on:
ghp:
condition: service_healthy
cap_add:
- IPC_LOCK
environment:
VAULT_DEV_ROOT_TOKEN_ID: root
VAULT_ADDR: http://127.0.0.1:8200
VAULT_TOKEN: root
command: ["server", "-dev", "-dev-listen-address=0.0.0.0:8200", "-config=/vault/vault.hcl"]
volumes:
- ./plugins:/vault/plugins:ro,z
- ./vault/vault.hcl:/vault/vault.hcl:ro,z
ports:
- "8200:8200"
healthcheck:
test: ["CMD", "vault", "status", "-address=http://127.0.0.1:8200"]
interval: 3s
timeout: 3s
retries: 20
-34
View File
@@ -1,34 +0,0 @@
terraform {
required_providers {
ghpvaultsecret = {
source = "git.unkin.net/unkin/ghpvaultsecret"
}
}
}
provider "ghpvaultsecret" {
address = "http://127.0.0.1:8200"
token = "root"
}
resource "ghpvaultsecret_secret_backend" "ghp" {
path = "ghp"
description = "ghp token engine (e2e)"
# Reachable from inside the vault container, where the plugin runs.
base_url = "http://ghp:3000"
tls_skip_verify = true
# Ephemeral test fixture, matched by the mock ghp MOCKGHP_ADMIN_TOKEN.
admin_token = "ghpsvc_e2e_fixture"
}
resource "ghpvaultsecret_secret_role" "ci" {
backend = ghpvaultsecret_secret_backend.ghp.path
name = "ci"
token_type = "agent"
installation_id = 12345
repositories = ["unkin/prodenv"]
scopes = ["contents:read", "pull_requests:write"]
ttl = 3600
max_ttl = 86400
}
-4
View File
@@ -1,4 +0,0 @@
# Combined with `-dev` so the dev server has a plugin_directory to register the
# ghp plugin binary mounted from ./plugins.
plugin_directory = "/vault/plugins"
api_addr = "http://127.0.0.1:8200"