feat: serve local terraform repos as a provider registry
Local terraform repos already spoke the network mirror protocol, which needs
per-consumer .terraformrc config. This adds the provider registry protocol so
`terraform init` installs from a bare source address
(artifactapi.k8s.../{repo}/{type}) with no client setup.
- serve /.well-known/terraform.json service discovery and the providers.v1
versions/download endpoints under /terraform/v1/providers
- map the Terraform namespace to the artifactapi repo name and locate the
provider by type; download_url points back at the existing local file path
- generate SHA256SUMS per version and sign it with a GPG key loaded from
TF_SIGNING_KEY_PATH; advertise the public key + key id in the download
response. No key configured -> registry stays disabled (endpoints 404)
- new internal/tfsign (key loading + detached signing) and
internal/api/terraform (registry handler); export ParseProviderZip for reuse
- add TF_SIGNING_KEY_PATH/PASSPHRASE and TF_PROVIDER_PROTOCOLS config
- unit test signing + verification; dockerised test of the full flow incl.
signature verification against the advertised key
Also anchor the terraform/ gitignore to the repo root so it stops swallowing
internal/api/terraform and internal/provider/terraform test files (the latter
had gone silently untracked).
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
|
||||
tfregistry "git.unkin.net/unkin/artifactapi/internal/api/terraform"
|
||||
v1 "git.unkin.net/unkin/artifactapi/internal/api/v1"
|
||||
v2 "git.unkin.net/unkin/artifactapi/internal/api/v2"
|
||||
"git.unkin.net/unkin/artifactapi/internal/cache"
|
||||
@@ -30,6 +31,7 @@ import (
|
||||
_ "git.unkin.net/unkin/artifactapi/internal/provider/terraform"
|
||||
"git.unkin.net/unkin/artifactapi/internal/proxy"
|
||||
"git.unkin.net/unkin/artifactapi/internal/storage"
|
||||
"git.unkin.net/unkin/artifactapi/internal/tfsign"
|
||||
"git.unkin.net/unkin/artifactapi/internal/virtual"
|
||||
)
|
||||
|
||||
@@ -43,6 +45,7 @@ type Server struct {
|
||||
engine *proxy.Engine
|
||||
virtEngine *virtual.Engine
|
||||
localHandler *v2.LocalHandler
|
||||
tfRegistry *tfregistry.Handler
|
||||
gc *gc.Collector
|
||||
}
|
||||
|
||||
@@ -67,6 +70,17 @@ func New(cfg *config.Config, version string) (*Server, error) {
|
||||
virtEngine := virtual.NewEngine(db, engine)
|
||||
collector := gc.New(db, s3, 1*time.Hour)
|
||||
|
||||
// A failure to load the signing key must not take the server down: the
|
||||
// terraform registry simply stays disabled until a valid key is present.
|
||||
signer, err := tfsign.Load(cfg.TFSigningKeyPath, cfg.TFSigningKeyPassphrase)
|
||||
if err != nil {
|
||||
slog.Warn("terraform provider registry disabled", "error", err)
|
||||
}
|
||||
tfRegistry := tfregistry.NewHandler(db, signer, cfg.TFProviderProtocols)
|
||||
if tfRegistry.Enabled() {
|
||||
slog.Info("terraform provider registry enabled", "key_id", signer.KeyID())
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
cfg: cfg,
|
||||
version: version,
|
||||
@@ -76,6 +90,7 @@ func New(cfg *config.Config, version string) (*Server, error) {
|
||||
engine: engine,
|
||||
virtEngine: virtEngine,
|
||||
localHandler: localHandler,
|
||||
tfRegistry: tfRegistry,
|
||||
gc: collector,
|
||||
}
|
||||
|
||||
@@ -97,6 +112,11 @@ func (s *Server) routes() chi.Router {
|
||||
r.Get("/", s.handleRoot)
|
||||
r.Get("/version", s.handleVersion)
|
||||
|
||||
// Terraform provider registry: service discovery at the well-known path,
|
||||
// providers.v1 protocol under /terraform/v1/providers.
|
||||
r.Get("/.well-known/terraform.json", s.tfRegistry.ServiceDiscovery)
|
||||
r.Mount(tfregistry.MountPath, s.tfRegistry.Routes())
|
||||
|
||||
proxyHandler := v1.NewProxyHandler(s.engine, s.virtEngine, s.db, s.store, s.localHandler)
|
||||
r.Mount("/api/v1", proxyHandler.Routes())
|
||||
r.Mount("/v2", proxyHandler.DockerV2Routes())
|
||||
|
||||
Reference in New Issue
Block a user