Skip to content
Snippets Groups Projects
Commit 842a28f0 authored by Timo Furrer's avatar Timo Furrer
Browse files

Write HTTP backend config discovery tool in Go

parent 0e88019b
No related branches found
No related tags found
No related merge requests found
ARG BASE_IMAGE=alpine:3.21.3
ARG BASE_IMAGE=docker.io/library/alpine:3.21.3
FROM docker.io/library/golang:1.24-alpine AS builder
WORKDIR /app
# Copy the Go source code
COPY src/auto-define-backend /app/
# Build the Go binary with static linking
RUN go build -o gitlab-tofu-auto-define-backend .
FROM $BASE_IMAGE
......@@ -27,6 +35,7 @@ RUN curl --proto '=https' --tlsv1.2 -fsSL https://get.opentofu.org/install-opent
WORKDIR /
COPY --chmod=755 src/gitlab-tofu.sh /usr/bin/gitlab-tofu
COPY --from=builder --chmod=755 /app/gitlab-tofu-auto-define-backend /usr/bin/
# Override ENTRYPOINT
ENTRYPOINT []
ARG BASE_IMAGE=debian:12.9-slim
ARG BASE_IMAGE=docker.io/library/debian:12.9-slim
FROM docker.io/library/golang:1.24-bookworm AS builder
WORKDIR /app
# Copy the Go source code
COPY src/auto-define-backend /app/
# Build the Go binary with static linking
RUN go build -o gitlab-tofu-auto-define-backend .
FROM $BASE_IMAGE
......@@ -42,6 +50,7 @@ RUN curl --proto '=https' --tlsv1.2 -fsSL https://get.opentofu.org/install-opent
WORKDIR /
COPY --chmod=755 src/gitlab-tofu.sh /usr/bin/gitlab-tofu
COPY --from=builder --chmod=755 /app/gitlab-tofu-auto-define-backend /usr/bin/
# Override ENTRYPOINT
ENTRYPOINT []
module gitlab.com/components/opentofu/src/discover-http-backend
go 1.24
require github.com/hashicorp/hcl/v2 v2.23.0
require (
github.com/agext/levenshtein v1.2.1 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
github.com/zclconf/go-cty v1.13.0 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/tools v0.6.0 // indirect
)
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/zclconf/go-cty v1.13.0 h1:It5dfKTTZHe9aeppbNOda3mN7Ag7sg6QkBNm6TkyFa0=
github.com/zclconf/go-cty v1.13.0/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
package main
import (
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
)
var ErrManuallyConfigured = errors.New("HTTP backend is manually configured")
var ErrDifferentManuallyConfigured = errors.New("Backend of different type is manually configured")
func isHTTPBackendConfigured(root *os.Root) (bool, error) {
err := fs.WalkDir(root.FS(), ".", func(path string, d fs.DirEntry, err error) error {
if path != "." && d.IsDir() {
return fs.SkipDir
}
name := filepath.Base(path)
ext := filepath.Ext(name)
if ext == "" || strings.HasPrefix(name, ".") || strings.HasSuffix(name, "~") || (strings.HasPrefix(name, "#") && strings.HasSuffix(name, "#")) {
return nil
}
if ext == ".tftest.hcl" || ext == ".tftest.json" || ext == ".tofutest.hcl" || ext == ".tofutest.json" {
return nil
}
// FIXME: should actually prefer .tofu if .tf does exist, too.
if ext != ".tf" && ext != ".tofu" {
return nil
}
file, err := root.Open(path)
if err != nil {
return err
}
data, err := io.ReadAll(file)
if err != nil {
return err
}
hclFile, diags := hclsyntax.ParseConfig(data, path, hcl.InitialPos)
if diags.HasErrors() {
return diags
}
root, _, diags := hclFile.Body.PartialContent(&hcl.BodySchema{Blocks: []hcl.BlockHeaderSchema{{Type: "terraform"}}})
if diags.HasErrors() {
return diags
}
for _, block := range root.Blocks {
content, _, diags := block.Body.PartialContent(&hcl.BodySchema{Blocks: []hcl.BlockHeaderSchema{{Type: "backend", LabelNames: []string{"type"}}}})
if diags.HasErrors() {
return diags
}
for _, cb := range content.Blocks {
if cb.Type == "backend" && len(cb.Labels) == 1 {
if cb.Labels[0] == "http" {
return ErrManuallyConfigured
} else {
return ErrDifferentManuallyConfigured
}
}
}
}
return nil
})
if err != nil {
switch err {
case ErrManuallyConfigured:
return true, nil
case ErrDifferentManuallyConfigured:
return true, err
default:
return false, err
}
}
return false, nil
}
func main() {
if len(os.Args) < 2 {
fmt.Printf("gitlab-tofu: no root directory for backend discovery provided as first argument\n")
os.Exit(1)
}
root, err := os.OpenRoot(os.Args[1])
if err != nil {
fmt.Printf("gitlab-tofu: unable to open root directory for backend discovery: %s\n", err)
os.Exit(1)
}
configured, err := isHTTPBackendConfigured(root)
if err != nil {
fmt.Printf("gitlab-tofu: error during backend discovery: %s\n", err)
os.Exit(1)
}
if configured {
fmt.Println("gitlab-tofu: HTTP backend manually configured, doing nothing")
os.Exit(0)
}
file, err := root.Create("__gitlab-opentofu-backend.tf")
if err != nil {
fmt.Printf("gitlab-tofu: failed to create __gitlab-opentofu-backend.tf to automatically define HTTP backend: %s\n", err)
os.Exit(1)
}
_, err = file.WriteString(`terraform {
backend "http" {}
}`)
if err != nil {
fmt.Printf("gitlab-tofu: failed to write __gitlab-opentofu-backend.tf to automatically define HTTP backend: %s\n", err)
os.Exit(1)
}
err = file.Close()
if err != nil {
fmt.Printf("gitlab-tofu: failed to close __gitlab-opentofu-backend.tf to automatically define HTTP backend: %s\n", err)
os.Exit(1)
}
fmt.Printf("gitlab-tofu: automatically defining the HTTP backend in __gitlab-opentofu-backend.tf")
}
......@@ -238,17 +238,7 @@ define_http_backend() {
return
fi
if ! grep -q '^[[:space:]]*backend[[:space:]]\+"http"[[:space:]]\+{.*$' "${abs_tf_root}" -r 2>/dev/null; then
echo "gitlab-tofu: automatically defining the HTTP backend in __gitlab-opentofu-backend.tf. If that is a mistake, please disable it with the auto_define_backend: false input."
cat <<EOF > __gitlab-opentofu-backend.tf
terraform {
backend "http" {}
}
EOF
else
echo "gitlab-tofu: auto_define_backend is enabled, but found manually configured HTTP backend, doing nothing."
fi
gitlab-tofu-auto-define-backend "${abs_tf_root}"
}
# configure_variables_for_tofu sets and exports all relevant variables for subsequent `tofu` command invocations.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment