Added a tool to update provider_meta/module_name, Terraform version and provider versions on batch basis.

Also includes a pipeline to manually release new versions and update all module names.
This commit is contained in:
Taneli Leppä 2022-06-09 11:57:51 +02:00
parent 93eaa2ee32
commit b28b379a7b
4 changed files with 322 additions and 0 deletions

65
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,65 @@
# 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.
name: |
Create a new release
on:
workflow_dispatch:
inputs:
version:
description: "Release version"
required: true
changelog:
description: "I have updated the CHANGELOG"
required: true
type: boolean
permissions:
contents: write
jobs:
release:
name: "Release new version"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: "Validate input"
run: |
[[ "${{ github.event.inputs.changelog }}" != "true" ]] && { echo 'You didn''t update the changelog.' ; exit 1; }
[[ -n "${{ github.event.inputs.version }}" ]] || { echo 'Version not specified!'; exit 1; }
[[ "${{ github.event.inputs.version }}" != v* ]] && { echo 'Version does not start with v!' ; exit 1; }
- uses: actions/setup-go@v3
with:
go-version: 1.16
- name: "Update all module names"
run: |
cd tools/tfeditor
go build .
./tfeditor -path ../.. -module-name "google-pso-tool/cloud-foundation-fabric/{{ .Module }}/${{ github.event.inputs.version }}"
cd ../..
git config --global user.name "Release Automation"
git config --global user.email "cloud-foundation-fabric@google.com"
git commit -a -m "Release version ${{ github.event.inputs.version }}"
git push origin master
- name: "Tag and release"
run: |
git tag ${{ github.event.inputs.version }}
git push origin ${{ github.event.inputs.version }}

8
tools/tfeditor/go.mod Normal file
View File

@ -0,0 +1,8 @@
module github.com/GoogleCloudPlatform/cloud-foundation-fabric/tools/tfeditor
go 1.16
require (
github.com/hashicorp/hcl/v2 v2.12.0 // indirect
github.com/zclconf/go-cty v1.8.0 // indirect
)

51
tools/tfeditor/go.sum Normal file
View File

@ -0,0 +1,51 @@
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
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.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/hashicorp/hcl/v2 v2.12.0 h1:PsYxySWpMD4KPaoJLnsHwtK5Qptvj/4Q6s0t4sUxZf4=
github.com/hashicorp/hcl/v2 v2.12.0/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
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=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

198
tools/tfeditor/main.go Normal file
View File

@ -0,0 +1,198 @@
/*
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.")
}