Skip to content

Commit 9c55d90

Browse files
authored
feat(runner): allow user to include terraform & terragrunt binaries in runner image (#254)
* feat(runner): add runnerBinaryPath to runner config * feat(runner): ensure terraform installation * feat(runner): ensure terragrunt installation * feat(runner): loop through all binaries to find Terragrunt
1 parent d93221c commit 9c55d90

File tree

6 files changed

+125
-14
lines changed

6 files changed

+125
-14
lines changed

cmd/runner/start.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ func buildRunnerStartCmd(app *burrito.App) *cobra.Command {
2020
}
2121

2222
cmd.Flags().StringVar(&app.Config.Runner.SSHKnownHostsConfigMapName, "ssh-known-hosts-cm-name", "burrito-ssh-known-hosts", "configmap name to get known hosts file from")
23+
cmd.Flags().StringVar(&app.Config.Runner.RunnerBinaryPath, "runner-binary-path", "/runner/bin", "binary path where the runner can expect to find terraform or terragrunt binaries")
2324
return cmd
2425
}

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxG
8585
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8686
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
8787
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
88+
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
8889
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
8990
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
9091
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
@@ -280,6 +281,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
280281
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
281282
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
282283
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
284+
github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
285+
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
283286
github.com/labstack/echo/v4 v4.11.2 h1:T+cTLQxWCDfqDEoydYm5kCobjmHwOwcv4OJAPHilmdE=
284287
github.com/labstack/echo/v4 v4.11.2/go.mod h1:UcGuQ8V6ZNRmSweBIJkPvGfwCMIlFmiqrPqiEBfPYws=
285288
github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8=

internal/burrito/config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ type RunnerConfig struct {
7979
Layer Layer `mapstructure:"layer"`
8080
Repository RepositoryConfig `mapstructure:"repository"`
8181
SSHKnownHostsConfigMapName string `mapstructure:"sshKnownHostsConfigMapName"`
82+
RunnerBinaryPath string `mapstructure:"runnerBinaryPath"`
8283
}
8384

