Skip to content

Commit

Permalink
feat(runner): allow user to include terraform & terragrunt binaries i…
Browse files Browse the repository at this point in the history
…n 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
  • Loading branch information
LucasMrqes authored Mar 15, 2024
1 parent d93221c commit 9c55d90
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 14 deletions.
1 change: 1 addition & 0 deletions cmd/runner/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ func buildRunnerStartCmd(app *burrito.App) *cobra.Command {
}

cmd.Flags().StringVar(&app.Config.Runner.SSHKnownHostsConfigMapName, "ssh-known-hosts-cm-name", "burrito-ssh-known-hosts", "configmap name to get known hosts file from")
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")
return cmd
}
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxG
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
Expand Down Expand Up @@ -280,6 +281,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
github.com/labstack/echo/v4 v4.11.2 h1:T+cTLQxWCDfqDEoydYm5kCobjmHwOwcv4OJAPHilmdE=
github.com/labstack/echo/v4 v4.11.2/go.mod h1:UcGuQ8V6ZNRmSweBIJkPvGfwCMIlFmiqrPqiEBfPYws=
github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8=
Expand Down
1 change: 1 addition & 0 deletions internal/burrito/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ type RunnerConfig struct {
Layer Layer `mapstructure:"layer"`
Repository RepositoryConfig `mapstructure:"repository"`
SSHKnownHostsConfigMapName string `mapstructure:"sshKnownHostsConfigMapName"`
RunnerBinaryPath string `mapstructure:"runnerBinaryPath"`
}

