Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(runner): allow user to include terraform & terragrunt binaries in runner image #254

Merged
merged 4 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading