Skip to content

Commit

Permalink
bt: Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidGamba committed Aug 30, 2023
1 parent 793ec59 commit 644c145
Show file tree
Hide file tree
Showing 23 changed files with 1,359 additions and 0 deletions.
64 changes: 64 additions & 0 deletions bt/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
= bt: Build Terraform

A no commitments Terraform wrapper that provides build caching functionality.

== Install

Install the binary into your `~/go/bin`:

----
go install github.com/DavidGamba/dgtools/bt@latest
----

Then setup the completion.

For bash:

----
complete -o default -C bt bt
----

For zsh:

----
autoload bashcompinit
bashcompinit
complete -o default -C bt bt
----

== Config file

The config file must be saved in a file named `.bt.cue`.
It will be searched from the current dir upwards.

Example:

.Config file .bt.cue
[source, cue]
----
terraform: {
init: {
backend_config: ["backend.tfvars"]
}
plan: {
var_file: ["vars.tfvars"]
}
workspaces: {
enabled: true
dir: "envs"
}
}
----

== Usage

== Caching Internals

After running `terraform init` it will save a `.tf.init` file.
It will use that file to determine if any files have changed and if re-running is required.

After running `terraform plan` it will save a `.tf.plan` or `.tf.plan-<workspace>` file.
It will use that file to determine if the plan already exists of if needs to be run.

After running `terraform apply` it will save a `.tf.apply` or `.tf.apply-<workspace>` file.
It will use that file to determine if the apply has already been made.
127 changes: 127 additions & 0 deletions bt/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package config

import (
"context"
"embed"
"fmt"
"log"
"os"
"path/filepath"

"github.com/DavidGamba/dgtools/cueutils"
)

//go:embed schema.cue
var f embed.FS

var Logger = log.New(os.Stderr, "", log.LstdFlags)

type Config struct {
Terraform struct {
Init struct {
BackendConfig []string `json:"backend_config"`
}
Plan struct {
VarFile []string `json:"var_file"`
}
Workspaces struct {
Enabled bool
Dir string
}
}
}

func (c *Config) String() string {
return fmt.Sprintf("backend_config files: %v, var files: %v, workspaces enabled: %t, ws dir: '%s'",
c.Terraform.Init.BackendConfig,
c.Terraform.Plan.VarFile,
c.Terraform.Workspaces.Enabled,
c.Terraform.Workspaces.Dir,
)
}

type contextKey string

const configKey contextKey = "config"

func NewConfigContext(ctx context.Context, value *Config) context.Context {
return context.WithValue(ctx, configKey, value)
}

func ConfigFromContext(ctx context.Context) *Config {
v, ok := ctx.Value(configKey).(*Config)
if ok {
return v
}
return &Config{}
}

func Get(ctx context.Context, filename string) (*Config, string, error) {
f, err := FindFileUpwards(ctx, filename)
if err != nil {
return &Config{}, f, fmt.Errorf("failed to find config file: %w", err)
}
cfg, err := Read(ctx, f)
if err != nil {
return &Config{}, f, fmt.Errorf("failed to read config: %w", err)
}
return cfg, f, nil
}

func Read(ctx context.Context, filename string) (*Config, error) {
configs := []cueutils.CueConfigFile{}

schemaFilename := "schema.cue"
schemaFH, err := f.Open(schemaFilename)
if err != nil {
return nil, fmt.Errorf("failed to open '%s': %w", schemaFilename, err)
}
defer schemaFH.Close()
configs = append(configs, cueutils.CueConfigFile{Data: schemaFH, Name: schemaFilename})

configFH, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("failed to open '%s': %w", filename, err)
}
defer configFH.Close()
configs = append(configs, cueutils.CueConfigFile{Data: configFH, Name: filename})

c := Config{}
err = cueutils.Unmarshal(configs, &c)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal: %w", err)
}

return &c, nil
}

func FindFileUpwards(ctx context.Context, filename string) (string, error) {
cwd, err := os.Getwd()
if err != nil {
return "", fmt.Errorf("failed to get cwd: %w", err)
}
check := func(dir string) bool {
f := filepath.Join(dir, filename)
if _, err := os.Stat(f); os.IsNotExist(err) {
return false
}
return true
}
d := cwd
for {
found := check(d)
if found {
return filepath.Join(d, filename), nil
}
a, err := filepath.Abs(d)
if err != nil {
return "", fmt.Errorf("failed to get abs path: %w", err)
}
if a == "/" {
break
}
d = filepath.Join(d, "../")
}

return "", fmt.Errorf("not found: %s", filename)
}
14 changes: 14 additions & 0 deletions bt/config/schema.cue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package bt

#Terraform: {
init?: {
backend_config: [...string]
}
plan?: {
var_file: [...string]
}
workspaces?: {
enabled: bool
dir: string
}
}
22 changes: 22 additions & 0 deletions bt/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module github.com/DavidGamba/dgtools/bt

go 1.21

