Skip to content

Commit

Permalink
♻️ cli: Improves structure, the UI & error reporting
Browse files Browse the repository at this point in the history
  • Loading branch information
bendoerr committed Aug 29, 2024
1 parent 6da79c7 commit 6f9771d
Show file tree
Hide file tree
Showing 9 changed files with 312 additions and 80 deletions.
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,6 @@ linters:
- forbidigo # forbids identifiers
- funlen # tool for detection of long functions
- gocheckcompilerdirectives # validates go compiler directive comments (//go:)
- gochecknoglobals # checks that no global variables exist
- gochecknoinits # checks that no init functions are present in Go code
- gochecksumtype # checks exhaustiveness on Go "sum types"
- gocognit # computes and checks the cognitive complexity of functions
Expand Down Expand Up @@ -277,6 +276,7 @@ linters:
#- varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope
#- wrapcheck # checks that errors returned from external packages are wrapped
#- zerologlint # detects the wrong usage of zerolog that a user forgets to dispatch zerolog.Event
#- gochecknoglobals # checks that no global variables exist

## disabled
#- containedctx # detects struct contained context.Context field
Expand Down
82 changes: 30 additions & 52 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
package main

import (
"context"
"os"
"strings"

"github.com/alecthomas/kong"
"github.com/hashicorp/hcl/v2/hclwrite"
"github.com/spf13/afero"

"github.com/bendoerr-terraform-modules/tflint-plugin-version-update/pkg/github"
"github.com/bendoerr-terraform-modules/tflint-plugin-version-update/pkg/tflint"
"github.com/bendoerr-terraform-modules/tflint-plugin-version-update/pkg/ui"
)

type Config struct {
Freeze bool `name:"freeze"`
Path string `name:"path" arg:"" type:"path"`
Freeze bool `name:"freeze"`
Verbose bool `name:"verbose"`
Path string `name:"path" arg:"" type:"path" optional:"true"`
}

