mmv1/provider/terraform.go (615 lines of code) (raw):
// Copyright 2024 Google Inc.
// 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.
package provider
import (
"bytes"
"errors"
"fmt"
"go/format"
"io/fs"
"log"
"maps"
"os"
"path"
"path/filepath"
"slices"
"strings"
"time"
"github.com/GoogleCloudPlatform/magic-modules/mmv1/api"
"github.com/GoogleCloudPlatform/magic-modules/mmv1/api/product"
"github.com/GoogleCloudPlatform/magic-modules/mmv1/api/resource"
"github.com/GoogleCloudPlatform/magic-modules/mmv1/google"
)
type Terraform struct {
ResourceCount int
IAMResourceCount int
ResourcesForVersion []map[string]string
TargetVersionName string
Version product.Version
Product *api.Product
StartTime time.Time
}
func NewTerraform(product *api.Product, versionName string, startTime time.Time) Terraform {
t := Terraform{
ResourceCount: 0,
IAMResourceCount: 0,
Product: product,
TargetVersionName: versionName,
Version: *product.VersionObjOrClosest(versionName),
StartTime: startTime,
}
t.Product.SetPropertiesBasedOnVersion(&t.Version)
for _, r := range t.Product.Objects {
r.SetCompiler(ProviderName(t))
r.ImportPath = ImportPathFromVersion(versionName)
}
return t
}
func (t Terraform) Generate(outputFolder, productPath, resourceToGenerate string, generateCode, generateDocs bool) {
if err := os.MkdirAll(outputFolder, os.ModePerm); err != nil {
log.Println(fmt.Errorf("error creating output directory %v: %v", outputFolder, err))
}
t.GenerateObjects(outputFolder, resourceToGenerate, generateCode, generateDocs)
if generateCode {
t.GenerateOperation(outputFolder)
}
}
func (t *Terraform) GenerateObjects(outputFolder, resourceToGenerate string, generateCode, generateDocs bool) {
for _, object := range t.Product.Objects {
object.ExcludeIfNotInVersion(&t.Version)
if resourceToGenerate != "" && object.Name != resourceToGenerate {
log.Printf("Excluding %s per user request", object.Name)
continue
}
t.GenerateObject(*object, outputFolder, t.TargetVersionName, generateCode, generateDocs)
}
}
func (t *Terraform) GenerateObject(object api.Resource, outputFolder, productPath string, generateCode, generateDocs bool) {
templateData := NewTemplateData(outputFolder, t.TargetVersionName)
if !object.IsExcluded() {
log.Printf("Generating %s resource", object.Name)
t.GenerateResource(object, *templateData, outputFolder, generateCode, generateDocs)
if generateCode {
// log.Printf("Generating %s tests", object.Name)
t.GenerateResourceTests(object, *templateData, outputFolder)
t.GenerateResourceSweeper(object, *templateData, outputFolder)
// log.Printf("Generating %s metadata", object.Name)
t.GenerateResourceMetadata(object, *templateData, outputFolder)
}
}
// if iam_policy is not defined or excluded, don't generate it
if object.IamPolicy == nil || object.IamPolicy.Exclude {
return
}
t.GenerateIamPolicy(object, *templateData, outputFolder, generateCode, generateDocs)
}
func (t *Terraform) GenerateResource(object api.Resource, templateData TemplateData, outputFolder string, generateCode, generateDocs bool) {
if generateCode {
productName := t.Product.ApiName
targetFolder := path.Join(outputFolder, t.FolderName(), "services", productName)
if err := os.MkdirAll(targetFolder, os.ModePerm); err != nil {
log.Println(fmt.Errorf("error creating parent directory %v: %v", targetFolder, err))
}
targetFilePath := path.Join(targetFolder, fmt.Sprintf("resource_%s.go", t.ResourceGoFilename(object)))
templateData.GenerateResourceFile(targetFilePath, object)
}
if generateDocs {
targetFolder := path.Join(outputFolder, "website", "docs", "r")
if err := os.MkdirAll(targetFolder, os.ModePerm); err != nil {
log.Println(fmt.Errorf("error creating parent directory %v: %v", targetFolder, err))
}
targetFilePath := path.Join(targetFolder, fmt.Sprintf("%s.html.markdown", t.FullResourceName(object)))
templateData.GenerateDocumentationFile(targetFilePath, object)
}
}
func (t *Terraform) GenerateResourceMetadata(object api.Resource, templateData TemplateData, outputFolder string) {
productName := t.Product.ApiName
targetFolder := path.Join(outputFolder, t.FolderName(), "services", productName)
if err := os.MkdirAll(targetFolder, os.ModePerm); err != nil {
log.Println(fmt.Errorf("error creating parent directory %v: %v", targetFolder, err))
}
targetFilePath := path.Join(targetFolder, fmt.Sprintf("resource_%s_generated_meta.yaml", t.FullResourceName(object)))
templateData.GenerateMetadataFile(targetFilePath, object)
}
func (t *Terraform) GenerateResourceTests(object api.Resource, templateData TemplateData, outputFolder string) {
eligibleExample := false
for _, example := range object.Examples {
if !example.ExcludeTest {
if object.ProductMetadata.VersionObjOrClosest(t.Version.Name).CompareTo(object.ProductMetadata.VersionObjOrClosest(example.MinVersion)) >= 0 {
eligibleExample = true
break
}
}
}
if !eligibleExample {
return
}
productName := t.Product.ApiName
targetFolder := path.Join(outputFolder, t.FolderName(), "services", productName)
if err := os.MkdirAll(targetFolder, os.ModePerm); err != nil {
log.Println(fmt.Errorf("error creating parent directory %v: %v", targetFolder, err))
}
targetFilePath := path.Join(targetFolder, fmt.Sprintf("resource_%s_generated_test.go", t.ResourceGoFilename(object)))
templateData.GenerateTestFile(targetFilePath, object)
}
func (t *Terraform) GenerateResourceSweeper(object api.Resource, templateData TemplateData, outputFolder string) {
if !object.ShouldGenerateSweepers() {
return
}
productName := t.Product.ApiName
targetFolder := path.Join(outputFolder, t.FolderName(), "services", productName)
if err := os.MkdirAll(targetFolder, os.ModePerm); err != nil {
log.Println(fmt.Errorf("error creating parent directory %v: %v", targetFolder, err))
}
targetFilePath := path.Join(targetFolder, fmt.Sprintf("resource_%s_sweeper.go", t.ResourceGoFilename(object)))
templateData.GenerateSweeperFile(targetFilePath, object)
}
func (t *Terraform) GenerateOperation(outputFolder string) {
asyncObjects := google.Select(t.Product.Objects, func(o *api.Resource) bool {
return o.AutogenAsync
})
if len(asyncObjects) == 0 {
return
}
targetFolder := path.Join(outputFolder, t.FolderName(), "services", t.Product.ApiName)
if err := os.MkdirAll(targetFolder, os.ModePerm); err != nil {
log.Println(fmt.Errorf("error creating parent directory %v: %v", targetFolder, err))
}
targetFilePath := path.Join(targetFolder, fmt.Sprintf("%s_operation.go", google.Underscore(t.Product.Name)))
templateData := NewTemplateData(outputFolder, t.TargetVersionName)
templateData.GenerateOperationFile(targetFilePath, *asyncObjects[0])
}
// Generate the IAM policy for this object. This is used to query and test
// IAM policies separately from the resource itself
func (t *Terraform) GenerateIamPolicy(object api.Resource, templateData TemplateData, outputFolder string, generateCode, generateDocs bool) {
if generateCode && object.IamPolicy != nil && (object.IamPolicy.MinVersion == "" || slices.Index(product.ORDER, object.IamPolicy.MinVersion) <= slices.Index(product.ORDER, t.TargetVersionName)) {
productName := t.Product.ApiName
targetFolder := path.Join(outputFolder, t.FolderName(), "services", productName)
if err := os.MkdirAll(targetFolder, os.ModePerm); err != nil {
log.Println(fmt.Errorf("error creating parent directory %v: %v", targetFolder, err))
}
targetFilePath := path.Join(targetFolder, fmt.Sprintf("iam_%s.go", t.ResourceGoFilename(object)))
templateData.GenerateIamPolicyFile(targetFilePath, object)
// Only generate test if testable examples exist.
examples := google.Reject(object.Examples, func(e resource.Examples) bool {
return e.ExcludeTest
})
if len(examples) != 0 {
targetFilePath := path.Join(targetFolder, fmt.Sprintf("iam_%s_generated_test.go", t.ResourceGoFilename(object)))
templateData.GenerateIamPolicyTestFile(targetFilePath, object)
}
}
if generateDocs {
t.GenerateIamDocumentation(object, templateData, outputFolder, generateCode, generateDocs)
}
}
func (t *Terraform) GenerateIamDocumentation(object api.Resource, templateData TemplateData, outputFolder string, generateCode, generateDocs bool) {
resourceDocFolder := path.Join(outputFolder, "website", "docs", "r")
if err := os.MkdirAll(resourceDocFolder, os.ModePerm); err != nil {
log.Println(fmt.Errorf("error creating parent directory %v: %v", resourceDocFolder, err))
}
targetFilePath := path.Join(resourceDocFolder, fmt.Sprintf("%s_iam.html.markdown", t.FullResourceName(object)))
templateData.GenerateIamResourceDocumentationFile(targetFilePath, object)
datasourceDocFolder := path.Join(outputFolder, "website", "docs", "d")
if err := os.MkdirAll(datasourceDocFolder, os.ModePerm); err != nil {
log.Println(fmt.Errorf("error creating parent directory %v: %v", datasourceDocFolder, err))
}
targetFilePath = path.Join(datasourceDocFolder, fmt.Sprintf("%s_iam_policy.html.markdown", t.FullResourceName(object)))
templateData.GenerateIamDatasourceDocumentationFile(targetFilePath, object)
}
// Finds the folder name for a given version of the terraform provider
func (t *Terraform) FolderName() string {
if t.TargetVersionName == "ga" {
return "google"
} else if t.TargetVersionName == "beta" {
return "google-beta"
}
return "google-private"
}
// Similar to FullResourceName, but override-aware to prevent things like ending in _test.
// Non-Go files should just use FullResourceName.
func (t *Terraform) ResourceGoFilename(object api.Resource) string {
// early exit if no override is set
if object.FilenameOverride == "" {
return t.FullResourceName(object)
}
resName := object.FilenameOverride
var productName string
if t.Product.LegacyName != "" {
productName = t.Product.LegacyName
} else {
productName = google.Underscore(t.Product.Name)
}
return fmt.Sprintf("%s_%s", productName, resName)
}
func (t *Terraform) FullResourceName(object api.Resource) string {
// early exit- resource-level legacy names override the product too
if object.LegacyName != "" {
return strings.Replace(object.LegacyName, "google_", "", 1)
}
var productName string
if t.Product.LegacyName != "" {
productName = t.Product.LegacyName
} else {
productName = google.Underscore(t.Product.Name)
}
return fmt.Sprintf("%s_%s", productName, google.Underscore(object.Name))
}
func (t Terraform) CopyCommonFiles(outputFolder string, generateCode, generateDocs bool) {
log.Printf("Copying common files for %s", ProviderName(t))
files := t.getCommonCopyFiles(t.TargetVersionName, generateCode, generateDocs)
t.CopyFileList(outputFolder, files, generateCode)
}
// To copy a new folder, add the folder to foldersCopiedToRootDir or foldersCopiedToGoogleDir.
// To copy a file, add the file to singleFiles
func (t Terraform) getCommonCopyFiles(versionName string, generateCode, generateDocs bool) map[string]string {
// key is the target file and value is the source file
commonCopyFiles := make(map[string]string, 0)
// Case 1: When copy all of files except .tmpl in a folder to the root directory of downstream repository,
// save the folder name to foldersCopiedToRootDir
foldersCopiedToRootDir := []string{"third_party/terraform/META.d", "third_party/terraform/version"}
// Copy TeamCity-related Kotlin & Markdown files to TPG only, not TPGB
if versionName == "ga" {
foldersCopiedToRootDir = append(foldersCopiedToRootDir, "third_party/terraform/.teamcity")
}
if generateCode {
foldersCopiedToRootDir = append(foldersCopiedToRootDir, "third_party/terraform/scripts")
}
if generateDocs {
foldersCopiedToRootDir = append(foldersCopiedToRootDir, "third_party/terraform/website")
}
for _, folder := range foldersCopiedToRootDir {
files := t.getCopyFilesInFolder(folder, ".")
maps.Copy(commonCopyFiles, files)
}
// Case 2: When copy all of files except .tmpl in a folder to the google directory of downstream repository,
// save the folder name to foldersCopiedToGoogleDir
var foldersCopiedToGoogleDir []string
if generateCode {
foldersCopiedToGoogleDir = []string{"third_party/terraform/services", "third_party/terraform/acctest", "third_party/terraform/sweeper", "third_party/terraform/provider", "third_party/terraform/tpgdclresource", "third_party/terraform/tpgiamresource", "third_party/terraform/tpgresource", "third_party/terraform/transport", "third_party/terraform/fwmodels", "third_party/terraform/fwprovider", "third_party/terraform/fwtransport", "third_party/terraform/fwresource", "third_party/terraform/fwutils", "third_party/terraform/fwvalidators", "third_party/terraform/verify", "third_party/terraform/envvar", "third_party/terraform/functions", "third_party/terraform/test-fixtures"}
}
googleDir := "google"
if versionName != "ga" {
googleDir = fmt.Sprintf("google-%s", versionName)
}
// Copy files to google(or google-beta or google-private) folder in downstream
for _, folder := range foldersCopiedToGoogleDir {
files := t.getCopyFilesInFolder(folder, googleDir)
maps.Copy(commonCopyFiles, files)
}
// Case 3: When copy a single file, save the target as key and source as value to the map singleFiles
singleFiles := map[string]string{
"go.sum": "third_party/terraform/go.sum",
"go.mod": "third_party/terraform/go.mod",
".go-version": "third_party/terraform/.go-version",
"terraform-registry-manifest.json": "third_party/terraform/terraform-registry-manifest.json.tmpl",
}
maps.Copy(commonCopyFiles, singleFiles)
return commonCopyFiles
}
func (t Terraform) getCopyFilesInFolder(folderPath, targetDir string) map[string]string {
m := make(map[string]string, 0)
filepath.WalkDir(folderPath, func(path string, di fs.DirEntry, err error) error {
if !di.IsDir() && !strings.HasSuffix(di.Name(), ".tmpl") && !strings.HasSuffix(di.Name(), ".erb") { // Exception files
if di.Name() == "gha-branch-renaming.png" || di.Name() == "clock-timings-of-branch-making-and-usage.png" {
return nil
}
fname := strings.TrimPrefix(path, "third_party/terraform/")
target := fname
if targetDir != "." {
target = fmt.Sprintf("%s/%s", targetDir, fname)
}
m[target] = path
}
return nil
})
return m
}
func (t Terraform) CopyFileList(outputFolder string, files map[string]string, generateCode bool) {
for target, source := range files {
targetFile := filepath.Join(outputFolder, target)
targetDir := filepath.Dir(targetFile)
if err := os.MkdirAll(targetDir, os.ModePerm); err != nil {
log.Println(fmt.Errorf("error creating output directory %v: %v", targetDir, err))
}
// If we've modified a file since starting an MM run, it's a reasonable
// assumption that it was this run that modified it.
if info, err := os.Stat(targetFile); !errors.Is(err, os.ErrNotExist) && t.StartTime.Before(info.ModTime()) {
log.Fatalf("%s was already modified during this run at %s", targetFile, info.ModTime().String())
}
sourceByte, err := os.ReadFile(source)
if err != nil {
log.Fatalf("Cannot read source file %s while copying: %s", source, err)
}
var permission fs.FileMode
if strings.HasSuffix(targetDir, "scripts") {
permission = 0755
} else {
permission = 0644
}
err = os.WriteFile(targetFile, sourceByte, permission)
if err != nil {
log.Fatalf("Cannot write target file %s while copying: %s", target, err)
}
// Replace import path based on version (beta/alpha)
if filepath.Ext(target) == ".go" || (filepath.Ext(target) == ".mod" && generateCode) {
t.replaceImportPath(outputFolder, target)
}
if filepath.Ext(target) == ".go" {
t.addHashicorpCopyRightHeader(outputFolder, target)
}
}
}
// Compiles files that are shared at the provider level
func (t Terraform) CompileCommonFiles(outputFolder string, products []*api.Product, overridePath string) {
t.generateResourcesForVersion(products)
files := t.getCommonCompileFiles(t.TargetVersionName)
templateData := NewTemplateData(outputFolder, t.TargetVersionName)
t.CompileFileList(outputFolder, files, *templateData, products)
}
// To compile a new folder, add the folder to foldersCompiledToRootDir or foldersCompiledToGoogleDir.
// To compile a file, add the file to singleFiles
func (t Terraform) getCommonCompileFiles(versionName string) map[string]string {
// key is the target file and the value is the source file
commonCompileFiles := make(map[string]string, 0)
// Case 1: When compile all of files except .tmpl in a folder to the root directory of downstream repository,
// save the folder name to foldersCopiedToRootDir
foldersCompiledToRootDir := []string{"third_party/terraform/scripts"}
for _, folder := range foldersCompiledToRootDir {
files := t.getCompileFilesInFolder(folder, ".")
maps.Copy(commonCompileFiles, files)
}
// Case 2: When compile all of files except .tmpl in a folder to the google directory of downstream repository,
// save the folder name to foldersCopiedToGoogleDir
foldersCompiledToGoogleDir := []string{"third_party/terraform/services", "third_party/terraform/acctest", "third_party/terraform/sweeper", "third_party/terraform/provider", "third_party/terraform/tpgdclresource", "third_party/terraform/tpgiamresource", "third_party/terraform/tpgresource", "third_party/terraform/transport", "third_party/terraform/fwmodels", "third_party/terraform/fwprovider", "third_party/terraform/fwtransport", "third_party/terraform/fwresource", "third_party/terraform/verify", "third_party/terraform/envvar", "third_party/terraform/functions", "third_party/terraform/test-fixtures"}
googleDir := "google"
if versionName != "ga" {
googleDir = fmt.Sprintf("google-%s", versionName)
}
for _, folder := range foldersCompiledToGoogleDir {
files := t.getCompileFilesInFolder(folder, googleDir)
maps.Copy(commonCompileFiles, files)
}
// Case 3: When compile a single file, save the target as key and source as value to the map singleFiles
singleFiles := map[string]string{
"main.go": "third_party/terraform/main.go.tmpl",
".goreleaser.yml": "third_party/terraform/.goreleaser.yml.tmpl",
".release/release-metadata.hcl": "third_party/terraform/release-metadata.hcl.tmpl",
".copywrite.hcl": "third_party/terraform/.copywrite.hcl.tmpl",
}
maps.Copy(commonCompileFiles, singleFiles)
return commonCompileFiles
}
func (t Terraform) getCompileFilesInFolder(folderPath, targetDir string) map[string]string {
m := make(map[string]string, 0)
filepath.WalkDir(folderPath, func(path string, di fs.DirEntry, err error) error {
if !di.IsDir() && strings.HasSuffix(di.Name(), ".tmpl") {
fname := strings.TrimPrefix(path, "third_party/terraform/")
fname = strings.TrimSuffix(fname, ".tmpl")
target := fname
if targetDir != "." {
target = fmt.Sprintf("%s/%s", targetDir, fname)
}
m[target] = path
}
return nil
})
return m
}
func (t Terraform) CompileFileList(outputFolder string, files map[string]string, fileTemplate TemplateData, products []*api.Product) {
providerWithProducts := ProviderWithProducts{
Terraform: t,
Products: products,
}
if err := os.MkdirAll(outputFolder, os.ModePerm); err != nil {
log.Println(fmt.Errorf("error creating output directory %v: %v", outputFolder, err))
}
for target, source := range files {
targetFile := filepath.Join(outputFolder, target)
targetDir := filepath.Dir(targetFile)
if err := os.MkdirAll(targetDir, os.ModePerm); err != nil {
log.Println(fmt.Errorf("error creating output directory %v: %v", targetDir, err))
}
templates := []string{
source,
}
formatFile := filepath.Ext(targetFile) == ".go"
fileTemplate.GenerateFile(targetFile, source, providerWithProducts, formatFile, templates...)
// continue to next file if no file was generated
if _, err := os.Stat(targetFile); errors.Is(err, os.ErrNotExist) {
continue
}
t.replaceImportPath(outputFolder, target)
t.addHashicorpCopyRightHeader(outputFolder, target)
}
}
func (t Terraform) addHashicorpCopyRightHeader(outputFolder, target string) {
if !expectedOutputFolder(outputFolder) {
log.Printf("Unexpected output folder (%s) detected "+
"when deciding to add HashiCorp copyright headers.\n"+
"Watch out for unexpected changes to copied files", outputFolder)
}
// only add copyright headers when generating TPG and TPGB
if !(strings.HasSuffix(outputFolder, "terraform-provider-google") || strings.HasSuffix(outputFolder, "terraform-provider-google-beta")) {
return
}
// Prevent adding copyright header to files with paths or names matching the strings below
// NOTE: these entries need to match the content of the .copywrite.hcl file originally
// created in https://github.com/GoogleCloudPlatform/magic-modules/pull/7336
// The test-fixtures folder is not included here as it's copied as a whole,
// not file by file
ignoredFolders := []string{".release/", ".changelog/", "examples/", "scripts/", "META.d/"}
ignoredFiles := []string{"go.mod", ".goreleaser.yml", ".golangci.yml", "terraform-registry-manifest.json", "_meta.yaml"}
shouldAddHeader := true
for _, folder := range ignoredFolders {
// folder will be path leading to file
if strings.HasPrefix(target, folder) {
shouldAddHeader = false
break
}
}
if !shouldAddHeader {
return
}
for _, file := range ignoredFiles {
// file will be the filename and extension, with no preceding path
if strings.HasSuffix(target, file) {
shouldAddHeader = false
break
}
}
if !shouldAddHeader {
return
}
lang := languageFromFilename(target)
// Some file types we don't want to add headers to
// e.g. .sh where headers are functional
// Also, this guards against new filetypes being added and triggering build errors
if lang == "unsupported" {
return
}
// File is not ignored and is appropriate file type to add header to
copyrightHeader := []string{"Copyright (c) HashiCorp, Inc.", "SPDX-License-Identifier: MPL-2.0"}
header := commentBlock(copyrightHeader, lang)
targetFile := filepath.Join(outputFolder, target)
sourceByte, err := os.ReadFile(targetFile)
if err != nil {
log.Fatalf("Cannot read file %s to add Hashicorp copy right: %s", targetFile, err)
}
sourceByte = google.Concat([]byte(header), sourceByte)
err = os.WriteFile(targetFile, sourceByte, 0644)
if err != nil {
log.Fatalf("Cannot write file %s to add Hashicorp copy right: %s", target, err)
}
}
func expectedOutputFolder(outputFolder string) bool {
expectedFolders := []string{"terraform-provider-google", "terraform-provider-google-beta", "terraform-next", "terraform-google-conversion", "tfplan2cai"}
folderName := filepath.Base(outputFolder) // Possible issue with Windows OS
isExpected := false
for _, folder := range expectedFolders {
if folderName == folder {
isExpected = true
break
}
}
return isExpected
}
func (t Terraform) replaceImportPath(outputFolder, target string) {
targetFile := filepath.Join(outputFolder, target)
sourceByte, err := os.ReadFile(targetFile)
if err != nil {
log.Fatalf("Cannot read file %s to replace import path: %s", targetFile, err)
}
data := string(sourceByte)
gaImportPath := ImportPathFromVersion("ga")
betaImportPath := ImportPathFromVersion("beta")
if strings.Contains(data, betaImportPath) {
log.Fatalf("Importing a package from module %s is not allowed in file %s. Please import a package from module %s.", betaImportPath, filepath.Base(target), gaImportPath)
}
if t.TargetVersionName == "ga" {
return
}
// Replace the import pathes in utility files
var tpg, dir string
switch t.TargetVersionName {
case "beta":
tpg = TERRAFORM_PROVIDER_BETA
dir = RESOURCE_DIRECTORY_BETA
default:
tpg = TERRAFORM_PROVIDER_PRIVATE
dir = RESOURCE_DIRECTORY_PRIVATE
}
sourceByte = bytes.Replace(sourceByte, []byte(gaImportPath), []byte(tpg+"/"+dir), -1)
sourceByte = bytes.Replace(sourceByte, []byte(TERRAFORM_PROVIDER_GA+"/version"), []byte(tpg+"/version"), -1)
sourceByte = bytes.Replace(sourceByte, []byte("module "+TERRAFORM_PROVIDER_GA), []byte("module "+tpg), -1)
if filepath.Ext(targetFile) == (".go") {
formatByte, err := format.Source(sourceByte)
if err != nil {
log.Printf("error formatting %s: %s", targetFile, err)
} else {
sourceByte = formatByte
}
}
err = os.WriteFile(targetFile, sourceByte, 0644)
if err != nil {
log.Fatalf("Cannot write file %s to replace import path: %s", target, err)
}
}
func (t Terraform) ProviderFromVersion() string {
var dir string
switch t.TargetVersionName {
case "ga":
dir = RESOURCE_DIRECTORY_GA
case "beta":
dir = RESOURCE_DIRECTORY_BETA
default:
dir = RESOURCE_DIRECTORY_PRIVATE
}
return dir
}
// Gets the list of services dependent on the version ga, beta, and private
// If there are some resources of a servcie is in GA,
// then this service is in GA. Otherwise, the service is in BETA
func (t Terraform) GetMmv1ServicesInVersion(products []*api.Product) []string {
var services []string
for _, product := range products {
if t.TargetVersionName == "ga" {
someResourceInGA := false
for _, object := range product.Objects {
if someResourceInGA {
break
}
if !object.Exclude && !object.NotInVersion(product.VersionObjOrClosest(t.TargetVersionName)) {
someResourceInGA = true
}
}
if someResourceInGA {
services = append(services, strings.ToLower(product.Name))
}
} else {
services = append(services, strings.ToLower(product.Name))
}
}
return services
}
// # Generates the list of resources, and gets the count of resources and iam resources
// # dependent on the version ga, beta or private.
// # The resource object has the format
// # {
// # terraform_name:
// # resource_name:
// # iam_class_name:
// # }
// # The variable resources_for_version is used to generate resources in file
// # mmv1/third_party/terraform/provider/provider_mmv1_resources.go.erb
func (t *Terraform) generateResourcesForVersion(products []*api.Product) {
for _, productDefinition := range products {
service := strings.ToLower(productDefinition.Name)
for _, object := range productDefinition.Objects {
if object.Exclude || object.NotInVersion(productDefinition.VersionObjOrClosest(t.TargetVersionName)) {
continue
}
var resourceName string
if !object.IsExcluded() {
t.ResourceCount++
resourceName = fmt.Sprintf("%s.Resource%s", service, object.ResourceName())
}
var iamClassName string
iamPolicy := object.IamPolicy
if iamPolicy != nil && !iamPolicy.Exclude {
t.IAMResourceCount += 3
if slices.Index(product.ORDER, iamPolicy.MinVersion) <= slices.Index(product.ORDER, t.TargetVersionName) {
iamClassName = fmt.Sprintf("%s.%s", service, object.ResourceName())
}
}
t.ResourcesForVersion = append(t.ResourcesForVersion, map[string]string{
"TerraformName": object.TerraformName(),
"ResourceName": resourceName,
"IamClassName": iamClassName,
})
}
}
}
// # Adapted from the method used in templating
// # See: mmv1/compile/core.rb
func commentBlock(text []string, lang string) string {
var headers []string
switch lang {
case "python", "yaml":
headers = commentText(text, "#")
case "go":
headers = commentText(text, "//")
default:
log.Fatalf("Unknown language for comment: %s", lang)
}
headerString := strings.Join(headers, "\n")
return fmt.Sprintf("%s\n", headerString) // add trailing newline to returned value
}
func commentText(text []string, symbols string) []string {
var header []string
for _, t := range text {
var comment string
if t == "" {
comment = symbols
} else {
comment = fmt.Sprintf("%s %s", symbols, t)
}
header = append(header, comment)
}
return header
}
func languageFromFilename(filename string) string {
switch extension := filepath.Ext(filename); extension {
case ".go":
return "go"
case ".rb":
return "rb"
case ".yaml", ".yml":
return "yaml"
default:
return "unsupported"
}
}
// Returns the extension for DCL packages for the given version. This is needed
// as the DCL uses "alpha" for preview resources, while we use "private"
func (t Terraform) DCLVersion() string {
switch t.TargetVersionName {
case "beta":
return "/beta"
case "private":
return "/alpha"
default:
return ""
}
}
// Gets the provider versions supported by a version
func (t Terraform) SupportedProviderVersions() []string {
var supported []string
for i, v := range product.ORDER {
if i == 0 {
continue
}
if i > slices.Index(product.ORDER, t.TargetVersionName) {
break
}
supported = append(supported, v)
}
return supported
}
type ProviderWithProducts struct {
Terraform
Products []*api.Product
}