Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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")
}