8485
type Layer struct {

internal/runner/runner.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ func newK8SClient() (client.Client, error) {
146146

147147
func (r *Runner) install() error {
148148
terraformVersion := configv1alpha1.GetTerraformVersion(r.repository, r.layer)
149-
terraformExec := terraform.NewTerraform(terraformVersion, PlanArtifact)
149+
terraformExec := terraform.NewTerraform(terraformVersion, PlanArtifact, r.config.Runner.RunnerBinaryPath)
150150
terraformRuntime := "terraform"
151151
if configv1alpha1.GetTerragruntEnabled(r.repository, r.layer) {
152152
terraformRuntime = "terragrunt"
@@ -157,7 +157,7 @@ func (r *Runner) install() error {
157157
r.exec = terraformExec
158158
case "terragrunt":
159159
log.Infof("using terragrunt")
160-
r.exec = terragrunt.NewTerragrunt(terraformExec, configv1alpha1.GetTerragruntVersion(r.repository, r.layer), PlanArtifact)
160+
r.exec = terragrunt.NewTerragrunt(terraformExec, configv1alpha1.GetTerragruntVersion(r.repository, r.layer), PlanArtifact, r.config.Runner.RunnerBinaryPath)
161161
}
162162
err := r.exec.Install()
163163
if err != nil {

internal/runner/terraform/terraform.go

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ import (
88
"os"
99

1010
"github.com/hashicorp/go-version"
11+
install "github.com/hashicorp/hc-install"
12+
"github.com/hashicorp/hc-install/fs"
1113
"github.com/hashicorp/hc-install/product"
1214
"github.com/hashicorp/hc-install/releases"
15+
"github.com/hashicorp/hc-install/src"
1316
"github.com/hashicorp/terraform-exec/tfexec"
1417
)
1518

@@ -18,12 +21,14 @@ type Terraform struct {
1821
version string
1922
ExecPath string
2023
planArtifactPath string
24+
runnerBinaryPath string
2125
}
2226

23-
func NewTerraform(version, planArtifactPath string) *Terraform {
27+
func NewTerraform(version, planArtifactPath string, runnerBinaryPath string) *Terraform {
2428
return &Terraform{
2529
version: version,
2630
planArtifactPath: planArtifactPath,
31+
runnerBinaryPath: runnerBinaryPath,
2732
}
2833
}
2934

@@ -32,11 +37,23 @@ func (t *Terraform) Install() error {
3237
if err != nil {
3338
return err
3439
}
35-
installer := &releases.ExactVersion{
40+
i := install.NewInstaller()
41+
version := version.Must(terraformVersion, nil)
42+
fs := fs.ExactVersion{
3643
Product: product.Terraform,
37-
Version: version.Must(terraformVersion, nil),
44+
Version: version,
45+
ExtraPaths: []string{
46+
t.runnerBinaryPath,
47+
},
3848
}
39-
execPath, err := installer.Install(context.Background())
49+
releases := releases.ExactVersion{
50+
Product: product.Terraform,
51+
Version: version,
52+
}
53+
execPath, err := i.Ensure(context.Background(), []src.Source{
54+
&fs,
55+
&releases,
56+
})
4057
if err != nil {
4158
return err
4259
}

internal/runner/terragrunt/terragrunt.go

Lines changed: 97 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package terragrunt
22

33
import (
4+
"crypto/sha256"
45
"errors"
56
"fmt"
67
"io"
@@ -9,12 +10,10 @@ import (
910
"os/exec"
1011
"path/filepath"
1112
"runtime"
13+
"strings"
1214

1315
"github.com/padok-team/burrito/internal/runner/terraform"
14-
)
15-
16-
const (
17-
BinWorkDir = "/runner/bin"
16+
log "github.com/sirupsen/logrus"
1817
)
1918

2019
type Terragrunt struct {
@@ -23,13 +22,15 @@ type Terragrunt struct {
2322
version string
2423
workingDir string
2524
terraform *terraform.Terraform
25+
runnerBinaryPath string
2626
}
2727

28-
func NewTerragrunt(terraformExec *terraform.Terraform, terragruntVersion, planArtifactPath string) *Terragrunt {
28+
func NewTerragrunt(terraformExec *terraform.Terraform, terragruntVersion, planArtifactPath string, runnerBinaryPath string) *Terragrunt {
2929
return &Terragrunt{
3030
version: terragruntVersion,
3131
terraform: terraformExec,
3232
planArtifactPath: planArtifactPath,
33+
runnerBinaryPath: runnerBinaryPath,
3334
}
3435
}
3536

@@ -43,7 +44,8 @@ func (t *Terragrunt) Install() error {
4344
if err != nil {
4445
return err
4546
}
46-
path, err := downloadTerragrunt(t.version)
47+
48+
path, err := ensureTerragrunt(t.version, t.runnerBinaryPath)
4749
if err != nil {
4850
return err
4951
}
@@ -115,7 +117,94 @@ func (t *Terragrunt) Show(mode string) ([]byte, error) {
115117
return output, nil
116118
}
117119

118-
func downloadTerragrunt(version string) (string, error) {
120+
func ensureTerragrunt(version string, runnerBinaryPath string) (string, error) {
121+
files, err := os.ReadDir(runnerBinaryPath)
122+
if err != nil {
123+
return "", err
124+
}
125+
126+
trustedHash, err := getTerragruntSHA256(version)
127+
if err != nil {
128+
return "", err
129+
}
130+
131+
for _, file := range files {
132+
if !file.IsDir() {
133+
runnerBinaryFullPath := filepath.Join(runnerBinaryPath, file.Name())
134+
hash, err := calculateFileSHA256(runnerBinaryFullPath)
135+
if err != nil {
136+
return "", err
137+
}
138+
139+
if hash == trustedHash {
140+
err = os.Chmod(runnerBinaryFullPath, 0755)
141+
if err != nil {
142+
return "", err
143+
}
144+
log.Infof("Terragrunt binary found at %s, using it", runnerBinaryFullPath)
145+
return filepath.Abs(runnerBinaryFullPath)
146+
}
147+
148+
}
149+
}
150+
151+
log.Infof("Terragrunt binary not found, downloading it... (Consider packaging binaries within your runner image to mitigate eventual network expenses)")
152+
path, err := downloadTerragrunt(version, runnerBinaryPath)
153+
log.Infof("Downloaded terragrunt binaries to %s", path)
154+
if err != nil {
155+
return "", err
156+
}
157+
158+
return path, nil
159+
}
160+
161+
func calculateFileSHA256(filename string) (string, error) {
162+
file, err := os.Open(filename)
163+
if err != nil {
164+
return "", err
165+
}
166+
defer file.Close()
167+
168+
hash := sha256.New()
169+
170+
if _, err := io.Copy(hash, file); err != nil {
171+
return "", err
172+
}
173+
174+
return fmt.Sprintf("%x", hash.Sum(nil)), nil
175+
}
176+
177+
func getTerragruntSHA256(version string) (string, error) {
178+
cpuArch := runtime.GOARCH
179+
response, err := http.Get(fmt.Sprintf("https://github.com/gruntwork-io/terragrunt/releases/download/v%s/SHA256SUMS", version))
180+
if err != nil {
181+
return "", err
182+
}
183+
defer response.Body.Close()
184+
185+
body, err := io.ReadAll(response.Body)
186+
if err != nil {
187+
return "", err
188+
}
189+
190+
lines := strings.Split(string(body), "\n")
191+
for _, line := range lines {
192+
parts := strings.Fields(line)
193+
if len(parts) != 2 {
194+
continue
195+
}
196+
sha := parts[0]
197+
filename := parts[1]
198+
199+
if strings.Contains(filename, fmt.Sprintf("linux_%s", cpuArch)) {
200+
return sha, nil
201+
}
202+
}
203+
204+
return "", errors.New("could not find a hash for this architecture in SHA256SUMS file")
205+
}
206+
207+
func downloadTerragrunt(version string, runnerBinaryPath string) (string, error) {
119208
cpuArch := runtime.GOARCH
120209

121210
url := fmt.Sprintf("https://github.com/gruntwork-io/terragrunt/releases/download/v%s/terragrunt_linux_%s", version, cpuArch)
@@ -126,7 +215,7 @@ func downloadTerragrunt(version string) (string, error) {
126215
}
127216
defer response.Body.Close()
128217

129-
filename := fmt.Sprintf("%s/terragrunt_%s", BinWorkDir, cpuArch)
218+
filename := fmt.Sprintf("%s/terragrunt_%s", runnerBinaryPath, cpuArch)
130219
file, err := os.Create(filename)
131220
if err != nil {
132221
return "", err

0 commit comments

Comments
 (0)