Skip to content
Snippets Groups Projects
main.go 3.35 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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")
    }