diff --git a/mmv1/api/resource.go b/mmv1/api/resource.go index 64de022edd3d..fd65b71b6b9b 100644 --- a/mmv1/api/resource.go +++ b/mmv1/api/resource.go @@ -132,7 +132,7 @@ type Resource struct { // // [Optional] (Api::Resource::IamPolicy) Configuration of a resource's // resource-specific IAM Policy. - IamPolicy resource.IamPolicy `yaml:"iam_policy"` + IamPolicy *resource.IamPolicy `yaml:"iam_policy"` // [Optional] If set to true, don't generate the resource itself; only // generate the IAM policy. diff --git a/mmv1/main.go b/mmv1/main.go index 2167e309793c..59088cf3bb2d 100644 --- a/mmv1/main.go +++ b/mmv1/main.go @@ -9,6 +9,7 @@ import ( "path/filepath" "sort" "strings" + "time" "golang.org/x/exp/slices" @@ -68,6 +69,7 @@ func main() { log.Fatalf("No product.yaml file found.") } + startTime := time.Now() log.Printf("Generating MM output to '%s'", *outputPath) log.Printf("Using %s version", *version) @@ -80,6 +82,7 @@ func main() { return false }) + var productsForVersion []map[string]interface{} for _, productName := range allProductFiles { productYamlPath := path.Join(productName, "go_product.yaml") @@ -137,7 +140,7 @@ func main() { productApi.Validate() // TODO Q2: set other providers via flag - providerToGenerate := provider.NewTerraform(productApi, *version) + providerToGenerate := provider.NewTerraform(productApi, *version, startTime) if !slices.Contains(productsToGenerate, productName) { log.Printf("%s not specified, skipping generation", productName) @@ -146,8 +149,33 @@ func main() { log.Printf("%s: Generating files", productName) providerToGenerate.Generate(*outputPath, productName, generateCode, generateDocs) + + // we need to preserve a single provider instance to use outside of this loop. + productsForVersion = append(productsForVersion, map[string]interface{}{ + "Definitions": productApi, + "Provider": providerToGenerate, + }) } // TODO Q2: copy common files } + + slices.SortFunc(productsForVersion, func(p1, p2 map[string]interface{}) int { + return strings.Compare(strings.ToLower(p1["Definitions"].(*api.Product).Name), strings.ToLower(p2["Definitions"].(*api.Product).Name)) + }) + + // In order to only copy/compile files once per provider this must be called outside + // of the products loop. This will get called with the provider from the final iteration + // of the loop + finalProduct := productsForVersion[len(productsForVersion)-1] + provider := finalProduct["Provider"].(*provider.Terraform) + + provider.CopyCommonFiles(*outputPath, generateCode, generateDocs) + + log.Printf("Compiling common files for terraform") + if generateCode { + provider.CompileCommonFiles(*outputPath, productsForVersion, "") + + // TODO Q2: product overrides + } } diff --git a/mmv1/provider/template_data.go b/mmv1/provider/template_data.go index 15f9e0ba84d9..4dd8a2126c4d 100644 --- a/mmv1/provider/template_data.go +++ b/mmv1/provider/template_data.go @@ -161,10 +161,6 @@ func (td *TemplateData) GenerateFile(filePath, templatePath string, input any, g } sourceByte := contents.Bytes() - // Replace import path based on version (beta/alpha) - if td.TerraformResourceDirectory != "google" { - sourceByte = bytes.Replace(sourceByte, []byte("github.com/hashicorp/terraform-provider-google/google"), []byte(td.TerraformProviderModule+"/"+td.TerraformResourceDirectory), -1) - } // if goFormat { // sourceByte, err = format.Source(sourceByte) diff --git a/mmv1/provider/terraform.go b/mmv1/provider/terraform.go index 0e3b926f5f97..7a08685141c7 100644 --- a/mmv1/provider/terraform.go +++ b/mmv1/provider/terraform.go @@ -14,12 +14,19 @@ package provider import ( + "bytes" + "errors" "fmt" + "go/format" + "io/fs" "log" + "maps" "os" "path" + "path/filepath" "reflect" "strings" + "time" "github.com/GoogleCloudPlatform/magic-modules/mmv1/api" "github.com/GoogleCloudPlatform/magic-modules/mmv1/api/product" @@ -38,26 +45,30 @@ type Terraform struct { IAMResourceCount int - ResourcesForVersion []api.Resource + ResourcesForVersion []map[string]string TargetVersionName string Version product.Version Product api.Product + + StartTime time.Time } -func NewTerraform(product *api.Product, versionName string) *Terraform { +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)} + Version: *product.VersionObjOrClosest(versionName), + StartTime: startTime, + } t.Product.SetPropertiesBasedOnVersion(&t.Version) for _, r := range t.Product.Objects { - r.SetCompiler(reflect.TypeOf(t).Name()) + r.SetCompiler(t.providerName()) } return &t @@ -222,571 +233,767 @@ func (t *Terraform) FullResourceName(object api.Resource) string { return fmt.Sprintf("%s_%s", productName, name) } +// def copy_common_files(output_folder, generate_code, generate_docs, provider_name = nil) +func (t Terraform) CopyCommonFiles(outputFolder string, generateCode, generateDocs bool) { + log.Printf("Copying common files for %s", t.providerName()) + + files := t.getCommonCopyFiles(t.TargetVersionName, generateCode, generateDocs) + t.CopyFileList(outputFolder, files) +} + +// To compile a new folder, add the folder to foldersCopiedToRootDir or foldersCopiedToGoogleDir. +// To compile 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/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", + } + 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") { + 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 +} + +// def copy_file_list(output_folder, files) +func (t Terraform) CopyFileList(outputFolder string, files map[string]string) { + 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) + } + + err = os.WriteFile(targetFile, sourceByte, 0644) + 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" { + t.replaceImportPath(outputFolder, target) + } + + if filepath.Ext(target) == ".go" { + t.addHashicorpCopyRightHeader(outputFolder, target) + } + } +} + +// Compiles files that are shared at the provider level +// +// def compile_common_files( +// output_folder, +// products, +// common_compile_file, +// override_path = nil +// ) +func (t Terraform) CompileCommonFiles(outputFolder string, products []map[string]interface{}, overridePath string) { + t.generateResourcesForVersion(products) + files := t.getCommonCompileFiles(t.TargetVersionName) + templateData := NewTemplateData(outputFolder, t.Version) + t.CompileFileList(outputFolder, files, *templateData) +} + +// 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 +} + +// def compile_file_list(output_folder, files, file_template, pwd = Dir.pwd) +func (t Terraform) CompileFileList(outputFolder string, files map[string]string, fileTemplate TemplateData) { + if err := os.MkdirAll(outputFolder, os.ModePerm); err != nil { + log.Println(fmt.Errorf("error creating output directory %v: %v", outputFolder, err)) + } + + // TODO: is this needed? + // err := os.Chdir(outputFolder) + // if err != nil { + // log.Fatalf("Could not move into the directory %s", outputFolder) + // } + + 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, + } + + fileTemplate.GenerateFile(targetFile, source, t, true, templates...) + t.replaceImportPath(outputFolder, target) + t.addHashicorpCopyRightHeader(outputFolder, target) + } + // TODO: is this needed? + // Dir.chdir pwd +} + +// def add_hashicorp_copyright_header(output_folder, 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"} + 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) + } +} + +// def expected_output_folder?(output_folder) +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 +} + +// def replace_import_path(output_folder, target) +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) + + betaImportPath := fmt.Sprintf("%s/%s", TERRAFORM_PROVIDER_BETA, RESOURCE_DIRECTORY_BETA) + gaImportPath := fmt.Sprintf("%s/%s", TERRAFORM_PROVIDER_GA, RESOURCE_DIRECTORY_GA) + 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) + + sourceByte, err = format.Source(sourceByte) + if err != nil { + log.Fatalf("error formatting %s", targetFile) + } + + err = os.WriteFile(targetFile, sourceByte, 0644) + if err != nil { + log.Fatalf("Cannot write file %s to replace import path: %s", target, err) + } +} + +// # 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 +// def get_mmv1_services_in_version(products, version) +// +// services = [] +// products.map do |product| +// product_definition = product[:definitions] +// if version == 'ga' +// some_resource_in_ga = false +// product_definition.objects.each do |object| +// break if some_resource_in_ga +// +// if !object.exclude && +// !object.not_in_version?(product_definition.version_obj_or_closest(version)) +// some_resource_in_ga = true +// end +// end +// +// services << product[:definitions].name.downcase if some_resource_in_ga +// else +// services << product[:definitions].name.downcase +// end +// end +// services // -// # generate_code and generate_docs are actually used because all of the variables -// # in scope in this method are made available within the templates by the compile call. -// # rubocop:disable Lint/UnusedMethodArgument -// def copy_common_files(output_folder, generate_code, generate_docs, provider_name = nil) -// # version_name is actually used because all of the variables in scope in this method -// # are made available within the templates by the compile call. -// # TODO: remove version_name, use @target_version_name or pass it in expicitly -// # rubocop:disable Lint/UselessAssignment -// version_name = @target_version_name -// # rubocop:enable Lint/UselessAssignment -// provider_name ||= self.class.name.split('::').last.downcase -// return unless File.exist?("provider/#{provider_name}/common~copy.yaml") -// -// Google::LOGGER.info "Copying common files for #{provider_name}" -// files = YAML.safe_load(compile("provider/#{provider_name}/common~copy.yaml")) -// copy_file_list(output_folder, files) -// end -// # rubocop:enable Lint/UnusedMethodArgument -// -// def copy_file_list(output_folder, files) -// files.map do |target, source| -// Thread.new do -// target_file = File.join(output_folder, target) -// target_dir = File.dirname(target_file) -// Google::LOGGER.debug "Copying #{source} => #{target}" -// FileUtils.mkpath target_dir -// -// # 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 File.exist?(target_file) && File.mtime(target_file) > @start_time -// raise "#{target_file} was already modified during this run. #{File.mtime(target_file)}" -// end -// -// FileUtils.copy_entry source, target_file -// -// add_hashicorp_copyright_header(output_folder, target) if File.extname(target) == '.go' -// if File.extname(target) == '.go' || File.extname(target) == '.mod' -// replace_import_path(output_folder, target) -// end -// end -// end.map(&:join) -// end -// -// # Compiles files that are shared at the provider level -// def compile_common_files( -// output_folder, -// products, -// common_compile_file, -// override_path = nil -// ) -// return unless File.exist?(common_compile_file) -// -// generate_resources_for_version(products, @target_version_name) -// -// files = YAML.safe_load(compile(common_compile_file)) -// return unless files -// -// file_template = ProviderFileTemplate.new( -// output_folder, -// @target_version_name, -// build_env, -// products, -// override_path -// ) -// compile_file_list(output_folder, files, file_template) -// end -// -// def compile_file_list(output_folder, files, file_template, pwd = Dir.pwd) -// FileUtils.mkpath output_folder -// Dir.chdir output_folder -// files.map do |target, source| -// Thread.new do -// Google::LOGGER.debug "Compiling #{source} => #{target}" -// file_template.generate(pwd, source, target, self) -// -// add_hashicorp_copyright_header(output_folder, target) -// replace_import_path(output_folder, target) -// end -// end.map(&:join) -// Dir.chdir pwd -// end -// -// def add_hashicorp_copyright_header(output_folder, target) -// unless expected_output_folder?(output_folder) -// Google::LOGGER.info "Unexpected output folder (#{output_folder}) detected " \ -// 'when deciding to add HashiCorp copyright headers. ' \ -// 'Watch out for unexpected changes to copied files' -// end -// # only add copyright headers when generating TPG and TPGB -// return unless output_folder.end_with?('terraform-provider-google') || -// output_folder.end_with?('terraform-provider-google-beta') -// -// # 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 (see common~copy.yaml) -// ignored_folders = [ -// '.release/', -// '.changelog/', -// 'examples/', -// 'scripts/', -// 'META.d/' -// ] -// ignored_files = [ -// 'go.mod', -// '.goreleaser.yml', -// '.golangci.yml', -// 'terraform-registry-manifest.json' -// ] -// should_add_header = true -// ignored_folders.each do |folder| -// # folder will be path leading to file -// next unless target.start_with? folder -// -// Google::LOGGER.debug 'Not adding HashiCorp copyright headers in ' \ -// "ignored folder #{folder} : #{target}" -// should_add_header = false -// end -// return unless should_add_header -// -// ignored_files.each do |file| -// # file will be the filename and extension, with no preceding path -// next unless target.end_with? file -// -// Google::LOGGER.debug 'Not adding HashiCorp copyright headers to ' \ -// "ignored file #{file} : #{target}" -// should_add_header = false -// end -// return unless should_add_header -// -// Google::LOGGER.debug "Adding HashiCorp copyright header to : #{target}" -// data = File.read("#{output_folder}/#{target}") -// -// copyright_header = ['Copyright (c) HashiCorp, Inc.', 'SPDX-License-Identifier: MPL-2.0'] -// lang = language_from_filename(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 -// return unless lang != :unsupported -// -// # File is not ignored and is appropriate file type to add header to -// header = comment_block(copyright_header, lang) -// File.write("#{output_folder}/#{target}", header) -// -// File.write("#{output_folder}/#{target}", data, mode: 'a') # append mode -// end -// -// def expected_output_folder?(output_folder) -// expected_folders = %w[ -// terraform-provider-google -// terraform-provider-google-beta -// terraform-next -// terraform-google-conversion -// tfplan2cai -// ] -// folder_name = output_folder.split('/')[-1] # Possible issue with Windows OS -// is_expected = false -// expected_folders.each do |folder| -// next unless folder_name == folder -// -// is_expected = true -// break -// end -// is_expected -// end -// -// def replace_import_path(output_folder, target) -// data = File.read("#{output_folder}/#{target}") -// -// if data.include? "#{TERRAFORM_PROVIDER_BETA}/#{RESOURCE_DIRECTORY_BETA}" -// raise 'Importing a package from module ' \ -// "#{TERRAFORM_PROVIDER_BETA}/#{RESOURCE_DIRECTORY_BETA} " \ -// "is not allowed in file #{target.split('/').last}. " \ -// 'Please import a package from module ' \ -// "#{TERRAFORM_PROVIDER_GA}/#{RESOURCE_DIRECTORY_GA}." -// end -// -// return if @target_version_name == 'ga' +// end // -// # Replace the import pathes in utility files -// case @target_version_name -// when 'beta' -// tpg = TERRAFORM_PROVIDER_BETA -// dir = RESOURCE_DIRECTORY_BETA -// else -// tpg = TERRAFORM_PROVIDER_PRIVATE -// dir = RESOURCE_DIRECTORY_PRIVATE -// end +// def generate_newyaml(pwd, data) // -// data = data.gsub( -// "#{TERRAFORM_PROVIDER_GA}/#{RESOURCE_DIRECTORY_GA}", -// "#{tpg}/#{dir}" -// ) -// data = data.gsub( -// "#{TERRAFORM_PROVIDER_GA}/version", -// "#{tpg}/version" -// ) +// # @api.api_name is the service folder name +// product_name = @api.api_name +// target_folder = File.join(folder_name(data.version), 'services', product_name) +// FileUtils.mkpath target_folder +// data.generate(pwd, +// '/templates/terraform/yaml_conversion.erb', +// "#{target_folder}/go_#{data.object.name}.yaml", +// self) +// return if File.exist?("#{target_folder}/go_product.yaml") // -// data = data.gsub( -// "module #{TERRAFORM_PROVIDER_GA}", -// "module #{tpg}" -// ) -// File.write("#{output_folder}/#{target}", data) -// end +// data.generate(pwd, +// '/templates/terraform/product_yaml_conversion.erb', +// "#{target_folder}/go_product.yaml", +// self) // +// end // -// # 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 -// def get_mmv1_services_in_version(products, version) -// services = [] -// products.map do |product| -// product_definition = product[:definitions] -// if version == 'ga' -// some_resource_in_ga = false -// product_definition.objects.each do |object| -// break if some_resource_in_ga -// -// if !object.exclude && -// !object.not_in_version?(product_definition.version_obj_or_closest(version)) -// some_resource_in_ga = true -// end -// end -// -// services << product[:definitions].name.downcase if some_resource_in_ga -// else -// services << product[:definitions].name.downcase -// end -// end -// services -// end +// def build_env // -// def generate_newyaml(pwd, data) -// # @api.api_name is the service folder name -// product_name = @api.api_name -// target_folder = File.join(folder_name(data.version), 'services', product_name) -// FileUtils.mkpath target_folder -// data.generate(pwd, -// '/templates/terraform/yaml_conversion.erb', -// "#{target_folder}/go_#{data.object.name}.yaml", -// self) -// return if File.exist?("#{target_folder}/go_product.yaml") +// { +// goformat_enabled: @go_format_enabled, +// start_time: @start_time +// } // -// data.generate(pwd, -// '/templates/terraform/product_yaml_conversion.erb', -// "#{target_folder}/go_product.yaml", -// self) -// end +// end // -// def build_env -// { -// goformat_enabled: @go_format_enabled, -// start_time: @start_time -// } -// end +// # used to determine and separate objects that have update methods +// # that target individual fields +// def field_specific_update_methods(properties) // -// # used to determine and separate objects that have update methods -// # that target individual fields -// def field_specific_update_methods(properties) -// properties_by_custom_update(properties).length.positive? -// end +// properties_by_custom_update(properties).length.positive? // -// # Filter the properties to keep only the ones requiring custom update -// # method and group them by update url & verb. -// def properties_by_custom_update(properties) -// update_props = properties.reject do |p| -// p.update_url.nil? || p.update_verb.nil? || p.update_verb == :NOOP || -// p.is_a?(Api::Type::KeyValueTerraformLabels) || -// p.is_a?(Api::Type::KeyValueLabels) # effective_labels is used for update -// end +// end // -// update_props.group_by do |p| -// { -// update_url: p.update_url, -// update_verb: p.update_verb, -// update_id: p.update_id, -// fingerprint_name: p.fingerprint_name -// } -// end -// end +// # Filter the properties to keep only the ones requiring custom update +// # method and group them by update url & verb. +// def properties_by_custom_update(properties) // -// # Filter the properties to keep only the ones don't have custom update -// # method and group them by update url & verb. -// def properties_without_custom_update(properties) -// properties.select do |p| -// p.update_url.nil? || p.update_verb.nil? || p.update_verb == :NOOP -// end -// end +// update_props = properties.reject do |p| +// p.update_url.nil? || p.update_verb.nil? || p.update_verb == :NOOP || +// p.is_a?(Api::Type::KeyValueTerraformLabels) || +// p.is_a?(Api::Type::KeyValueLabels) # effective_labels is used for update +// end // -// # Takes a update_url and returns the list of custom updatable properties -// # that can be updated at that URL. This allows flattened objects -// # to determine which parent property in the API should be updated with -// # the contents of the flattened object -// def custom_update_properties_by_key(properties, key) -// properties_by_custom_update(properties).select do |k, _| -// k[:update_url] == key[:update_url] && -// k[:update_id] == key[:update_id] && -// k[:fingerprint_name] == key[:fingerprint_name] -// end.first.last -// # .first is to grab the element from the select which returns a list -// # .last is because properties_by_custom_update returns a list of -// # [{update_url}, [properties,...]] and we only need the 2nd part -// end +// update_props.group_by do |p| +// { +// update_url: p.update_url, +// update_verb: p.update_verb, +// update_id: p.update_id, +// fingerprint_name: p.fingerprint_name +// } +// end // -// def update_url(resource, url_part) -// [resource.__product.base_url, update_uri(resource, url_part)].flatten.join -// end +// end // -// def update_uri(resource, url_part) -// return resource.self_link_uri if url_part.nil? +// # Filter the properties to keep only the ones don't have custom update +// # method and group them by update url & verb. +// def properties_without_custom_update(properties) // -// url_part -// end +// properties.select do |p| +// p.update_url.nil? || p.update_verb.nil? || p.update_verb == :NOOP +// end // -// def generating_hashicorp_repo? -// # The default Provider is used to generate TPG and TPGB in HashiCorp-owned repos. -// # The compiler deviates from the default behaviour with a -f flag to produce -// # non-HashiCorp downstreams. -// true -// end +// end // -// # ProductFileTemplate with Terraform specific fields -// class TerraformProductFileTemplate < Provider::ProductFileTemplate -// # The async object used for making operations. -// # We assume that all resources share the same async properties. -// attr_accessor :async +// # Takes a update_url and returns the list of custom updatable properties +// # that can be updated at that URL. This allows flattened objects +// # to determine which parent property in the API should be updated with +// # the contents of the flattened object +// def custom_update_properties_by_key(properties, key) // -// # When generating OiCS examples, we attach the example we're -// # generating to the data object. -// attr_accessor :example +// properties_by_custom_update(properties).select do |k, _| +// k[:update_url] == key[:update_url] && +// k[:update_id] == key[:update_id] && +// k[:fingerprint_name] == key[:fingerprint_name] +// end.first.last +// # .first is to grab the element from the select which returns a list +// # .last is because properties_by_custom_update returns a list of +// # [{update_url}, [properties,...]] and we only need the 2nd part // -// attr_accessor :resource_name -// end +// end // -// # Sorts properties in the order they should appear in the TF schema: -// # Required, Optional, Computed -// def order_properties(properties) -// properties.select(&:required).sort_by(&:name) + -// properties.reject(&:required).reject(&:output).sort_by(&:name) + -// properties.select(&:output).sort_by(&:name) -// end +// def update_url(resource, url_part) // -// def tf_type(property) -// tf_types[property.class] -// end +// [resource.__product.base_url, update_uri(resource, url_part)].flatten.join // -// # "Namespace" - prefix with product and resource - a property with -// # information from the "object" variable -// def namespace_property_from_object(property, object) -// name = property.name.camelize -// until property.parent.nil? -// property = property.parent -// name = property.name.camelize + name -// end +// end // -// "#{property.__resource.__product.api_name.camelize(:lower)}#{object.name}#{name}" -// end +// def update_uri(resource, url_part) // -// # Converts between the Magic Modules type of an object and its type in the -// # TF schema -// def tf_types -// { -// Api::Type::Boolean => 'schema.TypeBool', -// Api::Type::Double => 'schema.TypeFloat', -// Api::Type::Integer => 'schema.TypeInt', -// Api::Type::String => 'schema.TypeString', -// # Anonymous string property used in array of strings. -// 'Api::Type::String' => 'schema.TypeString', -// Api::Type::Time => 'schema.TypeString', -// Api::Type::Enum => 'schema.TypeString', -// Api::Type::ResourceRef => 'schema.TypeString', -// Api::Type::NestedObject => 'schema.TypeList', -// Api::Type::Array => 'schema.TypeList', -// Api::Type::KeyValuePairs => 'schema.TypeMap', -// Api::Type::KeyValueLabels => 'schema.TypeMap', -// Api::Type::KeyValueTerraformLabels => 'schema.TypeMap', -// Api::Type::KeyValueEffectiveLabels => 'schema.TypeMap', -// Api::Type::KeyValueAnnotations => 'schema.TypeMap', -// Api::Type::Map => 'schema.TypeSet', -// Api::Type::Fingerprint => 'schema.TypeString' -// } -// end +// return resource.self_link_uri if url_part.nil? // -// def updatable?(resource, properties) -// !resource.immutable || !properties.reject { |p| p.update_url.nil? }.empty? -// end +// url_part // -// def force_new?(property, resource) -// ( -// (!property.output || property.is_a?(Api::Type::KeyValueEffectiveLabels)) && -// (property.immutable || -// (resource.immutable && property.update_url.nil? && property.immutable.nil? && -// (property.parent.nil? || -// (force_new?(property.parent, resource) && -// !(property.parent.flatten_object && property.is_a?(Api::Type::KeyValueLabels)) -// ) -// ) -// ) -// ) -// ) || -// (property.is_a?(Api::Type::KeyValueTerraformLabels) && -// !updatable?(resource, resource.all_user_properties) && !resource.root_labels? -// ) -// end +// end // -// # Returns tuples of (fieldName, list of update masks) for -// # top-level updatable fields. Schema path refers to a given Terraform -// # field name (e.g. d.GetChange('fieldName)') -// def get_property_update_masks_groups(properties, mask_prefix: '') -// mask_groups = [] -// properties.each do |prop| -// if prop.flatten_object -// mask_groups += get_property_update_masks_groups( -// prop.properties, mask_prefix: "#{prop.api_name}." -// ) -// elsif prop.update_mask_fields -// mask_groups << [prop.name.underscore, prop.update_mask_fields] -// else -// mask_groups << [prop.name.underscore, [mask_prefix + prop.api_name]] -// end -// end -// mask_groups -// end +// def generating_hashicorp_repo? // -// # Returns an updated path for a given Terraform field path (e.g. -// # 'a_field', 'parent_field.0.child_name'). Returns nil if the property -// # is not included in the resource's properties and removes keys that have -// # been flattened -// # FYI: Fields that have been renamed should use the new name, however, flattened -// # fields still need to be included, ie: -// # flattenedField > newParent > renameMe should be passed to this function as -// # flattened_field.0.new_parent.0.im_renamed -// # TODO(emilymye): Change format of input for -// # exactly_one_of/at_least_one_of/etc to use camelcase, MM properities and -// # convert to snake in this method -// def get_property_schema_path(schema_path, resource) -// nested_props = resource.properties -// prop = nil -// path_tkns = schema_path.split('.0.').map do |pname| -// camel_pname = pname.camelize(:lower) -// prop = nested_props.find { |p| p.name == camel_pname } -// # if we couldn't find it, see if it was renamed at the top level -// prop = nested_props.find { |p| p.name == schema_path } if prop.nil? -// return nil if prop.nil? -// -// nested_props = prop.nested_properties || [] -// prop.flatten_object ? nil : pname.underscore -// end -// if path_tkns.empty? || path_tkns[-1].nil? -// nil -// else -// path_tkns.compact.join('.0.') -// end -// end +// # The default Provider is used to generate TPG and TPGB in HashiCorp-owned repos. +// # The compiler deviates from the default behaviour with a -f flag to produce +// # non-HashiCorp downstreams. +// true // -// # Transforms a format string with field markers to a regex string with -// # capture groups. -// # -// # For instance, -// # projects/{{project}}/global/networks/{{name}} -// # is transformed to -// # projects/(?P[^/]+)/global/networks/(?P[^/]+) -// # -// # Values marked with % are URL-encoded, and will match any number of /'s. -// # -// # Note: ?P indicates a Python-compatible named capture group. Named groups -// # aren't common in JS-based regex flavours, but are in Perl-based ones -// def format2regex(format) -// format -// .gsub(/\{\{%([[:word:]]+)\}\}/, '(?P<\1>.+)') -// .gsub(/\{\{([[:word:]]+)\}\}/, '(?P<\1>[^/]+)') -// end +// end // -// # Capitalize the first letter of a property name. -// # E.g. "creationTimestamp" becomes "CreationTimestamp". -// def titlelize_property(property) -// property.name.camelize(:upper) -// end +// # ProductFileTemplate with Terraform specific fields +// class TerraformProductFileTemplate < Provider::ProductFileTemplate // -// # 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 -// def generate_resources_for_version(products, version) -// products.each do |product| -// product_definition = product[:definitions] -// service = product_definition.name.downcase -// product_definition.objects.each do |object| -// if object.exclude || -// object.not_in_version?(product_definition.version_obj_or_closest(version)) -// next -// end -// -// @resource_count += 1 unless object&.exclude_resource -// -// tf_product = (object.__product.legacy_name || product_definition.name).underscore -// terraform_name = object.legacy_name || "google_#{tf_product}_#{object.name.underscore}" -// -// unless object&.exclude_resource -// resource_name = "#{service}.Resource#{product_definition.name}#{object.name}" -// end -// -// iam_policy = object&.iam_policy -// -// @iam_resource_count += 3 unless iam_policy.nil? || iam_policy.exclude -// -// unless iam_policy.nil? || iam_policy.exclude || -// (iam_policy.min_version && iam_policy.min_version < version) -// iam_class_name = "#{service}.#{product_definition.name}#{object.name}" -// end -// -// @resources_for_version << { terraform_name:, resource_name:, iam_class_name: } -// end -// end +// # The async object used for making operations. +// # We assume that all resources share the same async properties. +// attr_accessor :async // -// @resources_for_version = @resources_for_version.compact -// end +// # When generating OiCS examples, we attach the example we're +// # generating to the data object. +// attr_accessor :example // -// # TODO(nelsonjr): Review all object interfaces and move to private methods -// # that should not be exposed outside the object hierarchy. -// private +// attr_accessor :resource_name // -// def provider_name -// self.class.name.split('::').last.downcase -// end +// end // -// # Adapted from the method used in templating -// # See: mmv1/compile/core.rb -// def comment_block(text, lang) -// case lang -// when :ruby, :python, :yaml, :git, :gemfile -// header = text.map { |t| t&.empty? ? '#' : "# #{t}" } -// when :go -// header = text.map { |t| t&.empty? ? '//' : "// #{t}" } -// else -// raise "Unknown language for comment: #{lang}" -// end +// # Sorts properties in the order they should appear in the TF schema: +// # Required, Optional, Computed +// def order_properties(properties) // -// header_string = header.join("\n") -// "#{header_string}\n" # add trailing newline to returned value -// end +// properties.select(&:required).sort_by(&:name) + +// properties.reject(&:required).reject(&:output).sort_by(&:name) + +// properties.select(&:output).sort_by(&:name) // -// def language_from_filename(filename) -// extension = filename.split('.')[-1] -// case extension -// when 'go' -// :go -// when 'rb' -// :ruby -// when 'yaml', 'yml' -// :yaml -// else -// :unsupported -// end -// end +// end // +// def tf_type(property) +// +// tf_types[property.class] +// +// end +// +// # "Namespace" - prefix with product and resource - a property with +// # information from the "object" variable +// def namespace_property_from_object(property, object) +// +// name = property.name.camelize +// until property.parent.nil? +// property = property.parent +// name = property.name.camelize + name +// end +// +// "#{property.__resource.__product.api_name.camelize(:lower)}#{object.name}#{name}" +// +// end +// +// # Converts between the Magic Modules type of an object and its type in the +// # TF schema +// def tf_types +// +// { +// Api::Type::Boolean => 'schema.TypeBool', +// Api::Type::Double => 'schema.TypeFloat', +// Api::Type::Integer => 'schema.TypeInt', +// Api::Type::String => 'schema.TypeString', +// # Anonymous string property used in array of strings. +// 'Api::Type::String' => 'schema.TypeString', +// Api::Type::Time => 'schema.TypeString', +// Api::Type::Enum => 'schema.TypeString', +// Api::Type::ResourceRef => 'schema.TypeString', +// Api::Type::NestedObject => 'schema.TypeList', +// Api::Type::Array => 'schema.TypeList', +// Api::Type::KeyValuePairs => 'schema.TypeMap', +// Api::Type::KeyValueLabels => 'schema.TypeMap', +// Api::Type::KeyValueTerraformLabels => 'schema.TypeMap', +// Api::Type::KeyValueEffectiveLabels => 'schema.TypeMap', +// Api::Type::KeyValueAnnotations => 'schema.TypeMap', +// Api::Type::Map => 'schema.TypeSet', +// Api::Type::Fingerprint => 'schema.TypeString' +// } +// +// end +// +// def updatable?(resource, properties) +// +// !resource.immutable || !properties.reject { |p| p.update_url.nil? }.empty? +// +// end +// +// def force_new?(property, resource) +// +// ( +// (!property.output || property.is_a?(Api::Type::KeyValueEffectiveLabels)) && +// (property.immutable || +// (resource.immutable && property.update_url.nil? && property.immutable.nil? && +// (property.parent.nil? || +// (force_new?(property.parent, resource) && +// !(property.parent.flatten_object && property.is_a?(Api::Type::KeyValueLabels)) +// ) +// ) +// ) +// ) +// ) || +// (property.is_a?(Api::Type::KeyValueTerraformLabels) && +// !updatable?(resource, resource.all_user_properties) && !resource.root_labels? +// ) +// +// end +// +// # Returns tuples of (fieldName, list of update masks) for +// # top-level updatable fields. Schema path refers to a given Terraform +// # field name (e.g. d.GetChange('fieldName)') +// def get_property_update_masks_groups(properties, mask_prefix: ”) +// +// mask_groups = [] +// properties.each do |prop| +// if prop.flatten_object +// mask_groups += get_property_update_masks_groups( +// prop.properties, mask_prefix: "#{prop.api_name}." +// ) +// elsif prop.update_mask_fields +// mask_groups << [prop.name.underscore, prop.update_mask_fields] +// else +// mask_groups << [prop.name.underscore, [mask_prefix + prop.api_name]] +// end +// end +// mask_groups +// +// end +// +// # Returns an updated path for a given Terraform field path (e.g. +// # 'a_field', 'parent_field.0.child_name'). Returns nil if the property +// # is not included in the resource's properties and removes keys that have +// # been flattened +// # FYI: Fields that have been renamed should use the new name, however, flattened +// # fields still need to be included, ie: +// # flattenedField > newParent > renameMe should be passed to this function as +// # flattened_field.0.new_parent.0.im_renamed +// # TODO(emilymye): Change format of input for +// # exactly_one_of/at_least_one_of/etc to use camelcase, MM properities and +// # convert to snake in this method +// def get_property_schema_path(schema_path, resource) +// +// nested_props = resource.properties +// prop = nil +// path_tkns = schema_path.split('.0.').map do |pname| +// camel_pname = pname.camelize(:lower) +// prop = nested_props.find { |p| p.name == camel_pname } +// # if we couldn't find it, see if it was renamed at the top level +// prop = nested_props.find { |p| p.name == schema_path } if prop.nil? +// return nil if prop.nil? +// +// nested_props = prop.nested_properties || [] +// prop.flatten_object ? nil : pname.underscore +// end +// if path_tkns.empty? || path_tkns[-1].nil? +// nil +// else +// path_tkns.compact.join('.0.') +// end +// +// end +// +// # Transforms a format string with field markers to a regex string with +// # capture groups. +// # +// # For instance, +// # projects/{{project}}/global/networks/{{name}} +// # is transformed to +// # projects/(?P[^/]+)/global/networks/(?P[^/]+) +// # +// # Values marked with % are URL-encoded, and will match any number of /'s. +// # +// # Note: ?P indicates a Python-compatible named capture group. Named groups +// # aren't common in JS-based regex flavours, but are in Perl-based ones +// def format2regex(format) +// +// format +// .gsub(/\{\{%([[:word:]]+)\}\}/, '(?P<\1>.+)') +// .gsub(/\{\{([[:word:]]+)\}\}/, '(?P<\1>[^/]+)') +// +// end +// +// # Capitalize the first letter of a property name. +// # E.g. "creationTimestamp" becomes "CreationTimestamp". +// def titlelize_property(property) +// +// property.name.camelize(:upper) +// +// end +// +// # 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 +// def generate_resources_for_version(products, version) +func (t *Terraform) generateResourcesForVersion(products []map[string]interface{}) { + // products.each do |product| + for _, product := range products { + productDefinition := product["Definitions"].(*api.Product) + 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.ExcludeResource { + 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 !(iamPolicy.MinVersion != "" && iamPolicy.MinVersion < t.TargetVersionName) { + iamClassName = fmt.Sprintf("%s.Resource%s", service, object.ResourceName()) + } + } + + t.ResourcesForVersion = append(t.ResourcesForVersion, map[string]string{ + "TerraformName": object.TerraformName(), + "ResourceName": resourceName, + "IamClassName": iamClassName, + }) + } + } + + // @resources_for_version = @resources_for_version.compact +} + +// # TODO(nelsonjr): Review all object interfaces and move to private methods +// # that should not be exposed outside the object hierarchy. +// def provider_name +func (t Terraform) providerName() string { + return reflect.TypeOf(t).Name() +} + +// # Adapted from the method used in templating +// # See: mmv1/compile/core.rb +// def comment_block(text, lang) +func commentBlock(text []string, lang string) string { + var headers []string + switch lang { + case "ruby", "python", "yaml", "gemfile": + 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 +} + +// def language_from_filename(filename) +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" + } +} + // # Finds the folder name for a given version of the terraform provider // def folder_name(version) // version == 'ga' ? 'google' : "google-#{version}" diff --git a/mmv1/third_party/terraform/.copywrite.hcl.tmpl b/mmv1/third_party/terraform/.copywrite.hcl.tmpl new file mode 100644 index 000000000000..693bde6062ee --- /dev/null +++ b/mmv1/third_party/terraform/.copywrite.hcl.tmpl @@ -0,0 +1,33 @@ +schema_version = 1 + +project { + license = "MPL-2.0" + copyright_year = 2017 + + # (OPTIONAL) A list of globs that should not have copyright/license headers. + # Supports doublestar glob patterns for more flexibility in defining which + # files or folders should be ignored + header_ignore = [ + # Some ignores here are not strictly needed, but protects us if we change the types of files we put in those folders + # See here for file extensions altered by copywrite CLI (all other extensions are ignored) + # https://github.com/hashicorp/copywrite/blob/4af928579f5aa8f1dece9de1bb3098218903053d/addlicense/main.go#L357-L394 + ".github/**", + ".release/**", + ".changelog/**", + "examples/**", + "scripts/**", +{{- if or (eq $.TargetVersionName "") (eq $.TargetVersionName "ga")}} + "google/**/test-fixtures/**", +{{- else}} + "google-{{$.TargetVersionName}}/**/test-fixtures/**", +{{- end}} + "META.d/*.yml", + "META.d/*.yaml", + ".golangci.yml", + ".goreleaser.yml", + ] + + # (OPTIONAL) Links to an upstream repo for determining repo relationships + # This is for special cases and should not normally be set. + upstream = "GoogleCloudPlatform/magic-modules" +} diff --git a/mmv1/third_party/terraform/.goreleaser.yml.tmpl b/mmv1/third_party/terraform/.goreleaser.yml.tmpl new file mode 100644 index 000000000000..33eb62131c19 --- /dev/null +++ b/mmv1/third_party/terraform/.goreleaser.yml.tmpl @@ -0,0 +1,83 @@ +archives: + - files: + # Include built binary and license files in archive + - 'LICENSE' + format: zip + name_template: '{{"{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"}}' +builds: + - # Special binary naming is only necessary for Terraform CLI 0.12 + binary: '{{"{{ .ProjectName }}_v{{ .Version }}"}}_x5' + env: + - CGO_ENABLED=0 + flags: + - -trimpath + goos: + - darwin + - freebsd + - linux + - windows + goarch: + - '386' + - amd64 + - arm + - arm64 + ignore: + - goarch: arm + goos: windows + - goarch: arm64 + goos: freebsd + - goarch: arm64 + goos: windows + ldflags: + - -s -w -X github.com/hashicorp/terraform-provider-google{{- if ne $.TargetVersionName "ga" -}}-{{$.TargetVersionName}}{{- end }}/version.ProviderVersion={{"{{.Version}}"}} + mod_timestamp: '{{"{{ .CommitTimestamp }}"}}' +checksum: + extra_files: + - glob: 'terraform-registry-manifest.json' + name_template: '{{"{{ .ProjectName }}_{{ .Version }}"}}_manifest.json' + name_template: '{{"{{ .ProjectName }}_{{ .Version }}"}}_SHA256SUMS' + algorithm: sha256 +publishers: + - name: upload + checksum: true + extra_files: + - glob: 'terraform-registry-manifest.json' + name_template: '{{"{{ .ProjectName }}_{{ .Version }}"}}_manifest.json' + signature: true + cmd: hc-releases upload -product {{"{{ .ProjectName }} -version {{ .Version }} -file={{ .ArtifactPath }}={{ .ArtifactName }}"}} -header="x-terraform-protocol-version=5.0" -header="x-terraform-protocol-versions=5.0" + env: + - HC_RELEASES_HOST={{"{{ .Env.HC_RELEASES_HOST }}"}} + - HC_RELEASES_KEY={{"{{ .Env.HC_RELEASES_KEY }}"}} +release: + extra_files: + - glob: 'terraform-registry-manifest.json' + name_template: '{{"{{ .ProjectName }}_{{ .Version }}"}}_manifest.json' + ids: + - none +signs: + # Default Signature file (i.e. terraform-provider-awscc_VERSION_SHA256SUMS.sig) + - cmd: sh + args: + - -c + - >- + signore + sign + --dearmor + --file ${artifact} + --out ${signature} + artifacts: checksum + # Signature file with GPG Public Key ID in filename (i.e. terraform-provider-awscc_VERSION_SHA256SUMS.7685B676.sig) + - id: sig-with-gpg-public-key-id + signature: ${artifact}.72D7468F.sig + cmd: sh + args: + - -c + - >- + signore + sign + --dearmor + --file ${artifact} + --out ${signature} + artifacts: checksum +snapshot: + name_template: "{{"{{ .Tag }}"}}-next" diff --git a/mmv1/third_party/terraform/go.mod b/mmv1/third_party/terraform/go.mod new file mode 100644 index 000000000000..89f79e3d7263 --- /dev/null +++ b/mmv1/third_party/terraform/go.mod @@ -0,0 +1,110 @@ +module github.com/hashicorp/terraform-provider-google + +go 1.21 + +require ( + cloud.google.com/go/bigtable v1.19.0 + github.com/GoogleCloudPlatform/declarative-resource-client-library v1.64.0 + github.com/apparentlymart/go-cidr v1.1.0 + github.com/davecgh/go-spew v1.1.1 + github.com/dnaeon/go-vcr v1.0.1 + github.com/gammazero/workerpool v0.0.0-20181230203049-86a96b5d5d92 + github.com/google/go-cmp v0.6.0 + github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 + github.com/hashicorp/errwrap v1.0.0 + github.com/hashicorp/go-cleanhttp v0.5.2 + github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 + github.com/hashicorp/go-multierror v1.1.1 + github.com/hashicorp/go-version v1.6.0 + github.com/hashicorp/terraform-plugin-framework v1.7.0 + github.com/hashicorp/terraform-plugin-framework-validators v0.9.0 + github.com/hashicorp/terraform-plugin-go v0.22.1 + github.com/hashicorp/terraform-plugin-log v0.9.0 + github.com/hashicorp/terraform-plugin-mux v0.15.0 + github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0 + github.com/mitchellh/go-homedir v1.1.0 + github.com/mitchellh/hashstructure v1.1.0 + github.com/sirupsen/logrus v1.8.1 + golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 + golang.org/x/net v0.22.0 + golang.org/x/oauth2 v0.18.0 + google.golang.org/api v0.171.0 + google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c + google.golang.org/grpc v1.62.1 + google.golang.org/protobuf v1.33.0 +) + +require ( + bitbucket.org/creachadair/stringset v0.0.8 // indirect + cloud.google.com/go v0.112.0 // indirect + cloud.google.com/go/compute v1.23.4 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/iam v1.1.6 // indirect + cloud.google.com/go/longrunning v0.5.5 // indirect + github.com/ProtonMail/go-crypto v1.1.0-alpha.0 // indirect + github.com/agext/levenshtein v1.2.2 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/cenkalti/backoff v2.2.1+incompatible // indirect + github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cloudflare/circl v1.3.7 // indirect + github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe // indirect + github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa // indirect + github.com/envoyproxy/go-control-plane v0.12.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect + github.com/fatih/color v1.16.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/gammazero/deque v0.0.0-20180920172122-f6adf94963e4 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/glog v1.2.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/go-cpy v0.0.0-20211218193943-a9c933c06932 // indirect + github.com/google/s2a-go v0.1.7 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect + github.com/googleapis/gax-go/v2 v2.12.3 // indirect + github.com/hashicorp/go-checkpoint v0.5.0 // indirect + github.com/hashicorp/go-hclog v1.5.0 // indirect + github.com/hashicorp/go-plugin v1.6.0 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/hashicorp/hc-install v0.6.3 // indirect + github.com/hashicorp/hcl/v2 v2.19.1 // indirect + github.com/hashicorp/logutils v1.0.0 // indirect + github.com/hashicorp/terraform-exec v0.20.0 // indirect + github.com/hashicorp/terraform-json v0.21.0 // indirect + github.com/hashicorp/terraform-registry-address v0.2.3 // indirect + github.com/hashicorp/terraform-svchost v0.1.1 // indirect + github.com/hashicorp/yamux v0.1.1 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mitchellh/go-wordwrap v1.0.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/oklog/run v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/zclconf/go-cty v1.14.2 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto v0.0.0-20240205150955-31a09d347014 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/mmv1/third_party/terraform/main.go.tmpl b/mmv1/third_party/terraform/main.go.tmpl new file mode 100644 index 000000000000..70e1fa734bc3 --- /dev/null +++ b/mmv1/third_party/terraform/main.go.tmpl @@ -0,0 +1,60 @@ +package main + +import ( + "context" + "flag" + "log" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-go/tfprotov5/tf5server" + "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" + + "github.com/hashicorp/terraform-provider-google/google/fwprovider" + "github.com/hashicorp/terraform-provider-google/google/provider" + ver "github.com/hashicorp/terraform-provider-google/version" +) + +var ( + // these will be set by the goreleaser configuration + // to appropriate values for the compiled binary + version string = ver.ProviderVersion + + // goreleaser can also pass the specific commit if you want + // commit string = "" +) + +func main() { + var debug bool + + flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") + flag.Parse() + + // concat with sdkv2 provider + providers := []func() tfprotov5.ProviderServer{ + providerserver.NewProtocol5(fwprovider.New(version)), // framework provider + provider.Provider().GRPCProvider, // sdk provider + } + + // use the muxer + muxServer, err := tf5muxserver.NewMuxServer(context.Background(), providers...) + if err != nil { + log.Fatalf(err.Error()) + } + + var serveOpts []tf5server.ServeOpt + + if debug { + serveOpts = append(serveOpts, tf5server.WithManagedDebug()) + } + + err = tf5server.Serve( + "registry.terraform.io/hashicorp/google{{- if ne $.TargetVersionName "ga" -}}-{{$.TargetVersionName}}{{- end }}", + muxServer.ProviderServer, + serveOpts..., + ) + + if err != nil { + log.Fatal(err) + } +} diff --git a/mmv1/third_party/terraform/release-metadata.hcl.tmpl b/mmv1/third_party/terraform/release-metadata.hcl.tmpl new file mode 100644 index 000000000000..ca8618b3be33 --- /dev/null +++ b/mmv1/third_party/terraform/release-metadata.hcl.tmpl @@ -0,0 +1,2 @@ +url_source_repository = "https://github.com/hashicorp/terraform-provider-google{{- if ne $.TargetVersionName "ga" -}}-{{$.TargetVersionName}}{{- end }}" +url_license = "https://github.com/hashicorp/terraform-provider-google{{- if ne $.TargetVersionName "ga" -}}-{{$.TargetVersionName}}{{- end }}/blob/main/LICENSE" diff --git a/mmv1/third_party/terraform/services/containeranalysis/resource_container_analysis_occurrence_test.go b/mmv1/third_party/terraform/services/containeranalysis/resource_container_analysis_occurrence_test.go index 1e80394cc56d..d0f73e97df6e 100644 --- a/mmv1/third_party/terraform/services/containeranalysis/resource_container_analysis_occurrence_test.go +++ b/mmv1/third_party/terraform/services/containeranalysis/resource_container_analysis_occurrence_test.go @@ -18,7 +18,7 @@ import ( const testAttestationOccurrenceImageUrl = "gcr.io/cloud-marketplace/google/ubuntu1804" const testAttestationOccurrenceImageDigest = "sha256:3593cd4ac7d782d460dc86ba9870a3beaf81c8f5cdbcc8880bf9a5ef6af10c5a" -const testAttestationOccurrencePayloadTemplate = "test-fixtures/generated_payload.json.tmpl" +const testAttestationOccurrencePayloadTemplate = "test-fixtures/generated_payload.json" var testAttestationOccurrenceFullImagePath = fmt.Sprintf("%s@%s", testAttestationOccurrenceImageUrl, testAttestationOccurrenceImageDigest) diff --git a/mmv1/third_party/terraform/services/containeranalysis/test-fixtures/generated_payload.json.tmpl b/mmv1/third_party/terraform/services/containeranalysis/test-fixtures/generated_payload.json similarity index 100% rename from mmv1/third_party/terraform/services/containeranalysis/test-fixtures/generated_payload.json.tmpl rename to mmv1/third_party/terraform/services/containeranalysis/test-fixtures/generated_payload.json diff --git a/mmv1/third_party/terraform/services/deploymentmanager/resource_deployment_manager_deployment_test.go b/mmv1/third_party/terraform/services/deploymentmanager/resource_deployment_manager_deployment_test.go index e7092236e149..46186eb77978 100644 --- a/mmv1/third_party/terraform/services/deploymentmanager/resource_deployment_manager_deployment_test.go +++ b/mmv1/third_party/terraform/services/deploymentmanager/resource_deployment_manager_deployment_test.go @@ -22,7 +22,7 @@ func TestAccDeploymentManagerDeployment_basicFile(t *testing.T) { randSuffix := acctest.RandString(t, 10) deploymentId := "tf-dm-" + randSuffix accountId := "tf-dm-account-" + randSuffix - yamlPath := createYamlConfigFileForTest(t, "test-fixtures/service_account.yml.tmpl", map[string]interface{}{ + yamlPath := createYamlConfigFileForTest(t, "test-fixtures/service_account.yml", map[string]interface{}{ "account_id": accountId, }) @@ -97,7 +97,7 @@ func TestAccDeploymentManagerDeployment_imports(t *testing.T) { randStr := acctest.RandString(t, 10) deploymentName := "tf-dm-" + randStr accountId := "tf-dm-" + randStr - importFilepath := createYamlConfigFileForTest(t, "test-fixtures/service_account.yml.tmpl", map[string]interface{}{ + importFilepath := createYamlConfigFileForTest(t, "test-fixtures/service_account.yml", map[string]interface{}{ "account_id": "{{ env['name'] }}", }) diff --git a/mmv1/third_party/terraform/services/deploymentmanager/test-fixtures/service_account.yml.tmpl b/mmv1/third_party/terraform/services/deploymentmanager/test-fixtures/service_account.yml similarity index 100% rename from mmv1/third_party/terraform/services/deploymentmanager/test-fixtures/service_account.yml.tmpl rename to mmv1/third_party/terraform/services/deploymentmanager/test-fixtures/service_account.yml diff --git a/mmv1/third_party/terraform/terraform-registry-manifest.json b/mmv1/third_party/terraform/terraform-registry-manifest.json new file mode 100644 index 000000000000..1931b0e00217 --- /dev/null +++ b/mmv1/third_party/terraform/terraform-registry-manifest.json @@ -0,0 +1,6 @@ +{ + "version": 1, + "metadata": { + "protocol_versions": ["5.0"] + } +} diff --git a/mmv1/third_party/terraform/utils/provider_versionfive_upgrade_test.go.erb b/mmv1/third_party/terraform/utils/provider_versionfive_upgrade_test.go.erb deleted file mode 100644 index 161ec6ccf34d..000000000000 --- a/mmv1/third_party/terraform/utils/provider_versionfive_upgrade_test.go.erb +++ /dev/null @@ -1,646 +0,0 @@ -<% autogen_exception -%> -package google - -import ( - "fmt" - "strings" - "testing" - - "github.com/hashicorp/terraform-provider-google/google/acctest" - "github.com/hashicorp/terraform-provider-google/google/envvar" - "github.com/hashicorp/terraform-provider-google/google/tpgresource" - transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" -) - -func TestProvider_versionfive_upgrade(t *testing.T) { - acctest.SkipIfVcr(t) - t.Parallel() - - org := envvar.GetTestOrgFromEnv(t) - billingId := envvar.GetTestBillingAccountFromEnv(t) - project := fmt.Sprintf("tf-test-%d", acctest.RandInt(t)) - - name1 := fmt.Sprintf("tf-test-%d", acctest.RandInt(t)) - name2 := fmt.Sprintf("tf-test-%d", acctest.RandInt(t)) - name3 := fmt.Sprintf("tf-test-%d", acctest.RandInt(t)) - name4 := fmt.Sprintf("tf-test-%d", acctest.RandInt(t)) - - acctest.VcrTest(t, resource.TestCase{ - PreCheck: func() { acctest.AccTestPreCheck(t) }, - Steps: []resource.TestStep{ - { - ExternalProviders: map[string]resource.ExternalProvider{ - "google": { - VersionConstraint: "4.80.0", - Source: "hashicorp/google-beta", - }, - }, - Config: testProvider_versionfive_upgrades(project, org, billingId, name1, name2, name3, name4), - }, - { - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), - Config: testProvider_versionfive_upgrades(project, org, billingId, name1, name2, name3, name4), - PlanOnly: true, - }, - { - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), - ResourceName: "google_data_fusion_instance.unset", - ImportState: true, - ImportStateVerify: true, - }, - { - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), - ResourceName: "google_data_fusion_instance.set", - ImportState: true, - ImportStateVerify: true, - }, - { - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), - ResourceName: "google_data_fusion_instance.reference", - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func TestProvider_versionfive_ignorereads_upgrade(t *testing.T) { - acctest.SkipIfVcr(t) - t.Parallel() - - var itName = fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) - var tpName = fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) - var igmName = fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) - var autoscalerName = fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) - - diskName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) - policyName := fmt.Sprintf("tf-test-policy-%s", acctest.RandString(t, 10)) - - endpointContext := map[string]interface{}{ - "random_suffix": acctest.RandString(t, 10), - } - - var itNameRegion = fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) - var tpNameRegion = fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) - var igmNameRegion = fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) - var autoscalerNameRegion = fmt.Sprintf("tf-test-region-autoscaler-%s", acctest.RandString(t, 10)) - - policyContext := map[string]interface{}{ - "random_suffix": acctest.RandString(t, 10), - } - - attachmentContext := map[string]interface{}{ - "random_suffix": acctest.RandString(t, 10), - } - - acctest.VcrTest(t, resource.TestCase{ - PreCheck: func() { acctest.AccTestPreCheck(t) }, - CheckDestroy: resource.ComposeTestCheckFunc(testAccCheckComputeResourcePolicyDestroyProducer(t), - testAccCheckComputeRegionAutoscalerDestroyProducer(t), - testAccCheckComputeNetworkEndpointGroupDestroyProducer(t), - testAccCheckComputeAutoscalerDestroyProducer(t), - ), - Steps: []resource.TestStep{ - { - ExternalProviders: map[string]resource.ExternalProvider{ - "google": { - VersionConstraint: "4.80.0", - Source: "hashicorp/google-beta", - }, - }, - Config: testProvider_versionfive_upgrades_ignorereads(itName, tpName, igmName, autoscalerName, diskName, policyName, - itNameRegion, tpNameRegion, igmNameRegion, autoscalerNameRegion, endpointContext, - policyContext, attachmentContext), - }, - { - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), - Config: testProvider_versionfive_upgrades_ignorereads(itName, tpName, igmName, autoscalerName, diskName, policyName, - itNameRegion, tpNameRegion, igmNameRegion, autoscalerNameRegion, endpointContext, - policyContext, attachmentContext), - PlanOnly: true, - }, - }, - }) -} - -func testProvider_versionfive_upgrades(project, org, billing, name1, name2, name3, name4 string) string { - return fmt.Sprintf(` -resource "google_project" "host" { - project_id = "%s" - name = "%s" - org_id = "%s" - billing_account = "%s" -} - -resource "google_project_service" "dfapi" { - project = google_project.host.project_id - service = "datafusion.googleapis.com" - - disable_dependent_services = false -} - -resource "google_data_fusion_instance" "unset" { - name = "%s" - type = "BASIC" - options = { - prober_test_run = "true" - } -} - -resource "google_data_fusion_instance" "set" { - name = "%s" - region = "us-west1" - type = "BASIC" - options = { - prober_test_run = "true" - } -} - -resource "google_data_fusion_instance" "reference" { - project = google_project.host.project_id - name = "%s" - type = "DEVELOPER" - options = { - prober_test_run = "true" - } - zone = "us-west1-a" - depends_on = [ - google_project_service.dfapi - ] -} - -resource "google_redis_instance" "overridewithnonstandardlogic" { - name = "%s" - memory_size_gb = 1 - location_id = "us-south1-a" -} - - -`, project, project, org, billing, name1, name2, name3, name4) -} - -func testProvider_versionfive_upgrades_ignorereads(itName, tpName, igmName, autoscalerName, diskName, policyName, itNameRegion, tpNameRegion, igmNameRegion, autoscalerNameRegion string, endpointContext, policyContext, attachmentContext map[string]interface{}) string { - return testAccComputeAutoscaler_basic(itName, tpName, igmName, autoscalerName) + - testAccComputeDiskResourcePolicyAttachment_basic(diskName, policyName) + - testAccComputeNetworkEndpointGroup_networkEndpointGroup(endpointContext) + - testAccComputeRegionAutoscaler_basic(itNameRegion, tpNameRegion, igmNameRegion, autoscalerNameRegion) + - testAccComputeResourcePolicy_resourcePolicyBasicExample(policyContext) + - testAccComputeServiceAttachment_serviceAttachmentBasicExample(attachmentContext) -} - -// need to make copies of all the respective resource functions within here as *_test.go files can not be imported -// checkdestroys -func testAccCheckComputeResourcePolicyDestroyProducer(t *testing.T) func(s *terraform.State) error { - return func(s *terraform.State) error { - for name, rs := range s.RootModule().Resources { - if rs.Type != "google_compute_resource_policy" { - continue - } - if strings.HasPrefix(name, "data.") { - continue - } - - config := acctest.GoogleProviderConfig(t) - - url, err := tpgresource.ReplaceVarsForTest(config, rs, "{{ComputeBasePath}}projects/{{project}}/regions/{{region}}/resourcePolicies/{{name}}") - if err != nil { - return err - } - - billingProject := "" - - if config.BillingProject != "" { - billingProject = config.BillingProject - } - - _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ - Config: config, - Method: "GET", - Project: billingProject, - RawURL: url, - UserAgent: config.UserAgent, - }) - if err == nil { - return fmt.Errorf("ComputeResourcePolicy still exists at %s", url) - } - } - - return nil - } -} - -func testAccCheckComputeRegionAutoscalerDestroyProducer(t *testing.T) func(s *terraform.State) error { - return func(s *terraform.State) error { - for name, rs := range s.RootModule().Resources { - if rs.Type != "google_compute_region_autoscaler" { - continue - } - if strings.HasPrefix(name, "data.") { - continue - } - - config := acctest.GoogleProviderConfig(t) - - url, err := tpgresource.ReplaceVarsForTest(config, rs, "{{ComputeBasePath}}projects/{{project}}/regions/{{region}}/autoscalers/{{name}}") - if err != nil { - return err - } - - billingProject := "" - - if config.BillingProject != "" { - billingProject = config.BillingProject - } - - _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ - Config: config, - Method: "GET", - Project: billingProject, - RawURL: url, - UserAgent: config.UserAgent, - }) - if err == nil { - return fmt.Errorf("ComputeRegionAutoscaler still exists at %s", url) - } - } - - return nil - } -} - -func testAccCheckComputeNetworkEndpointGroupDestroyProducer(t *testing.T) func(s *terraform.State) error { - return func(s *terraform.State) error { - for name, rs := range s.RootModule().Resources { - if rs.Type != "google_compute_network_endpoint_group" { - continue - } - if strings.HasPrefix(name, "data.") { - continue - } - - config := acctest.GoogleProviderConfig(t) - - url, err := tpgresource.ReplaceVarsForTest(config, rs, "{{ComputeBasePath}}projects/{{project}}/zones/{{zone}}/networkEndpointGroups/{{name}}") - if err != nil { - return err - } - - billingProject := "" - - if config.BillingProject != "" { - billingProject = config.BillingProject - } - - _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ - Config: config, - Method: "GET", - Project: billingProject, - RawURL: url, - UserAgent: config.UserAgent, - }) - if err == nil { - return fmt.Errorf("ComputeNetworkEndpointGroup still exists at %s", url) - } - } - - return nil - } -} - -func testAccCheckComputeAutoscalerDestroyProducer(t *testing.T) func(s *terraform.State) error { - return func(s *terraform.State) error { - for name, rs := range s.RootModule().Resources { - if rs.Type != "google_compute_autoscaler" { - continue - } - if strings.HasPrefix(name, "data.") { - continue - } - - config := acctest.GoogleProviderConfig(t) - - url, err := tpgresource.ReplaceVarsForTest(config, rs, "{{ComputeBasePath}}projects/{{project}}/zones/{{zone}}/autoscalers/{{name}}") - if err != nil { - return err - } - - billingProject := "" - - if config.BillingProject != "" { - billingProject = config.BillingProject - } - - _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ - Config: config, - Method: "GET", - Project: billingProject, - RawURL: url, - UserAgent: config.UserAgent, - }) - if err == nil { - return fmt.Errorf("ComputeAutoscaler still exists at %s", url) - } - } - - return nil - } -} - -// tests -func testAccComputeAutoscaler_scaffolding(itName, tpName, igmName string) string { - return fmt.Sprintf(` -data "google_compute_image" "my_image1" { - family = "debian-11" - project = "debian-cloud" -} - -resource "google_compute_instance_template" "foobar1" { - name = "%s" - machine_type = "e2-medium" - can_ip_forward = false - tags = ["foo", "bar"] - - disk { - source_image = data.google_compute_image.my_image1.self_link - auto_delete = true - boot = true - } - - network_interface { - network = "default" - } - - service_account { - scopes = ["userinfo-email", "compute-ro", "storage-ro"] - } -} - -resource "google_compute_target_pool" "foobar1" { - description = "Resource created for Terraform acceptance testing" - name = "%s" - session_affinity = "CLIENT_IP_PROTO" - region = "us-west1" -} - -resource "google_compute_instance_group_manager" "foobar1" { - description = "Terraform test instance group manager" - name = "%s" - version { - instance_template = google_compute_instance_template.foobar1.self_link - name = "primary" - } - target_pools = [google_compute_target_pool.foobar1.self_link] - base_instance_name = "foobar1" - zone = "us-west1-a" -} -`, itName, tpName, igmName) - -} - -func testAccComputeAutoscaler_basic(itName, tpName, igmName, autoscalerName string) string { - return testAccComputeAutoscaler_scaffolding(itName, tpName, igmName) + fmt.Sprintf(` -resource "google_compute_autoscaler" "foobar1" { - description = "Resource created for Terraform acceptance testing" - name = "%s" - zone = "us-west1-a" - target = google_compute_instance_group_manager.foobar1.self_link - autoscaling_policy { - max_replicas = 5 - min_replicas = 1 - cooldown_period = 60 - cpu_utilization { - target = 0.5 - } - } -} -`, autoscalerName) -} - -func testAccComputeDiskResourcePolicyAttachment_basic(diskName, policyName string) string { - return fmt.Sprintf(` -data "google_compute_image" "my_image2" { - family = "debian-11" - project = "debian-cloud" -} - -resource "google_compute_disk" "foobar2" { - name = "%s" - image = data.google_compute_image.my_image2.self_link - size = 1000 - type = "pd-extreme" - zone = "us-west1-c" - labels = { - my-label = "my-label-value" - } - provisioned_iops = 90000 -} - -resource "google_compute_resource_policy" "foobar2" { - name = "%s" - region = "us-west1" - snapshot_schedule_policy { - schedule { - daily_schedule { - days_in_cycle = 1 - start_time = "04:00" - } - } - } -} - -resource "google_compute_disk_resource_policy_attachment" "foobar2" { - name = google_compute_resource_policy.foobar2.name - disk = google_compute_disk.foobar2.name - zone = "us-west1-c" -} -`, diskName, policyName) -} - -func testAccComputeNetworkEndpointGroup_networkEndpointGroup(context map[string]interface{}) string { - return acctest.Nprintf(` -resource "google_compute_network_endpoint_group" "neg3" { - name = "tf-test-my-lb-neg%{random_suffix}" - network = google_compute_network.default3.id - default_port = "90" - zone = "us-west1-a" -} - -resource "google_compute_network" "default3" { - name = "tf-test-neg-network%{random_suffix}" - auto_create_subnetworks = true -} -`, context) -} - -func testAccComputeRegionAutoscaler_scaffolding(itName, tpName, igmName string) string { - return fmt.Sprintf(` -data "google_compute_image" "my_image4" { - family = "debian-11" - project = "debian-cloud" -} - -resource "google_compute_instance_template" "foobar4" { - name = "%s" - machine_type = "e2-medium" - can_ip_forward = false - tags = ["foo", "bar"] - - disk { - source_image = data.google_compute_image.my_image4.self_link - auto_delete = true - boot = true - } - - network_interface { - network = "default" - } - - service_account { - scopes = ["userinfo-email", "compute-ro", "storage-ro"] - } -} - -resource "google_compute_target_pool" "foobar4" { - description = "Resource created for Terraform acceptance testing" - name = "%s" - session_affinity = "CLIENT_IP_PROTO" - region = "us-west1" -} - -resource "google_compute_region_instance_group_manager" "foobar4" { - description = "Terraform test instance group manager" - name = "%s" - version { - instance_template = google_compute_instance_template.foobar4.self_link - name = "primary" - } - target_pools = [google_compute_target_pool.foobar4.self_link] - base_instance_name = "tf-test-foobar4" - region = "us-west1" -} - -`, itName, tpName, igmName) -} - -func testAccComputeRegionAutoscaler_basic(itName, tpName, igmName, autoscalerName string) string { - return testAccComputeRegionAutoscaler_scaffolding(itName, tpName, igmName) + fmt.Sprintf(` -resource "google_compute_region_autoscaler" "foobar4" { - description = "Resource created for Terraform acceptance testing" - name = "%s" - region = "us-west1" - target = google_compute_region_instance_group_manager.foobar4.self_link - autoscaling_policy { - max_replicas = 5 - min_replicas = 0 - cooldown_period = 60 - cpu_utilization { - target = 0.5 - } - } -} -`, autoscalerName) -} - -func testAccComputeResourcePolicy_resourcePolicyBasicExample(context map[string]interface{}) string { - return acctest.Nprintf(` -resource "google_compute_resource_policy" "foo5" { - name = "tf-test-gce-policy%{random_suffix}" - region = "us-west1" - snapshot_schedule_policy { - schedule { - daily_schedule { - days_in_cycle = 1 - start_time = "04:00" - } - } - } -} -`, context) -} - -func testAccComputeServiceAttachment_serviceAttachmentBasicExample(context map[string]interface{}) string { - return acctest.Nprintf(` -resource "google_compute_service_attachment" "psc_ilb_service_attachment6" { - name = "tf-test-my-psc-ilb%{random_suffix}" - region = "us-west2" - description = "A service attachment configured with Terraform" - - enable_proxy_protocol = true - connection_preference = "ACCEPT_AUTOMATIC" - nat_subnets = [google_compute_subnetwork.psc_ilb_nat6.id] - target_service = google_compute_forwarding_rule.psc_ilb_target_service6.id - reconcile_connections = true -} - -resource "google_compute_address" "psc_ilb_consumer_address6" { - name = "tf-test-psc-ilb-consumer-address%{random_suffix}" - region = "us-west2" - - subnetwork = "default" - address_type = "INTERNAL" -} - -resource "google_compute_forwarding_rule" "psc_ilb_consumer6" { - name = "tf-test-psc-ilb-consumer-forwarding-rule%{random_suffix}" - region = "us-west2" - - target = google_compute_service_attachment.psc_ilb_service_attachment6.id - load_balancing_scheme = "" # need to override EXTERNAL default when target is a service attachment - network = "default" - ip_address = google_compute_address.psc_ilb_consumer_address6.id -} - -resource "google_compute_forwarding_rule" "psc_ilb_target_service6" { - name = "tf-test-producer-forwarding-rule%{random_suffix}" - region = "us-west2" - - load_balancing_scheme = "INTERNAL" - backend_service = google_compute_region_backend_service.producer_service_backend6.id - all_ports = true - network = google_compute_network.psc_ilb_network6.name - subnetwork = google_compute_subnetwork.psc_ilb_producer_subnetwork6.name -} - -resource "google_compute_region_backend_service" "producer_service_backend6" { - name = "tf-test-producer-service%{random_suffix}" - region = "us-west2" - - health_checks = [google_compute_health_check.producer_service_health_check6.id] -} - -resource "google_compute_health_check" "producer_service_health_check6" { - name = "tf-test-producer-service-health-check%{random_suffix}" - - check_interval_sec = 1 - timeout_sec = 1 - tcp_health_check { - port = "80" - } -} - -resource "google_compute_network" "psc_ilb_network6" { - name = "tf-test-psc-ilb-network%{random_suffix}" - auto_create_subnetworks = false -} - -resource "google_compute_subnetwork" "psc_ilb_producer_subnetwork6" { - name = "tf-test-psc-ilb-producer-subnetwork%{random_suffix}" - region = "us-west2" - - network = google_compute_network.psc_ilb_network6.id - ip_cidr_range = "10.0.0.0/16" -} - -resource "google_compute_subnetwork" "psc_ilb_nat6" { - name = "tf-test-psc-ilb-nat%{random_suffix}" - region = "us-west2" - - network = google_compute_network.psc_ilb_network6.id - purpose = "PRIVATE_SERVICE_CONNECT" - ip_cidr_range = "10.1.0.0/16" -} -`, context) -} diff --git a/mmv1/third_party/terraform/website/docs/r/compute_firewall_policy_rule.html.markdown b/mmv1/third_party/terraform/website/docs/r/compute_firewall_policy_rule.html.markdown deleted file mode 100644 index 741feff67645..000000000000 --- a/mmv1/third_party/terraform/website/docs/r/compute_firewall_policy_rule.html.markdown +++ /dev/null @@ -1,207 +0,0 @@ ---- -# ---------------------------------------------------------------------------- -# -# *** AUTO GENERATED CODE *** Type: DCL *** -# -# ---------------------------------------------------------------------------- -# -# This file is managed by Magic Modules (https:#github.com/GoogleCloudPlatform/magic-modules) -# and is based on the DCL (https:#github.com/GoogleCloudPlatform/declarative-resource-client-library). -# Changes will need to be made to the DCL or Magic Modules instead of here. -# -# We are not currently able to accept contributions to this file. If changes -# are required, please file an issue at https:#github.com/hashicorp/terraform-provider-google/issues/new/choose -# -# ---------------------------------------------------------------------------- -subcategory: "Compute Engine" -description: |- - Specific rules to add to a hierarchical firewall policy ---- - -# google\_compute\_firewall\_policy\_rule - -Hierarchical firewall policy rules let you create and enforce a consistent firewall policy across your organization. Rules can explicitly allow or deny connections or delegate evaluation to lower level policies. - -For more information see the [official documentation](https://cloud.google.com/vpc/docs/using-firewall-policies#create-rules) - -## Example Usage - -```hcl -resource "google_network_security_address_group" "basic_global_networksecurity_address_group" { - provider = google-beta - - name = "policy" - parent = "organizations/12345" - description = "Sample global networksecurity_address_group" - location = "global" - items = ["208.80.154.224/32"] - type = "IPV4" - capacity = 100 -} - -resource "google_compute_firewall_policy" "default" { - parent = "organizations/12345" - short_name = "my-policy" - description = "Example Resource" -} - -resource "google_compute_firewall_policy_rule" "default" { - firewall_policy = google_compute_firewall_policy.default.id - description = "Example Resource" - priority = 9000 - enable_logging = true - action = "allow" - direction = "EGRESS" - disabled = false - match { - layer4_configs { - ip_protocol = "tcp" - ports = [80, 8080] - } - dest_ip_ranges = ["11.100.0.1/32"] - dest_fqdns = ["google.com"] - dest_region_codes = ["US"] - dest_threat_intelligences = ["iplist-public-clouds"] - dest_address_groups = [google_network_security_address_group.basic_global_networksecurity_address_group.id] - } -} -``` - -## Argument Reference - -The following arguments are supported: - -* `action` - - (Required) - The Action to perform when the client connection triggers the rule. Can currently be either "allow" or "deny()" where valid values for status are 403, 404, and 502. - -* `direction` - - (Required) - The direction in which this rule applies. Possible values: INGRESS, EGRESS - -* `firewall_policy` - - (Required) - The firewall policy of the resource. - -* `match` - - (Required) - A match condition that incoming traffic is evaluated against. If it evaluates to true, the corresponding 'action' is enforced. Structure is [documented below](#nested_match). - -* `priority` - - (Required) - An integer indicating the priority of a rule in the list. The priority must be a positive value between 0 and 2147483647. Rules are evaluated from highest to lowest priority where 0 is the highest priority and 2147483647 is the lowest prority. - - - -The `match` block supports: - -* `dest_address_groups` - - (Optional) - Address groups which should be matched against the traffic destination. Maximum number of destination address groups is 10. Destination address groups is only supported in Egress rules. - -* `dest_fqdns` - - (Optional) - Domain names that will be used to match against the resolved domain name of destination of traffic. Can only be specified if DIRECTION is egress. - -* `dest_ip_ranges` - - (Optional) - CIDR IP address range. Maximum number of destination CIDR IP ranges allowed is 5000. - -* `dest_region_codes` - - (Optional) - The Unicode country codes whose IP addresses will be used to match against the source of traffic. Can only be specified if DIRECTION is egress. - -* `dest_threat_intelligences` - - (Optional) - Name of the Google Cloud Threat Intelligence list. - -* `layer4_configs` - - (Required) - Pairs of IP protocols and ports that the rule should match. Structure is [documented below](#nested_layer4_configs). - -* `src_address_groups` - - (Optional) - Address groups which should be matched against the traffic source. Maximum number of source address groups is 10. Source address groups is only supported in Ingress rules. - -* `src_fqdns` - - (Optional) - Domain names that will be used to match against the resolved domain name of source of traffic. Can only be specified if DIRECTION is ingress. - -* `src_ip_ranges` - - (Optional) - CIDR IP address range. Maximum number of source CIDR IP ranges allowed is 5000. - -* `src_region_codes` - - (Optional) - The Unicode country codes whose IP addresses will be used to match against the source of traffic. Can only be specified if DIRECTION is ingress. - -* `src_threat_intelligences` - - (Optional) - Name of the Google Cloud Threat Intelligence list. - -The `layer4_configs` block supports: - -* `ip_protocol` - - (Required) - The IP protocol to which this rule applies. The protocol type is required when creating a firewall rule. This value can either be one of the following well known protocol strings (`tcp`, `udp`, `icmp`, `esp`, `ah`, `ipip`, `sctp`), or the IP protocol number. - -* `ports` - - (Optional) - An optional list of ports to which this rule applies. This field is only applicable for UDP or TCP protocol. Each entry must be either an integer or a range. If not specified, this rule applies to connections through any port. Example inputs include: ``. - -- - - - -* `description` - - (Optional) - An optional description for this resource. - -* `disabled` - - (Optional) - Denotes whether the firewall policy rule is disabled. When set to true, the firewall policy rule is not enforced and traffic behaves as if it did not exist. If this is unspecified, the firewall policy rule will be enabled. - -* `enable_logging` - - (Optional) - Denotes whether to enable logging for a particular rule. If logging is enabled, logs will be exported to the configured export destination in Stackdriver. Logs may be exported to BigQuery or Pub/Sub. Note: you cannot enable logging on "goto_next" rules. - -* `target_resources` - - (Optional) - A list of network resource URLs to which this rule applies. This field allows you to control which network's VMs get this rule. If this field is left blank, all VMs within the organization will receive the rule. - -* `target_service_accounts` - - (Optional) - A list of service accounts indicating the sets of instances that are applied with this rule. - - - -## Attributes Reference - -In addition to the arguments listed above, the following computed attributes are exported: - -* `id` - an identifier for the resource with format `locations/global/firewallPolicies/{{firewall_policy}}/rules/{{priority}}` - -* `kind` - - Type of the resource. Always `compute#firewallPolicyRule` for firewall policy rules - -* `rule_tuple_count` - - Calculation of the complexity of a single firewall policy rule. - -## Timeouts - -This resource provides the following -[Timeouts](https://developer.hashicorp.com/terraform/plugin/sdkv2/resources/retries-and-customizable-timeouts) configuration options: configuration options: - -- `create` - Default is 20 minutes. -- `update` - Default is 20 minutes. -- `delete` - Default is 20 minutes. - -## Import - -FirewallPolicyRule can be imported using any of these accepted formats: - -``` -$ terraform import google_compute_firewall_policy_rule.default locations/global/firewallPolicies/{{firewall_policy}}/rules/{{priority}} -$ terraform import google_compute_firewall_policy_rule.default {{firewall_policy}}/{{priority}} -``` - - -