feat(terraform): add version information

resolves #1455
This commit is contained in:
Jan De Dobbeleer 2022-02-09 14:31:51 +01:00 committed by Jan De Dobbeleer
parent 0068d9c67f
commit b3d9981eec
7 changed files with 198 additions and 55 deletions

View file

@ -27,12 +27,17 @@ This requires a terraform binary in your PATH and will only show in directories
}
```
## Properties
- fetch_version: `boolean` - fetch the version information from `versions.tf`, `main.tf` or `terraform.tfstate` -
defaults to `false`
## Template ([info][templates])
:::note default template
``` template
{{ .WorkspaceName }}
{{ .WorkspaceName }}{{ if .Version }} {{ .Version }}{{ end }}
```
:::
@ -40,5 +45,6 @@ This requires a terraform binary in your PATH and will only show in directories
### Properties
- `.WorkspaceName`: `string` - is the current workspace name
- `.Version`: `string` - terraform version (set `fetch_version` to `true`)
[templates]: /docs/config-templates

View file

@ -33,6 +33,7 @@ require (
)
require (
github.com/hashicorp/hcl/v2 v2.11.1
golang.org/x/mod v0.5.1
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)
@ -62,6 +63,13 @@ require (
gopkg.in/yaml.v2 v2.4.0 // indirect
)
require github.com/shopspring/decimal v1.3.1 // indirect
require (
github.com/agext/levenshtein v1.2.1 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/zclconf/go-cty v1.8.0 // indirect
)
replace github.com/distatus/battery v0.10.0 => github.com/JanDeDobbeleer/battery v0.10.0-2

View file

@ -12,6 +12,7 @@ github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmy
github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
@ -20,7 +21,9 @@ github.com/alecthomas/colour v0.1.0/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142 h1:8Uy0oSf5co/NZXje7U1z8Mpep++QJOldL2hs/sBQf48=
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM=
github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0=
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
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=
@ -32,6 +35,7 @@ github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzP
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
@ -56,8 +60,11 @@ github.com/gookit/goutil v0.4.0 h1:sj2pefgJEQdvHo6KoMMNjm2LwblcJ4HylFHa5eXnCS8=
github.com/gookit/goutil v0.4.0/go.mod h1:qlGVh0PI+WnWSjYnIocfz/7tkeogxL6+EDNP1mRe+7o=
github.com/gookit/ini/v2 v2.0.11 h1:Wl651xN2AaJbFrb8daBwWo8kS+sQHL3TddFi0/PRNXs=
github.com/gookit/ini/v2 v2.0.11/go.mod h1:rIY8Uup5WDdPsrEE7VrF7fcMdGCCcPGA22Bk5R7roJQ=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl/v2 v2.10.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=
github.com/hashicorp/hcl/v2 v2.11.1 h1:yTyWcXcm9XB0TEkyU/JCRU6rYy4K+mgLtzn2wlrJbcc=
github.com/hashicorp/hcl/v2 v2.11.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
@ -74,6 +81,7 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
@ -86,6 +94,7 @@ github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa1
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
@ -137,6 +146,7 @@ github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHg
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
github.com/yosuke-furukawa/json5 v0.1.1/go.mod h1:sw49aWDqNdRJ6DYUtIQiaA3xyj2IL9tjeNYmX2ixwcU=
github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
github.com/zclconf/go-cty v1.8.0 h1:s4AvqaeQzJIu3ndv4gVIhplVD0krU+bgrcLSVUnaWuA=
github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

View file

@ -1,8 +1,14 @@
package segments
import (
"encoding/json"
"errors"
"oh-my-posh/environment"
"oh-my-posh/properties"
"path/filepath"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclparse"
)
type Terraform struct {
@ -10,10 +16,11 @@ type Terraform struct {
env environment.Environment
WorkspaceName string
TerraformBlock
}
func (tf *Terraform) Template() string {
return " {{ .WorkspaceName }} "
return " {{ .WorkspaceName }}{{ if .Version }} {{ .Version }}{{ end }} "
}
func (tf *Terraform) Init(props properties.Properties, env environment.Environment) {
@ -21,11 +28,74 @@ func (tf *Terraform) Init(props properties.Properties, env environment.Environme
tf.env = env
}
type TerraFormConfig struct {
Terraform *TerraformBlock `hcl:"terraform,block"`
}
type TerraformBlock struct {
Version *string `hcl:"required_version" json:"terraform_version"`
}
func (tf *Terraform) Enabled() bool {
cmd := "terraform"
if !tf.env.HasCommand(cmd) || !tf.env.HasFolder(tf.env.Pwd()+"/.terraform") {
terraformFolder := filepath.Join(tf.env.Pwd(), ".terraform")
fetchVersion := tf.props.GetBool(properties.FetchVersion, false)
if fetchVersion {
// known version files
files := []string{"versions.tf", "main.tf", "terraform.tfstate"}
var hasFiles bool
for _, file := range files {
if tf.env.HasFiles(file) {
hasFiles = true
break
}
}
fetchVersion = hasFiles
}
inContext := tf.env.HasFolder(terraformFolder) || fetchVersion
if !tf.env.HasCommand(cmd) || !inContext {
return false
}
tf.WorkspaceName, _ = tf.env.RunCommand(cmd, "workspace", "show")
if !fetchVersion {
return true
}
if err := tf.setVersionFromTfFiles(); err == nil {
return true
}
tf.setVersionFromTfStateFile()
return true
}
func (tf *Terraform) setVersionFromTfFiles() error {
files := []string{"versions.tf", "main.tf"}
for _, file := range files {
if !tf.env.HasFiles(file) {
continue
}
parser := hclparse.NewParser()
content := tf.env.FileContent(file)
hclFile, diags := parser.ParseHCL([]byte(content), file)
if diags != nil {
continue
}
var config TerraFormConfig
diags = gohcl.DecodeBody(hclFile.Body, nil, &config)
if diags != nil {
continue
}
tf.TerraformBlock = *config.Terraform
return nil
}
return errors.New("no valid terraform files found")
}
func (tf *Terraform) setVersionFromTfStateFile() {
file := "terraform.tfstate"
if !tf.env.HasFiles(file) {
return
}
content := tf.env.FileContent(file)
_ = json.Unmarshal([]byte(content), &tf.TerraformBlock)
}

View file

@ -1,6 +1,7 @@
package segments
import (
"io/ioutil"
"oh-my-posh/mock"
"oh-my-posh/properties"
"testing"
@ -8,59 +9,98 @@ import (
"github.com/stretchr/testify/assert"
)
type terraformArgs struct {
hasTfCommand bool
hasTfFolder bool
workspaceName string
}
func bootStrapTerraformTest(args *terraformArgs) *Terraform {
env := new(mock.MockedEnvironment)
env.On("HasCommand", "terraform").Return(args.hasTfCommand)
env.On("HasFolder", "/.terraform").Return(args.hasTfFolder)
env.On("Pwd").Return("")
env.On("RunCommand", "terraform", []string{"workspace", "show"}).Return(args.workspaceName, nil)
k := &Terraform{
env: env,
props: properties.Map{},
func TestTerraform(t *testing.T) {
cases := []struct {
Case string
Template string
HasTfCommand bool
HasTfFolder bool
HasTfFiles bool
HasTfStateFile bool
FetchVersion bool
WorkspaceName string
ExpectedString string
ExpectedEnabled bool
}{
{
Case: "default workspace",
ExpectedString: "default",
ExpectedEnabled: true,
WorkspaceName: "default",
HasTfFolder: true,
HasTfCommand: true,
},
{
Case: "no command",
ExpectedString: "",
WorkspaceName: "default",
HasTfFolder: true,
},
{
Case: "no directory, no files",
ExpectedString: "",
WorkspaceName: "default",
HasTfCommand: true,
},
{
Case: "no files",
ExpectedString: "",
WorkspaceName: "default",
HasTfCommand: true,
FetchVersion: true,
},
{
Case: "files",
ExpectedString: ">= 1.0.10",
ExpectedEnabled: true,
WorkspaceName: "default",
Template: "{{ .Version }}",
HasTfFiles: true,
HasTfCommand: true,
FetchVersion: true,
},
{
Case: "files",
ExpectedString: "0.12.24",
ExpectedEnabled: true,
WorkspaceName: "default",
Template: "{{ .Version }}",
HasTfStateFile: true,
HasTfCommand: true,
FetchVersion: true,
},
}
return k
}
func TestTerraformWriterDisabled(t *testing.T) {
args := &terraformArgs{
hasTfCommand: false,
hasTfFolder: false,
}
terraform := bootStrapTerraformTest(args)
assert.False(t, terraform.Enabled())
}
for _, tc := range cases {
env := new(mock.MockedEnvironment)
func TestTerraformMissingDir(t *testing.T) {
args := &terraformArgs{
hasTfCommand: true,
hasTfFolder: false,
env.On("HasCommand", "terraform").Return(tc.HasTfCommand)
env.On("HasFolder", ".terraform").Return(tc.HasTfFolder)
env.On("Pwd").Return("")
env.On("RunCommand", "terraform", []string{"workspace", "show"}).Return(tc.WorkspaceName, nil)
env.On("HasFiles", "versions.tf").Return(tc.HasTfFiles)
env.On("HasFiles", "main.tf").Return(tc.HasTfFiles)
env.On("HasFiles", "terraform.tfstate").Return(tc.HasTfStateFile)
if tc.HasTfFiles {
content, _ := ioutil.ReadFile("../test/versions.tf")
env.On("FileContent", "versions.tf").Return(string(content))
}
if tc.HasTfStateFile {
content, _ := ioutil.ReadFile("../test/terraform.tfstate")
env.On("FileContent", "terraform.tfstate").Return(string(content))
}
tf := &Terraform{
env: env,
props: properties.Map{
properties.FetchVersion: tc.FetchVersion,
},
}
template := tc.Template
if len(template) == 0 {
template = tf.Template()
}
assert.Equal(t, tc.ExpectedEnabled, tf.Enabled(), tc.Case)
var got = renderTemplate(env, template, tf)
assert.Equal(t, tc.ExpectedString, got, tc.Case)
}
terraform := bootStrapTerraformTest(args)
assert.False(t, terraform.Enabled())
}
func TestTerraformMissingBinary(t *testing.T) {
args := &terraformArgs{
hasTfCommand: false,
hasTfFolder: true,
}
terraform := bootStrapTerraformTest(args)
assert.False(t, terraform.Enabled())
}
func TestTerraformEnabled(t *testing.T) {
expected := "default"
args := &terraformArgs{
hasTfCommand: true,
hasTfFolder: true,
workspaceName: expected,
}
terraform := bootStrapTerraformTest(args)
assert.True(t, terraform.Enabled())
}

View file

@ -0,0 +1,6 @@
{
"version": 4,
"terraform_version": "0.12.24",
"serial": 22,
"lineage": "6cc278b4-0426-a833-9920-356a6635038c"
}

3
src/test/versions.tf Normal file
View file

@ -0,0 +1,3 @@
terraform {
required_version = ">= 1.0.10"
}