require (
cuelang.org/go v0.6.0 // indirect
github.com/DavidGamba/dgtools/buildutils v0.2.0 // indirect
github.com/DavidGamba/dgtools/cueutils v0.0.0-20230620071340-793ec59816c0 // indirect
github.com/DavidGamba/dgtools/fsmodtime v0.1.0 // indirect
github.com/DavidGamba/dgtools/run v0.7.0 // indirect
github.com/DavidGamba/go-getoptions v0.27.0 // indirect
github.com/cockroachdb/apd/v2 v2.0.2 // indirect
github.com/cockroachdb/apd/v3 v3.2.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de // indirect
github.com/pkg/errors v0.9.1 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/text v0.12.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
35 changes: 35 additions & 0 deletions bt/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
cuelang.org/go v0.6.0 h1:dJhgKCog+FEZt7OwAYV1R+o/RZPmE8aqFoptmxSWyr8=
cuelang.org/go v0.6.0/go.mod h1:9CxOX8aawrr3BgSdqPj7V0RYoXo7XIb+yDFC6uESrOQ=
github.com/DavidGamba/dgtools/buildutils v0.2.0 h1:YrINv+hsXQFhdfdYFdDVheBLbz3D7OQtNFuH+eKNyo0=
github.com/DavidGamba/dgtools/buildutils v0.2.0/go.mod h1:gEikilH0xsJVMydPErNv4mlUPhJGFT5k6v5Ss+Fn+d4=
github.com/DavidGamba/dgtools/cueutils v0.0.0-20230620071340-793ec59816c0 h1:hrLbd4GGBVUvoQX3gfuNA+kbMyva4omjWNTyFuLAW34=
github.com/DavidGamba/dgtools/cueutils v0.0.0-20230620071340-793ec59816c0/go.mod h1:Ccl14sr0J7MOMsP0JcmBzZbjaxobN2/2nRzd+AV2NBA=
github.com/DavidGamba/dgtools/fsmodtime v0.1.0 h1:aoKDoKesWyDL06dDtRaTNOZqcUPRgQxLpj3zigSGjiY=
github.com/DavidGamba/dgtools/fsmodtime v0.1.0/go.mod h1:ruwqMvW2pWDbSQlAupP7F0QaojfbuXPyUOUKR4Ev3pQ=
github.com/DavidGamba/dgtools/run v0.7.0 h1:ENTskNQvBM1/n/b42PxqJktU9EzQVqPZRGUBtVdEVdE=
github.com/DavidGamba/dgtools/run v0.7.0/go.mod h1:3P1fMJupTWqsiE8IXsXrk2HtgkZBTCFRLbaTjRlmDe0=
github.com/DavidGamba/go-getoptions v0.27.0 h1:hldKJSwO9SwvR+z9pe6ojhEcYECrRiO/bar9B7MnBKA=
github.com/DavidGamba/go-getoptions v0.27.0/go.mod h1:qLaLSYeQ8sUVOfKuu5JT5qKKS3OCwyhkYSJnoG+ggmo=
github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E=
github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw=
github.com/cockroachdb/apd/v3 v3.2.0 h1:79kHCn4tO0VGu3W0WujYrMjBDk8a2H4KEUYcXf7whcg=
github.com/cockroachdb/apd/v3 v3.2.0/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de h1:D5x39vF5KCwKQaw+OC9ZPiLVHXz3UFw2+psEX+gYcto=
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de/go.mod h1:kJun4WP5gFuHZgRjZUWWuH1DTxCtxbHDOIJsudS8jzY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
55 changes: 55 additions & 0 deletions bt/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package main

import (
"errors"
"fmt"
"io"
"log"
"os"

"github.com/DavidGamba/dgtools/bt/config"
"github.com/DavidGamba/dgtools/bt/terraform"
"github.com/DavidGamba/go-getoptions"
)

var Logger = log.New(os.Stderr, "", log.LstdFlags)

func main() {
os.Exit(program(os.Args))
}

func program(args []string) int {
ctx, cancel, done := getoptions.InterruptContext()
defer func() { cancel(); <-done }()

// Read config and store it in context
cfg, _, _ := config.Get(ctx, ".bt.cue")
ctx = config.NewConfigContext(ctx, cfg)

opt := getoptions.New()
opt.Self("", "Terraform build system built as a no lock-in wrapper")
opt.Bool("quiet", false, opt.GetEnv("QUIET"))
opt.SetUnknownMode(getoptions.Pass)

terraform.NewCommand(ctx, opt)

opt.HelpCommand("help", opt.Alias("?"))
remaining, err := opt.Parse(args[1:])
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
return 1
}
if opt.Called("quiet") {
Logger.SetOutput(io.Discard)
}

err = opt.Dispatch(ctx, remaining)
if err != nil {
if errors.Is(err, getoptions.ErrorHelpCalled) {
return 1
}
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
return 1
}
return 0
}
Loading

0 comments on commit 644c145

Please sign in to comment.