type Layer struct {
Expand Down
4 changes: 2 additions & 2 deletions internal/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func newK8SClient() (client.Client, error) {

func (r *Runner) install() error {
terraformVersion := configv1alpha1.GetTerraformVersion(r.repository, r.layer)
terraformExec := terraform.NewTerraform(terraformVersion, PlanArtifact)
terraformExec := terraform.NewTerraform(terraformVersion, PlanArtifact, r.config.Runner.RunnerBinaryPath)
terraformRuntime := "terraform"
if configv1alpha1.GetTerragruntEnabled(r.repository, r.layer) {
terraformRuntime = "terragrunt"
Expand All @@ -157,7 +157,7 @@ func (r *Runner) install() error {
r.exec = terraformExec
case "terragrunt":
log.Infof("using terragrunt")
r.exec = terragrunt.NewTerragrunt(terraformExec, configv1alpha1.GetTerragruntVersion(r.repository, r.layer), PlanArtifact)
r.exec = terragrunt.NewTerragrunt(terraformExec, configv1alpha1.GetTerragruntVersion(r.repository, r.layer), PlanArtifact, r.config.Runner.RunnerBinaryPath)
}
err := r.exec.Install()
if err != nil {
Expand Down
25 changes: 21 additions & 4 deletions internal/runner/terraform/terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import (
"os"

"github.com/hashicorp/go-version"
install "github.com/hashicorp/hc-install"
"github.com/hashicorp/hc-install/fs"
"github.com/hashicorp/hc-install/product"
"github.com/hashicorp/hc-install/releases"
"github.com/hashicorp/hc-install/src"
"github.com/hashicorp/terraform-exec/tfexec"
)

Expand All @@ -18,12 +21,14 @@ type Terraform struct {
version string
ExecPath string
planArtifactPath string
runnerBinaryPath string
}

func NewTerraform(version, planArtifactPath string) *Terraform {
func NewTerraform(version, planArtifactPath string, runnerBinaryPath string) *Terraform {
return &Terraform{
version: version,
planArtifactPath: planArtifactPath,
runnerBinaryPath: runnerBinaryPath,
}
}

Expand All @@ -32,11 +37,23 @@ func (t *Terraform) Install() error {
if err != nil {
return err
}
installer := &releases.ExactVersion{
i := install.NewInstaller()
version := version.Must(terraformVersion, nil)
fs := fs.ExactVersion{
Product: product.Terraform,
Version: version.Must(terraformVersion, nil),
Version: version,
ExtraPaths: []string{
t.runnerBinaryPath,
},
}
execPath, err := installer.Install(context.Background())
releases := releases.ExactVersion{
Product: product.Terraform,
Version: version,
}
execPath, err := i.Ensure(context.Background(), []src.Source{
&fs,
&releases,
})
if err != nil {
return err
}
Expand Down
105 changes: 97 additions & 8 deletions internal/runner/terragrunt/terragrunt.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package terragrunt

import (
"crypto/sha256"
"errors"
"fmt"
"io"
Expand All @@ -9,12 +10,10 @@ import (
"os/exec"
"path/filepath"
"runtime"
"strings"

"github.com/padok-team/burrito/internal/runner/terraform"
)

const (
BinWorkDir = "/runner/bin"
log "github.com/sirupsen/logrus"
)

type Terragrunt struct {
Expand All @@ -23,13 +22,15 @@ type Terragrunt struct {
version string
workingDir string
terraform *terraform.Terraform
runnerBinaryPath string
}

func NewTerragrunt(terraformExec *terraform.Terraform, terragruntVersion, planArtifactPath string) *Terragrunt {
func NewTerragrunt(terraformExec *terraform.Terraform, terragruntVersion, planArtifactPath string, runnerBinaryPath string) *Terragrunt {
return &Terragrunt{
version: terragruntVersion,
terraform: terraformExec,
planArtifactPath: planArtifactPath,
runnerBinaryPath: runnerBinaryPath,
}
}

Expand All @@ -43,7 +44,8 @@ func (t *Terragrunt) Install() error {
if err != nil {
return err
}
path, err := downloadTerragrunt(t.version)

path, err := ensureTerragrunt(t.version, t.runnerBinaryPath)
if err != nil {
return err
}
Expand Down Expand Up @@ -115,7 +117,94 @@ func (t *Terragrunt) Show(mode string) ([]byte, error) {
return output, nil
}

func downloadTerragrunt(version string) (string, error) {
func ensureTerragrunt(version string, runnerBinaryPath string) (string, error) {
files, err := os.ReadDir(runnerBinaryPath)
if err != nil {
return "", err
}

trustedHash, err := getTerragruntSHA256(version)
if err != nil {
return "", err
}

for _, file := range files {
if !file.IsDir() {
runnerBinaryFullPath := filepath.Join(runnerBinaryPath, file.Name())
hash, err := calculateFileSHA256(runnerBinaryFullPath)
if err != nil {
return "", err
}

if hash == trustedHash {
err = os.Chmod(runnerBinaryFullPath, 0755)
if err != nil {
return "", err
}
log.Infof("Terragrunt binary found at %s, using it", runnerBinaryFullPath)
return filepath.Abs(runnerBinaryFullPath)
}

}
}

log.Infof("Terragrunt binary not found, downloading it... (Consider packaging binaries within your runner image to mitigate eventual network expenses)")
path, err := downloadTerragrunt(version, runnerBinaryPath)
log.Infof("Downloaded terragrunt binaries to %s", path)
if err != nil {
return "", err
}

return path, nil
}

func calculateFileSHA256(filename string) (string, error) {
file, err := os.Open(filename)
if err != nil {
return "", err
}
defer file.Close()

hash := sha256.New()

if _, err := io.Copy(hash, file); err != nil {
return "", err
}

return fmt.Sprintf("%x", hash.Sum(nil)), nil
}

func getTerragruntSHA256(version string) (string, error) {
cpuArch := runtime.GOARCH
response, err := http.Get(fmt.Sprintf("https://github.com/gruntwork-io/terragrunt/releases/download/v%s/SHA256SUMS", version))
if err != nil {
return "", err
}
defer response.Body.Close()

body, err := io.ReadAll(response.Body)
if err != nil {
return "", err
}

lines := strings.Split(string(body), "\n")
for _, line := range lines {
parts := strings.Fields(line)
if len(parts) != 2 {
continue
}
sha := parts[0]
filename := parts[1]

if strings.Contains(filename, fmt.Sprintf("linux_%s", cpuArch)) {
return sha, nil
}
}

return "", errors.New("could not find a hash for this architecture in SHA256SUMS file")
}

func downloadTerragrunt(version string, runnerBinaryPath string) (string, error) {
cpuArch := runtime.GOARCH

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

filename := fmt.Sprintf("%s/terragrunt_%s", BinWorkDir, cpuArch)
filename := fmt.Sprintf("%s/terragrunt_%s", runnerBinaryPath, cpuArch)
file, err := os.Create(filename)
if err != nil {
return "", err
Expand Down

0 comments on commit 9c55d90

Please sign in to comment.