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

add tenvlib package to easily use tenv as a library #240

Merged
merged 19 commits into from
Sep 8, 2024
Merged
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Terraform, Terragrunt and Atmos.
- [Semver 2.0.0](https://semver.org/) Compatibility: Utilizes [go-version](https://github.com/hashicorp/go-version) for semantic versioning and use the [HCL](https://github.com/hashicorp/hcl) parser to extract required version constraint from OpenTofu/Terraform/Terragrunt files (see [required_version](#required_version) and [Terragrunt hcl](#terragrunt-hcl-file)).
- Signature verification: Supports [cosign](https://github.com/sigstore/cosign) (if present on your machine) and PGP (via [gopenpgp](https://github.com/ProtonMail/gopenpgp)), see [signature support](#signature-support).
- Intuitive installation: Simple installation process with Homebrew and manual options.
- Callable as [Go](https://go.dev) module, with a [Semver compatibility promise](https://semver.org/#summary) on [tenvlib](https://github.com/tofuutils/tenv/tree/main/versionmanager/tenvlib) wrapper package (get more information in [TENV_AS_LIB.md](https://github.com/tofuutils/tenv/blob/main/TENV_AS_LIB.md)).

<a id="difference-with-asdf"></a>
### Difference with asdf
Expand Down
71 changes: 71 additions & 0 deletions TENV_AS_LIB.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# How to use tenv as a library

## Get started

### Prerequisites

**tenv** requires [Go](https://go.dev) version [1.21](https://go.dev/doc/devel/release#go1.21.0) or above.

### Getting tenv module

`tenvlib` package is available since tenv v3.2

```console
go get -u github.com/tofuutils/tenv/v3@latest
```

### Basic example

```go
package main

import (
"context"
"fmt"

"github.com/tofuutils/tenv/v3/config/cmdconst"
"github.com/tofuutils/tenv/v3/versionmanager/tenvlib"
)

func main() {
tenv, err := tenvlib.Make(tenvlib.AutoInstall, tenvlib.IgnoreEnv, tenvlib.DisableDisplay)
if err != nil {
fmt.Println("init failed :", err)
return
}

err = tenv.DetectedCommandProxy(context.Background(), cmdconst.TofuName, "version")
if err != nil {
fmt.Println("proxy call failed :", err)
}
}
```

## Documentation

See the [API documentation on go.dev](https://pkg.go.dev/github.com/tofuutils/tenv/v3/versionmanager/tenvlib) and [examples](https://github.com/tofuutils/tenv/tree/main/versionmanager/tenvlib/examples).

### Overview

Available Tenv struct creation options :

- `AddTool(toolName string, builderFunc builder.BuilderFunc)`, extend `tenvlib` to support other tool use cases.
- `AutoInstall`, shortcut to force auto install feature enabling in `config.Config`.
- `DisableDisplay`, do not display or log anything.
- `IgnoreEnv`, ignore **tenv** environment variables (`TENV_AUTO_INSTALL`, `TOFUENV_TOFU_VERSION`, etc.).
- `WithConfig(conf *config.Config)`, replace default `Config` (one from a `InitConfigFromEnv` or `DefaultConfig` call depending on `IgnoreEnv` usage).
- `WithDisplayer(displayer loghelper.Displayer)`, replace default `Displayer` with a custom to handle `tenvlib` output (standard and log).
- `WithHCLParser(hclParser *hclparse.Parser)`, use passed `Parser` instead of creating a new one.

Tenv methods list :

- `[Detected]Command[Proxy]`
- `Detect`
- `Evaluate`
- `Install[Multiple]`
- `List[Local|Remote]`
- `LocallyInstalled`
- `[Res|S]etDefault[Constraint|Version]`, manage `constraint` and `version` files in `<rootPath>/<tool>/`
- `Uninstall[Multiple]`

Happy hacking !
15 changes: 10 additions & 5 deletions cmd/tenv/subcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package main

import (
"bytes"
"context"
"os"
"strconv"
"strings"
Expand Down Expand Up @@ -88,7 +89,8 @@ func newDetectCmd(conf *config.Config, versionManager versionmanager.VersionMana
conf.InitDisplayer(false)
conf.InitInstall(forceInstall, forceNoInstall)

detectedVersion, err := versionManager.Detect(false)
ctx := context.Background()
detectedVersion, err := versionManager.Detect(ctx, false)
if err != nil {
loghelper.StdDisplay(err.Error())

Expand Down Expand Up @@ -138,6 +140,7 @@ If a parameter is passed, available options:
Run: func(_ *cobra.Command, args []string) {
conf.InitDisplayer(false)

ctx := context.Background()
if len(args) == 0 {
version, err := versionManager.Resolve(semantic.LatestKey)
if err != nil {
Expand All @@ -146,14 +149,14 @@ If a parameter is passed, available options:
return
}

if err = versionManager.Install(version); err != nil {
if err = versionManager.Install(ctx, version); err != nil {
loghelper.StdDisplay(err.Error())
}

return
}

if err := versionManager.Install(args[0]); err != nil {
if err := versionManager.Install(ctx, args[0]); err != nil {
loghelper.StdDisplay(err.Error())
}
},
Expand Down Expand Up @@ -244,7 +247,8 @@ func newListRemoteCmd(conf *config.Config, versionManager versionmanager.Version
Run: func(_ *cobra.Command, _ []string) {
conf.InitDisplayer(false)

versions, err := versionManager.ListRemote(reverseOrder)
ctx := context.Background()
versions, err := versionManager.ListRemote(ctx, reverseOrder)
if err != nil {
loghelper.StdDisplay(err.Error())

Expand Down Expand Up @@ -378,7 +382,8 @@ Available parameter options:
conf.InitDisplayer(false)
conf.InitInstall(forceInstall, forceNoInstall)

if err := versionManager.Use(args[0], workingDir); err != nil {
ctx := context.Background()
if err := versionManager.Use(ctx, args[0], workingDir); err != nil {
loghelper.StdDisplay(err.Error())
}
},
Expand Down
4 changes: 3 additions & 1 deletion cmd/tenv/tenv.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package main

import (
"context"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -157,7 +158,8 @@ func manageNoArgsCmd(conf *config.Config, hclParser *hclparse.Parser) {
return
}

if err := toolUI(conf, hclParser); err != nil {
ctx := context.Background()
if err := toolUI(ctx, conf, hclParser); err != nil {
fmt.Println(err.Error())

os.Exit(1)
Expand Down
11 changes: 6 additions & 5 deletions cmd/tenv/textui.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package main

import (
"context"
"fmt"
"io"
"slices"
Expand Down Expand Up @@ -172,7 +173,7 @@ func (m itemModel) View() string {
return "\n" + m.list.View()
}

func toolUI(conf *config.Config, hclParser *hclparse.Parser) error {
func toolUI(ctx context.Context, conf *config.Config, hclParser *hclparse.Parser) error {
conf.InitDisplayer(false)

// shared object
Expand Down Expand Up @@ -212,7 +213,7 @@ func toolUI(conf *config.Config, hclParser *hclparse.Parser) error {
for _, toolItem := range tools {
tool := toolItem.FilterValue()
if _, selected := selection[tool]; selected {
if err = manageUI(builder.Builders[tool](conf, hclParser)); err != nil {
if err = manageUI(ctx, builder.Builders[tool](conf, hclParser)); err != nil {
return err
}
}
Expand All @@ -221,10 +222,10 @@ func toolUI(conf *config.Config, hclParser *hclparse.Parser) error {
return nil
}

func manageUI(versionManager versionmanager.VersionManager) error {
func manageUI(ctx context.Context, versionManager versionmanager.VersionManager) error {
installed := versionManager.LocalSet()

remoteVersions, err := versionManager.ListRemote(true)
remoteVersions, err := versionManager.ListRemote(ctx, true)
if err != nil {
return err
}
Expand Down Expand Up @@ -286,7 +287,7 @@ func manageUI(versionManager versionmanager.VersionManager) error {
return nil
}

return versionManager.InstallMultiple(toInstall)
return versionManager.InstallMultiple(ctx, toInstall)
}

func uninstallUI(versionManager versionmanager.VersionManager) error {
Expand Down
30 changes: 25 additions & 5 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
)

const (
defaultDirName = ".tenv"
githubActionsEnvName = "GITHUB_ACTIONS"

archEnvName = "ARCH"
Expand Down Expand Up @@ -132,10 +133,10 @@ type Config struct {
ForceRemote bool
GithubActions bool
GithubToken string
NoInstall bool
remoteConfLoaded bool
RemoteConfPath string
RootPath string
SkipInstall bool
SkipSignature bool
Tf RemoteConfig
TfKeyPath string
Expand All @@ -145,6 +146,25 @@ type Config struct {
UserPath string
}

func DefaultConfig() (Config, error) {
userPath, err := os.UserHomeDir()
if err != nil {
return Config{}, err
}

return Config{
Arch: runtime.GOARCH,
Atmos: makeDefaultRemoteConfig(defaultAtmosGithubURL, baseGithubURL),
remoteConfLoaded: true,
RootPath: filepath.Join(userPath, defaultDirName),
SkipInstall: true,
Tf: makeDefaultRemoteConfig(defaultHashicorpURL, defaultHashicorpURL),
Tg: makeDefaultRemoteConfig(defaultTerragruntGithubURL, baseGithubURL),
Tofu: makeDefaultRemoteConfig(DefaultTofuGithubURL, baseGithubURL),
UserPath: userPath,
}, nil
}

func InitConfigFromEnv() (Config, error) {
userPath, err := os.UserHomeDir()
if err != nil {
Expand All @@ -168,7 +188,7 @@ func InitConfigFromEnv() (Config, error) {

rootPath := configutils.GetenvFallback(tenvRootPathEnvName, tofuRootPathEnvName, tfRootPathEnvName)
if rootPath == "" {
rootPath = filepath.Join(userPath, ".tenv")
rootPath = filepath.Join(userPath, defaultDirName)
}

quiet, err := configutils.GetenvBoolFallback(false, tenvQuietEnvName)
Expand All @@ -188,9 +208,9 @@ func InitConfigFromEnv() (Config, error) {
ForceRemote: forceRemote,
GithubActions: gha,
GithubToken: configutils.GetenvFallback(tenvTokenEnvName, tofuTokenEnvName),
NoInstall: !autoInstall,
RemoteConfPath: os.Getenv(tenvRemoteConfEnvName),
RootPath: rootPath,
SkipInstall: !autoInstall,
Tf: makeRemoteConfig(TfRemoteURLEnvName, tfListURLEnvName, tfInstallModeEnvName, tfListModeEnvName, defaultHashicorpURL, defaultHashicorpURL),
TfKeyPath: os.Getenv(tfHashicorpPGPKeyEnvName),
Tg: makeRemoteConfig(TgRemoteURLEnvName, tgListURLEnvName, tgInstallModeEnvName, tgListModeEnvName, defaultTerragruntGithubURL, baseGithubURL),
Expand Down Expand Up @@ -228,9 +248,9 @@ func (conf *Config) InitDisplayer(proxyCall bool) {
func (conf *Config) InitInstall(forceInstall bool, forceNoInstall bool) {
switch {
case forceNoInstall: // higher priority to --no-install
conf.NoInstall = true
conf.SkipInstall = true
case forceInstall:
conf.NoInstall = false
conf.SkipInstall = false
}
}

Expand Down
6 changes: 6 additions & 0 deletions config/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ type RemoteConfig struct {
RemoteURLEnv string // value from env
}

func makeDefaultRemoteConfig(defaultURL string, defaultBaseURL string) RemoteConfig {
return RemoteConfig{
defaultBaseURL: defaultBaseURL, defaultURL: defaultURL, Data: map[string]string{},
}
}

func makeRemoteConfig(remoteURLEnvName string, listURLEnvName string, installModeEnvName string, listModeEnvName string, defaultURL string, defaultBaseURL string) RemoteConfig {
return RemoteConfig{
defaultBaseURL: defaultBaseURL, defaultURL: defaultURL, installMode: os.Getenv(installModeEnvName), listMode: os.Getenv(listModeEnvName),
Expand Down
29 changes: 14 additions & 15 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
module github.com/tofuutils/tenv/v3

go 1.21
go 1.23

toolchain go1.23.1

require (
github.com/BurntSushi/toml v1.4.0
github.com/ProtonMail/gopenpgp/v2 v2.7.5
github.com/PuerkitoBio/goquery v1.9.2
github.com/charmbracelet/bubbles v0.19.0
github.com/charmbracelet/bubbletea v0.27.1
github.com/PuerkitoBio/goquery v1.10.0
github.com/charmbracelet/bubbles v0.20.0
github.com/charmbracelet/bubbletea v1.1.0
github.com/charmbracelet/lipgloss v0.13.0
github.com/fatih/color v1.17.0
github.com/hashicorp/go-hclog v1.6.3
Expand All @@ -27,11 +29,9 @@ require (
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/x/ansi v0.1.4 // indirect
github.com/charmbracelet/x/input v0.1.3 // indirect
github.com/charmbracelet/x/term v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.1.2 // indirect
github.com/cloudflare/circl v1.3.9 // indirect
github.com/charmbracelet/x/ansi v0.2.3 // indirect
github.com/charmbracelet/x/term v0.2.0 // indirect
github.com/cloudflare/circl v1.4.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/go-test/deep v1.1.0 // indirect
Expand All @@ -50,12 +50,11 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sahilm/fuzzy v0.1.1 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/mod v0.20.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/tools v0.24.0 // indirect
)
Loading
Loading