-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
793ec59
commit 644c145
Showing
23 changed files
with
1,359 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.