Skip to content

Commit

Permalink
add tenvlib package to easily use tenv as a library (#240)
Browse files Browse the repository at this point in the history
* add tenvlib package (use context.Context)
* add TENV_AS_LIB.md
* add examples

Signed-off-by: Denis Vaumoron <dvaumoron@gmail.com>
  • Loading branch information
dvaumoron authored Sep 8, 2024
1 parent ba67865 commit e51f2a8
Show file tree
Hide file tree
Showing 24 changed files with 712 additions and 168 deletions.
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

0 comments on commit e51f2a8

Please sign in to comment.