Skip to content

Commit

Permalink
Extract HCL rewrite logic into tfupdate package and updater interface
Browse files Browse the repository at this point in the history
  • Loading branch information
minamijoyo committed Sep 3, 2019
1 parent b264126 commit bd9fb4b
Show file tree
Hide file tree
Showing 9 changed files with 436 additions and 32 deletions.
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -4,6 +4,7 @@ go 1.12

require (
github.com/hashicorp/hcl2 v0.0.0-20190809210004-72d32879a5c5
github.com/pkg/errors v0.8.1
github.com/zclconf/go-cty v1.0.0
)

Expand Down
2 changes: 2 additions & 0 deletions go.sum
Expand Up @@ -31,6 +31,8 @@ github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZX
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
Expand Down
49 changes: 17 additions & 32 deletions main.go
Expand Up @@ -8,40 +8,48 @@ import (

"github.com/hashicorp/hcl2/hcl"
"github.com/hashicorp/hcl2/hclwrite"
"github.com/zclconf/go-cty/cty"
"github.com/minamijoyo/tfupdate/tfupdate"
)

func main() {
filename := "./main.tf"

err := updateFile(filename)
updaterType := "terraform"
name := ""
version := "0.12.7"

// updaterType := "provider"
// name := "aws"
// version := "2.23.0"

err := updateFile(filename, updaterType, name, version)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

func updateFile(filename string) error {
func updateFile(filename string, updaterType string, name string, version string) error {
r, err := os.Open(filename)
if err != nil {
return fmt.Errorf("failed to open file: %+v", err)
}

err = update(r, filename, os.Stdout)
err = update(r, os.Stdout, filename, updaterType, name, version)
if err != nil {
return err
}

return nil
}

func update(r io.Reader, filename string, w io.Writer) error {
func update(r io.Reader, w io.Writer, filename string, updaterType string, name string, version string) error {
f, err := parseHCL(r, filename)
if err != nil {
return err
}

err = updateHCL(f)
err = updateHCL(f, updaterType, name, version)
if err != nil {
return err
}
Expand Down Expand Up @@ -78,34 +86,11 @@ func writeHCL(f *hclwrite.File, w io.Writer) error {
return nil
}

func updateHCL(f *hclwrite.File) error {
err := updateTerraform(f)
if err != nil {
return err
}

err = updateProvider(f)
func updateHCL(f *hclwrite.File, updaterType string, name string, version string) error {
u, err := tfupdate.NewUpdater(updaterType, name, version)
if err != nil {
return err
}

return nil
}

func updateTerraform(f *hclwrite.File) error {
tf := f.Body().FirstMatchingBlock("terraform", []string{})
tf.Body().SetAttributeValue("required_version", cty.StringVal("0.12.6"))

return nil
}

func updateProvider(f *hclwrite.File) error {
tf := f.Body().FirstMatchingBlock("terraform", []string{})
providers := tf.Body().FirstMatchingBlock("required_providers", []string{})
providers.Body().SetAttributeValue("null", cty.StringVal("2.1.2"))

aws := f.Body().FirstMatchingBlock("provider", []string{"aws"})
aws.Body().SetAttributeValue("version", cty.StringVal("2.23.0"))

return nil
return u.Update(f)
}
69 changes: 69 additions & 0 deletions tfupdate/provider.go
@@ -0,0 +1,69 @@
package tfupdate

import (
"github.com/hashicorp/hcl2/hclwrite"
"github.com/pkg/errors"
"github.com/zclconf/go-cty/cty"
)

// ProviderUpdater is a updater implementation which updates the provider version constraint.
type ProviderUpdater struct {
name string
version string
}

// NewProviderUpdater is a factory method which returns an ProviderUpdater instance.
func NewProviderUpdater(name string, version string) (Updater, error) {
if len(name) == 0 {
return nil, errors.Errorf("failed to new provider updater. name is required.")
}

if len(version) == 0 {
return nil, errors.Errorf("failed to new provider updater. version is required.")
}

return &ProviderUpdater{
name: name,
version: version,
}, nil
}

// Update updates the provider version constraint.
// Note that this method will rewrite the AST passed as an argument.
func (u *ProviderUpdater) Update(f *hclwrite.File) error {
if err := u.updateTerraformBlock(f); err != nil {
return err
}

if err := u.updateProviderBlock(f); err != nil {
return err
}

return nil
}

func (u *ProviderUpdater) updateTerraformBlock(f *hclwrite.File) error {
tf := f.Body().FirstMatchingBlock("terraform", []string{})
if tf == nil {
return nil
}

p := tf.Body().FirstMatchingBlock("required_providers", []string{})
if p == nil {
return nil
}
p.Body().SetAttributeValue(u.name, cty.StringVal(u.version))

return nil
}

func (u *ProviderUpdater) updateProviderBlock(f *hclwrite.File) error {
p := f.Body().FirstMatchingBlock("provider", []string{u.name})
if p == nil {
return nil
}

p.Body().SetAttributeValue("version", cty.StringVal(u.version))

return nil
}
128 changes: 128 additions & 0 deletions tfupdate/provider_test.go
@@ -0,0 +1,128 @@
package tfupdate

import (
"reflect"
"testing"

"github.com/hashicorp/hcl2/hcl"
"github.com/hashicorp/hcl2/hclwrite"
)

func TestNewProviderUpdater(t *testing.T) {
cases := []struct {
name string
version string
want Updater
ok bool
}{
{
name: "aws",
version: "2.23.0",
want: &ProviderUpdater{
name: "aws",
version: "2.23.0",
},
ok: true,
},
{
name: "",
version: "2.23.0",
want: nil,
ok: false,
},
{
name: "aws",
version: "",
want: nil,
ok: false,
},
}

for _, tc := range cases {
got, err := NewProviderUpdater(tc.name, tc.version)
if tc.ok && err != nil {
t.Errorf("NewProviderUpdater() with name = %s, version = %s returns unexpected err: %+v", tc.name, tc.version, err)
}

if !tc.ok && err == nil {
t.Errorf("NewProviderUpdater() with name = %s, version = %s expects to return an error, but no error: %+v", tc.name, tc.version, err)
}

if !reflect.DeepEqual(got, tc.want) {
t.Errorf("NewProviderUpdater() with name = %s, version = %s returns %#v, but want = %#v", tc.name, tc.version, got, tc.want)
}
}
}

func TestUpdateProvider(t *testing.T) {
cases := []struct {
src string
name string
version string
want string
ok bool
}{
{
src: `
terraform {
required_version = "0.12.4"
required_providers {
null = "2.1.1"
}
}
`,
name: "null",
version: "2.1.2",
want: `
terraform {
required_version = "0.12.4"
required_providers {
null = "2.1.2"
}
}
`,
ok: true,
},
{
src: `
provider "aws" {
version = "2.11.0"
region = "ap-northeast-1"
}
`,
name: "aws",
version: "2.23.0",
want: `
provider "aws" {
version = "2.23.0"
region = "ap-northeast-1"
}
`,
ok: true,
},
}

for _, tc := range cases {
u := &ProviderUpdater{
name: tc.name,
version: tc.version,
}
f, diags := hclwrite.ParseConfig([]byte(tc.src), "", hcl.Pos{Line: 1, Column: 1})
if diags.HasErrors() {
t.Fatalf("unexpected diagnostics: %s", diags)
}

err := u.Update(f)
if tc.ok && err != nil {
t.Errorf("Update() with src = %s, name = %s, version = %s returns unexpected err: %+v", tc.src, tc.name, tc.version, err)
}
if !tc.ok && err == nil {
t.Errorf("Update() with src = %s, name = %s, version = %s expects to return an error, but no error", tc.src, tc.name, tc.version)
}

got := string(hclwrite.Format(f.BuildTokens(nil).Bytes()))
if got != tc.want {
t.Errorf("Update() with src = %s, name = %s, version = %s returns %s, but want = %s", tc.src, tc.name, tc.version, got, tc.want)
}
}
}
32 changes: 32 additions & 0 deletions tfupdate/terraform.go
@@ -0,0 +1,32 @@
package tfupdate

import (
"github.com/hashicorp/hcl2/hclwrite"
"github.com/pkg/errors"
"github.com/zclconf/go-cty/cty"
)

// TerraformUpdater is a updater implementation which updates the terraform version constraint.
type TerraformUpdater struct {
version string
}

// NewTerraformUpdater is a factory method which returns an TerraformUpdater instance.
func NewTerraformUpdater(version string) (Updater, error) {
if len(version) == 0 {
return nil, errors.Errorf("failed to new terraform updater. version is required.")
}

return &TerraformUpdater{
version: version,
}, nil
}

// Update updates the terraform version constraint.
// Note that this method will rewrite the AST passed as an argument.
func (u *TerraformUpdater) Update(f *hclwrite.File) error {
tf := f.Body().FirstMatchingBlock("terraform", []string{})
tf.Body().SetAttributeValue("required_version", cty.StringVal(u.version))

return nil
}

0 comments on commit bd9fb4b

Please sign in to comment.