func main() {
Expand All @@ -23,66 +24,43 @@ func main() {

_ = kong.Parse(&cfg)

tflFile, err := tflint.OpenConfig(afero.Afero{Fs: afero.NewOsFs()}, cfg.Path)
if err != nil {
panic(err)
}
ctx := context.Background()
ctx = ui.ToContext(ctx, ui.NewUI(os.Stdout, cfg.Verbose))

tflData, err := tflint.NewData(tflFile)
tflFile, err := tflint.OpenConfig(ctx, afero.Afero{Fs: afero.NewOsFs()}, cfg.Path)
if err != nil {
panic(err)
if cfg.Verbose {
panic(err)
}
os.Exit(1)
}

tflHcl, err := tflData.ParseForRead()
if err != nil {
panic(err)
}
tfl := tflint.NewTFLint(tflFile)

tflHclW, err := tflData.ParseForWrite()
err = tfl.ParseHCL(ctx)
if err != nil {
panic(err)
if cfg.Verbose {
panic(err)
}
os.Exit(1)
}

plugins, err := tflint.FindPluginVersions(tflHcl)
err = tfl.UpdatePlugins(ctx, cfg.Freeze, github.LatestVersion)
if err != nil {
panic(err)
}

runUpdate(plugins, cfg, tflHclW)

_, _ = tflHclW.WriteTo(os.Stdout)
}

func runUpdate(plugins []*tflint.PluginConfig, cfg Config, tflHclW *hclwrite.File) {
for _, plugin := range plugins {
latestVersion, err := github.LatestVersion(plugin.SourceOwner, plugin.SourceRepo)
if err != nil {
if cfg.Verbose {
panic(err)
}
os.Exit(1)
}

if cfg.Freeze {
if plugin.Version == latestVersion.ReleaseSHA {
continue
}
} else {
if plugin.Version == latestVersion.ReleaseTag || "v"+plugin.Version == latestVersion.ReleaseTag {
continue
}
}

if cfg.Freeze {
err = tflint.UpdatePluginVersion(plugin.Name, latestVersion.ReleaseSHA, latestVersion.ReleaseTag, tflHclW)
if err != nil {
panic(err)
}
} else {
// Stylistically tflint drops the 'v' in their documentation,
// so we'll follow that as well.
version := strings.TrimPrefix(latestVersion.ReleaseTag, "v")
err = tflint.UpdatePluginVersion(plugin.Name, version, "", tflHclW)
if err != nil {
panic(err)
}
err = tfl.Write(ctx)
if err != nil {
if cfg.Verbose {
panic(err)
}
os.Exit(1)
}

ui.Stop(ctx)
ui.Info(ctx, "✨ Done")
}
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ go 1.23.0

require (
github.com/alecthomas/kong v0.9.0
github.com/briandowns/spinner v1.23.1
github.com/fatih/color v1.7.0
github.com/hashicorp/hcl/v2 v2.22.0
github.com/spf13/afero v1.11.0
github.com/zclconf/go-cty v1.15.0
Expand All @@ -13,9 +15,13 @@ require (
github.com/agext/levenshtein v1.2.1 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/mattn/go-colorable v0.1.2 // indirect
github.com/mattn/go-isatty v0.0.8 // indirect
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/term v0.1.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
)
13 changes: 13 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/briandowns/spinner v1.23.1 h1:t5fDPmScwUjozhDj4FA46p5acZWIPXYE30qW2Ptu650=
github.com/briandowns/spinner v1.23.1/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXOs0RcKV0wYKM=
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/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
Expand All @@ -18,6 +22,10 @@ github.com/hashicorp/hcl/v2 v2.22.0 h1:hkZ3nCtqeJsDhPRFz5EA9iwcG1hNWGePOTw6oyul1
github.com/hashicorp/hcl/v2 v2.22.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
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/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
Expand All @@ -30,6 +38,11 @@ golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
Expand Down
20 changes: 5 additions & 15 deletions pkg/github/latest.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@ import (
"time"
)

type Latest struct {
ReleaseTag string
ReleaseSHA string
ReleaseDescription string
}

type latestResponse struct {
TagName string `json:"tag_name"`
Description string `json:"body"`
Expand Down Expand Up @@ -81,20 +75,16 @@ func getTag(owner, repo, tag string) (*tagResponse, error) {
return result, nil
}

func LatestVersion(owner, repo string) (*Latest, error) {
func LatestVersion(owner, repo string) (string, string, string, error) {
release, err := getLatest(owner, repo)
if err != nil {
return nil, err
return "", "", "", err
}

tag, err := getTag(owner, repo, release.TagName)
tagV, err := getTag(owner, repo, release.TagName)
if err != nil {
return nil, err
return "", "", "", err
}

return &Latest{
ReleaseTag: release.TagName,
ReleaseSHA: tag.Object.SHA,
ReleaseDescription: release.Description,
}, nil
return release.TagName, tagV.Object.SHA, release.Description, nil
}
32 changes: 20 additions & 12 deletions pkg/tflint/open.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
package tflint

import (
"context"
"errors"
"fmt"
"log"
"os"

"github.com/spf13/afero"

"github.com/bendoerr-terraform-modules/tflint-plugin-version-update/pkg/ui"
)

// OpenConfig loads a TFLint config file following the logic from tflint except
// it will not auto load from the home directory, to update that provide the
// path explictly.
// path explicitly.
//
// tflint's LoadConfig @ tflint/tflint/config.go
//
Expand All @@ -23,35 +25,41 @@ import (
//
// For 1 and 2, if the file does not exist, an error will be returned immediately.
// If 3 fails then an error will be returned.
func OpenConfig(fs afero.Afero, file string) (afero.File, error) {
func OpenConfig(ctx context.Context, fs afero.Afero, file string) (afero.File, error) {
ui.Update(ctx, "🔧 Finding config")

// Load the file passed by the --config option
if file != "" {
log.Printf("[INFO] Load config: %s", file)
f, err := fs.Open(file)
ui.Info(ctx, "🔧 Using provided config "+file)
f, err := fs.OpenFile(file, os.O_RDWR, os.ModePerm)
if err != nil {
return nil, fmt.Errorf("unable to open file='%s': %w", file, err)
ui.Error(ctx, "🚨 Couldn't open provided config file!")
return nil, fmt.Errorf("unable to open config='%s': %w", file, err)
}
return f, nil
}

// Load the file set by the environment variable
envFile := os.Getenv("TFLINT_CONFIG_FILE")
if envFile != "" {
log.Printf("[INFO] Load config: %s", envFile)
ui.Info(ctx, "🔧 Using env.TFLINT_CONFIG_FILE "+envFile)
f, err := fs.Open(envFile)
if err != nil {
ui.Error(ctx, "🚨 Couldn't open env.TFLINT_CONFIG_FILE")
return nil, fmt.Errorf("unable to open TFLINT_CONFIG_FILE='%s': %w", envFile, err)
}
return f, nil
}

// Load the default config file
var defaultConfigFile = ".tflint.hcl"
log.Printf("[INFO] Load default config: %s", defaultConfigFile)
if f, err := fs.Open(defaultConfigFile); err == nil {
return f, nil
ui.Info(ctx, "🔧 Using default config "+defaultConfigFile)
f, err := fs.Open(defaultConfigFile)
if err != nil {
ui.Error(ctx, "🚨 Couldn't open default config")
return nil, errors.New("no config file found")
}
log.Printf("[INFO] Default config not found")

return nil, errors.New("no config file found")
ui.Update(ctx, "")
return f, nil
}
Loading

0 comments on commit 6f9771d

Please sign in to comment.