cloud-foundation-fabric/tools/tfeditor/main.go

199 lines
6.1 KiB
Go

/*
Copyright 2022 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
This tool updates in-place versions.tf files to change module_name parameter
on an automated basis. In retains all existing comments and structures.
*/
package main
import (
"bytes"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
"strings"
"text/template"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclwrite"
"github.com/zclconf/go-cty/cty"
)
type Provider struct {
Source string
Version string
}
func main() {
var path string
var pattern string
var moduleName string
var terraformVersion string
var providerVersions string
var updateProviderVersions bool = false
var updateTerraformVersion bool = false
var updateModuleName bool = false
flag.StringVar(&path, "path", "./", "Path to search for file pattern (default ./")
flag.StringVar(&pattern, "pattern", "versions.tf", "Pattern to search (default versions.tf)")
flag.StringVar(&moduleName, "module-name", "", "module_name attribute for provider_meta (can be templated with {{ .Module }})")
flag.StringVar(&providerVersions, "provider-versions", "", "set provider versions (usage: hashicorp/google>= 4.0.0,hashicorp/googlegoogle-beta== 4.0.0)")
flag.StringVar(&terraformVersion, "terraform-version", "", "Update terraform required_version")
flag.Parse()
if !strings.HasSuffix(path, "/") {
path = fmt.Sprintf("%s/", path)
}
if moduleName != "" {
updateModuleName = true
log.Printf("Updating module_name to: %s", moduleName)
}
if terraformVersion != "" {
updateTerraformVersion = true
log.Printf("Updating Terraform version to: %s", terraformVersion)
}
providerVersionsMap := map[string]Provider{}
if providerVersions != "" {
for _, v := range strings.Split(providerVersions, ",") {
re := regexp.MustCompile("([a-zA-Z_/-]+)(.+)")
split := re.FindAllStringSubmatch(v, -1)
for _, splitN := range split {
providerKey := filepath.Base(splitN[1])
providerVersionsMap[providerKey] = Provider{Source: splitN[1], Version: splitN[2]}
log.Printf("Updating provider %s to: %s %s", providerKey, providerVersionsMap[providerKey].Source, providerVersionsMap[providerKey].Version)
}
}
updateProviderVersions = true
}
log.Printf("Looking for files (%s) in: %s", pattern, path)
var foundFiles []string
err := filepath.Walk(path, func(file string, f os.FileInfo, err error) error {
if isMatch, _ := filepath.Match(pattern, filepath.Base(file)); isMatch {
foundFiles = append(foundFiles, file)
}
return nil
})
log.Printf("Found %d files.", len(foundFiles))
for _, foundFile := range foundFiles {
fileBytes, fErr := ioutil.ReadFile(foundFile)
if fErr != nil {
panic(fErr)
}
fileBasename := filepath.Base(foundFile)
hclFile, diag := hclwrite.ParseConfig(fileBytes, fileBasename, hcl.Pos{})
if diag == nil {
hclBody := hclFile.Body()
for _, block := range hclBody.Blocks() {
if block.Type() == "terraform" {
if updateTerraformVersion {
for k, _ := range block.Body().Attributes() {
if k == "required_version" {
block.Body().SetAttributeValue("required_version", cty.StringVal(terraformVersion))
}
}
}
hasProviderMetaForGoogle := false
hasProviderMetaForGoogleBeta := false
// Expand template
tmpl, tErr := template.New("modulename").Parse(moduleName)
if tErr != nil {
panic(tErr)
}
expandedBuffer := new(bytes.Buffer)
tErr = tmpl.Execute(expandedBuffer, map[string]string{
"Module": filepath.Base(filepath.Dir(foundFile)),
})
if tErr != nil {
panic(tErr)
}
expandedModuleName := expandedBuffer.String()
for _, tfBlock := range block.Body().Blocks() {
if tfBlock.Type() == "required_providers" && updateProviderVersions {
for k, _ := range tfBlock.Body().Attributes() {
if provider, ok := providerVersionsMap[k]; ok {
tfBlock.Body().SetAttributeValue(k, cty.ObjectVal(map[string]cty.Value{
"source": cty.StringVal(provider.Source),
"version": cty.StringVal(provider.Version),
}))
}
}
}
if tfBlock.Type() == "provider_meta" && updateModuleName {
labels := tfBlock.Labels()
if len(labels) > 0 {
if labels[0] == "google" {
hasProviderMetaForGoogle = true
tfBlock.Body().SetAttributeValue("module_name", cty.StringVal(expandedModuleName))
}
if labels[0] == "google-beta" {
hasProviderMetaForGoogleBeta = true
tfBlock.Body().SetAttributeValue("module_name", cty.StringVal(expandedModuleName))
}
}
}
}
if updateModuleName {
if !hasProviderMetaForGoogle {
providerMetaGoogleBlock := hclwrite.NewBlock("provider_meta", []string{"google"})
providerMetaGoogleBlock.Body().SetAttributeValue("module_name", cty.StringVal(expandedModuleName))
block.Body().AppendBlock(providerMetaGoogleBlock)
}
if !hasProviderMetaForGoogleBeta {
providerMetaGoogleBlock := hclwrite.NewBlock("provider_meta", []string{"google-beta"})
providerMetaGoogleBlock.Body().SetAttributeValue("module_name", cty.StringVal(expandedModuleName))
block.Body().AppendBlock(providerMetaGoogleBlock)
}
}
}
}
log.Printf("Updating: %s", foundFile)
info, sErr := os.Stat(foundFile)
if sErr != nil {
panic(sErr)
}
tempFilePath := fmt.Sprintf("%s.tmp", foundFile)
wErr := os.WriteFile(tempFilePath, hclFile.Bytes(), info.Mode())
if wErr != nil {
panic(wErr)
}
os.Rename(tempFilePath, foundFile)
} else {
panic(diag)
}
}
if err != nil {
panic(err)
}
log.Printf("All done.")
}