Skip to content

Commit

Permalink
improve terragrunt support #36
Browse files Browse the repository at this point in the history
  • Loading branch information
dvaumoron committed Jan 29, 2024
1 parent c35e69f commit cb7e208
Show file tree
Hide file tree
Showing 14 changed files with 308 additions and 49 deletions.
4 changes: 3 additions & 1 deletion cmd/tenv/subcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,9 @@ func addForceRemoteAndNoInstallFlags(flags *pflag.FlagSet, conf *config.Config,
}

func addKeyFlag(flags *pflag.FlagSet, params subCmdParams) {
flags.StringVarP(params.pPublicKeyPath, "key-file", "k", "", "local path to PGP public key file (replace check against remote one)")
if params.pPublicKeyPath != nil {
flags.StringVarP(params.pPublicKeyPath, "key-file", "k", "", "local path to PGP public key file (replace check against remote one)")
}
}

func addRemoteUrlFlag(flags *pflag.FlagSet, conf *config.Config, params subCmdParams) {
Expand Down
14 changes: 14 additions & 0 deletions cmd/tenv/tenv.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
const (
rootVersionHelp = "Display tenv current version."
tfHelp = "subcommands that help manage several version of Terraform (https://www.terraform.io)."
tgHelp = "subcommands that help manage several version of Terragrunt (https://terragrunt.gruntwork.io/)."
)

// can be overridden with ldflags.
Expand Down Expand Up @@ -88,6 +89,19 @@ func initRootCmd(conf *config.Config) *cobra.Command {

rootCmd.AddCommand(tfCmd)

tgCmd := &cobra.Command{
Use: "tg",
Short: tgHelp,
Long: tgHelp,
}

tgParams := subCmdParams{
needToken: true, remoteEnvName: config.TgRemoteURLEnvName, pRemote: &conf.TgRemoteURL,
}
initSubCmds(tgCmd, conf, builder.BuildTgManager(conf), tgParams)

rootCmd.AddCommand(tfCmd)

return rootCmd
}

Expand Down
28 changes: 28 additions & 0 deletions cmd/terragrunt/terragrunt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
*
* Copyright 2024 tofuutils authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package main

import (
"github.com/tofuutils/tenv/versionmanager/builder"
"github.com/tofuutils/tenv/versionmanager/proxy"
)

func main() {
proxy.ExecProxy(builder.BuildTgManager, "terragrunt")
}
16 changes: 14 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ import (
)

const (
defaultTfHashicorpURL = "https://releases.hashicorp.com/terraform"
defaultTofuGithubURL = "https://api.github.com/repos/opentofu/opentofu/releases"
defaultTfHashicorpURL = "https://releases.hashicorp.com/terraform"
defaultTerragruntGithubURL = "https://api.github.com/repos/gruntwork-io/terragrunt/releases"
defaultTofuGithubURL = "https://api.github.com/repos/opentofu/opentofu/releases"

autoInstallEnvName = "AUTO_INSTALL"
forceRemoteEnvName = "FORCE_REMOTE"
Expand All @@ -43,6 +44,10 @@ const (
tfVerboseEnvName = tfenvPrefix + verboseEnvName
TfVersionEnvName = tfenvPrefix + "TERRAFORM_VERSION"

tgPrefix = "TG_"
TgRemoteURLEnvName = tgPrefix + remoteURLEnvName
TgVersionEnvName = tgPrefix + "VERSION"

tofuenvPrefix = "TOFUENV_"
tofuAutoInstallEnvName = tofuenvPrefix + autoInstallEnvName
tofuForceRemoteEnvName = tofuenvPrefix + forceRemoteEnvName
Expand All @@ -61,6 +66,7 @@ type Config struct {
RootPath string
TfKeyPath string
TfRemoteURL string
TgRemoteURL string
TofuKeyPath string
TofuRemoteURL string
UserPath string
Expand Down Expand Up @@ -96,6 +102,11 @@ func InitConfigFromEnv() (Config, error) {
tfRemoteURL = defaultTfHashicorpURL
}

tgRemoteURL := os.Getenv(TgRemoteURLEnvName)
if tfRemoteURL == "" {
tfRemoteURL = defaultTerragruntGithubURL
}

tofuRemoteURL := os.Getenv(TofuRemoteURLEnvName)
if tofuRemoteURL == "" {
tofuRemoteURL = defaultTofuGithubURL
Expand All @@ -122,6 +133,7 @@ func InitConfigFromEnv() (Config, error) {
RootPath: rootPath,
TfKeyPath: os.Getenv(tfHashicorpPGPKeyEnvName),
TfRemoteURL: tfRemoteURL,
TgRemoteURL: tgRemoteURL,
TofuKeyPath: os.Getenv(tofuOpenTofuPGPKeyEnvName),
TofuRemoteURL: tofuRemoteURL,
UserPath: userPath,
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/tofuutils/tenv
go 1.21

require (
github.com/BurntSushi/toml v1.3.2
github.com/ProtonMail/gopenpgp/v2 v2.7.4
github.com/hashicorp/go-version v1.6.0
github.com/hashicorp/hcl/v2 v2.19.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
Expand Down
12 changes: 10 additions & 2 deletions versionmanager/builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,25 @@ import (
"github.com/tofuutils/tenv/config"
"github.com/tofuutils/tenv/versionmanager"
terraformretriever "github.com/tofuutils/tenv/versionmanager/retriever/terraform"
terragruntretriever "github.com/tofuutils/tenv/versionmanager/retriever/terragrunt"
tofuretriever "github.com/tofuutils/tenv/versionmanager/retriever/tofu"
"github.com/tofuutils/tenv/versionmanager/semantic"
)

func BuildTfManager(conf *config.Config) versionmanager.VersionManager {
tfRetriever := terraformretriever.NewTerraformRetriever(conf)

return versionmanager.MakeVersionManager(conf, "Terraform", tfRetriever, config.TfVersionEnvName, ".terraform-version", ".tfswitchrc")
return versionmanager.MakeVersionManager(conf, "Terraform", semantic.TfPredicateReaders, tfRetriever, config.TfVersionEnvName, ".terraform-version", ".tfswitchrc")
}

func BuildTgManager(conf *config.Config) versionmanager.VersionManager {
tgRetriever := terragruntretriever.NewTerragruntRetriever(conf)

return versionmanager.MakeVersionManager(conf, "Terragrunt", semantic.TgPredicateReaders, tgRetriever, config.TgVersionEnvName, ".terragrunt-version", ".tgswitchrc")
}

func BuildTofuManager(conf *config.Config) versionmanager.VersionManager {
tofuRetriever := tofuretriever.NewTofuRetriever(conf)

return versionmanager.MakeVersionManager(conf, "OpenTofu", tofuRetriever, config.TofuVersionEnvName, ".opentofu-version")
return versionmanager.MakeVersionManager(conf, "OpenTofu", semantic.TfPredicateReaders, tofuRetriever, config.TofuVersionEnvName, ".opentofu-version")
}
21 changes: 7 additions & 14 deletions versionmanager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import (
"github.com/hashicorp/go-version"
"github.com/tofuutils/tenv/config"
"github.com/tofuutils/tenv/pkg/reversecmp"
"github.com/tofuutils/tenv/pkg/zip"
"github.com/tofuutils/tenv/versionmanager/semantic"
)

Expand All @@ -39,22 +38,23 @@ var (
)

type ReleaseInfoRetriever interface {
DownloadReleaseZip(version string) ([]byte, error)
InstallRelease(version string, targetPath string) error
LatestRelease() (string, error)
ListReleases() ([]string, error)
}

type VersionManager struct {
conf *config.Config
FolderName string
predicateReaders []func(*config.Config) (func(string) bool, bool, error)
retriever ReleaseInfoRetriever
VersionEnvName string
VersionFileName string
otherVersionFileNames []string
}

func MakeVersionManager(conf *config.Config, folderName string, retriever ReleaseInfoRetriever, versionEnvName string, versionFileName string, otherVersionFileNames ...string) VersionManager {
return VersionManager{conf: conf, FolderName: folderName, retriever: retriever, VersionEnvName: versionEnvName, VersionFileName: versionFileName, otherVersionFileNames: otherVersionFileNames}
func MakeVersionManager(conf *config.Config, folderName string, predicateReaders []func(*config.Config) (func(string) bool, bool, error), retriever ReleaseInfoRetriever, versionEnvName string, versionFileName string, otherVersionFileNames ...string) VersionManager {
return VersionManager{conf: conf, FolderName: folderName, predicateReaders: predicateReaders, retriever: retriever, VersionEnvName: versionEnvName, VersionFileName: versionFileName, otherVersionFileNames: otherVersionFileNames}
}

// detect version (can install depending on auto install env var).
Expand All @@ -76,7 +76,7 @@ func (m VersionManager) Install(requestedVersion string) error {
return err
}

predicate, reverseOrder, err := semantic.ParsePredicate(requestedVersion, m.FolderName, m.conf.Verbose)
predicate, reverseOrder, err := semantic.ParsePredicate(requestedVersion, m.FolderName, m.predicateReaders, m.conf)
if err != nil {
return err
}
Expand Down Expand Up @@ -237,7 +237,7 @@ func (m VersionManager) detect(requestedVersion string) (string, error) {
return cleanedVersion, m.installSpecificVersion(cleanedVersion)
}

predicate, reverseOrder, err := semantic.ParsePredicate(requestedVersion, m.FolderName, m.conf.Verbose)
predicate, reverseOrder, err := semantic.ParsePredicate(requestedVersion, m.FolderName, m.predicateReaders, m.conf)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -306,14 +306,7 @@ func (m VersionManager) installSpecificVersion(version string) error {
fmt.Println("Installation of", m.FolderName, version) //nolint
}

data, err := m.retriever.DownloadReleaseZip(version)
if err != nil {
return err
}

targetPath := path.Join(installPath, version)

return zip.UnzipToDir(data, targetPath)
return m.retriever.InstallRelease(version, path.Join(installPath, version))
}

func (m VersionManager) searchInstallRemote(predicate func(string) bool, reverseOrder bool, noInstall bool) (string, error) {
Expand Down
17 changes: 9 additions & 8 deletions versionmanager/retriever/terraform/terraformretriever.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
pgpcheck "github.com/tofuutils/tenv/pkg/check/pgp"
sha256check "github.com/tofuutils/tenv/pkg/check/sha256"
"github.com/tofuutils/tenv/pkg/download"
"github.com/tofuutils/tenv/pkg/zip"
"github.com/tofuutils/tenv/versionmanager/semantic"
)

Expand All @@ -47,42 +48,42 @@ func NewTerraformRetriever(conf *config.Config) *TerraformRetriever {
return &TerraformRetriever{conf: conf}
}

func (r *TerraformRetriever) DownloadReleaseZip(version string) ([]byte, error) {
func (r *TerraformRetriever) InstallRelease(version string, targetPath string) error {
// assume that terraform version do not start with a 'v'
if version[0] == 'v' {
version = version[1:]
}

baseVersionURL, err := url.JoinPath(r.conf.TfRemoteURL, version) //nolint
if err != nil {
return nil, err
return err
}

versionUrl, err := url.JoinPath(baseVersionURL, indexJson) //nolint
if err != nil {
return nil, err
return err
}

value, err := apiGetRequest(versionUrl)
if err != nil {
return nil, err
return err
}

fileName, downloadURL, downloadSumsURL, downloadSumsSigURL, err := extractAssetUrls(baseVersionURL, runtime.GOOS, runtime.GOARCH, value)
if err != nil {
return nil, err
return err
}

data, err := download.Bytes(downloadURL)
if err != nil {
return nil, err
return err
}

if err = r.checkSumAndSig(fileName, data, downloadSumsURL, downloadSumsSigURL); err != nil {
return nil, err
return err
}

return data, nil
return zip.UnzipToDir(data, targetPath)
}

func (r *TerraformRetriever) LatestRelease() (string, error) {
Expand Down
98 changes: 98 additions & 0 deletions versionmanager/retriever/terragrunt/terragruntretriever.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
*
* Copyright 2024 tofuutils authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package terragruntretriever

import (
"os"
"path/filepath"
"runtime"
"strings"

"github.com/tofuutils/tenv/config"
sha256check "github.com/tofuutils/tenv/pkg/check/sha256"
"github.com/tofuutils/tenv/pkg/download"
"github.com/tofuutils/tenv/pkg/github"
)

const baseFileName = "terragrunt_"

type TerragruntRetriever struct {
conf *config.Config
}

func NewTerragruntRetriever(conf *config.Config) *TerragruntRetriever {
return &TerragruntRetriever{conf: conf}
}

func (r *TerragruntRetriever) InstallRelease(versionStr string, targetPath string) error {
tag := versionStr
// assume that terragrunt tags start with a 'v'
if tag[0] != 'v' {
tag = "v" + versionStr
}

assetNames := buildAssetNames()
assets, err := github.DownloadAssetURL(tag, assetNames, r.conf.TgRemoteURL, r.conf.GithubToken)
if err != nil {
return err
}

data, err := download.Bytes(assets[assetNames[0]])
if err != nil {
return err
}

dataSums, err := download.Bytes(assets[assetNames[1]])
if err != nil {
return err
}

if err = sha256check.Check(data, dataSums, assetNames[0]); err != nil {
return err
}

err = os.MkdirAll(targetPath, 0755)
if err != nil {
return err
}

return os.WriteFile(filepath.Join(targetPath, "terragrunt"), data, 0755)
}

func (r *TerragruntRetriever) LatestRelease() (string, error) {
return github.LatestRelease(r.conf.TgRemoteURL, r.conf.GithubToken)
}

func (r *TerragruntRetriever) ListReleases() ([]string, error) {
return github.ListReleases(r.conf.TgRemoteURL, r.conf.GithubToken)
}

func buildAssetNames() []string {
var nameBuilder strings.Builder
nameBuilder.WriteString(baseFileName)
nameBuilder.WriteByte('_')
nameBuilder.WriteString(runtime.GOOS)
nameBuilder.WriteByte('_')
nameBuilder.WriteString(runtime.GOARCH)
if runtime.GOOS == "windows" {
nameBuilder.WriteString(".exe")
}

return []string{nameBuilder.String(), "SHA256SUMS"}
}
Loading

0 comments on commit cb7e208

Please sign in to comment.