diff --git a/.ci/gcb-push-downstream.yml b/.ci/gcb-push-downstream.yml index c2081f601a6f..f03700d5d44a 100644 --- a/.ci/gcb-push-downstream.yml +++ b/.ci/gcb-push-downstream.yml @@ -35,7 +35,7 @@ steps: id: tpg-sync waitFor: ["build-magician-binary"] args: - - wait-for-commit + - 'wait-for-commit' - 'tpg-sync' - $BRANCH_NAME - $COMMIT_SHA @@ -46,7 +46,7 @@ steps: id: tpgb-sync waitFor: ["build-magician-binary"] args: - - wait-for-commit + - 'wait-for-commit' - 'tpgb-sync' - $BRANCH_NAME - $COMMIT_SHA @@ -57,7 +57,7 @@ steps: id: tgc-sync waitFor: ["build-magician-binary"] args: - - wait-for-commit + - 'wait-for-commit' - 'tgc-sync' - $BRANCH_NAME - $COMMIT_SHA @@ -68,7 +68,7 @@ steps: id: tf-oics-sync waitFor: ["build-magician-binary"] args: - - wait-for-commit + - 'wait-for-commit' - 'tf-oics-sync' - $BRANCH_NAME - $COMMIT_SHA diff --git a/.ci/magician/cmd/generate_downstream.go b/.ci/magician/cmd/generate_downstream.go index 0cdc8526c682..fd28cf0978db 100644 --- a/.ci/magician/cmd/generate_downstream.go +++ b/.ci/magician/cmd/generate_downstream.go @@ -98,6 +98,24 @@ func execGenerateDownstream(baseBranch, command, repo, version, ref string, gh G baseBranch = "main" } + var syncBranchPrefix string + if repo == "terraform" { + if version == "beta" { + syncBranchPrefix = "tpgb-sync" + } else if version == "ga" { + syncBranchPrefix = "tpg-sync" + } + } else if repo == "terraform-google-conversion" { + syncBranchPrefix = "tgc-sync" + } else if repo == "tf-oics" { + syncBranchPrefix = "tf-oics-sync" + } + syncBranch := getSyncBranch(syncBranchPrefix, baseBranch) + if syncBranchHasCommit(ref, syncBranch, rnr) { + fmt.Printf("Sync branch %s already has commit %s, skipping generation\n", syncBranch, ref) + os.Exit(0) + } + mmLocalPath := filepath.Join(rnr.GetCWD(), "..", "..") mmCopyPath := filepath.Join(mmLocalPath, "..", fmt.Sprintf("mm-%s-%s-%s", repo, version, command)) if _, err := rnr.Run("cp", []string{"-rp", mmLocalPath, mmCopyPath}, nil); err != nil { diff --git a/.ci/magician/cmd/sync_branch.go b/.ci/magician/cmd/sync_branch.go index 7722cc4f4c83..d3950b268235 100644 --- a/.ci/magician/cmd/sync_branch.go +++ b/.ci/magician/cmd/sync_branch.go @@ -58,6 +58,11 @@ func execSyncBranchCmd(syncBranchPrefix, baseBranch, sha, githubToken string, ru syncBranch := getSyncBranch(syncBranchPrefix, baseBranch) fmt.Println("SYNC_BRANCH: ", syncBranch) + if syncBranchHasCommit(sha, syncBranch, runner) { + fmt.Printf("Commit %s already in sync branch %s, skipping sync\n", sha, syncBranch) + return nil + } + _, err := runner.Run("git", []string{"push", fmt.Sprintf("https://modular-magician:%s@github.com/GoogleCloudPlatform/magic-modules", githubToken), fmt.Sprintf("%s:%s", sha, syncBranch)}, nil) return err } diff --git a/.ci/magician/cmd/wait_for_commit.go b/.ci/magician/cmd/wait_for_commit.go index 2059885e35c8..ecf3a2a7ebb4 100644 --- a/.ci/magician/cmd/wait_for_commit.go +++ b/.ci/magician/cmd/wait_for_commit.go @@ -48,7 +48,8 @@ func execWaitForCommit(syncBranchPrefix, baseBranch, sha string, runner source.R fmt.Println("SYNC_BRANCH: ", syncBranch) if syncBranchHasCommit(sha, syncBranch, runner) { - return fmt.Errorf("found %s in history of %s - dying to avoid double-generating that commit", sha, syncBranch) + fmt.Printf("found %s in history of %s - skipping wait\n", sha, syncBranch) + return nil } for { @@ -68,7 +69,7 @@ func execWaitForCommit(syncBranchPrefix, baseBranch, sha string, runner source.R } fmt.Println("sync branch is at: ", syncHead) fmt.Println("current commit is: ", sha) - + if _, err := runner.Run("git", []string{"fetch", "origin", syncBranch}, nil); err != nil { return err } diff --git a/.ci/magician/github/membership.go b/.ci/magician/github/membership.go index 3ccf61b38c59..d320a8c6a1ca 100644 --- a/.ci/magician/github/membership.go +++ b/.ci/magician/github/membership.go @@ -82,6 +82,11 @@ var ( startDate: newDate(2024, 5, 22, pdtLoc), endDate: newDate(2024, 5, 28, pdtLoc), }, + { + id: "melinath", + startDate: newDate(2024, 6, 26, pdtLoc), + endDate: newDate(2024, 7, 22, pdtLoc), + }, } ) diff --git a/.ci/magician/vcr/tester.go b/.ci/magician/vcr/tester.go index 46e4a36473c1..c7b1343b4ef3 100644 --- a/.ci/magician/vcr/tester.go +++ b/.ci/magician/vcr/tester.go @@ -97,7 +97,8 @@ func (vt *Tester) FetchCassettes(version provider.Version, baseBranch, prNumber } cassettePath = filepath.Join(vt.baseDir, "cassettes", version.String()) vt.rnr.Mkdir(cassettePath) - if baseBranch != "FEATURE-BRANCH-major-release-5.0.0" { + if baseBranch != "FEATURE-BRANCH-major-release-6.0.0" { + // pull main cassettes (major release uses branch specific casssettes as primary ones) bucketPath := fmt.Sprintf("gs://ci-vcr-cassettes/%sfixtures/*", version.BucketPath()) if err := vt.fetchBucketPath(bucketPath, cassettePath); err != nil { fmt.Println("Error fetching cassettes: ", err) diff --git a/docs/content/develop/breaking-changes/make-a-breaking-change.md b/docs/content/develop/breaking-changes/make-a-breaking-change.md index a7ead0be56bf..3282c5ed83e9 100644 --- a/docs/content/develop/breaking-changes/make-a-breaking-change.md +++ b/docs/content/develop/breaking-changes/make-a-breaking-change.md @@ -62,7 +62,7 @@ The general process for contributing a breaking change to the 1. Make the `main` branch forwards-compatible with the major release 2. Add deprecations and warnings to the `main` branch of `magic-modules` -3. Add upgrade guide entries to the `main` branch of `magic-modules` +3. Add upgrade guide entries to the `FEATURE-BRANCH-major-release-6.0.0` branch of `magic-modules` 4. Make the breaking change on `FEATURE-BRANCH-major-release-{{% param "majorVersion" %}}` These are covered in more detail in the following sections. The upgrade guide @@ -169,20 +169,7 @@ The deprecation message will automatically show up in the resource documentation Other breaking changes should be called out in the docs for the impacted field or resource. It is also great to log warnings at runtime if possible. -### Add upgrade guide entries to the `main` branch of `magic-modules` - -Upgrade guide entries should be added to -[{{< param upgradeGuide >}}](https://github.com/GoogleCloudPlatform/magic-modules/blob/main/mmv1/third_party/terraform/website/docs/guides/{{< param upgradeGuide >}}). -Entries should focus on the changes that users need to make when upgrading -to `{{% param "majorVersion" %}}`, rather than how to write configurations -after upgrading. - -See [Terraform provider for Google Cloud 5.0.0 Upgrade Guide](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/version_5_upgrade) -and other upgrade guides for examples. - -The upgrade guide and the actual breaking change will be merged only after both are completed. - -### Make the breaking change on `FEATURE-BRANCH-major-release-{{% param "majorVersion" %}}` +### Make the change on `FEATURE-BRANCH-major-release-{{% param "majorVersion" %}}` When working on your breaking change, make sure that your base branch is `FEATURE-BRANCH-major-release-{{% param "majorVersion" %}}`. This @@ -207,14 +194,17 @@ with the following changes: are present on the major release branch. Changes to the `main` branch will be merged into the major release branch every Monday. 1. Make the breaking change. +1. Add the upgrade guide entries to +[{{< param upgradeGuide >}}](https://github.com/GoogleCloudPlatform/magic-modules/blob/FEATURE-BRANCH-major-release-6.0.0/mmv1/third_party/terraform/website/docs/guides/{{< param upgradeGuide >}}). Entries should focus on the changes that users need to make when upgrading +to `{{% param "majorVersion" %}}`, rather than how to write configurations +after upgrading. See [Terraform provider for Google Cloud 5.0.0 Upgrade Guide](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/version_5_upgrade) +and other upgrade guides for examples. 1. Remove any deprecation notices and warnings (including in documentation) not already removed by the breaking change. 1. When you create your pull request, [change the base branch](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/changing-the-base-branch-of-a-pull-request) to `FEATURE-BRANCH-major-release-{{% param "majorVersion" %}}` 1. To resolve merge conflicts with `git rebase` or `git merge`, use `FEATURE-BRANCH-major-release-{{% param "majorVersion" %}}` instead of `main`. -The upgrade guide and the actual breaking change will be merged only after both are completed. - ## What's next? - [Run tests]({{< ref "/develop/test/run-tests.md" >}}) diff --git a/mmv1/api/resource.go b/mmv1/api/resource.go index ea122cd6ef82..c0fb56b4be85 100644 --- a/mmv1/api/resource.go +++ b/mmv1/api/resource.go @@ -16,8 +16,8 @@ import ( "fmt" "maps" "regexp" - "strings" "sort" + "strings" "github.com/GoogleCloudPlatform/magic-modules/mmv1/api/product" "github.com/GoogleCloudPlatform/magic-modules/mmv1/api/resource" @@ -593,9 +593,6 @@ func buildEffectiveLabelsField(name string, labels *Type) *Type { "including the %s configured through Terraform, other clients and services.", name, name) t := "KeyValueEffectiveLabels" - if name == "annotations" { - t = "KeyValueEffectiveAnnotations" - } n := fmt.Sprintf("effective%s", strings.Title(name)) @@ -671,6 +668,10 @@ func getLabelsFieldNote(title string) string { title, title, title) } +func (r Resource) StateMigrationFile() string { + return fmt.Sprintf("templates/terraform/state_migrations/go/%s_%s.go.tmpl", google.Underscore(r.ProductMetadata.Name), google.Underscore(r.Name)) +} + // ==================== // Version-related methods // ==================== @@ -948,10 +949,7 @@ func ImportIdFormats(importFormat, identity []string, baseUrl string) []string { var idFormats []string if len(importFormat) == 0 { underscoredBaseUrl := baseUrl - // TODO Q2: underscore base url needed? - // underscored_base_url = base_url.gsub( - // /{{[[:word:]]+}}/, &:underscore - // ) + if len(identity) == 0 { idFormats = []string{fmt.Sprintf("%s/{{name}}", underscoredBaseUrl)} } else { @@ -960,7 +958,7 @@ func ImportIdFormats(importFormat, identity []string, baseUrl string) []string { transformedIdentity = append(transformedIdentity, fmt.Sprintf("{{%s}}", id)) } identityPath := strings.Join(transformedIdentity, "/") - idFormats = []string{fmt.Sprintf("%s/%s", underscoredBaseUrl, identityPath)} + idFormats = []string{fmt.Sprintf("%s/%s", underscoredBaseUrl, google.Underscore(identityPath))} } } else { idFormats = importFormat @@ -985,15 +983,9 @@ func ImportIdFormats(importFormat, identity []string, baseUrl string) []string { // `{{project}}/{{%name}}` as there is no way to differentiate between // project-name/resource-name and resource-name/with-slash if !strings.Contains(idFormats[0], "%") { - idFormats = append(idFormats, shortIdFormat, shortIdDefaultProjectFormat) - if shortIdDefaultProjectFormat != shortIdDefaultFormat { - idFormats = append(idFormats, shortIdDefaultFormat) - } + idFormats = append(idFormats, shortIdFormat, shortIdDefaultProjectFormat, shortIdDefaultFormat) } - idFormats = google.Reject(slices.Compact(idFormats), func(i string) bool { - return i == "" - }) slices.SortFunc(idFormats, func(a, b string) int { i := strings.Count(a, "/") j := strings.Count(b, "/") @@ -1003,7 +995,25 @@ func ImportIdFormats(importFormat, identity []string, baseUrl string) []string { return i - j }) slices.Reverse(idFormats) - return idFormats + + // Remove duplicates from idFormats + uniq := make([]string, len(idFormats)) + uniq[0] = idFormats[0] + i := 1 + j := 1 + for j < len(idFormats) { + format := idFormats[j] + if format != uniq[i-1] { + uniq[i] = format + i++ + } + j++ + } + + uniq = google.Reject(slices.Compact(uniq), func(i string) bool { + return i == "" + }) + return uniq } func (r Resource) IgnoreReadPropertiesToString(e resource.Examples) string { @@ -1014,7 +1024,7 @@ func (r Resource) IgnoreReadPropertiesToString(e resource.Examples) string { } } for _, tp := range e.IgnoreReadExtra { - props = append(props, fmt.Sprintf("\"%s\"", google.Underscore(tp))) + props = append(props, fmt.Sprintf("\"%s\"", tp)) } for _, tp := range r.IgnoreReadLabelsFields(r.PropertiesWithExcluded()) { props = append(props, fmt.Sprintf("\"%s\"", google.Underscore(tp))) @@ -1379,6 +1389,9 @@ func (r Resource) GetPropertyUpdateMasksGroups(properties []*Type, maskPrefix st // Formats whitespace in the style of the old Ruby generator's descriptions in documentation func (r Resource) FormatDocDescription(desc string, indent bool) string { + if desc == "" { + return "" + } returnString := desc if indent { returnString = strings.ReplaceAll(returnString, "\n\n", "\n") @@ -1387,7 +1400,7 @@ func (r Resource) FormatDocDescription(desc string, indent bool) string { // fix removing for ruby -> go transition diffs returnString = strings.ReplaceAll(returnString, "\n \n **Note**: This field is non-authoritative,", "\n\n **Note**: This field is non-authoritative,") - return strings.TrimSuffix(returnString, "\n ") + return fmt.Sprintf("\n %s", strings.TrimSuffix(returnString, "\n ")) } return strings.TrimSuffix(returnString, "\n") } @@ -1455,8 +1468,8 @@ func (r Resource) propertiesWithCustomUpdate(properties []*Type) []*Type { }) } -func (r Resource) PropertiesByCustomUpdate() map[UpdateGroup][]*Type { - customUpdateProps := r.propertiesWithCustomUpdate(r.RootProperties()) +func (r Resource) PropertiesByCustomUpdate(properties []*Type) map[UpdateGroup][]*Type { + customUpdateProps := r.propertiesWithCustomUpdate(properties) groupedCustomUpdateProps := map[UpdateGroup][]*Type{} for _, prop := range customUpdateProps { groupedProperty := UpdateGroup{UpdateUrl: prop.UpdateUrl, @@ -1477,21 +1490,28 @@ func (r Resource) PropertiesByCustomUpdateGroups() []UpdateGroup { UpdateId: prop.UpdateId, FingerprintName: prop.FingerprintName} - if slices.Contains(updateGroups, groupedProperty){ + if slices.Contains(updateGroups, groupedProperty) { continue } updateGroups = append(updateGroups, groupedProperty) } - sort.Slice(updateGroups, func(i, j int) bool { return updateGroups[i].UpdateId < updateGroups[i].UpdateId }) + sort.Slice(updateGroups, func(i, j int) bool { + a := updateGroups[i] + b := updateGroups[j] + if a.UpdateVerb != b.UpdateVerb { + return a.UpdateVerb > b.UpdateVerb + } + return a.UpdateId < b.UpdateId + }) return updateGroups } func (r Resource) FieldSpecificUpdateMethods() bool { - return (len(r.PropertiesByCustomUpdate()) > 0) + return (len(r.PropertiesByCustomUpdate(r.RootProperties())) > 0) } -func (r Resource) CustomUpdatePropertiesByKey(updateUrl string, updateId string, fingerprintName string, updateVerb string) []*Type { - groupedProperties := r.PropertiesByCustomUpdate() +func (r Resource) CustomUpdatePropertiesByKey(properties []*Type, updateUrl string, updateId string, fingerprintName string, updateVerb string) []*Type { + groupedProperties := r.PropertiesByCustomUpdate(properties) groupedProperty := UpdateGroup{UpdateUrl: updateUrl, UpdateVerb: updateVerb, UpdateId: updateId, @@ -1526,3 +1546,11 @@ func (r Resource) VersionedProvider(exampleVersion string) bool { } return vp != "" && vp != "ga" } + +func (r Resource) StateUpgradersCount() []int { + var nums []int + for i := r.StateUpgradeBaseSchemaVersion; i < r.SchemaVersion; i++ { + nums = append(nums, i) + } + return nums +} diff --git a/mmv1/api/resource.rb b/mmv1/api/resource.rb index de04aafe0e83..98d2b0fe775a 100644 --- a/mmv1/api/resource.rb +++ b/mmv1/api/resource.rb @@ -396,7 +396,11 @@ def all_nested_properties(props) def convert_go_file(file) dir, base = File.split(file) base.slice! '.erb' - "#{dir}/go/#{base}.tmpl" + if dir.end_with?('terraform') + "#{dir}/#{base}.go.tmpl" + else + "#{dir}/go/#{base}.tmpl" + end end # All settable properties in the resource. diff --git a/mmv1/api/resource/docs.go b/mmv1/api/resource/docs.go index 01fec263dd87..2b81541c09c3 100644 --- a/mmv1/api/resource/docs.go +++ b/mmv1/api/resource/docs.go @@ -33,10 +33,10 @@ type Docs struct { Note string // attr_reader : - RequiredProperties string + RequiredProperties string `yaml:"required_properties"` // attr_reader : - OptionalProperties string + OptionalProperties string `yaml:"optional_properties"` // attr_reader : Attributes string diff --git a/mmv1/api/resource/examples.go b/mmv1/api/resource/examples.go index a1386f8f7081..c669e2e89570 100644 --- a/mmv1/api/resource/examples.go +++ b/mmv1/api/resource/examples.go @@ -204,6 +204,7 @@ func (e *Examples) SetHCLText() { } e.TestEnvVars = docTestEnvVars e.DocumentationHCLText = ExecuteTemplate(e, e.ConfigPath, true) + e.DocumentationHCLText = regexp.MustCompile(`\n\n$`).ReplaceAllString(e.DocumentationHCLText, "\n") // Remove region tags re1 := regexp.MustCompile(`# \[[a-zA-Z_ ]+\]\n`) diff --git a/mmv1/api/type.go b/mmv1/api/type.go index 8059e57beead..b3c00d744c7a 100644 --- a/mmv1/api/type.go +++ b/mmv1/api/type.go @@ -293,12 +293,12 @@ func (t *Type) SetDefault(r *Resource) { switch { case t.IsA("Array"): t.ItemType.ParentName = t.Name - t.ItemType.ParentMetadata = t.ParentMetadata + t.ItemType.ParentMetadata = t t.ItemType.SetDefault(r) case t.IsA("Map"): t.KeyExpander = "tpgresource.ExpandString" t.ValueType.ParentName = t.Name - t.ValueType.ParentMetadata = t.ParentMetadata + t.ValueType.ParentMetadata = t t.ValueType.SetDefault(r) case t.IsA("NestedObject"): if t.Name == "" { @@ -443,7 +443,11 @@ func (t *Type) GetPrefix() string { t.Prefix = fmt.Sprintf("%s%s", nestedPrefix, t.ResourceMetadata.ResourceName()) } else { - t.Prefix = fmt.Sprintf("%s%s", t.ParentMetadata.GetPrefix(), t.ParentMetadata.TitlelizeProperty()) + if t.ParentMetadata != nil && (t.ParentMetadata.IsA("Array") || t.ParentMetadata.IsA("Map")) { + t.Prefix = t.ParentMetadata.GetPrefix() + } else { + t.Prefix = fmt.Sprintf("%s%s", t.ParentMetadata.GetPrefix(), t.ParentMetadata.TitlelizeProperty()) + } } } return t.Prefix @@ -562,7 +566,7 @@ func (t Type) AtLeastOneOfList() []string { // Returns list of properties that needs exactly one of their fields set. // func (t *Type) exactly_one_of_list() { func (t Type) ExactlyOneOfList() []string { - if t.ResourceMetadata == nil || t.Parent() != nil { + if t.ResourceMetadata == nil { return []string{} } @@ -1326,13 +1330,78 @@ func (t *Type) GoLiteral(value interface{}) string { // def force_new?(property, resource) func (t *Type) IsForceNew() bool { + if t.IsA("KeyValueLabels") && t.ResourceMetadata.RootLabels() { + return false + } + + if t.IsA("KeyValueTerraformLabels") && !t.ResourceMetadata.Updatable() && !t.ResourceMetadata.RootLabels() { + return true + } + parent := t.Parent() - return (((!t.Output || t.IsA("KeyValueEffectiveLabels")) && + return (!t.Output || t.IsA("KeyValueEffectiveLabels")) && (t.Immutable || - (t.ResourceMetadata.Immutable && t.UpdateUrl == "" && !t.Immutable && + (t.ResourceMetadata.Immutable && t.UpdateUrl == "" && (parent == nil || (parent.IsForceNew() && - !(parent.FlattenObject && t.IsA("KeyValueLabels"))))))) || - (t.IsA("KeyValueTerraformLabels") && - t.ResourceMetadata.Updatable() && !t.ResourceMetadata.RootLabels())) + !(parent.FlattenObject && t.IsA("KeyValueLabels")))))) +} + +// 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) +func (t *Type) GetPropertySchemaPath(schemaPath string) string { + nestedProps := t.ResourceMetadata.UserProperites() + + var pathTkns []string + for _, pname := range strings.Split(schemaPath, ".0.") { + camelPname := google.Camelize(pname, "lower") + index := slices.IndexFunc(nestedProps, func(p *Type) bool { + return p.Name == camelPname + }) + + // if we couldn't find it, see if it was renamed at the top level + if index == -1 { + index = slices.IndexFunc(nestedProps, func(p *Type) bool { + return p.Name == schemaPath + }) + } + + if index == -1 { + continue + } + + prop := nestedProps[index] + + nestedProps = prop.NestedProperties() + if !prop.FlattenObject { + pathTkns = append(pathTkns, google.Underscore(pname)) + } + } + + if len(pathTkns) == 0 || pathTkns[len(pathTkns)-1] == "" { + return "" + } + + return strings.Join(pathTkns[:], ".0.") +} + +func (t Type) GetPropertySchemaPathList(propertyList []string) []string { + var list []string + for _, path := range propertyList { + path = t.GetPropertySchemaPath(path) + if path != "" { + list = append(list, path) + } + } + return list } diff --git a/mmv1/description-copy.go b/mmv1/description-copy.go index 0294174e95c8..1cd004b31a2d 100644 --- a/mmv1/description-copy.go +++ b/mmv1/description-copy.go @@ -161,5 +161,9 @@ func terminateText(line string) bool { } } + if regexp.MustCompile(`^\s*https:[\s$]*`).MatchString(line) { + return false + } + return regexp.MustCompile(`^\s*[a-z_]+:[\s$]*`).MatchString(line) } diff --git a/mmv1/google/string_utils.go b/mmv1/google/string_utils.go index daeaf56baaf6..63d8fce9c683 100644 --- a/mmv1/google/string_utils.go +++ b/mmv1/google/string_utils.go @@ -158,6 +158,7 @@ func Format2Regex(format string) string { // TODO: the trims may not be needed with more effecient regex word := strings.TrimPrefix(match, "{{") word = strings.TrimSuffix(word, "}}") + word = strings.ReplaceAll(word, "%", "") return fmt.Sprintf("(?P<%s>.+)", word) }) re = regexp.MustCompile(`\{\{([[:word:]]+)\}\}`) diff --git a/mmv1/google/template_utils.go b/mmv1/google/template_utils.go index 1053894233ba..78eb2dea26ac 100644 --- a/mmv1/google/template_utils.go +++ b/mmv1/google/template_utils.go @@ -14,10 +14,15 @@ package google import ( + "bytes" "errors" + "fmt" + "path/filepath" "strings" "text/template" + + "github.com/golang/glog" ) // Build a map(map[string]interface{}) from a list of paramerter @@ -66,4 +71,58 @@ var TemplateFunctions = template.FuncMap{ "sub": subtract, "plus": plus, "firstSentence": FirstSentence, + "trimTemplate": TrimTemplate, +} + +// Temporary function to simulate how Ruby MMv1's lines() function works +// for nested documentation. Can replace with normal "template" after switchover +func TrimTemplate(templatePath string, e any) string { + templates := []string{ + fmt.Sprintf("templates/terraform/%s", templatePath), + "templates/terraform/expand_resource_ref.tmpl", + } + templateFileName := filepath.Base(templatePath) + + // Need to remake TemplateFunctions, referencing it directly here + // causes a declaration loop + var templateFunctions = template.FuncMap{ + "title": SpaceSeparatedTitle, + "replace": strings.Replace, + "replaceAll": strings.ReplaceAll, + "camelize": Camelize, + "underscore": Underscore, + "plural": Plural, + "contains": strings.Contains, + "join": strings.Join, + "lower": strings.ToLower, + "upper": strings.ToUpper, + "dict": wrapMultipleParams, + "format2regex": Format2Regex, + "hasPrefix": strings.HasPrefix, + "sub": subtract, + "plus": plus, + "firstSentence": FirstSentence, + "trimTemplate": TrimTemplate, + } + + tmpl, err := template.New(templateFileName).Funcs(templateFunctions).ParseFiles(templates...) + if err != nil { + glog.Exit(err) + } + + contents := bytes.Buffer{} + if err = tmpl.ExecuteTemplate(&contents, templateFileName, e); err != nil { + glog.Exit(err) + } + + rs := contents.String() + + if rs == "" { + return rs + } + + for strings.HasSuffix(rs, "\n") { + rs = strings.TrimSuffix(rs, "\n") + } + return fmt.Sprintf("%s\n", rs) } diff --git a/mmv1/products/accessapproval/go_FolderSettings.yaml b/mmv1/products/accessapproval/go_FolderSettings.yaml new file mode 100644 index 000000000000..a466d51e29dd --- /dev/null +++ b/mmv1/products/accessapproval/go_FolderSettings.yaml @@ -0,0 +1,151 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'FolderSettings' +legacy_name: 'google_folder_access_approval_settings' +description: | + Access Approval enables you to require your explicit approval whenever Google support and engineering need to access your customer content. +references: + guides: + api: 'https://cloud.google.com/access-approval/docs/reference/rest/v1/folders' +docs: +base_url: 'folders/{{folder_id}}/accessApprovalSettings' +self_link: 'folders/{{folder_id}}/accessApprovalSettings' +create_verb: 'PATCH' +update_verb: 'PATCH' +update_mask: true +import_format: + - 'folders/{{folder_id}}/accessApprovalSettings' +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +custom_code: + constants: 'templates/terraform/constants/go/access_approval.go.tmpl' + pre_create: 'templates/terraform/update_mask.go.tmpl' + custom_delete: 'templates/terraform/custom_delete/go/clear_folder_access_approval_settings.go.tmpl' +examples: + - name: 'folder_access_approval_full' + primary_resource_id: 'folder_access_approval' + vars: + folder_name: 'my-folder' + test_env_vars: + org_id: 'ORG_ID' + skip_test: true + - name: 'folder_access_approval_active_key_version' + primary_resource_id: 'folder_access_approval' + vars: + folder_name: 'my-folder' + test_env_vars: + org_id: 'ORG_ID' + skip_test: true +parameters: + - name: 'folder_id' + type: String + description: | + ID of the folder of the access approval settings. + url_param_only: true + required: true + immutable: true +properties: + - name: 'name' + type: String + description: | + The resource name of the settings. Format is "folders/{folder_id}/accessApprovalSettings" + output: true + - name: 'notificationEmails' + type: Array + description: | + A list of email addresses to which notifications relating to approval requests should be sent. + Notifications relating to a resource will be sent to all emails in the settings of ancestor + resources of that resource. A maximum of 50 email addresses are allowed. + is_set: true + default_from_api: true + item_type: + type: String + max_size: 50 + - name: 'enrolledServices' + type: Array + description: | + A list of Google Cloud Services for which the given resource has Access Approval enrolled. + Access requests for the resource given by name against any of these services contained here will be required + to have explicit approval. Enrollment can only be done on an all or nothing basis. + + A maximum of 10 enrolled services will be enforced, to be expanded as the set of supported services is expanded. + is_set: true + required: true + set_hash_func: accessApprovalEnrolledServicesHash + item_type: + type: NestedObject + properties: + - name: 'cloudProduct' + type: String + description: | + The product for which Access Approval will be enrolled. Allowed values are listed (case-sensitive): + * all + * App Engine + * BigQuery + * Cloud Bigtable + * Cloud Key Management Service + * Compute Engine + * Cloud Dataflow + * Cloud Identity and Access Management + * Cloud Pub/Sub + * Cloud Storage + * Persistent Disk + + Note: These values are supported as input, but considered a legacy format: + * all + * appengine.googleapis.com + * bigquery.googleapis.com + * bigtable.googleapis.com + * cloudkms.googleapis.com + * compute.googleapis.com + * dataflow.googleapis.com + * iam.googleapis.com + * pubsub.googleapis.com + * storage.googleapis.com + required: true + - name: 'enrollmentLevel' + type: Enum + description: | + The enrollment level of the service. + default_value: "BLOCK_ALL" + enum_values: + - 'BLOCK_ALL' + - name: 'enrolledAncestor' + type: Boolean + description: | + If the field is true, that indicates that at least one service is enrolled for Access Approval in one or more ancestors of the Folder. + output: true + - name: 'activeKeyVersion' + type: String + description: | + The asymmetric crypto key version to use for signing approval requests. + Empty active_key_version indicates that a Google-managed key should be used for signing. + This property will be ignored if set by an ancestor of the resource, and new non-empty values may not be set. + - name: 'ancestorHasActiveKeyVersion' + type: Boolean + description: | + If the field is true, that indicates that an ancestor of this Folder has set active_key_version. + output: true + - name: 'invalidKeyVersion' + type: Boolean + description: | + If the field is true, that indicates that there is some configuration issue with the active_key_version + configured on this Folder (e.g. it doesn't exist or the Access Approval service account doesn't have the + correct permissions on it, etc.) This key version is not necessarily the effective key version at this level, + as key versions are inherited top-down. + output: true diff --git a/mmv1/products/accessapproval/go_OrganizationSettings.yaml b/mmv1/products/accessapproval/go_OrganizationSettings.yaml new file mode 100644 index 000000000000..c486995d8b83 --- /dev/null +++ b/mmv1/products/accessapproval/go_OrganizationSettings.yaml @@ -0,0 +1,131 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'OrganizationSettings' +legacy_name: 'google_organization_access_approval_settings' +description: | + Access Approval enables you to require your explicit approval whenever Google support and engineering need to access your customer content. +references: + guides: + api: 'https://cloud.google.com/access-approval/docs/reference/rest/v1/organizations' +docs: +base_url: 'organizations/{{organization_id}}/accessApprovalSettings' +self_link: 'organizations/{{organization_id}}/accessApprovalSettings' +create_verb: 'PATCH' +update_verb: 'PATCH' +update_mask: true +import_format: + - 'organizations/{{organization_id}}/accessApprovalSettings' +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +custom_code: + pre_create: 'templates/terraform/update_mask.go.tmpl' + custom_delete: 'templates/terraform/custom_delete/go/clear_organization_access_approval_settings.go.tmpl' +examples: + - name: 'organization_access_approval_full' + primary_resource_id: 'organization_access_approval' + test_env_vars: + org_id: 'ORG_ID' + skip_test: true + - name: 'organization_access_approval_active_key_version' + primary_resource_id: 'organization_access_approval' + test_env_vars: + org_id: 'ORG_ID' + skip_test: true +parameters: + - name: 'organization_id' + type: String + description: | + ID of the organization of the access approval settings. + url_param_only: true + required: true + immutable: true +properties: + - name: 'name' + type: String + description: | + The resource name of the settings. Format is "organizations/{organization_id}/accessApprovalSettings" + output: true + - name: 'notificationEmails' + type: Array + description: | + A list of email addresses to which notifications relating to approval requests should be sent. + Notifications relating to a resource will be sent to all emails in the settings of ancestor + resources of that resource. A maximum of 50 email addresses are allowed. + is_set: true + default_from_api: true + item_type: + type: String + max_size: 50 + - name: 'enrolledServices' + type: Array + description: | + A list of Google Cloud Services for which the given resource has Access Approval enrolled. + Access requests for the resource given by name against any of these services contained here will be required + to have explicit approval. Enrollment can be done for individual services. + + A maximum of 10 enrolled services will be enforced, to be expanded as the set of supported services is expanded. + is_set: true + required: true + set_hash_func: accessApprovalEnrolledServicesHash + item_type: + type: NestedObject + properties: + - name: 'cloudProduct' + type: String + description: | + The product for which Access Approval will be enrolled. Allowed values are listed (case-sensitive): + all + appengine.googleapis.com + bigquery.googleapis.com + bigtable.googleapis.com + cloudkms.googleapis.com + compute.googleapis.com + dataflow.googleapis.com + iam.googleapis.com + pubsub.googleapis.com + storage.googleapis.com + required: true + - name: 'enrollmentLevel' + type: Enum + description: | + The enrollment level of the service. + default_value: "BLOCK_ALL" + enum_values: + - 'BLOCK_ALL' + - name: 'enrolledAncestor' + type: Boolean + description: | + This field will always be unset for the organization since organizations do not have ancestors. + output: true + - name: 'activeKeyVersion' + type: String + description: | + The asymmetric crypto key version to use for signing approval requests. + Empty active_key_version indicates that a Google-managed key should be used for signing. + - name: 'ancestorHasActiveKeyVersion' + type: Boolean + description: | + This field will always be unset for the organization since organizations do not have ancestors. + output: true + - name: 'invalidKeyVersion' + type: Boolean + description: | + If the field is true, that indicates that there is some configuration issue with the active_key_version + configured on this Organization (e.g. it doesn't exist or the Access Approval service account doesn't have the + correct permissions on it, etc.). + output: true diff --git a/mmv1/products/accessapproval/go_ProjectSettings.yaml b/mmv1/products/accessapproval/go_ProjectSettings.yaml new file mode 100644 index 000000000000..806609b22c4f --- /dev/null +++ b/mmv1/products/accessapproval/go_ProjectSettings.yaml @@ -0,0 +1,140 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'ProjectSettings' +legacy_name: 'google_project_access_approval_settings' +description: | + Access Approval enables you to require your explicit approval whenever Google support and engineering need to access your customer content. +references: + guides: + api: 'https://cloud.google.com/access-approval/docs/reference/rest/v1/projects' +docs: +base_url: 'projects/{{project_id}}/accessApprovalSettings' +self_link: 'projects/{{project_id}}/accessApprovalSettings' +create_verb: 'PATCH' +update_verb: 'PATCH' +update_mask: true +import_format: + - 'projects/{{project_id}}/accessApprovalSettings' +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +custom_code: + pre_create: 'templates/terraform/update_mask.go.tmpl' + custom_delete: 'templates/terraform/custom_delete/go/clear_project_access_approval_settings.go.tmpl' +examples: + - name: 'project_access_approval_full' + primary_resource_id: 'project_access_approval' + test_env_vars: + project: 'PROJECT_NAME' + org_id: 'ORG_ID' + skip_test: true + - name: 'project_access_approval_active_key_version' + primary_resource_id: 'project_access_approval' + test_env_vars: + project: 'PROJECT_NAME' + org_id: 'ORG_ID' + skip_test: true +parameters: + - name: 'project_id' + type: String + description: | + ID of the project of the access approval settings. + url_param_only: true + required: true + immutable: true +properties: + - name: 'name' + type: String + description: | + The resource name of the settings. Format is "projects/{project_id}/accessApprovalSettings" + output: true + - name: 'notificationEmails' + type: Array + description: | + A list of email addresses to which notifications relating to approval requests should be sent. + Notifications relating to a resource will be sent to all emails in the settings of ancestor + resources of that resource. A maximum of 50 email addresses are allowed. + is_set: true + default_from_api: true + item_type: + type: String + max_size: 50 + - name: 'enrolledServices' + type: Array + description: | + A list of Google Cloud Services for which the given resource has Access Approval enrolled. + Access requests for the resource given by name against any of these services contained here will be required + to have explicit approval. Enrollment can only be done on an all or nothing basis. + + A maximum of 10 enrolled services will be enforced, to be expanded as the set of supported services is expanded. + is_set: true + required: true + set_hash_func: accessApprovalEnrolledServicesHash + item_type: + type: NestedObject + properties: + - name: 'cloudProduct' + type: String + description: | + The product for which Access Approval will be enrolled. Allowed values are listed (case-sensitive): + all + appengine.googleapis.com + bigquery.googleapis.com + bigtable.googleapis.com + cloudkms.googleapis.com + compute.googleapis.com + dataflow.googleapis.com + iam.googleapis.com + pubsub.googleapis.com + storage.googleapis.com + required: true + - name: 'enrollmentLevel' + type: Enum + description: | + The enrollment level of the service. + default_value: "BLOCK_ALL" + enum_values: + - 'BLOCK_ALL' + - name: 'enrolledAncestor' + type: Boolean + description: | + If the field is true, that indicates that at least one service is enrolled for Access Approval in one or more ancestors of the Project. + output: true + - name: 'activeKeyVersion' + type: String + description: | + The asymmetric crypto key version to use for signing approval requests. + Empty active_key_version indicates that a Google-managed key should be used for signing. + This property will be ignored if set by an ancestor of the resource, and new non-empty values may not be set. + - name: 'ancestorHasActiveKeyVersion' + type: Boolean + description: | + If the field is true, that indicates that an ancestor of this Project has set active_key_version. + output: true + - name: 'invalidKeyVersion' + type: Boolean + description: | + If the field is true, that indicates that there is some configuration issue with the active_key_version + configured on this Project (e.g. it doesn't exist or the Access Approval service account doesn't have the + correct permissions on it, etc.) This key version is not necessarily the effective key version at this level, + as key versions are inherited top-down. + output: true + - name: 'project' + type: String + description: | + Project id. + deprecation_message: '`project` is deprecated and will be removed in a future major release. Use `project_id` instead.' diff --git a/mmv1/products/accessapproval/go_product.yaml b/mmv1/products/accessapproval/go_product.yaml new file mode 100644 index 000000000000..d925b1742897 --- /dev/null +++ b/mmv1/products/accessapproval/go_product.yaml @@ -0,0 +1,22 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'AccessApproval' +display_name: 'Access Approval' +versions: + - name: 'ga' + base_url: 'https://accessapproval.googleapis.com/v1/' +scopes: + - 'https://www.googleapis.com/auth/cloud-platform' diff --git a/mmv1/products/accesscontextmanager/go_AccessLevel.yaml b/mmv1/products/accesscontextmanager/go_AccessLevel.yaml new file mode 100644 index 000000000000..7fcbe6d33c1a --- /dev/null +++ b/mmv1/products/accesscontextmanager/go_AccessLevel.yaml @@ -0,0 +1,311 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'AccessLevel' +description: | + An AccessLevel is a label that can be applied to requests to GCP services, + along with a list of requirements necessary for the label to be applied. +references: + guides: + 'Access Policy Quickstart': 'https://cloud.google.com/access-context-manager/docs/quickstart' + api: 'https://cloud.google.com/access-context-manager/docs/reference/rest/v1/accessPolicies.accessLevels' +docs: + warning: | + If you are using User ADCs (Application Default Credentials) with this resource, + you must specify a `billing_project` and set `user_project_override` to true + in the provider configuration. Otherwise the ACM API will return a 403 error. + Your account must have the `serviceusage.services.use` permission on the + `billing_project` you defined. +id_format: '{{name}}' +base_url: '' +self_link: '{{name}}' +create_url: '{{parent}}/accessLevels' +update_verb: 'PATCH' +update_mask: true +import_format: + - '{{name}}' +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +autogen_async: true +async: + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: true + error: + path: 'error' + message: 'message' +custom_code: + encoder: 'templates/terraform/encoders/go/access_level_never_send_parent.go.tmpl' + custom_import: 'templates/terraform/custom_import/go/set_access_policy_parent_from_self_link.go.tmpl' +skip_sweeper: true +examples: + - name: 'access_context_manager_access_level_basic' + primary_resource_id: 'access-level' + vars: + access_level_name: 'chromeos_no_lock' + skip_test: true +parameters: + - name: 'parent' + type: String + description: | + The AccessPolicy this AccessLevel lives in. + Format: accessPolicies/{policy_id} + required: true + immutable: true + ignore_read: true + - name: 'name' + type: String + description: | + Resource name for the Access Level. The short_name component must begin + with a letter and only include alphanumeric and '_'. + Format: accessPolicies/{policy_id}/accessLevels/{short_name} + required: true + immutable: true +properties: + - name: 'title' + type: String + description: | + Human readable title. Must be unique within the Policy. + required: true + - name: 'description' + type: String + description: | + Description of the AccessLevel and its use. Does not affect behavior. + - name: 'basic' + type: NestedObject + description: | + A set of predefined conditions for the access level and a combining function. + conflicts: + - custom + properties: + - name: 'combiningFunction' + type: Enum + description: | + How the conditions list should be combined to determine if a request + is granted this AccessLevel. If AND is used, each Condition in + conditions must be satisfied for the AccessLevel to be applied. If + OR is used, at least one Condition in conditions must be satisfied + for the AccessLevel to be applied. + custom_flatten: 'templates/terraform/custom_flatten/go/default_if_empty.tmpl' + default_value: "AND" + enum_values: + - 'AND' + - 'OR' + - name: 'conditions' + type: Array + description: | + A set of requirements for the AccessLevel to be granted. + required: true + item_type: + type: NestedObject + properties: + - name: 'ipSubnetworks' + type: Array + description: | + A list of CIDR block IP subnetwork specification. May be IPv4 + or IPv6. + Note that for a CIDR IP address block, the specified IP address + portion must be properly truncated (i.e. all the host bits must + be zero) or the input is considered malformed. For example, + "192.0.2.0/24" is accepted but "192.0.2.1/24" is not. Similarly, + for IPv6, "2001:db8::/32" is accepted whereas "2001:db8::1/32" + is not. The originating IP of a request must be in one of the + listed subnets in order for this Condition to be true. + If empty, all IP addresses are allowed. + item_type: + type: String + - name: 'requiredAccessLevels' + type: Array + description: | + A list of other access levels defined in the same Policy, + referenced by resource name. Referencing an AccessLevel which + does not exist is an error. All access levels listed must be + granted for the Condition to be true. + Format: accessPolicies/{policy_id}/accessLevels/{short_name} + item_type: + type: String + - name: 'members' + type: Array + description: | + An allowed list of members (users, service accounts). + Using groups is not supported yet. + + The signed-in user originating the request must be a part of one + of the provided members. If not specified, a request may come + from any user (logged in/not logged in, not present in any + groups, etc.). + Formats: `user:{emailid}`, `serviceAccount:{emailid}` + item_type: + type: String + - name: 'negate' + type: Boolean + description: | + Whether to negate the Condition. If true, the Condition becomes + a NAND over its non-empty fields, each field must be false for + the Condition overall to be satisfied. Defaults to false. + - name: 'devicePolicy' + type: NestedObject + description: | + Device specific restrictions, all restrictions must hold for + the Condition to be true. If not specified, all devices are + allowed. + properties: + - name: 'requireScreenLock' + type: Boolean + description: | + Whether or not screenlock is required for the DevicePolicy + to be true. Defaults to false. + api_name: requireScreenlock + - name: 'allowedEncryptionStatuses' + type: Array + description: | + A list of allowed encryptions statuses. + An empty list allows all statuses. + item_type: + type: Enum + description: | + This field only has a name and description because of MM + limitations. It should not appear in downstreams. + enum_values: + - 'ENCRYPTION_UNSPECIFIED' + - 'ENCRYPTION_UNSUPPORTED' + - 'UNENCRYPTED' + - 'ENCRYPTED' + - name: 'allowedDeviceManagementLevels' + type: Array + description: | + A list of allowed device management levels. + An empty list allows all management levels. + item_type: + type: Enum + description: | + This field only has a name and description because of MM + limitations. It should not appear in downstreams. + enum_values: + - 'MANAGEMENT_UNSPECIFIED' + - 'NONE' + - 'BASIC' + - 'COMPLETE' + - name: 'osConstraints' + type: Array + description: | + A list of allowed OS versions. + An empty list allows all types and all versions. + item_type: + type: NestedObject + properties: + - name: 'minimumVersion' + type: String + description: | + The minimum allowed OS version. If not set, any version + of this OS satisfies the constraint. + Format: "major.minor.patch" such as "10.5.301", "9.2.1". + - name: 'requireVerifiedChromeOs' + type: Boolean + description: + If you specify DESKTOP_CHROME_OS for osType, you can + optionally include requireVerifiedChromeOs to require + Chrome Verified Access. + - name: 'osType' + type: Enum + description: | + The operating system type of the device. + required: true + enum_values: + - 'OS_UNSPECIFIED' + - 'DESKTOP_MAC' + - 'DESKTOP_WINDOWS' + - 'DESKTOP_LINUX' + - 'DESKTOP_CHROME_OS' + - 'ANDROID' + - 'IOS' + - name: 'requireAdminApproval' + type: Boolean + description: | + Whether the device needs to be approved by the customer admin. + - name: 'requireCorpOwned' + type: Boolean + description: | + Whether the device needs to be corp owned. + - name: 'regions' + type: Array + description: | + The request must originate from one of the provided + countries/regions. + Format: A valid ISO 3166-1 alpha-2 code. + item_type: + type: String + - name: 'vpcNetworkSources' + type: Array + description: 'The request must originate from one of the provided VPC networks in Google Cloud. Cannot specify this field together with `ip_subnetworks`.' + item_type: + type: NestedObject + properties: + - name: 'vpcSubnetwork' + type: NestedObject + description: 'Sub networks within a VPC network.' + properties: + - name: 'network' + type: String + description: 'Required. Network name to be allowed by this Access Level. Networks of foreign organizations requires `compute.network.get` permission to be granted to caller.' + required: true + - name: 'vpcIpSubnetworks' + type: Array + description: 'CIDR block IP subnetwork specification. Must be IPv4.' + item_type: + type: String + min_size: 1 + - name: 'custom' + type: NestedObject + description: | + Custom access level conditions are set using the Cloud Common Expression Language to represent the necessary conditions for the level to apply to a request. + See CEL spec at: https://github.com/google/cel-spec. + conflicts: + - basic + properties: + - name: 'expr' + type: NestedObject + description: | + Represents a textual expression in the Common Expression Language (CEL) syntax. CEL is a C-like expression language. + This page details the objects and attributes that are used to the build the CEL expressions for + custom access levels - https://cloud.google.com/access-context-manager/docs/custom-access-level-spec. + required: true + properties: + - name: 'expression' + type: String + description: + Textual representation of an expression in Common Expression + Language syntax. + required: true + - name: 'title' + type: String + description: + Title for the expression, i.e. a short string describing its + purpose. + - name: 'description' + type: String + description: Description of the expression + - name: 'location' + type: String + description: + String indicating the location of the expression for error + reporting, e.g. a file name and a position in the file diff --git a/mmv1/products/accesscontextmanager/go_AccessLevelCondition.yaml b/mmv1/products/accesscontextmanager/go_AccessLevelCondition.yaml new file mode 100644 index 000000000000..87288da3a1a0 --- /dev/null +++ b/mmv1/products/accesscontextmanager/go_AccessLevelCondition.yaml @@ -0,0 +1,243 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'AccessLevelCondition' +description: | + Allows configuring a single access level condition to be appended to an access level's conditions. + This resource is intended to be used in cases where it is not possible to compile a full list + of conditions to include in a `google_access_context_manager_access_level` resource, + to enable them to be added separately. + + ~> **Note:** If this resource is used alongside a `google_access_context_manager_access_level` resource, + the access level resource must have a `lifecycle` block with `ignore_changes = [basic[0].conditions]` so + they don't fight over which service accounts should be included. +references: + guides: + 'Access Policy Quickstart': 'https://cloud.google.com/access-context-manager/docs/quickstart' + api: 'https://cloud.google.com/access-context-manager/docs/reference/rest/v1/accessPolicies.accessLevels' +docs: + warning: | + If you are using User ADCs (Application Default Credentials) with this resource, + you must specify a `billing_project` and set `user_project_override` to true + in the provider configuration. Otherwise the ACM API will return a 403 error. + Your account must have the `serviceusage.services.use` permission on the + `billing_project` you defined. +id_format: '{{access_level}}' +base_url: '' +self_link: '{{access_level}}' +create_url: '{{access_level}}' +create_verb: 'PATCH' +update_mask: true +delete_verb: 'PATCH' +immutable: true +mutex: '{{access_level}}' +import_format: + - '{{access_level}}' +exclude_import: true +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +autogen_async: true +async: + type: 'PollAsync' + check_response_func_existence: 'transport_tpg.PollCheckForExistence' + check_response_func_absence: 'transport_tpg.PollCheckForAbsence' + suppress_error: false + target_occurrences: 1 + actions: ['create'] +identity: + - ipSubnetworks + - requiredAccessLevels + - members + - negate + - devicePolicy + - regions +nested_query: + keys: + - basic + - conditions + is_list_of_ids: false + modify_by_patch: true +custom_code: +exclude_tgc: true +skip_sweeper: true +examples: + - name: 'access_context_manager_access_level_condition_basic' + primary_resource_id: 'access-level-condition' + vars: + access_level_name: 'chromeos_no_lock' + account_id: 'my-account-id' + skip_test: true +parameters: + - name: 'accessLevel' + type: ResourceRef + description: | + The name of the Access Level to add this condition to. + url_param_only: true + required: true + immutable: true + resource: 'AccessLevel' + imports: 'name' +properties: + - name: 'ipSubnetworks' + type: Array + description: | + A list of CIDR block IP subnetwork specification. May be IPv4 + or IPv6. + Note that for a CIDR IP address block, the specified IP address + portion must be properly truncated (i.e. all the host bits must + be zero) or the input is considered malformed. For example, + "192.0.2.0/24" is accepted but "192.0.2.1/24" is not. Similarly, + for IPv6, "2001:db8::/32" is accepted whereas "2001:db8::1/32" + is not. The originating IP of a request must be in one of the + listed subnets in order for this Condition to be true. + If empty, all IP addresses are allowed. + item_type: + type: String + - name: 'requiredAccessLevels' + type: Array + description: | + A list of other access levels defined in the same Policy, + referenced by resource name. Referencing an AccessLevel which + does not exist is an error. All access levels listed must be + granted for the Condition to be true. + Format: accessPolicies/{policy_id}/accessLevels/{short_name} + item_type: + type: String + - name: 'members' + type: Array + description: | + An allowed list of members (users, service accounts). + Using groups is not supported yet. + + The signed-in user originating the request must be a part of one + of the provided members. If not specified, a request may come + from any user (logged in/not logged in, not present in any + groups, etc.). + Formats: `user:{emailid}`, `serviceAccount:{emailid}` + item_type: + type: String + - name: 'negate' + type: Boolean + description: | + Whether to negate the Condition. If true, the Condition becomes + a NAND over its non-empty fields, each field must be false for + the Condition overall to be satisfied. Defaults to false. + - name: 'devicePolicy' + type: NestedObject + description: | + Device specific restrictions, all restrictions must hold for + the Condition to be true. If not specified, all devices are + allowed. + properties: + - name: 'requireScreenLock' + type: Boolean + description: | + Whether or not screenlock is required for the DevicePolicy + to be true. Defaults to false. + api_name: requireScreenlock + - name: 'allowedEncryptionStatuses' + type: Array + description: | + A list of allowed encryptions statuses. + An empty list allows all statuses. + item_type: + type: Enum + description: | + This field only has a name and description because of MM + limitations. It should not appear in downstreams. + enum_values: + - 'ENCRYPTION_UNSPECIFIED' + - 'ENCRYPTION_UNSUPPORTED' + - 'UNENCRYPTED' + - 'ENCRYPTED' + - name: 'allowedDeviceManagementLevels' + type: Array + description: | + A list of allowed device management levels. + An empty list allows all management levels. + item_type: + type: Enum + description: | + This field only has a name and description because of MM + limitations. It should not appear in downstreams. + enum_values: + - 'MANAGEMENT_UNSPECIFIED' + - 'NONE' + - 'BASIC' + - 'COMPLETE' + - name: 'osConstraints' + type: Array + description: | + A list of allowed OS versions. + An empty list allows all types and all versions. + item_type: + type: NestedObject + properties: + - name: 'minimumVersion' + type: String + description: | + The minimum allowed OS version. If not set, any version + of this OS satisfies the constraint. + Format: "major.minor.patch" such as "10.5.301", "9.2.1". + - name: 'osType' + type: Enum + description: | + The operating system type of the device. + required: true + enum_values: + - 'OS_UNSPECIFIED' + - 'DESKTOP_MAC' + - 'DESKTOP_WINDOWS' + - 'DESKTOP_LINUX' + - 'DESKTOP_CHROME_OS' + - 'ANDROID' + - 'IOS' + - name: 'requireAdminApproval' + type: Boolean + description: | + Whether the device needs to be approved by the customer admin. + - name: 'requireCorpOwned' + type: Boolean + description: | + Whether the device needs to be corp owned. + - name: 'regions' + type: Array + description: | + The request must originate from one of the provided + countries/regions. + Format: A valid ISO 3166-1 alpha-2 code. + item_type: + type: String + - name: 'vpcNetworkSources' + type: Array + description: 'The request must originate from one of the provided VPC networks in Google Cloud. Cannot specify this field together with `ip_subnetworks`.' + item_type: + type: NestedObject + properties: + - name: 'vpcSubnetwork' + type: NestedObject + description: 'Sub networks within a VPC network.' + properties: + - name: 'network' + type: String + description: 'Required. Network name to be allowed by this Access Level. Networks of foreign organizations requires `compute.network.get` permission to be granted to caller.' + required: true + - name: 'vpcIpSubnetworks' + type: Array + description: 'CIDR block IP subnetwork specification. Must be IPv4.' + item_type: + type: String diff --git a/mmv1/products/accesscontextmanager/go_AccessLevels.yaml b/mmv1/products/accesscontextmanager/go_AccessLevels.yaml new file mode 100644 index 000000000000..d82488e54e1c --- /dev/null +++ b/mmv1/products/accesscontextmanager/go_AccessLevels.yaml @@ -0,0 +1,315 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'AccessLevels' +description: | + Replace all existing Access Levels in an Access Policy with the Access Levels provided. This is done atomically. + This is a bulk edit of all Access Levels and may override existing Access Levels created by `google_access_context_manager_access_level`, + thus causing a permadiff if used alongside `google_access_context_manager_access_level` on the same parent. +references: + guides: + 'Access Policy Quickstart': 'https://cloud.google.com/access-context-manager/docs/quickstart' + api: 'https://cloud.google.com/access-context-manager/docs/reference/rest/v1/accessPolicies.accessLevels' +docs: + warning: | + This resource is authoritative over the access levels under an access policy. Due to a limitation in Terraform, + it will overwrite all preexisting access levels during a create opration without displaying the old values on + the left side of plan. To prevent this, we recommend importing the resource before applying it if overwriting + preexisting rules, as the plan will correctly display the complete changes to your access policy if the + resource is present in state. +id_format: '{{parent}}/accessLevels' +base_url: '{{parent}}/accessLevels:replaceAll' +self_link: '{{parent}}/accessLevels' +update_url: '{{parent}}/accessLevels:replaceAll' +update_verb: 'POST' +import_format: + - '{{parent}}/accessLevels' +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +autogen_async: true +async: + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: true + error: + path: 'error' + message: 'message' +custom_code: + custom_delete: 'templates/terraform/custom_delete/go/replace_all_access_levels_empty_list.go.tmpl' + custom_import: 'templates/terraform/custom_import/go/set_access_policy_parent_from_access_policy.go.tmpl' +skip_sweeper: true +examples: + - name: 'access_context_manager_access_levels_basic' + primary_resource_id: 'access-levels' + vars: + access_level_name1: 'chromeos_no_lock' + access_level_name2: 'mac_no_lock' + skip_test: true +parameters: + - name: 'parent' + type: String + description: | + The AccessPolicy this AccessLevel lives in. + Format: accessPolicies/{policy_id} + url_param_only: true + required: true + immutable: true + ignore_read: true +properties: + - name: 'accessLevels' + type: Array + description: | + The desired Access Levels that should replace all existing Access Levels in the Access Policy. + is_set: true + item_type: + type: NestedObject + properties: + - name: 'name' + type: String + description: | + Resource name for the Access Level. The short_name component must begin + with a letter and only include alphanumeric and '_'. + Format: accessPolicies/{policy_id}/accessLevels/{short_name} + required: true + immutable: true + - name: 'title' + type: String + description: | + Human readable title. Must be unique within the Policy. + required: true + - name: 'description' + type: String + description: | + Description of the AccessLevel and its use. Does not affect behavior. + - name: 'basic' + type: NestedObject + description: | + A set of predefined conditions for the access level and a combining function. + # conflicts: + # - custom + properties: + - name: 'combiningFunction' + type: Enum + description: | + How the conditions list should be combined to determine if a request + is granted this AccessLevel. If AND is used, each Condition in + conditions must be satisfied for the AccessLevel to be applied. If + OR is used, at least one Condition in conditions must be satisfied + for the AccessLevel to be applied. + custom_flatten: 'templates/terraform/custom_flatten/go/default_if_empty.tmpl' + default_value: "AND" + enum_values: + - 'AND' + - 'OR' + - name: 'conditions' + type: Array + description: | + A set of requirements for the AccessLevel to be granted. + required: true + item_type: + type: NestedObject + properties: + - name: 'ipSubnetworks' + type: Array + description: | + A list of CIDR block IP subnetwork specification. May be IPv4 + or IPv6. + Note that for a CIDR IP address block, the specified IP address + portion must be properly truncated (i.e. all the host bits must + be zero) or the input is considered malformed. For example, + "192.0.2.0/24" is accepted but "192.0.2.1/24" is not. Similarly, + for IPv6, "2001:db8::/32" is accepted whereas "2001:db8::1/32" + is not. The originating IP of a request must be in one of the + listed subnets in order for this Condition to be true. + If empty, all IP addresses are allowed. + item_type: + type: String + - name: 'requiredAccessLevels' + type: Array + description: | + A list of other access levels defined in the same Policy, + referenced by resource name. Referencing an AccessLevel which + does not exist is an error. All access levels listed must be + granted for the Condition to be true. + Format: accessPolicies/{policy_id}/accessLevels/{short_name} + item_type: + type: String + - name: 'members' + type: Array + description: | + An allowed list of members (users, service accounts). + Using groups is not supported yet. + + The signed-in user originating the request must be a part of one + of the provided members. If not specified, a request may come + from any user (logged in/not logged in, not present in any + groups, etc.). + Formats: `user:{emailid}`, `serviceAccount:{emailid}` + item_type: + type: String + - name: 'negate' + type: Boolean + description: | + Whether to negate the Condition. If true, the Condition becomes + a NAND over its non-empty fields, each field must be false for + the Condition overall to be satisfied. Defaults to false. + - name: 'devicePolicy' + type: NestedObject + description: | + Device specific restrictions, all restrictions must hold for + the Condition to be true. If not specified, all devices are + allowed. + properties: + - name: 'requireScreenLock' + type: Boolean + description: | + Whether or not screenlock is required for the DevicePolicy + to be true. Defaults to false. + api_name: requireScreenlock + - name: 'allowedEncryptionStatuses' + type: Array + description: | + A list of allowed encryptions statuses. + An empty list allows all statuses. + item_type: + type: Enum + description: | + This field only has a name and description because of MM + limitations. It should not appear in downstreams. + enum_values: + - 'ENCRYPTION_UNSPECIFIED' + - 'ENCRYPTION_UNSUPPORTED' + - 'UNENCRYPTED' + - 'ENCRYPTED' + - name: 'allowedDeviceManagementLevels' + type: Array + description: | + A list of allowed device management levels. + An empty list allows all management levels. + item_type: + type: Enum + description: | + This field only has a name and description because of MM + limitations. It should not appear in downstreams. + enum_values: + - 'MANAGEMENT_UNSPECIFIED' + - 'NONE' + - 'BASIC' + - 'COMPLETE' + - name: 'osConstraints' + type: Array + description: | + A list of allowed OS versions. + An empty list allows all types and all versions. + item_type: + type: NestedObject + properties: + - name: 'minimumVersion' + type: String + description: | + The minimum allowed OS version. If not set, any version + of this OS satisfies the constraint. + Format: "major.minor.patch" such as "10.5.301", "9.2.1". + - name: 'osType' + type: Enum + description: | + The operating system type of the device. + required: true + enum_values: + - 'OS_UNSPECIFIED' + - 'DESKTOP_MAC' + - 'DESKTOP_WINDOWS' + - 'DESKTOP_LINUX' + - 'DESKTOP_CHROME_OS' + - 'ANDROID' + - 'IOS' + - name: 'requireAdminApproval' + type: Boolean + description: | + Whether the device needs to be approved by the customer admin. + - name: 'requireCorpOwned' + type: Boolean + description: | + Whether the device needs to be corp owned. + - name: 'regions' + type: Array + description: | + The request must originate from one of the provided + countries/regions. + Format: A valid ISO 3166-1 alpha-2 code. + item_type: + type: String + - name: 'vpcNetworkSources' + type: Array + description: 'The request must originate from one of the provided VPC networks in Google Cloud. Cannot specify this field together with `ip_subnetworks`.' + item_type: + type: NestedObject + properties: + - name: 'vpcSubnetwork' + type: NestedObject + description: 'Sub networks within a VPC network.' + properties: + - name: 'network' + type: String + description: 'Required. Network name to be allowed by this Access Level. Networks of foreign organizations requires `compute.network.get` permission to be granted to caller.' + required: true + - name: 'vpcIpSubnetworks' + type: Array + description: 'CIDR block IP subnetwork specification. Must be IPv4.' + item_type: + type: String + min_size: 1 + - name: 'custom' + type: NestedObject + description: | + Custom access level conditions are set using the Cloud Common Expression Language to represent the necessary conditions for the level to apply to a request. + See CEL spec at: https://github.com/google/cel-spec. + # conflicts: + # - basic + properties: + - name: 'expr' + type: NestedObject + description: | + Represents a textual expression in the Common Expression Language (CEL) syntax. CEL is a C-like expression language. + This page details the objects and attributes that are used to the build the CEL expressions for + custom access levels - https://cloud.google.com/access-context-manager/docs/custom-access-level-spec. + required: true + properties: + - name: 'expression' + type: String + description: + Textual representation of an expression in Common Expression + Language syntax. + required: true + - name: 'title' + type: String + description: + Title for the expression, i.e. a short string describing its + purpose. + - name: 'description' + type: String + description: Description of the expression + - name: 'location' + type: String + description: + String indicating the location of the expression for error + reporting, e.g. a file name and a position in the file diff --git a/mmv1/products/accesscontextmanager/go_AccessPolicy.yaml b/mmv1/products/accesscontextmanager/go_AccessPolicy.yaml new file mode 100644 index 000000000000..87b0638bebc7 --- /dev/null +++ b/mmv1/products/accesscontextmanager/go_AccessPolicy.yaml @@ -0,0 +1,117 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'AccessPolicy' +description: | + AccessPolicy is a container for AccessLevels (which define the necessary + attributes to use GCP services) and ServicePerimeters (which define + regions of services able to freely pass data within a perimeter). An + access policy is globally visible within an organization, and the + restrictions it specifies apply to all projects within an organization. +references: + guides: + 'Access Policy Quickstart': 'https://cloud.google.com/access-context-manager/docs/quickstart' + api: 'https://cloud.google.com/access-context-manager/docs/reference/rest/v1/accessPolicies' +docs: + warning: | + If you are using User ADCs (Application Default Credentials) with this resource, + you must specify a `billing_project` and set `user_project_override` to true + in the provider configuration. Otherwise the ACM API will return a 403 error. + Your account must have the `serviceusage.services.use` permission on the + `billing_project` you defined. +id_format: '{{name}}' +base_url: 'accessPolicies' +self_link: 'accessPolicies/{{name}}' +update_verb: 'PATCH' +update_mask: true +import_format: + - '{{name}}' +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +autogen_async: true +async: + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: true + error: + path: 'error' + message: 'message' +iam_policy: + method_name_separator: ':' + fetch_iam_policy_verb: 'POST' + allowed_iam_role: 'roles/accesscontextmanager.policyAdmin' + parent_resource_attribute: 'name' + import_format: + - 'accessPolicies/{{name}}' + - '{{name}}' +custom_code: + post_create: 'templates/terraform/post_create/go/accesspolicy.tmpl' +skip_sweeper: true +examples: + - name: 'access_context_manager_access_policy_basic' + primary_resource_id: 'access-policy' + skip_test: true + - name: 'access_context_manager_access_policy_scoped' + primary_resource_id: 'access-policy' + test_env_vars: + org_id: 'ORG_ID' + project: 'PROJECT_NAME' + skip_test: true + skip_import_test: true +parameters: + - name: 'parent' + type: String + description: | + The parent of this AccessPolicy in the Cloud Resource Hierarchy. + Format: organizations/{organization_id} + required: true + immutable: true + - name: 'title' + type: String + description: | + Human readable title. Does not affect behavior. + required: true + - name: 'scopes' + type: Array + description: | + Folder or project on which this policy is applicable. + Format: folders/{{folder_id}} or projects/{{project_id}} + item_type: + type: String + max_size: 1 +properties: + - name: 'name' + type: String + description: | + Resource name of the AccessPolicy. Format: {policy_id} + output: true + custom_flatten: 'templates/terraform/custom_flatten/go/name_from_self_link.tmpl' + - name: 'createTime' + type: Time + description: | + Time the AccessPolicy was created in UTC. + output: true + - name: 'updateTime' + type: Time + description: | + Time the AccessPolicy was updated in UTC. + output: true diff --git a/mmv1/products/accesscontextmanager/go_AuthorizedOrgsDesc.yaml b/mmv1/products/accesscontextmanager/go_AuthorizedOrgsDesc.yaml new file mode 100644 index 000000000000..abb66161283c --- /dev/null +++ b/mmv1/products/accesscontextmanager/go_AuthorizedOrgsDesc.yaml @@ -0,0 +1,145 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'AuthorizedOrgsDesc' +description: | + An authorized organizations description describes a list of organizations + (1) that have been authorized to use certain asset (for example, device) data + owned by different organizations at the enforcement points, or (2) with certain + asset (for example, device) have been authorized to access the resources in + another organization at the enforcement points. +references: + guides: + 'gcloud docs': 'https://cloud.google.com/beyondcorp-enterprise/docs/cross-org-authorization' + api: 'https://cloud.google.com/access-context-manager/docs/reference/rest/v1/accessPolicies.authorizedOrgsDescs' +docs: + warning: | + If you are using User ADCs (Application Default Credentials) with this resource, + you must specify a `billing_project` and set `user_project_override` to true + in the provider configuration. Otherwise the ACM API will return a 403 error. + Your account must have the `serviceusage.services.use` permission on the + `billing_project` you defined. +id_format: '{{name}}' +base_url: '' +self_link: '{{name}}' +create_url: '{{parent}}/authorizedOrgsDescs' +update_verb: 'PATCH' +import_format: + - '{{name}}' +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +autogen_async: true +async: + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: true + error: + path: 'error' + message: 'message' +custom_code: + encoder: 'templates/terraform/encoders/go/access_level_never_send_parent.go.tmpl' + post_create: 'templates/terraform/post_create/go/sleep_2_min.go.tmpl' + pre_update: 'templates/terraform/update_mask.go.tmpl' + custom_import: 'templates/terraform/custom_import/go/set_access_policy_parent_from_self_link.go.tmpl' +skip_sweeper: true +examples: + - name: 'access_context_manager_authorized_orgs_desc_basic' + primary_resource_id: 'authorized-orgs-desc' + skip_test: true +parameters: + - name: 'parent' + type: String + description: | + Required. Resource name for the access policy which owns this `AuthorizedOrgsDesc`. + required: true + immutable: true + ignore_read: true + - name: 'name' + type: String + description: | + Resource name for the `AuthorizedOrgsDesc`. Format: + `accessPolicies/{access_policy}/authorizedOrgsDescs/{authorized_orgs_desc}`. + The `authorized_orgs_desc` component must begin with a letter, followed by + alphanumeric characters or `_`. + After you create an `AuthorizedOrgsDesc`, you cannot change its `name`. + required: true + immutable: true + - name: 'orgs' + type: Array + description: | + The list of organization ids in this AuthorizedOrgsDesc. + Format: `organizations/` + Example: `organizations/123456` + item_type: + type: String + - name: 'assetType' + type: Enum + description: | + The type of entities that need to use the authorization relationship during + evaluation, such as a device. Valid values are "ASSET_TYPE_DEVICE" and + "ASSET_TYPE_CREDENTIAL_STRENGTH". + immutable: true + enum_values: + - 'ASSET_TYPE_DEVICE' + - 'ASSET_TYPE_CREDENTIAL_STRENGTH' + - name: 'authorizationDirection' + type: Enum + description: | + The direction of the authorization relationship between this organization + and the organizations listed in the "orgs" field. The valid values for this + field include the following: + + AUTHORIZATION_DIRECTION_FROM: Allows this organization to evaluate traffic + in the organizations listed in the `orgs` field. + + AUTHORIZATION_DIRECTION_TO: Allows the organizations listed in the `orgs` + field to evaluate the traffic in this organization. + + For the authorization relationship to take effect, all of the organizations + must authorize and specify the appropriate relationship direction. For + example, if organization A authorized organization B and C to evaluate its + traffic, by specifying "AUTHORIZATION_DIRECTION_TO" as the authorization + direction, organizations B and C must specify + "AUTHORIZATION_DIRECTION_FROM" as the authorization direction in their + "AuthorizedOrgsDesc" resource. + immutable: true + enum_values: + - 'AUTHORIZATION_DIRECTION_TO' + - 'AUTHORIZATION_DIRECTION_FROM' + - name: 'authorizationType' + type: Enum + description: | + A granular control type for authorization levels. Valid value is "AUTHORIZATION_TYPE_TRUST". + immutable: true + enum_values: + - 'AUTHORIZATION_TYPE_TRUST' +properties: + - name: 'createTime' + type: Time + description: | + Time the AuthorizedOrgsDesc was created in UTC. + output: true + - name: 'updateTime' + type: Time + description: | + Time the AuthorizedOrgsDesc was updated in UTC. + output: true diff --git a/mmv1/products/accesscontextmanager/go_EgressPolicy.yaml b/mmv1/products/accesscontextmanager/go_EgressPolicy.yaml new file mode 100644 index 000000000000..91f2abd49b74 --- /dev/null +++ b/mmv1/products/accesscontextmanager/go_EgressPolicy.yaml @@ -0,0 +1,78 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'EgressPolicy' +description: | + This resource has been deprecated, please refer to ServicePerimeterEgressPolicy. +references: + guides: + api: 'https://cloud.google.com/access-context-manager/docs/reference/rest/v1/accessPolicies.servicePerimeters#egresspolicy' +docs: +id_format: '{{egress_policy_name}}/{{resource}}' +base_url: '' +self_link: '{{egress_policy_name}}' +create_url: '{{egress_policy_name}}' +create_verb: 'PATCH' +update_mask: true +delete_verb: 'PATCH' +immutable: true +import_format: + - '{{egress_policy_name}}/{{resource}}' +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +autogen_async: true +async: + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: true + error: + path: 'error' + message: 'message' +identity: + - resource +nested_query: + keys: + - status + - resources + is_list_of_ids: true + modify_by_patch: true +custom_code: + custom_import: 'templates/terraform/custom_import/go/access_context_manager_service_perimeter_egress_policy.go.tmpl' +exclude_tgc: true +skip_sweeper: true +parameters: + - name: 'egressPolicyName' + type: ResourceRef + description: | + The name of the Service Perimeter to add this resource to. + url_param_only: true + required: true + immutable: true + resource: 'ServicePerimeter' + imports: 'name' +properties: + - name: 'resource' + type: String + description: | + A GCP resource that is inside of the service perimeter. + required: true + immutable: true diff --git a/mmv1/products/accesscontextmanager/go_GcpUserAccessBinding.yaml b/mmv1/products/accesscontextmanager/go_GcpUserAccessBinding.yaml new file mode 100644 index 000000000000..be828126326a --- /dev/null +++ b/mmv1/products/accesscontextmanager/go_GcpUserAccessBinding.yaml @@ -0,0 +1,90 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'GcpUserAccessBinding' +description: | + Restricts access to Cloud Console and Google Cloud APIs for a set of users using Context-Aware Access. +references: + guides: + api: 'https://cloud.google.com/access-context-manager/docs/reference/rest/v1/organizations.gcpUserAccessBindings' +docs: +id_format: '{{name}}' +base_url: 'organizations/{{organization_id}}/gcpUserAccessBindings' +self_link: '{{name}}' +update_verb: 'PATCH' +update_mask: true +import_format: + - '{{name}}' +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +autogen_async: true +async: + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: true + error: + path: 'error' + message: 'message' +custom_code: + custom_import: 'templates/terraform/custom_import/go/set_id_name_with_slashes.go.tmpl' +exclude_tgc: true +examples: + - name: 'access_context_manager_gcp_user_access_binding_basic' + primary_resource_id: 'gcp_user_access_binding' + vars: + group_id: 'my-identity-group' + access_level_id: 'access_level_id_for_user_access_binding' + access_level_name: 'chromeos_no_lock' + test_env_vars: + org_id: 'ORG_ID' + org_domain: 'ORG_DOMAIN' + cust_id: 'CUST_ID' + skip_test: true +parameters: + - name: 'organizationId' + type: String + description: | + Required. ID of the parent organization. + url_param_only: true + required: true + immutable: true +properties: + - name: 'name' + type: String + description: | + Immutable. Assigned by the server during creation. The last segment has an arbitrary length and has only URI unreserved characters (as defined by RFC 3986 Section 2.3). Should not be specified by the client during creation. Example: "organizations/256/gcpUserAccessBindings/b3-BhcX_Ud5N" + output: true + - name: 'groupKey' + type: String + description: | + Required. Immutable. Google Group id whose members are subject to this binding's restrictions. See "id" in the G Suite Directory API's Groups resource. If a group's email address/alias is changed, this resource will continue to point at the changed group. This field does not accept group email addresses or aliases. Example: "01d520gv4vjcrht" + required: true + immutable: true + - name: 'accessLevels' + type: Array + description: | + Required. Access level that a user must have to be granted access. Only one access level is supported, not multiple. This repeated field must have exactly one element. Example: "accessPolicies/9522/accessLevels/device_trusted" + required: true + item_type: + type: String + min_size: 1 + max_size: 1 diff --git a/mmv1/products/accesscontextmanager/go_IngressPolicy.yaml b/mmv1/products/accesscontextmanager/go_IngressPolicy.yaml new file mode 100644 index 000000000000..83fe4955ad85 --- /dev/null +++ b/mmv1/products/accesscontextmanager/go_IngressPolicy.yaml @@ -0,0 +1,78 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'IngressPolicy' +description: | + This resource has been deprecated, please refer to ServicePerimeterIngressPolicy. +references: + guides: + api: 'https://cloud.google.com/access-context-manager/docs/reference/rest/v1/accessPolicies.servicePerimeters#ingresspolicy' +docs: +id_format: '{{ingress_policy_name}}/{{resource}}' +base_url: '' +self_link: '{{ingress_policy_name}}' +create_url: '{{ingress_policy_name}}' +create_verb: 'PATCH' +update_mask: true +delete_verb: 'PATCH' +immutable: true +import_format: + - '{{ingress_policy_name}}/{{resource}}' +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +autogen_async: true +async: + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: true + error: + path: 'error' + message: 'message' +identity: + - resource +nested_query: + keys: + - status + - resources + is_list_of_ids: true + modify_by_patch: true +custom_code: + custom_import: 'templates/terraform/custom_import/go/access_context_manager_service_perimeter_ingress_policy.go.tmpl' +exclude_tgc: true +skip_sweeper: true +parameters: + - name: 'ingressPolicyName' + type: ResourceRef + description: | + The name of the Service Perimeter to add this resource to. + url_param_only: true + required: true + immutable: true + resource: 'ServicePerimeter' + imports: 'name' +properties: + - name: 'resource' + type: String + description: | + A GCP resource that is inside of the service perimeter. + required: true + immutable: true diff --git a/mmv1/products/accesscontextmanager/go_ServicePerimeter.yaml b/mmv1/products/accesscontextmanager/go_ServicePerimeter.yaml new file mode 100644 index 000000000000..b6fef8a42c86 --- /dev/null +++ b/mmv1/products/accesscontextmanager/go_ServicePerimeter.yaml @@ -0,0 +1,768 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'ServicePerimeter' +description: | + ServicePerimeter describes a set of GCP resources which can freely import + and export data amongst themselves, but not export outside of the + ServicePerimeter. If a request with a source within this ServicePerimeter + has a target outside of the ServicePerimeter, the request will be blocked. + Otherwise the request is allowed. There are two types of Service Perimeter + - Regular and Bridge. Regular Service Perimeters cannot overlap, a single + GCP project can only belong to a single regular Service Perimeter. Service + Perimeter Bridges can contain only GCP projects as members, a single GCP + project may belong to multiple Service Perimeter Bridges. +references: + guides: + 'Service Perimeter Quickstart': 'https://cloud.google.com/vpc-service-controls/docs/quickstart' + api: 'https://cloud.google.com/access-context-manager/docs/reference/rest/v1/accessPolicies.servicePerimeters' +docs: + warning: | + If you are using User ADCs (Application Default Credentials) with this resource, + you must specify a `billing_project` and set `user_project_override` to true + in the provider configuration. Otherwise the ACM API will return a 403 error. + Your account must have the `serviceusage.services.use` permission on the + `billing_project` you defined. +id_format: '{{name}}' +base_url: '' +self_link: '{{name}}' +create_url: '{{parent}}/servicePerimeters' +update_verb: 'PATCH' +update_mask: true +mutex: '{{name}}' +import_format: + - '{{name}}' +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +autogen_async: true +async: + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: true + error: + path: 'error' + message: 'message' +custom_code: + encoder: 'templates/terraform/encoders/go/access_level_never_send_parent.go.tmpl' + custom_import: 'templates/terraform/custom_import/go/set_access_policy_parent_from_self_link.go.tmpl' +skip_sweeper: true +examples: + - name: 'access_context_manager_service_perimeter_basic' + primary_resource_id: 'service-perimeter' + vars: + access_level_name: 'chromeos_no_lock' + service_perimeter_name: 'restrict_storage' + skip_test: true + - name: 'access_context_manager_service_perimeter_secure_data_exchange' + primary_resource_id: 'secure-data-exchange' + vars: + access_level_name: 'secure_data_exchange' + skip_test: true + - name: 'access_context_manager_service_perimeter_dry-run' + primary_resource_id: 'service-perimeter' + vars: + service_perimeter_name: 'restrict_bigquery_dryrun_storage' + skip_test: true +parameters: + - name: 'parent' + type: String + description: | + The AccessPolicy this ServicePerimeter lives in. + Format: accessPolicies/{policy_id} + required: true + immutable: true + ignore_read: true + - name: 'name' + type: String + description: | + Resource name for the ServicePerimeter. The short_name component must + begin with a letter and only include alphanumeric and '_'. + Format: accessPolicies/{policy_id}/servicePerimeters/{short_name} + required: true + immutable: true +properties: + - name: 'title' + type: String + description: | + Human readable title. Must be unique within the Policy. + required: true + - name: 'description' + type: String + description: | + Description of the ServicePerimeter and its use. Does not affect + behavior. + - name: 'createTime' + type: Time + description: | + Time the AccessPolicy was created in UTC. + output: true + - name: 'updateTime' + type: Time + description: | + Time the AccessPolicy was updated in UTC. + output: true + - name: 'perimeterType' + type: Enum + description: | + Specifies the type of the Perimeter. There are two types: regular and + bridge. Regular Service Perimeter contains resources, access levels, + and restricted services. Every resource can be in at most + ONE regular Service Perimeter. + + In addition to being in a regular service perimeter, a resource can also + be in zero or more perimeter bridges. A perimeter bridge only contains + resources. Cross project operations are permitted if all effected + resources share some perimeter (whether bridge or regular). Perimeter + Bridge does not contain access levels or services: those are governed + entirely by the regular perimeter that resource is in. + + Perimeter Bridges are typically useful when building more complex + topologies with many independent perimeters that need to share some data + with a common perimeter, but should not be able to share data among + themselves. + immutable: true + custom_flatten: 'templates/terraform/custom_flatten/go/default_if_empty.tmpl' + default_value: "PERIMETER_TYPE_REGULAR" + enum_values: + - 'PERIMETER_TYPE_REGULAR' + - 'PERIMETER_TYPE_BRIDGE' + - name: 'status' + type: NestedObject + description: | + ServicePerimeter configuration. Specifies sets of resources, + restricted services and access levels that determine + perimeter content and boundaries. + properties: + - name: 'resources' + type: Array + description: | + A list of GCP resources that are inside of the service perimeter. + Currently only projects are allowed. + Format: projects/{project_number} + is_set: true + at_least_one_of: + - 'status.0.resources' + - 'status.0.access_levels' + - 'status.0.restricted_services' + item_type: + type: String + - name: 'accessLevels' + type: Array + description: | + A list of AccessLevel resource names that allow resources within + the ServicePerimeter to be accessed from the internet. + AccessLevels listed must be in the same policy as this + ServicePerimeter. Referencing a nonexistent AccessLevel is a + syntax error. If no AccessLevel names are listed, resources within + the perimeter can only be accessed via GCP calls with request + origins within the perimeter. For Service Perimeter Bridge, must + be empty. + + Format: accessPolicies/{policy_id}/accessLevels/{access_level_name} + is_set: true + at_least_one_of: + - 'status.0.resources' + - 'status.0.access_levels' + - 'status.0.restricted_services' + item_type: + type: String + - name: 'restrictedServices' + type: Array + description: | + GCP services that are subject to the Service Perimeter + restrictions. Must contain a list of services. For example, if + `storage.googleapis.com` is specified, access to the storage + buckets inside the perimeter must meet the perimeter's access + restrictions. + is_set: true + at_least_one_of: + - 'status.0.resources' + - 'status.0.access_levels' + - 'status.0.restricted_services' + item_type: + type: String + - name: 'vpcAccessibleServices' + type: NestedObject + description: | + Specifies how APIs are allowed to communicate within the Service + Perimeter. + properties: + - name: 'enableRestriction' + type: Boolean + description: | + Whether to restrict API calls within the Service Perimeter to the + list of APIs specified in 'allowedServices'. + - name: 'allowedServices' + type: Array + description: | + The list of APIs usable within the Service Perimeter. + Must be empty unless `enableRestriction` is True. + is_set: true + item_type: + type: String + - name: 'ingressPolicies' + type: Array + description: | + List of `IngressPolicies` to apply to the perimeter. A perimeter may + have multiple `IngressPolicies`, each of which is evaluated + separately. Access is granted if any `Ingress Policy` grants it. + Must be empty for a perimeter bridge. + item_type: + type: NestedObject + properties: + - name: 'ingressFrom' + type: NestedObject + description: | + Defines the conditions on the source of a request causing this `IngressPolicy` + to apply. + properties: + - name: 'identityType' + type: Enum + description: | + Specifies the type of identities that are allowed access from outside the + perimeter. If left unspecified, then members of `identities` field will be + allowed access. + enum_values: + - 'IDENTITY_TYPE_UNSPECIFIED' + - 'ANY_IDENTITY' + - 'ANY_USER_ACCOUNT' + - 'ANY_SERVICE_ACCOUNT' + - name: 'identities' + type: Array + description: | + A list of identities that are allowed access through this ingress policy. + Should be in the format of email address. The email address should represent + individual user or service account only. + is_set: true + item_type: + type: String + - name: 'sources' + type: Array + description: | + Sources that this `IngressPolicy` authorizes access from. + item_type: + type: NestedObject + properties: + - name: 'accessLevel' + type: String + description: | + An `AccessLevel` resource name that allow resources within the + `ServicePerimeters` to be accessed from the internet. `AccessLevels` listed + must be in the same policy as this `ServicePerimeter`. Referencing a nonexistent + `AccessLevel` will cause an error. If no `AccessLevel` names are listed, + resources within the perimeter can only be accessed via Google Cloud calls + with request origins within the perimeter. + Example `accessPolicies/MY_POLICY/accessLevels/MY_LEVEL.` + If * is specified, then all IngressSources will be allowed. + - name: 'resource' + type: String + description: | + A Google Cloud resource that is allowed to ingress the perimeter. + Requests from these resources will be allowed to access perimeter data. + Currently only projects are allowed. Format `projects/{project_number}` + The project may be in any Google Cloud organization, not just the + organization that the perimeter is defined in. `*` is not allowed, the case + of allowing all Google Cloud resources only is not supported. + - name: 'ingressTo' + type: NestedObject + description: | + Defines the conditions on the `ApiOperation` and request destination that cause + this `IngressPolicy` to apply. + properties: + - name: 'resources' + type: Array + description: | + A list of resources, currently only projects in the form + `projects/`, protected by this `ServicePerimeter` + that are allowed to be accessed by sources defined in the + corresponding `IngressFrom`. A request matches if it contains + a resource in this list. If `*` is specified for resources, + then this `IngressTo` rule will authorize access to all + resources inside the perimeter, provided that the request + also matches the `operations` field. + is_set: true + item_type: + type: String + - name: 'operations' + type: Array + description: | + A list of `ApiOperations` the sources specified in corresponding `IngressFrom` + are allowed to perform in this `ServicePerimeter`. + item_type: + type: NestedObject + properties: + - name: 'serviceName' + type: String + description: | + The name of the API whose methods or permissions the `IngressPolicy` or + `EgressPolicy` want to allow. A single `ApiOperation` with `serviceName` + field set to `*` will allow all methods AND permissions for all services. + - name: 'methodSelectors' + type: Array + description: | + API methods or permissions to allow. Method or permission must belong to + the service specified by serviceName field. A single `MethodSelector` entry + with `*` specified for the method field will allow all methods AND + permissions for the service specified in `serviceName`. + item_type: + type: NestedObject + properties: + - name: 'method' + type: String + description: | + Value for method should be a valid method name for the corresponding + serviceName in `ApiOperation`. If `*` used as value for `method`, then + ALL methods and permissions are allowed. + - name: 'permission' + type: String + description: | + Value for permission should be a valid Cloud IAM permission for the + corresponding `serviceName` in `ApiOperation`. + - name: 'egressPolicies' + type: Array + description: | + List of EgressPolicies to apply to the perimeter. A perimeter may + have multiple EgressPolicies, each of which is evaluated separately. + Access is granted if any EgressPolicy grants it. Must be empty for + a perimeter bridge. + item_type: + type: NestedObject + properties: + - name: 'egressFrom' + type: NestedObject + description: | + Defines conditions on the source of a request causing this `EgressPolicy` to apply. + properties: + - name: 'identityType' + type: Enum + description: | + Specifies the type of identities that are allowed access to outside the + perimeter. If left unspecified, then members of `identities` field will + be allowed access. + enum_values: + - 'IDENTITY_TYPE_UNSPECIFIED' + - 'ANY_IDENTITY' + - 'ANY_USER_ACCOUNT' + - 'ANY_SERVICE_ACCOUNT' + - name: 'sources' + type: Array + description: 'Sources that this EgressPolicy authorizes access from.' + item_type: + type: NestedObject + properties: + - name: 'accessLevel' + type: String + description: 'An AccessLevel resource name that allows resources outside the ServicePerimeter to be accessed from the inside.' + - name: 'sourceRestriction' + type: Enum + description: 'Whether to enforce traffic restrictions based on `sources` field. If the `sources` field is non-empty, then this field must be set to `SOURCE_RESTRICTION_ENABLED`.' + enum_values: + - 'SOURCE_RESTRICTION_UNSPECIFIED' + - 'SOURCE_RESTRICTION_ENABLED' + - 'SOURCE_RESTRICTION_DISABLED' + - name: 'identities' + type: Array + description: | + A list of identities that are allowed access through this `EgressPolicy`. + Should be in the format of email address. The email address should + represent individual user or service account only. + is_set: true + item_type: + type: String + - name: 'egressTo' + type: NestedObject + description: | + Defines the conditions on the `ApiOperation` and destination resources that + cause this `EgressPolicy` to apply. + properties: + - name: 'resources' + type: Array + description: | + A list of resources, currently only projects in the form + `projects/`, that match this to stanza. A request matches + if it contains a resource in this list. If * is specified for resources, + then this `EgressTo` rule will authorize access to all resources outside + the perimeter. + is_set: true + item_type: + type: String + - name: 'externalResources' + type: Array + description: | + A list of external resources that are allowed to be accessed. A request + matches if it contains an external resource in this list (Example: + s3://bucket/path). Currently '*' is not allowed. + is_set: true + item_type: + type: String + - name: 'operations' + type: Array + description: | + A list of `ApiOperations` that this egress rule applies to. A request matches + if it contains an operation/service in this list. + item_type: + type: NestedObject + properties: + - name: 'serviceName' + type: String + description: | + The name of the API whose methods or permissions the `IngressPolicy` or + `EgressPolicy` want to allow. A single `ApiOperation` with serviceName + field set to `*` will allow all methods AND permissions for all services. + - name: 'methodSelectors' + type: Array + description: | + API methods or permissions to allow. Method or permission must belong + to the service specified by `serviceName` field. A single MethodSelector + entry with `*` specified for the `method` field will allow all methods + AND permissions for the service specified in `serviceName`. + item_type: + type: NestedObject + properties: + - name: 'method' + type: String + description: | + Value for `method` should be a valid method name for the corresponding + `serviceName` in `ApiOperation`. If `*` used as value for method, + then ALL methods and permissions are allowed. + - name: 'permission' + type: String + description: | + Value for permission should be a valid Cloud IAM permission for the + corresponding `serviceName` in `ApiOperation`. + - name: 'spec' + type: NestedObject + description: | + Proposed (or dry run) ServicePerimeter configuration. + This configuration allows to specify and test ServicePerimeter configuration + without enforcing actual access restrictions. Only allowed to be set when + the `useExplicitDryRunSpec` flag is set. + properties: + - name: 'resources' + type: Array + description: | + A list of GCP resources that are inside of the service perimeter. + Currently only projects are allowed. + Format: projects/{project_number} + is_set: true + at_least_one_of: + - 'spec.0.resources' + - 'spec.0.access_levels' + - 'spec.0.restricted_services' + item_type: + type: String + - name: 'accessLevels' + type: Array + description: | + A list of AccessLevel resource names that allow resources within + the ServicePerimeter to be accessed from the internet. + AccessLevels listed must be in the same policy as this + ServicePerimeter. Referencing a nonexistent AccessLevel is a + syntax error. If no AccessLevel names are listed, resources within + the perimeter can only be accessed via GCP calls with request + origins within the perimeter. For Service Perimeter Bridge, must + be empty. + + Format: accessPolicies/{policy_id}/accessLevels/{access_level_name} + is_set: true + at_least_one_of: + - 'spec.0.resources' + - 'spec.0.access_levels' + - 'spec.0.restricted_services' + item_type: + type: String + - name: 'restrictedServices' + type: Array + description: | + GCP services that are subject to the Service Perimeter + restrictions. Must contain a list of services. For example, if + `storage.googleapis.com` is specified, access to the storage + buckets inside the perimeter must meet the perimeter's access + restrictions. + is_set: true + at_least_one_of: + - 'spec.0.resources' + - 'spec.0.access_levels' + - 'spec.0.restricted_services' + item_type: + type: String + - name: 'vpcAccessibleServices' + type: NestedObject + description: | + Specifies how APIs are allowed to communicate within the Service + Perimeter. + properties: + - name: 'enableRestriction' + type: Boolean + description: | + Whether to restrict API calls within the Service Perimeter to the + list of APIs specified in 'allowedServices'. + - name: 'allowedServices' + type: Array + description: | + The list of APIs usable within the Service Perimeter. + Must be empty unless `enableRestriction` is True. + is_set: true + item_type: + type: String + - name: 'ingressPolicies' + type: Array + description: | + List of `IngressPolicies` to apply to the perimeter. A perimeter may + have multiple `IngressPolicies`, each of which is evaluated + separately. Access is granted if any `Ingress Policy` grants it. + Must be empty for a perimeter bridge. + item_type: + type: NestedObject + properties: + - name: 'ingressFrom' + type: NestedObject + description: | + Defines the conditions on the source of a request causing this `IngressPolicy` + to apply. + properties: + - name: 'identityType' + type: Enum + description: | + Specifies the type of identities that are allowed access from outside the + perimeter. If left unspecified, then members of `identities` field will be + allowed access. + enum_values: + - 'IDENTITY_TYPE_UNSPECIFIED' + - 'ANY_IDENTITY' + - 'ANY_USER_ACCOUNT' + - 'ANY_SERVICE_ACCOUNT' + - name: 'identities' + type: Array + description: | + A list of identities that are allowed access through this ingress policy. + Should be in the format of email address. The email address should represent + individual user or service account only. + is_set: true + item_type: + type: String + - name: 'sources' + type: Array + description: | + Sources that this `IngressPolicy` authorizes access from. + item_type: + type: NestedObject + properties: + - name: 'accessLevel' + type: String + description: | + An `AccessLevel` resource name that allow resources within the + `ServicePerimeters` to be accessed from the internet. `AccessLevels` listed + must be in the same policy as this `ServicePerimeter`. Referencing a nonexistent + `AccessLevel` will cause an error. If no `AccessLevel` names are listed, + resources within the perimeter can only be accessed via Google Cloud calls + with request origins within the perimeter. + Example `accessPolicies/MY_POLICY/accessLevels/MY_LEVEL.` + If * is specified, then all IngressSources will be allowed. + - name: 'resource' + type: String + description: | + A Google Cloud resource that is allowed to ingress the perimeter. + Requests from these resources will be allowed to access perimeter data. + Currently only projects are allowed. Format `projects/{project_number}` + The project may be in any Google Cloud organization, not just the + organization that the perimeter is defined in. `*` is not allowed, the case + of allowing all Google Cloud resources only is not supported. + - name: 'ingressTo' + type: NestedObject + description: | + Defines the conditions on the `ApiOperation` and request destination that cause + this `IngressPolicy` to apply. + properties: + - name: 'resources' + type: Array + description: | + A list of resources, currently only projects in the form + `projects/`, protected by this `ServicePerimeter` + that are allowed to be accessed by sources defined in the + corresponding `IngressFrom`. A request matches if it contains + a resource in this list. If `*` is specified for resources, + then this `IngressTo` rule will authorize access to all + resources inside the perimeter, provided that the request + also matches the `operations` field. + is_set: true + item_type: + type: String + - name: 'operations' + type: Array + description: | + A list of `ApiOperations` the sources specified in corresponding `IngressFrom` + are allowed to perform in this `ServicePerimeter`. + item_type: + type: NestedObject + properties: + - name: 'serviceName' + type: String + description: | + The name of the API whose methods or permissions the `IngressPolicy` or + `EgressPolicy` want to allow. A single `ApiOperation` with `serviceName` + field set to `*` will allow all methods AND permissions for all services. + - name: 'methodSelectors' + type: Array + description: | + API methods or permissions to allow. Method or permission must belong to + the service specified by serviceName field. A single `MethodSelector` entry + with `*` specified for the method field will allow all methods AND + permissions for the service specified in `serviceName`. + item_type: + type: NestedObject + properties: + - name: 'method' + type: String + description: | + Value for method should be a valid method name for the corresponding + serviceName in `ApiOperation`. If `*` used as value for `method`, then + ALL methods and permissions are allowed. + - name: 'permission' + type: String + description: | + Value for permission should be a valid Cloud IAM permission for the + corresponding `serviceName` in `ApiOperation`. + - name: 'egressPolicies' + type: Array + description: | + List of EgressPolicies to apply to the perimeter. A perimeter may + have multiple EgressPolicies, each of which is evaluated separately. + Access is granted if any EgressPolicy grants it. Must be empty for + a perimeter bridge. + item_type: + type: NestedObject + properties: + - name: 'egressFrom' + type: NestedObject + description: | + Defines conditions on the source of a request causing this `EgressPolicy` to apply. + properties: + - name: 'identityType' + type: Enum + description: | + Specifies the type of identities that are allowed access to outside the + perimeter. If left unspecified, then members of `identities` field will + be allowed access. + enum_values: + - 'IDENTITY_TYPE_UNSPECIFIED' + - 'ANY_IDENTITY' + - 'ANY_USER_ACCOUNT' + - 'ANY_SERVICE_ACCOUNT' + - name: 'sources' + type: Array + description: 'Sources that this EgressPolicy authorizes access from.' + item_type: + type: NestedObject + properties: + - name: 'accessLevel' + type: String + description: 'An AccessLevel resource name that allows resources outside the ServicePerimeter to be accessed from the inside.' + - name: 'sourceRestriction' + type: Enum + description: 'Whether to enforce traffic restrictions based on `sources` field. If the `sources` field is non-empty, then this field must be set to `SOURCE_RESTRICTION_ENABLED`.' + enum_values: + - 'SOURCE_RESTRICTION_UNSPECIFIED' + - 'SOURCE_RESTRICTION_ENABLED' + - 'SOURCE_RESTRICTION_DISABLED' + - name: 'identities' + type: Array + description: | + A list of identities that are allowed access through this `EgressPolicy`. + Should be in the format of email address. The email address should + represent individual user or service account only. + is_set: true + item_type: + type: String + - name: 'egressTo' + type: NestedObject + description: | + Defines the conditions on the `ApiOperation` and destination resources that + cause this `EgressPolicy` to apply. + properties: + - name: 'resources' + type: Array + description: | + A list of resources, currently only projects in the form + `projects/`, that match this to stanza. A request matches + if it contains a resource in this list. If * is specified for resources, + then this `EgressTo` rule will authorize access to all resources outside + the perimeter. + is_set: true + item_type: + type: String + - name: 'externalResources' + type: Array + description: | + A list of external resources that are allowed to be accessed. A request + matches if it contains an external resource in this list (Example: + s3://bucket/path). Currently '*' is not allowed. + is_set: true + item_type: + type: String + - name: 'operations' + type: Array + description: | + A list of `ApiOperations` that this egress rule applies to. A request matches + if it contains an operation/service in this list. + item_type: + type: NestedObject + properties: + - name: 'serviceName' + type: String + description: | + The name of the API whose methods or permissions the `IngressPolicy` or + `EgressPolicy` want to allow. A single `ApiOperation` with serviceName + field set to `*` will allow all methods AND permissions for all services. + - name: 'methodSelectors' + type: Array + description: | + API methods or permissions to allow. Method or permission must belong + to the service specified by `serviceName` field. A single MethodSelector + entry with `*` specified for the `method` field will allow all methods + AND permissions for the service specified in `serviceName`. + item_type: + type: NestedObject + properties: + - name: 'method' + type: String + description: | + Value for `method` should be a valid method name for the corresponding + `serviceName` in `ApiOperation`. If `*` used as value for method, + then ALL methods and permissions are allowed. + - name: 'permission' + type: String + description: | + Value for permission should be a valid Cloud IAM permission for the + corresponding `serviceName` in `ApiOperation`. + - name: 'useExplicitDryRunSpec' + type: Boolean + description: | + Use explicit dry run spec flag. Ordinarily, a dry-run spec implicitly exists + for all Service Perimeters, and that spec is identical to the status for those + Service Perimeters. When this flag is set, it inhibits the generation of the + implicit spec, thereby allowing the user to explicitly provide a + configuration ("spec") to use in a dry-run version of the Service Perimeter. + This allows the user to test changes to the enforced config ("status") without + actually enforcing them. This testing is done through analyzing the differences + between currently enforced and suggested restrictions. useExplicitDryRunSpec must + bet set to True if any of the fields in the spec are set to non-default values. diff --git a/mmv1/products/accesscontextmanager/go_ServicePerimeterDryRunResource.yaml b/mmv1/products/accesscontextmanager/go_ServicePerimeterDryRunResource.yaml new file mode 100644 index 000000000000..c5df3c9fc897 --- /dev/null +++ b/mmv1/products/accesscontextmanager/go_ServicePerimeterDryRunResource.yaml @@ -0,0 +1,105 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'ServicePerimeterDryRunResource' +description: | + Allows configuring a single GCP resource that should be inside of the `spec` block of a dry run service perimeter. + This resource is intended to be used in cases where it is not possible to compile a full list + of projects to include in a `google_access_context_manager_service_perimeter` resource, + to enable them to be added separately. + If your perimeter is NOT in dry-run mode use `google_access_context_manager_service_perimeter_resource` instead. + + ~> **Note:** If this resource is used alongside a `google_access_context_manager_service_perimeter` resource, + the service perimeter resource must have a `lifecycle` block with `ignore_changes = [spec[0].resources]` so + they don't fight over which resources should be in the policy. +references: + guides: + 'Service Perimeter Quickstart': 'https://cloud.google.com/vpc-service-controls/docs/quickstart' + api: 'https://cloud.google.com/access-context-manager/docs/reference/rest/v1/accessPolicies.servicePerimeters' +docs: + warning: | + If you are using User ADCs (Application Default Credentials) with this resource, + you must specify a `billing_project` and set `user_project_override` to true + in the provider configuration. Otherwise the ACM API will return a 403 error. + Your account must have the `serviceusage.services.use` permission on the + `billing_project` you defined. +id_format: '{{perimeter_name}}/{{resource}}' +base_url: '' +self_link: '{{perimeter_name}}' +create_url: '{{perimeter_name}}' +create_verb: 'PATCH' +update_mask: true +delete_verb: 'PATCH' +immutable: true +mutex: '{{perimeter_name}}' +import_format: + - '{{perimeter_name}}/{{resource}}' +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +autogen_async: true +async: + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: true + error: + path: 'error' + message: 'message' +identity: + - resource +nested_query: + keys: + - spec + - resources + is_list_of_ids: true + modify_by_patch: true +custom_code: + pre_create: 'templates/terraform/pre_create/go/access_context_manager_service_perimeter_dry_run_resource.go.tmpl' + pre_update: 'templates/terraform/pre_create/go/access_context_manager_service_perimeter_dry_run_resource.go.tmpl' + pre_delete: 'templates/terraform/pre_create/go/access_context_manager_service_perimeter_dry_run_resource.go.tmpl' + custom_import: 'templates/terraform/custom_import/go/access_context_manager_service_perimeter_resource.go.tmpl' +exclude_tgc: true +skip_sweeper: true +examples: + - name: 'access_context_manager_service_perimeter_dry_run_resource_basic' + primary_resource_id: 'service-perimeter-dry-run-resource' + vars: + service_perimeter_name: 'restrict_all' + skip_test: true +parameters: + - name: 'perimeterName' + type: ResourceRef + description: | + The name of the Service Perimeter to add this resource to. + url_param_only: true + required: true + immutable: true + resource: 'ServicePerimeter' + imports: 'name' +properties: + - name: 'resource' + type: String + description: | + A GCP resource that is inside of the service perimeter. + Currently only projects are allowed. + Format: projects/{project_number} + required: true + immutable: true diff --git a/mmv1/products/accesscontextmanager/go_ServicePerimeterEgressPolicy.yaml b/mmv1/products/accesscontextmanager/go_ServicePerimeterEgressPolicy.yaml new file mode 100644 index 000000000000..64f807f6f375 --- /dev/null +++ b/mmv1/products/accesscontextmanager/go_ServicePerimeterEgressPolicy.yaml @@ -0,0 +1,184 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'ServicePerimeterEgressPolicy' +description: | + EgressPolicies match requests based on egressFrom and egressTo stanzas. + For an EgressPolicy to match, both egressFrom and egressTo stanzas must be matched. + If an EgressPolicy matches a request, the request is allowed to span the ServicePerimeter + boundary. For example, an EgressPolicy can be used to allow VMs on networks + within the ServicePerimeter to access a defined set of projects outside the + perimeter in certain contexts (e.g. to read data from a Cloud Storage bucket + or query against a BigQuery dataset). + + ~> **Note:** By default, updates to this resource will remove the EgressPolicy from the + from the perimeter and add it back in a non-atomic manner. To ensure that the new EgressPolicy + is added before the old one is removed, add a `lifecycle` block with `create_before_destroy = true` to this resource. +references: + guides: + api: 'https://cloud.google.com/access-context-manager/docs/reference/rest/v1/accessPolicies.servicePerimeters#egresspolicy' +docs: +id_format: '{{perimeter}}' +base_url: '' +self_link: '{{perimeter}}' +create_url: '{{perimeter}}' +create_verb: 'PATCH' +update_mask: true +delete_verb: 'PATCH' +immutable: true +mutex: '{{perimeter}}' +import_format: + - '{{perimeter}}' +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +autogen_async: true +async: + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: true + error: + path: 'error' + message: 'message' +identity: + - egressFrom + - egressTo +nested_query: + keys: + - status + - egressPolicies + is_list_of_ids: false + modify_by_patch: true +custom_code: + custom_import: 'templates/terraform/custom_import/go/access_context_manager_service_perimeter_ingress_policy.go.tmpl' +exclude_tgc: true +skip_sweeper: true +examples: + - name: 'access_context_manager_service_perimeter_egress_policy' + skip_test: true +parameters: + - name: 'perimeter' + type: ResourceRef + description: | + The name of the Service Perimeter to add this resource to. + url_param_only: true + required: true + resource: 'ServicePerimeter' + imports: 'name' +properties: + - name: 'egressFrom' + type: NestedObject + description: | + Defines conditions on the source of a request causing this `EgressPolicy` to apply. + properties: + - name: 'identityType' + type: Enum + description: | + Specifies the type of identities that are allowed access to outside the + perimeter. If left unspecified, then members of `identities` field will + be allowed access. + enum_values: + - 'ANY_IDENTITY' + - 'ANY_USER_ACCOUNT' + - 'ANY_SERVICE_ACCOUNT' + - name: 'identities' + type: Array + description: | + A list of identities that are allowed access through this `EgressPolicy`. + Should be in the format of email address. The email address should + represent individual user or service account only. + item_type: + type: String + - name: 'sources' + type: Array + description: 'Sources that this EgressPolicy authorizes access from.' + item_type: + type: NestedObject + properties: + - name: 'accessLevel' + type: String + description: 'An AccessLevel resource name that allows resources outside the ServicePerimeter to be accessed from the inside.' + - name: 'sourceRestriction' + type: Enum + description: 'Whether to enforce traffic restrictions based on `sources` field. If the `sources` field is non-empty, then this field must be set to `SOURCE_RESTRICTION_ENABLED`.' + enum_values: + - 'SOURCE_RESTRICTION_UNSPECIFIED' + - 'SOURCE_RESTRICTION_ENABLED' + - 'SOURCE_RESTRICTION_DISABLED' + - name: 'egressTo' + type: NestedObject + description: | + Defines the conditions on the `ApiOperation` and destination resources that + cause this `EgressPolicy` to apply. + properties: + - name: 'resources' + type: Array + description: | + A list of resources, currently only projects in the form + `projects/`, that match this to stanza. A request matches + if it contains a resource in this list. If * is specified for resources, + then this `EgressTo` rule will authorize access to all resources outside + the perimeter. + item_type: + type: String + - name: 'externalResources' + type: Array + description: | + A list of external resources that are allowed to be accessed. A request + matches if it contains an external resource in this list (Example: + s3://bucket/path). Currently '*' is not allowed. + item_type: + type: String + - name: 'operations' + type: Array + description: | + A list of `ApiOperations` that this egress rule applies to. A request matches + if it contains an operation/service in this list. + item_type: + type: NestedObject + properties: + - name: 'serviceName' + type: String + description: | + The name of the API whose methods or permissions the `IngressPolicy` or + `EgressPolicy` want to allow. A single `ApiOperation` with serviceName + field set to `*` will allow all methods AND permissions for all services. + - name: 'methodSelectors' + type: Array + description: | + API methods or permissions to allow. Method or permission must belong + to the service specified by `serviceName` field. A single MethodSelector + entry with `*` specified for the `method` field will allow all methods + AND permissions for the service specified in `serviceName`. + item_type: + type: NestedObject + properties: + - name: 'method' + type: String + description: | + Value for `method` should be a valid method name for the corresponding + `serviceName` in `ApiOperation`. If `*` used as value for method, + then ALL methods and permissions are allowed. + - name: 'permission' + type: String + description: | + Value for permission should be a valid Cloud IAM permission for the + corresponding `serviceName` in `ApiOperation`. diff --git a/mmv1/products/accesscontextmanager/go_ServicePerimeterIngressPolicy.yaml b/mmv1/products/accesscontextmanager/go_ServicePerimeterIngressPolicy.yaml new file mode 100644 index 000000000000..af1361dfa975 --- /dev/null +++ b/mmv1/products/accesscontextmanager/go_ServicePerimeterIngressPolicy.yaml @@ -0,0 +1,192 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'ServicePerimeterIngressPolicy' +description: | + IngressPolicies match requests based on ingressFrom and ingressTo stanzas. For an ingress policy to match, + both the ingressFrom and ingressTo stanzas must be matched. If an IngressPolicy matches a request, + the request is allowed through the perimeter boundary from outside the perimeter. + For example, access from the internet can be allowed either based on an AccessLevel or, + for traffic hosted on Google Cloud, the project of the source network. + For access from private networks, using the project of the hosting network is required. + Individual ingress policies can be limited by restricting which services and/ + or actions they match using the ingressTo field. + + ~> **Note:** By default, updates to this resource will remove the IngressPolicy from the + from the perimeter and add it back in a non-atomic manner. To ensure that the new IngressPolicy + is added before the old one is removed, add a `lifecycle` block with `create_before_destroy = true` to this resource. +references: + guides: + api: 'https://cloud.google.com/access-context-manager/docs/reference/rest/v1/accessPolicies.servicePerimeters#ingresspolicy' +docs: +id_format: '{{perimeter}}' +base_url: '' +self_link: '{{perimeter}}' +create_url: '{{perimeter}}' +create_verb: 'PATCH' +update_mask: true +delete_verb: 'PATCH' +immutable: true +mutex: '{{perimeter}}' +import_format: + - '{{perimeter}}' +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +autogen_async: true +async: + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: true + error: + path: 'error' + message: 'message' +identity: + - ingressFrom + - ingressTo +nested_query: + keys: + - status + - ingressPolicies + is_list_of_ids: false + modify_by_patch: true +custom_code: + custom_import: 'templates/terraform/custom_import/go/access_context_manager_service_perimeter_ingress_policy.go.tmpl' +exclude_tgc: true +skip_sweeper: true +examples: + - name: 'access_context_manager_service_perimeter_ingress_policy' + skip_test: true +parameters: + - name: 'perimeter' + type: ResourceRef + description: | + The name of the Service Perimeter to add this resource to. + url_param_only: true + required: true + resource: 'ServicePerimeter' + imports: 'name' +properties: + - name: 'ingressFrom' + type: NestedObject + description: | + Defines the conditions on the source of a request causing this `IngressPolicy` + to apply. + properties: + - name: 'identityType' + type: Enum + description: | + Specifies the type of identities that are allowed access from outside the + perimeter. If left unspecified, then members of `identities` field will be + allowed access. + enum_values: + - 'ANY_IDENTITY' + - 'ANY_USER_ACCOUNT' + - 'ANY_SERVICE_ACCOUNT' + - name: 'identities' + type: Array + description: | + A list of identities that are allowed access through this ingress policy. + Should be in the format of email address. The email address should represent + individual user or service account only. + item_type: + type: String + - name: 'sources' + type: Array + description: | + Sources that this `IngressPolicy` authorizes access from. + item_type: + type: NestedObject + properties: + - name: 'accessLevel' + type: String + description: | + An `AccessLevel` resource name that allow resources within the + `ServicePerimeters` to be accessed from the internet. `AccessLevels` listed + must be in the same policy as this `ServicePerimeter`. Referencing a nonexistent + `AccessLevel` will cause an error. If no `AccessLevel` names are listed, + resources within the perimeter can only be accessed via Google Cloud calls + with request origins within the perimeter. + Example `accessPolicies/MY_POLICY/accessLevels/MY_LEVEL.` + If * is specified, then all IngressSources will be allowed. + - name: 'resource' + type: String + description: | + A Google Cloud resource that is allowed to ingress the perimeter. + Requests from these resources will be allowed to access perimeter data. + Currently only projects are allowed. Format `projects/{project_number}` + The project may be in any Google Cloud organization, not just the + organization that the perimeter is defined in. `*` is not allowed, the case + of allowing all Google Cloud resources only is not supported. + - name: 'ingressTo' + type: NestedObject + description: | + Defines the conditions on the `ApiOperation` and request destination that cause + this `IngressPolicy` to apply. + properties: + - name: 'resources' + type: Array + description: | + A list of resources, currently only projects in the form + `projects/`, protected by this `ServicePerimeter` + that are allowed to be accessed by sources defined in the + corresponding `IngressFrom`. A request matches if it contains + a resource in this list. If `*` is specified for resources, + then this `IngressTo` rule will authorize access to all + resources inside the perimeter, provided that the request + also matches the `operations` field. + item_type: + type: String + - name: 'operations' + type: Array + description: | + A list of `ApiOperations` the sources specified in corresponding `IngressFrom` + are allowed to perform in this `ServicePerimeter`. + item_type: + type: NestedObject + properties: + - name: 'serviceName' + type: String + description: | + The name of the API whose methods or permissions the `IngressPolicy` or + `EgressPolicy` want to allow. A single `ApiOperation` with `serviceName` + field set to `*` will allow all methods AND permissions for all services. + - name: 'methodSelectors' + type: Array + description: | + API methods or permissions to allow. Method or permission must belong to + the service specified by serviceName field. A single `MethodSelector` entry + with `*` specified for the method field will allow all methods AND + permissions for the service specified in `serviceName`. + item_type: + type: NestedObject + properties: + - name: 'method' + type: String + description: | + Value for method should be a valid method name for the corresponding + serviceName in `ApiOperation`. If `*` used as value for `method`, then + ALL methods and permissions are allowed. + - name: 'permission' + type: String + description: | + Value for permission should be a valid Cloud IAM permission for the + corresponding `serviceName` in `ApiOperation`. diff --git a/mmv1/products/accesscontextmanager/go_ServicePerimeterResource.yaml b/mmv1/products/accesscontextmanager/go_ServicePerimeterResource.yaml new file mode 100644 index 000000000000..840a238a552a --- /dev/null +++ b/mmv1/products/accesscontextmanager/go_ServicePerimeterResource.yaml @@ -0,0 +1,102 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'ServicePerimeterResource' +description: | + Allows configuring a single GCP resource that should be inside the `status` block of a service perimeter. + This resource is intended to be used in cases where it is not possible to compile a full list + of projects to include in a `google_access_context_manager_service_perimeter` resource, + to enable them to be added separately. + If your perimeter is in dry-run mode use `google_access_context_manager_service_perimeter_dry_run_resource` instead. + + ~> **Note:** If this resource is used alongside a `google_access_context_manager_service_perimeter` resource, + the service perimeter resource must have a `lifecycle` block with `ignore_changes = [status[0].resources]` so + they don't fight over which resources should be in the policy. +references: + guides: + 'Service Perimeter Quickstart': 'https://cloud.google.com/vpc-service-controls/docs/quickstart' + api: 'https://cloud.google.com/access-context-manager/docs/reference/rest/v1/accessPolicies.servicePerimeters' +docs: + warning: | + If you are using User ADCs (Application Default Credentials) with this resource, + you must specify a `billing_project` and set `user_project_override` to true + in the provider configuration. Otherwise the ACM API will return a 403 error. + Your account must have the `serviceusage.services.use` permission on the + `billing_project` you defined. +id_format: '{{perimeter_name}}/{{resource}}' +base_url: '' +self_link: '{{perimeter_name}}' +create_url: '{{perimeter_name}}' +create_verb: 'PATCH' +update_mask: true +delete_verb: 'PATCH' +immutable: true +mutex: '{{perimeter_name}}' +import_format: + - '{{perimeter_name}}/{{resource}}' +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +autogen_async: true +async: + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: true + error: + path: 'error' + message: 'message' +identity: + - resource +nested_query: + keys: + - status + - resources + is_list_of_ids: true + modify_by_patch: true +custom_code: + custom_import: 'templates/terraform/custom_import/go/access_context_manager_service_perimeter_resource.go.tmpl' +exclude_tgc: true +skip_sweeper: true +examples: + - name: 'access_context_manager_service_perimeter_resource_basic' + primary_resource_id: 'service-perimeter-resource' + vars: + service_perimeter_name: 'restrict_all' + skip_test: true +parameters: + - name: 'perimeterName' + type: ResourceRef + description: | + The name of the Service Perimeter to add this resource to. + url_param_only: true + required: true + immutable: true + resource: 'ServicePerimeter' + imports: 'name' +properties: + - name: 'resource' + type: String + description: | + A GCP resource that is inside of the service perimeter. + Currently only projects are allowed. + Format: projects/{project_number} + required: true + immutable: true diff --git a/mmv1/products/accesscontextmanager/go_ServicePerimeters.yaml b/mmv1/products/accesscontextmanager/go_ServicePerimeters.yaml new file mode 100644 index 000000000000..90c478decf6b --- /dev/null +++ b/mmv1/products/accesscontextmanager/go_ServicePerimeters.yaml @@ -0,0 +1,765 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'ServicePerimeters' +description: | + Replace all existing Service Perimeters in an Access Policy with the Service Perimeters provided. This is done atomically. + This is a bulk edit of all Service Perimeters and may override existing Service Perimeters created by `google_access_context_manager_service_perimeter`, + thus causing a permadiff if used alongside `google_access_context_manager_service_perimeter` on the same parent. +references: + guides: + 'Service Perimeter Quickstart': 'https://cloud.google.com/vpc-service-controls/docs/quickstart' + api: 'https://cloud.google.com/access-context-manager/docs/reference/rest/v1/accessPolicies.servicePerimeters' +docs: +id_format: '{{parent}}/servicePerimeters' +base_url: '{{parent}}/servicePerimeters:replaceAll' +self_link: '{{parent}}/servicePerimeters' +update_url: '{{parent}}/servicePerimeters:replaceAll' +update_verb: 'POST' +import_format: + - '{{parent}}/servicePerimeters' +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +autogen_async: true +async: + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: true + error: + path: 'error' + message: 'message' +custom_code: + custom_delete: 'templates/terraform/custom_delete/go/replace_all_service_perimeters_empty_list.go.tmpl' + custom_import: 'templates/terraform/custom_import/go/set_access_policy_parent_from_access_policy.go.tmpl' +skip_sweeper: true +examples: + - name: 'access_context_manager_service_perimeters_basic' + primary_resource_id: 'service-perimeter' + vars: + access_level_name: 'chromeos_no_lock' + service_perimeter_name: 'restrict_storage' + skip_test: true +parameters: + - name: 'parent' + type: String + description: | + The AccessPolicy this ServicePerimeter lives in. + Format: accessPolicies/{policy_id} + required: true + immutable: true + ignore_read: true +properties: + - name: 'servicePerimeters' + type: Array + description: | + The desired Service Perimeters that should replace all existing Service Perimeters in the Access Policy. + custom_flatten: 'templates/terraform/custom_flatten/go/accesscontextmanager_serviceperimeters_custom_flatten.go.tmpl' + item_type: + type: NestedObject + properties: + - name: 'name' + type: String + description: | + Resource name for the ServicePerimeter. The short_name component must + begin with a letter and only include alphanumeric and '_'. + Format: accessPolicies/{policy_id}/servicePerimeters/{short_name} + required: true + immutable: true + - name: 'title' + type: String + description: | + Human readable title. Must be unique within the Policy. + required: true + - name: 'description' + type: String + description: | + Description of the ServicePerimeter and its use. Does not affect + behavior. + - name: 'createTime' + type: Time + description: | + Time the AccessPolicy was created in UTC. + output: true + - name: 'updateTime' + type: Time + description: | + Time the AccessPolicy was updated in UTC. + output: true + - name: 'perimeterType' + type: Enum + description: | + Specifies the type of the Perimeter. There are two types: regular and + bridge. Regular Service Perimeter contains resources, access levels, + and restricted services. Every resource can be in at most + ONE regular Service Perimeter. + + In addition to being in a regular service perimeter, a resource can also + be in zero or more perimeter bridges. A perimeter bridge only contains + resources. Cross project operations are permitted if all effected + resources share some perimeter (whether bridge or regular). Perimeter + Bridge does not contain access levels or services: those are governed + entirely by the regular perimeter that resource is in. + + Perimeter Bridges are typically useful when building more complex + topologies with many independent perimeters that need to share some data + with a common perimeter, but should not be able to share data among + themselves. + immutable: true + custom_flatten: 'templates/terraform/custom_flatten/go/default_if_empty.tmpl' + default_value: "PERIMETER_TYPE_REGULAR" + enum_values: + - 'PERIMETER_TYPE_REGULAR' + - 'PERIMETER_TYPE_BRIDGE' + - name: 'status' + type: NestedObject + description: | + ServicePerimeter configuration. Specifies sets of resources, + restricted services and access levels that determine + perimeter content and boundaries. + properties: + - name: 'resources' + type: Array + description: | + A list of GCP resources that are inside of the service perimeter. + Currently only projects are allowed. + Format: projects/{project_number} + # TODO: (mbang) won't work for arrays yet, uncomment here once they are supported. + # (github.com/hashicorp/terraform-plugin-sdk/issues/470) + # at_least_one_of: + # - status.0.resources + # - status.0.access_levels + # - status.0.restricted_services + is_set: true + item_type: + type: String + - name: 'accessLevels' + type: Array + description: | + A list of AccessLevel resource names that allow resources within + the ServicePerimeter to be accessed from the internet. + AccessLevels listed must be in the same policy as this + ServicePerimeter. Referencing a nonexistent AccessLevel is a + syntax error. If no AccessLevel names are listed, resources within + the perimeter can only be accessed via GCP calls with request + origins within the perimeter. For Service Perimeter Bridge, must + be empty. + + Format: accessPolicies/{policy_id}/accessLevels/{access_level_name} + # TODO: (mbang) won't work for arrays yet, uncomment here once they are supported. + # (github.com/hashicorp/terraform-plugin-sdk/issues/470) + # at_least_one_of: + # - status.0.resources + # - status.0.access_levels + # - status.0.restricted_services + is_set: true + item_type: + type: String + - name: 'restrictedServices' + type: Array + description: | + GCP services that are subject to the Service Perimeter + restrictions. Must contain a list of services. For example, if + `storage.googleapis.com` is specified, access to the storage + buckets inside the perimeter must meet the perimeter's access + restrictions. + # TODO: (mbang) won't work for arrays yet, uncomment here once they are supported. + # (github.com/hashicorp/terraform-plugin-sdk/issues/470) + # at_least_one_of: + # - status.0.resources + # - status.0.access_levels + # - status.0.restricted_services + is_set: true + item_type: + type: String + - name: 'vpcAccessibleServices' + type: NestedObject + description: | + Specifies how APIs are allowed to communicate within the Service + Perimeter. + properties: + - name: 'enableRestriction' + type: Boolean + description: | + Whether to restrict API calls within the Service Perimeter to the + list of APIs specified in 'allowedServices'. + - name: 'allowedServices' + type: Array + description: | + The list of APIs usable within the Service Perimeter. + Must be empty unless `enableRestriction` is True. + is_set: true + item_type: + type: String + - name: 'ingressPolicies' + type: Array + description: | + List of `IngressPolicies` to apply to the perimeter. A perimeter may + have multiple `IngressPolicies`, each of which is evaluated + separately. Access is granted if any `Ingress Policy` grants it. + Must be empty for a perimeter bridge. + is_set: true + item_type: + type: NestedObject + properties: + - name: 'ingressFrom' + type: NestedObject + description: | + Defines the conditions on the source of a request causing this `IngressPolicy` + to apply. + properties: + - name: 'identityType' + type: Enum + description: | + Specifies the type of identities that are allowed access from outside the + perimeter. If left unspecified, then members of `identities` field will be + allowed access. + enum_values: + - 'IDENTITY_TYPE_UNSPECIFIED' + - 'ANY_IDENTITY' + - 'ANY_USER_ACCOUNT' + - 'ANY_SERVICE_ACCOUNT' + - name: 'identities' + type: Array + description: | + A list of identities that are allowed access through this ingress policy. + Should be in the format of email address. The email address should represent + individual user or service account only. + is_set: true + item_type: + type: String + - name: 'sources' + type: Array + description: | + Sources that this `IngressPolicy` authorizes access from. + item_type: + type: NestedObject + properties: + - name: 'accessLevel' + type: String + description: | + An `AccessLevel` resource name that allow resources within the + `ServicePerimeters` to be accessed from the internet. `AccessLevels` listed + must be in the same policy as this `ServicePerimeter`. Referencing a nonexistent + `AccessLevel` will cause an error. If no `AccessLevel` names are listed, + resources within the perimeter can only be accessed via Google Cloud calls + with request origins within the perimeter. + Example `accessPolicies/MY_POLICY/accessLevels/MY_LEVEL.` + If * is specified, then all IngressSources will be allowed. + - name: 'resource' + type: String + description: | + A Google Cloud resource that is allowed to ingress the perimeter. + Requests from these resources will be allowed to access perimeter data. + Currently only projects are allowed. Format `projects/{project_number}` + The project may be in any Google Cloud organization, not just the + organization that the perimeter is defined in. `*` is not allowed, the case + of allowing all Google Cloud resources only is not supported. + - name: 'ingressTo' + type: NestedObject + description: | + Defines the conditions on the `ApiOperation` and request destination that cause + this `IngressPolicy` to apply. + properties: + - name: 'resources' + type: Array + description: | + A list of resources, currently only projects in the form + `projects/`, protected by this `ServicePerimeter` + that are allowed to be accessed by sources defined in the + corresponding `IngressFrom`. A request matches if it contains + a resource in this list. If `*` is specified for resources, + then this `IngressTo` rule will authorize access to all + resources inside the perimeter, provided that the request + also matches the `operations` field. + is_set: true + item_type: + type: String + - name: 'operations' + type: Array + description: | + A list of `ApiOperations` the sources specified in corresponding `IngressFrom` + are allowed to perform in this `ServicePerimeter`. + item_type: + type: NestedObject + properties: + - name: 'serviceName' + type: String + description: | + The name of the API whose methods or permissions the `IngressPolicy` or + `EgressPolicy` want to allow. A single `ApiOperation` with `serviceName` + field set to `*` will allow all methods AND permissions for all services. + - name: 'methodSelectors' + type: Array + description: | + API methods or permissions to allow. Method or permission must belong to + the service specified by serviceName field. A single `MethodSelector` entry + with `*` specified for the method field will allow all methods AND + permissions for the service specified in `serviceName`. + item_type: + type: NestedObject + properties: + - name: 'method' + type: String + description: | + Value for method should be a valid method name for the corresponding + serviceName in `ApiOperation`. If `*` used as value for `method`, then + ALL methods and permissions are allowed. + - name: 'permission' + type: String + description: | + Value for permission should be a valid Cloud IAM permission for the + corresponding `serviceName` in `ApiOperation`. + - name: 'egressPolicies' + type: Array + description: | + List of EgressPolicies to apply to the perimeter. A perimeter may + have multiple EgressPolicies, each of which is evaluated separately. + Access is granted if any EgressPolicy grants it. Must be empty for + a perimeter bridge. + item_type: + type: NestedObject + properties: + - name: 'egressFrom' + type: NestedObject + description: | + Defines conditions on the source of a request causing this `EgressPolicy` to apply. + properties: + - name: 'identityType' + type: Enum + description: | + Specifies the type of identities that are allowed access to outside the + perimeter. If left unspecified, then members of `identities` field will + be allowed access. + enum_values: + - 'IDENTITY_TYPE_UNSPECIFIED' + - 'ANY_IDENTITY' + - 'ANY_USER_ACCOUNT' + - 'ANY_SERVICE_ACCOUNT' + - name: 'identities' + type: Array + description: | + A list of identities that are allowed access through this `EgressPolicy`. + Should be in the format of email address. The email address should + represent individual user or service account only. + is_set: true + item_type: + type: String + - name: 'sources' + type: Array + description: 'Sources that this EgressPolicy authorizes access from.' + item_type: + type: NestedObject + properties: + - name: 'accessLevel' + type: String + description: 'An AccessLevel resource name that allows resources outside the ServicePerimeter to be accessed from the inside.' + - name: 'sourceRestriction' + type: Enum + description: 'Whether to enforce traffic restrictions based on `sources` field. If the `sources` field is non-empty, then this field must be set to `SOURCE_RESTRICTION_ENABLED`.' + enum_values: + - 'SOURCE_RESTRICTION_UNSPECIFIED' + - 'SOURCE_RESTRICTION_ENABLED' + - 'SOURCE_RESTRICTION_DISABLED' + - name: 'egressTo' + type: NestedObject + description: | + Defines the conditions on the `ApiOperation` and destination resources that + cause this `EgressPolicy` to apply. + properties: + - name: 'resources' + type: Array + description: | + A list of resources, currently only projects in the form + `projects/`, that match this to stanza. A request matches + if it contains a resource in this list. If * is specified for resources, + then this `EgressTo` rule will authorize access to all resources outside + the perimeter. + is_set: true + item_type: + type: String + - name: 'externalResources' + type: Array + description: | + A list of external resources that are allowed to be accessed. A request + matches if it contains an external resource in this list (Example: + s3://bucket/path). Currently '*' is not allowed. + is_set: true + item_type: + type: String + - name: 'operations' + type: Array + description: | + A list of `ApiOperations` that this egress rule applies to. A request matches + if it contains an operation/service in this list. + item_type: + type: NestedObject + properties: + - name: 'serviceName' + type: String + description: | + The name of the API whose methods or permissions the `IngressPolicy` or + `EgressPolicy` want to allow. A single `ApiOperation` with serviceName + field set to `*` will allow all methods AND permissions for all services. + - name: 'methodSelectors' + type: Array + description: | + API methods or permissions to allow. Method or permission must belong + to the service specified by `serviceName` field. A single MethodSelector + entry with `*` specified for the `method` field will allow all methods + AND permissions for the service specified in `serviceName`. + item_type: + type: NestedObject + properties: + - name: 'method' + type: String + description: | + Value for `method` should be a valid method name for the corresponding + `serviceName` in `ApiOperation`. If `*` used as value for method, + then ALL methods and permissions are allowed. + - name: 'permission' + type: String + description: | + Value for permission should be a valid Cloud IAM permission for the + corresponding `serviceName` in `ApiOperation`. + - name: 'spec' + type: NestedObject + description: | + Proposed (or dry run) ServicePerimeter configuration. + This configuration allows to specify and test ServicePerimeter configuration + without enforcing actual access restrictions. Only allowed to be set when + the `useExplicitDryRunSpec` flag is set. + properties: + - name: 'resources' + type: Array + description: | + A list of GCP resources that are inside of the service perimeter. + Currently only projects are allowed. + Format: projects/{project_number} + # TODO: (mbang) won't work for arrays yet, uncomment here once they are supported. + # (github.com/hashicorp/terraform-plugin-sdk/issues/470) + # at_least_one_of: + # - spec.0.resources + # - spec.0.access_levels + # - spec.0.restricted_services + is_set: true + item_type: + type: String + - name: 'accessLevels' + type: Array + description: | + A list of AccessLevel resource names that allow resources within + the ServicePerimeter to be accessed from the internet. + AccessLevels listed must be in the same policy as this + ServicePerimeter. Referencing a nonexistent AccessLevel is a + syntax error. If no AccessLevel names are listed, resources within + the perimeter can only be accessed via GCP calls with request + origins within the perimeter. For Service Perimeter Bridge, must + be empty. + + Format: accessPolicies/{policy_id}/accessLevels/{access_level_name} + # TODO: (mbang) won't work for arrays yet, uncomment here once they are supported. + # (github.com/hashicorp/terraform-plugin-sdk/issues/470) + # at_least_one_of: + # - spec.0.resources + # - spec.0.access_levels + # - spec.0.restricted_services + is_set: true + item_type: + type: String + - name: 'restrictedServices' + type: Array + description: | + GCP services that are subject to the Service Perimeter + restrictions. Must contain a list of services. For example, if + `storage.googleapis.com` is specified, access to the storage + buckets inside the perimeter must meet the perimeter's access + restrictions. + # TODO: (mbang) won't work for arrays yet, uncomment here once they are supported. + # (github.com/hashicorp/terraform-plugin-sdk/issues/470) + # at_least_one_of: + # - spec.0.resources + # - spec.0.access_levels + # - spec.0.restricted_services + is_set: true + item_type: + type: String + - name: 'vpcAccessibleServices' + type: NestedObject + description: | + Specifies how APIs are allowed to communicate within the Service + Perimeter. + properties: + - name: 'enableRestriction' + type: Boolean + description: | + Whether to restrict API calls within the Service Perimeter to the + list of APIs specified in 'allowedServices'. + - name: 'allowedServices' + type: Array + description: | + The list of APIs usable within the Service Perimeter. + Must be empty unless `enableRestriction` is True. + is_set: true + item_type: + type: String + - name: 'ingressPolicies' + type: Array + description: | + List of `IngressPolicies` to apply to the perimeter. A perimeter may + have multiple `IngressPolicies`, each of which is evaluated + separately. Access is granted if any `Ingress Policy` grants it. + Must be empty for a perimeter bridge. + item_type: + type: NestedObject + properties: + - name: 'ingressFrom' + type: NestedObject + description: | + Defines the conditions on the source of a request causing this `IngressPolicy` + to apply. + properties: + - name: 'identityType' + type: Enum + description: | + Specifies the type of identities that are allowed access from outside the + perimeter. If left unspecified, then members of `identities` field will be + allowed access. + enum_values: + - 'IDENTITY_TYPE_UNSPECIFIED' + - 'ANY_IDENTITY' + - 'ANY_USER_ACCOUNT' + - 'ANY_SERVICE_ACCOUNT' + - name: 'identities' + type: Array + description: | + A list of identities that are allowed access through this ingress policy. + Should be in the format of email address. The email address should represent + individual user or service account only. + is_set: true + item_type: + type: String + - name: 'sources' + type: Array + description: | + Sources that this `IngressPolicy` authorizes access from. + item_type: + type: NestedObject + properties: + - name: 'accessLevel' + type: String + description: | + An `AccessLevel` resource name that allow resources within the + `ServicePerimeters` to be accessed from the internet. `AccessLevels` listed + must be in the same policy as this `ServicePerimeter`. Referencing a nonexistent + `AccessLevel` will cause an error. If no `AccessLevel` names are listed, + resources within the perimeter can only be accessed via Google Cloud calls + with request origins within the perimeter. + Example `accessPolicies/MY_POLICY/accessLevels/MY_LEVEL.` + If * is specified, then all IngressSources will be allowed. + - name: 'resource' + type: String + description: | + A Google Cloud resource that is allowed to ingress the perimeter. + Requests from these resources will be allowed to access perimeter data. + Currently only projects are allowed. Format `projects/{project_number}` + The project may be in any Google Cloud organization, not just the + organization that the perimeter is defined in. `*` is not allowed, the case + of allowing all Google Cloud resources only is not supported. + - name: 'ingressTo' + type: NestedObject + description: | + Defines the conditions on the `ApiOperation` and request destination that cause + this `IngressPolicy` to apply. + properties: + - name: 'resources' + type: Array + description: | + A list of resources, currently only projects in the form + `projects/`, protected by this `ServicePerimeter` + that are allowed to be accessed by sources defined in the + corresponding `IngressFrom`. A request matches if it contains + a resource in this list. If `*` is specified for resources, + then this `IngressTo` rule will authorize access to all + resources inside the perimeter, provided that the request + also matches the `operations` field. + is_set: true + item_type: + type: String + - name: 'operations' + type: Array + description: | + A list of `ApiOperations` the sources specified in corresponding `IngressFrom` + are allowed to perform in this `ServicePerimeter`. + item_type: + type: NestedObject + properties: + - name: 'serviceName' + type: String + description: | + The name of the API whose methods or permissions the `IngressPolicy` or + `EgressPolicy` want to allow. A single `ApiOperation` with `serviceName` + field set to `*` will allow all methods AND permissions for all services. + - name: 'methodSelectors' + type: Array + description: | + API methods or permissions to allow. Method or permission must belong to + the service specified by serviceName field. A single `MethodSelector` entry + with `*` specified for the method field will allow all methods AND + permissions for the service specified in `serviceName`. + item_type: + type: NestedObject + properties: + - name: 'method' + type: String + description: | + Value for method should be a valid method name for the corresponding + serviceName in `ApiOperation`. If `*` used as value for `method`, then + ALL methods and permissions are allowed. + - name: 'permission' + type: String + description: | + Value for permission should be a valid Cloud IAM permission for the + corresponding `serviceName` in `ApiOperation`. + - name: 'egressPolicies' + type: Array + description: | + List of EgressPolicies to apply to the perimeter. A perimeter may + have multiple EgressPolicies, each of which is evaluated separately. + Access is granted if any EgressPolicy grants it. Must be empty for + a perimeter bridge. + item_type: + type: NestedObject + properties: + - name: 'egressFrom' + type: NestedObject + description: | + Defines conditions on the source of a request causing this `EgressPolicy` to apply. + properties: + - name: 'identityType' + type: Enum + description: | + Specifies the type of identities that are allowed access to outside the + perimeter. If left unspecified, then members of `identities` field will + be allowed access. + enum_values: + - 'IDENTITY_TYPE_UNSPECIFIED' + - 'ANY_IDENTITY' + - 'ANY_USER_ACCOUNT' + - 'ANY_SERVICE_ACCOUNT' + - name: 'identities' + type: Array + description: | + A list of identities that are allowed access through this `EgressPolicy`. + Should be in the format of email address. The email address should + represent individual user or service account only. + is_set: true + item_type: + type: String + - name: 'sources' + type: Array + description: 'Sources that this EgressPolicy authorizes access from.' + item_type: + type: NestedObject + properties: + - name: 'accessLevel' + type: String + description: 'An AccessLevel resource name that allows resources outside the ServicePerimeter to be accessed from the inside.' + - name: 'sourceRestriction' + type: Enum + description: 'Whether to enforce traffic restrictions based on `sources` field. If the `sources` field is non-empty, then this field must be set to `SOURCE_RESTRICTION_ENABLED`.' + enum_values: + - 'SOURCE_RESTRICTION_UNSPECIFIED' + - 'SOURCE_RESTRICTION_ENABLED' + - 'SOURCE_RESTRICTION_DISABLED' + - name: 'egressTo' + type: NestedObject + description: | + Defines the conditions on the `ApiOperation` and destination resources that + cause this `EgressPolicy` to apply. + properties: + - name: 'resources' + type: Array + description: | + A list of resources, currently only projects in the form + `projects/`, that match this to stanza. A request matches + if it contains a resource in this list. If * is specified for resources, + then this `EgressTo` rule will authorize access to all resources outside + the perimeter. + is_set: true + item_type: + type: String + - name: 'externalResources' + type: Array + description: | + A list of external resources that are allowed to be accessed. A request + matches if it contains an external resource in this list (Example: + s3://bucket/path). Currently '*' is not allowed. + is_set: true + item_type: + type: String + - name: 'operations' + type: Array + description: | + A list of `ApiOperations` that this egress rule applies to. A request matches + if it contains an operation/service in this list. + item_type: + type: NestedObject + properties: + - name: 'serviceName' + type: String + description: | + The name of the API whose methods or permissions the `IngressPolicy` or + `EgressPolicy` want to allow. A single `ApiOperation` with serviceName + field set to `*` will allow all methods AND permissions for all services. + - name: 'methodSelectors' + type: Array + description: | + API methods or permissions to allow. Method or permission must belong + to the service specified by `serviceName` field. A single MethodSelector + entry with `*` specified for the `method` field will allow all methods + AND permissions for the service specified in `serviceName`. + item_type: + type: NestedObject + properties: + - name: 'method' + type: String + description: | + Value for `method` should be a valid method name for the corresponding + `serviceName` in `ApiOperation`. If `*` used as value for method, + then ALL methods and permissions are allowed. + - name: 'permission' + type: String + description: | + Value for permission should be a valid Cloud IAM permission for the + corresponding `serviceName` in `ApiOperation`. + - name: 'useExplicitDryRunSpec' + type: Boolean + description: | + Use explicit dry run spec flag. Ordinarily, a dry-run spec implicitly exists + for all Service Perimeters, and that spec is identical to the status for those + Service Perimeters. When this flag is set, it inhibits the generation of the + implicit spec, thereby allowing the user to explicitly provide a + configuration ("spec") to use in a dry-run version of the Service Perimeter. + This allows the user to test changes to the enforced config ("status") without + actually enforcing them. This testing is done through analyzing the differences + between currently enforced and suggested restrictions. useExplicitDryRunSpec must + bet set to True if any of the fields in the spec are set to non-default values. diff --git a/mmv1/products/accesscontextmanager/go_product.yaml b/mmv1/products/accesscontextmanager/go_product.yaml new file mode 100644 index 000000000000..a6d7fbade4e6 --- /dev/null +++ b/mmv1/products/accesscontextmanager/go_product.yaml @@ -0,0 +1,34 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'AccessContextManager' +display_name: 'Access Context Manager (VPC Service Controls)' +versions: + - name: 'ga' + base_url: 'https://accesscontextmanager.googleapis.com/v1/' +scopes: + - 'https://www.googleapis.com/auth/cloud-platform' +async: + type: "OpAsync" + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: true + error: + path: 'error' + message: 'message' diff --git a/mmv1/products/apigee/go_AddonsConfig.yaml b/mmv1/products/apigee/go_AddonsConfig.yaml new file mode 100644 index 000000000000..295fb7026857 --- /dev/null +++ b/mmv1/products/apigee/go_AddonsConfig.yaml @@ -0,0 +1,131 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'AddonsConfig' +description: | + Configures the add-ons for the Apigee organization. The existing add-on configuration will be fully replaced. +references: + guides: + 'Creating an API organization': 'https://cloud.google.com/apigee/docs/api-platform/get-started/create-org' + api: 'https://cloud.google.com/apigee/docs/reference/apis/apigee/rest/v1/organizations#setaddons' +docs: +base_url: 'organizations' +self_link: 'organizations/{{org}}' +create_url: 'organizations/{{org}}:setAddons' +update_url: 'organizations/{{org}}:setAddons' +update_verb: 'POST' +delete_url: 'organizations/{{org}}:setAddons' +delete_verb: 'POST' +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +async: + actions: ['create', 'update', 'delete'] + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: false + error: + path: 'error' + message: 'message' +custom_code: + custom_import: 'templates/terraform/custom_import/go/apigee_addons.go.tmpl' + test_check_destroy: 'templates/terraform/custom_check_destroy/go/apigee_addons_override.go.tmpl' +examples: + - name: 'apigee_addons_basic' + skip_test: true + - name: 'apigee_addons_full' + skip_test: true + - name: 'apigee_addons_test' + primary_resource_id: 'apigee_org_addons' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + skip_docs: true +parameters: + - name: 'org' + type: String + description: | + Name of the Apigee organization. + url_param_only: true + required: true + immutable: true +properties: + - name: 'addonsConfig' + type: NestedObject + description: Addon configurations of the Apigee organization. + properties: + - name: 'advancedApiOpsConfig' + type: NestedObject + description: Configuration for the Monetization add-on. + properties: + - name: 'enabled' + type: Boolean + description: + Flag that specifies whether the Advanced API Ops add-on is + enabled. + - name: 'integrationConfig' + type: NestedObject + description: Configuration for the Monetization add-on. + properties: + - name: 'enabled' + type: Boolean + description: + Flag that specifies whether the Advanced API Ops add-on is + enabled. + - name: 'monetizationConfig' + type: NestedObject + description: Configuration for the Monetization add-on. + properties: + - name: 'enabled' + type: Boolean + description: + Flag that specifies whether the Advanced API Ops add-on is + enabled. + - name: 'apiSecurityConfig' + type: NestedObject + description: Configuration for the Monetization add-on. + properties: + - name: 'enabled' + type: Boolean + description: + Flag that specifies whether the Advanced API Ops add-on is + enabled. + - name: 'expiresAt' + type: String + description: + Flag that specifies whether the Advanced API Ops add-on is + enabled. + output: true + - name: 'connectorsPlatformConfig' + type: NestedObject + description: Configuration for the Monetization add-on. + properties: + - name: 'enabled' + type: Boolean + description: + Flag that specifies whether the Advanced API Ops add-on is + enabled. + - name: 'expiresAt' + type: String + description: + Flag that specifies whether the Advanced API Ops add-on is + enabled. + output: true diff --git a/mmv1/products/apigee/go_EndpointAttachment.yaml b/mmv1/products/apigee/go_EndpointAttachment.yaml new file mode 100644 index 000000000000..f7cd692f1d9a --- /dev/null +++ b/mmv1/products/apigee/go_EndpointAttachment.yaml @@ -0,0 +1,105 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'EndpointAttachment' +description: | + Apigee Endpoint Attachment. +references: + guides: + 'Creating an environment': 'https://cloud.google.com/apigee/docs/api-platform/get-started/create-environment' + api: 'https://cloud.google.com/apigee/docs/reference/apis/apigee/rest/v1/organizations.endpointAttachments/create' +docs: +base_url: 'endpointAttachments' +self_link: '{{org_id}}/endpointAttachments/{{endpoint_attachment_id}}' +create_url: '{{org_id}}/endpointAttachments?endpointAttachmentId={{endpoint_attachment_id}}' +delete_url: '{{org_id}}/endpointAttachments/{{endpoint_attachment_id}}' +immutable: true +import_format: + - '{{org_id}}/endpointAttachments/{{endpoint_attachment_id}}' + - '{{org_id}}/{{endpoint_attachment_id}}' +timeouts: + insert_minutes: 30 + update_minutes: 20 + delete_minutes: 30 +autogen_async: true +async: + actions: ['create', 'delete', 'update'] + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: true + error: + path: 'error' + message: 'message' +custom_code: + custom_import: 'templates/terraform/custom_import/go/apigee_endpoint_attachment.go.tmpl' +skip_sweeper: true +examples: + - name: 'apigee_endpoint_attachment_basic' + skip_test: true + - name: 'apigee_endpoint_attachment_basic_test' + primary_resource_id: 'apigee_endpoint_attachment' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + skip_docs: true + skip_vcr: true +parameters: + - name: 'orgId' + type: String + description: | + The Apigee Organization associated with the Apigee instance, + in the format `organizations/{{org_name}}`. + url_param_only: true + required: true + immutable: true + - name: 'endpointAttachmentId' + type: String + description: | + ID of the endpoint attachment. + url_param_only: true + required: true + immutable: true +properties: + - name: 'name' + type: String + description: | + Name of the Endpoint Attachment in the following format: + organizations/{organization}/endpointAttachments/{endpointAttachment}. + output: true + - name: 'location' + type: String + description: | + Location of the endpoint attachment. + required: true + - name: 'host' + type: String + description: | + Host that can be used in either HTTP Target Endpoint directly, or as the host in Target Server. + output: true + - name: 'serviceAttachment' + type: String + description: | + Format: projects/*/regions/*/serviceAttachments/* + required: true + - name: 'connectionState' + type: String + description: | + State of the endpoint attachment connection to the service attachment. + output: true diff --git a/mmv1/products/apigee/go_EnvKeystore.yaml b/mmv1/products/apigee/go_EnvKeystore.yaml new file mode 100644 index 000000000000..d0d9405d4385 --- /dev/null +++ b/mmv1/products/apigee/go_EnvKeystore.yaml @@ -0,0 +1,68 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'EnvKeystore' +description: | + An `Environment KeyStore` in Apigee. +references: + guides: + 'Creating an environment': 'https://cloud.google.com/apigee/docs/api-platform/get-started/create-environment' + api: 'https://cloud.google.com/apigee/docs/reference/apis/apigee/rest/v1/organizations.environments.keystores/create' +docs: +base_url: '{{env_id}}/keystores' +self_link: '{{env_id}}/keystores/{{name}}' +create_url: '{{env_id}}/keystores' +delete_url: '{{env_id}}/keystores/{{name}}' +immutable: true +import_format: + - '{{env_id}}/keystores/{{name}}' + - '{{env_id}}/{{name}}' +timeouts: + insert_minutes: 1 + update_minutes: 20 + delete_minutes: 1 +autogen_async: true +custom_code: + custom_import: 'templates/terraform/custom_import/go/apigee_environment_keystore.go.tmpl' +skip_sweeper: true +examples: + - name: 'apigee_environment_keystore_test' + primary_resource_id: 'apigee_environment_keystore' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + skip_docs: true +parameters: + - name: 'envId' + type: String + description: | + The Apigee environment group associated with the Apigee environment, + in the format `organizations/{{org_name}}/environments/{{env_name}}`. + url_param_only: true + required: true + immutable: true + - name: 'name' + type: String + description: | + The name of the newly created keystore. + immutable: true +properties: + - name: 'aliases' + type: Array + description: | + Aliases in this keystore. + output: true + item_type: + type: String diff --git a/mmv1/products/apigee/go_EnvReferences.yaml b/mmv1/products/apigee/go_EnvReferences.yaml new file mode 100644 index 000000000000..57bef5c4ef68 --- /dev/null +++ b/mmv1/products/apigee/go_EnvReferences.yaml @@ -0,0 +1,79 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'EnvReferences' +description: | + An `Environment Reference` in Apigee. +references: + guides: + 'Creating an environment': 'https://cloud.google.com/apigee/docs/api-platform/get-started/create-environment' + api: 'https://cloud.google.com/apigee/docs/reference/apis/apigee/rest/v1/organizations.environments.references/create' +docs: +base_url: '{{env_id}}/references' +self_link: '{{env_id}}/references/{{name}}' +create_url: '{{env_id}}/references/' +delete_url: '{{env_id}}/references/{{name}}' +immutable: true +import_format: + - '{{env_id}}/references/{{name}}' + - '{{env_id}}/{{name}}' +timeouts: + insert_minutes: 1 + update_minutes: 20 + delete_minutes: 1 +autogen_async: true +custom_code: + custom_import: 'templates/terraform/custom_import/go/apigee_environment_reference.go.tmpl' +skip_sweeper: true +examples: + - name: 'apigee_environment_reference_test' + primary_resource_id: 'apigee_environment_reference' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + skip_docs: true +parameters: + - name: 'envId' + type: String + description: | + The Apigee environment group associated with the Apigee environment, + in the format `organizations/{{org_name}}/environments/{{env_name}}`. + url_param_only: true + required: true + immutable: true +properties: + - name: 'name' + type: String + description: | + Required. The resource id of this reference. Values must match the regular expression [\w\s-.]+. + required: true + immutable: true + - name: 'description' + type: String + description: | + Optional. A human-readable description of this reference. + immutable: true + - name: 'resourceType' + type: String + description: | + The type of resource referred to by this reference. Valid values are 'KeyStore' or 'TrustStore'. + required: true + immutable: true + - name: 'refers' + type: String + description: | + Required. The id of the resource to which this reference refers. Must be the id of a resource that exists in the parent environment and is of the given resourceType. + required: true + immutable: true diff --git a/mmv1/products/apigee/go_Envgroup.yaml b/mmv1/products/apigee/go_Envgroup.yaml new file mode 100644 index 000000000000..fc52152fe58f --- /dev/null +++ b/mmv1/products/apigee/go_Envgroup.yaml @@ -0,0 +1,86 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'Envgroup' +description: | + An `Environment group` in Apigee. +references: + guides: + 'Creating an environment': 'https://cloud.google.com/apigee/docs/api-platform/get-started/create-environment' + api: 'https://cloud.google.com/apigee/docs/reference/apis/apigee/rest/v1/organizations.envgroups/create' +docs: +base_url: 'envgroups' +self_link: '{{org_id}}/envgroups/{{name}}' +create_url: '{{org_id}}/envgroups' +update_verb: 'PATCH' +update_mask: true +import_format: + - '{{org_id}}/envgroups/{{name}}' + - '{{org_id}}/{{name}}' +timeouts: + insert_minutes: 30 + update_minutes: 20 + delete_minutes: 30 +autogen_async: true +async: + actions: ['create', 'delete', 'update'] + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: true + error: + path: 'error' + message: 'message' +custom_code: + custom_import: 'templates/terraform/custom_import/go/apigee_environment_group.go.tmpl' +examples: + - name: 'apigee_environment_group_basic' + vars: + envgroup_name: 'my-envgroup' + skip_test: true + - name: 'apigee_environment_group_basic_test' + primary_resource_id: 'apigee_environment_group' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + skip_docs: true + skip_vcr: true +parameters: + - name: 'orgId' + type: String + description: | + The Apigee Organization associated with the Apigee environment group, + in the format `organizations/{{org_name}}`. + url_param_only: true + required: true + immutable: true +properties: + - name: 'name' + type: String + description: | + The resource ID of the environment group. + required: true + immutable: true + - name: 'hostnames' + type: Array + description: | + Hostnames of the environment group. + required: false + item_type: + type: String diff --git a/mmv1/products/apigee/go_EnvgroupAttachment.yaml b/mmv1/products/apigee/go_EnvgroupAttachment.yaml new file mode 100644 index 000000000000..9f59f1128e28 --- /dev/null +++ b/mmv1/products/apigee/go_EnvgroupAttachment.yaml @@ -0,0 +1,85 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'EnvgroupAttachment' +description: | + An `Environment Group attachment` in Apigee. +references: + guides: + 'Creating an environment': 'https://cloud.google.com/apigee/docs/api-platform/get-started/create-environment' + api: 'https://cloud.google.com/apigee/docs/reference/apis/apigee/rest/v1/organizations.envgroups.attachments/create' +docs: +base_url: '{{envgroup_id}}/attachments' +self_link: '{{envgroup_id}}/attachments/{{name}}' +create_url: '{{envgroup_id}}/attachments' +delete_url: '{{envgroup_id}}/attachments/{{name}}' +immutable: true +import_format: + - '{{envgroup_id}}/attachments/{{name}}' + - '{{envgroup_id}}/{{name}}' +timeouts: + insert_minutes: 30 + update_minutes: 20 + delete_minutes: 30 +autogen_async: true +async: + actions: ['create', 'delete', 'update'] + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: true + error: + path: 'error' + message: 'message' +custom_code: + custom_import: 'templates/terraform/custom_import/go/apigee_environment_group_attachment.go.tmpl' +skip_sweeper: true +examples: + - name: 'apigee_environment_group_attachment_basic' + vars: + project_id: 'my-project' + envgroup_name: 'my-envgroup' + environment_name: 'my-environment' + skip_test: true + - name: 'apigee_environment_group_attachment_basic_test' + primary_resource_id: 'apigee_environment_group_attachment' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + skip_docs: true + skip_vcr: true +parameters: + - name: 'envgroupId' + type: String + description: | + The Apigee environment group associated with the Apigee environment, + in the format `organizations/{{org_name}}/envgroups/{{envgroup_name}}`. + url_param_only: true + required: true +properties: + - name: 'environment' + type: String + description: | + The resource ID of the environment. + required: true + - name: 'name' + type: String + description: | + The name of the newly created attachment (output parameter). + output: true diff --git a/mmv1/products/apigee/go_Environment.yaml b/mmv1/products/apigee/go_Environment.yaml new file mode 100644 index 000000000000..50a168e36fb0 --- /dev/null +++ b/mmv1/products/apigee/go_Environment.yaml @@ -0,0 +1,187 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'Environment' +description: | + An `Environment` in Apigee. +references: + guides: + 'Creating an environment': 'https://cloud.google.com/apigee/docs/api-platform/get-started/create-environment' + api: 'https://cloud.google.com/apigee/docs/reference/apis/apigee/rest/v1/organizations.environments/create' +docs: +base_url: 'environments' +self_link: '{{org_id}}/environments/{{name}}' +create_url: '{{org_id}}/environments' +update_url: '{{org_id}}/environments/{{name}}' +update_verb: 'PATCH' +update_mask: true +import_format: + - '{{org_id}}/environments/{{name}}' + - '{{org_id}}/{{name}}' +timeouts: + insert_minutes: 30 + update_minutes: 20 + delete_minutes: 30 +autogen_async: true +async: + actions: ['create', 'delete', 'update'] + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: true + error: + path: 'error' + message: 'message' +iam_policy: + method_name_separator: ':' + parent_resource_attribute: 'env_id' + base_url: '{{org_id}}/environments/{{name}}' + self_link: '{{org_id}}/environments/{{name}}' + import_format: + - '{{%org_id}}/environments/{{name}}' + - '{{name}}' +custom_code: + custom_import: 'templates/terraform/custom_import/go/apigee_environment.go.tmpl' +examples: + - name: 'apigee_environment_basic' + vars: + environment_name: 'my-environment' + skip_test: true + - name: 'apigee_environment_basic_test' + primary_resource_id: 'apigee_environment' + primary_resource_name: 'fmt.Sprintf("organizations/tf-test%s", context["random_suffix"]), fmt.Sprintf("tf-test%s", context["random_suffix"])' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + skip_docs: true + skip_vcr: true + - name: 'apigee_environment_basic_deployment_apiproxy_type_test' + primary_resource_id: 'apigee_environment' + primary_resource_name: 'fmt.Sprintf("organizations/tf-test%s", context["random_suffix"]), fmt.Sprintf("tf-test%s", context["random_suffix"])' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + skip_docs: true + skip_vcr: true + - name: 'apigee_environment_patch_update_test' + primary_resource_id: 'apigee_environment' + primary_resource_name: 'fmt.Sprintf("organizations/tf-test%s", context["random_suffix"]), fmt.Sprintf("tf-test%s", context["random_suffix"])' + min_version: 'beta' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + skip_docs: true + skip_vcr: true +parameters: + - name: 'orgId' + type: String + description: | + The Apigee Organization associated with the Apigee environment, + in the format `organizations/{{org_name}}`. + url_param_only: true + required: true + immutable: true +properties: + - name: 'name' + type: String + description: | + The resource ID of the environment. + required: true + immutable: true + - name: 'displayName' + type: String + description: | + Display name of the environment. + required: false + immutable: true + - name: 'description' + type: String + description: | + Description of the environment. + required: false + immutable: true + - name: 'deploymentType' + type: Enum + description: | + Optional. Deployment type supported by the environment. The deployment type can be + set when creating the environment and cannot be changed. When you enable archive + deployment, you will be prevented from performing a subset of actions within the + environment, including: + Managing the deployment of API proxy or shared flow revisions; + Creating, updating, or deleting resource files; + Creating, updating, or deleting target servers. + immutable: true + default_from_api: true + enum_values: + - 'DEPLOYMENT_TYPE_UNSPECIFIED' + - 'PROXY' + - 'ARCHIVE' + - name: 'apiProxyType' + type: Enum + description: | + Optional. API Proxy type supported by the environment. The type can be set when creating + the Environment and cannot be changed. + immutable: true + default_from_api: true + enum_values: + - 'API_PROXY_TYPE_UNSPECIFIED' + - 'PROGRAMMABLE' + - 'CONFIGURABLE' + - name: 'nodeConfig' + type: NestedObject + description: | + NodeConfig for setting the min/max number of nodes associated with the environment. + default_from_api: true + properties: + - name: 'minNodeCount' + type: String + description: | + The minimum total number of gateway nodes that the is reserved for all instances that + has the specified environment. If not specified, the default is determined by the + recommended minimum number of nodes for that gateway. + - name: 'maxNodeCount' + type: String + description: | + The maximum total number of gateway nodes that the is reserved for all instances that + has the specified environment. If not specified, the default is determined by the + recommended maximum number of nodes for that gateway. + - name: 'currentAggregateNodeCount' + type: String + description: | + The current total number of gateway nodes that each environment currently has across + all instances. + output: true + - name: 'type' + type: Enum + description: | + Types that can be selected for an Environment. Each of the types are + limited by capability and capacity. Refer to Apigee's public documentation + to understand about each of these types in details. + An Apigee org can support heterogeneous Environments. + default_from_api: true + enum_values: + - 'ENVIRONMENT_TYPE_UNSPECIFIED' + - 'BASE' + - 'INTERMEDIATE' + - 'COMPREHENSIVE' + - name: 'forwardProxyUri' + type: String + description: | + Optional. URI of the forward proxy to be applied to the runtime instances in this environment. Must be in the format of {scheme}://{hostname}:{port}. Note that the scheme must be one of "http" or "https", and the port must be supplied. + required: false diff --git a/mmv1/products/apigee/go_Instance.yaml b/mmv1/products/apigee/go_Instance.yaml new file mode 100644 index 000000000000..9f8927bebc19 --- /dev/null +++ b/mmv1/products/apigee/go_Instance.yaml @@ -0,0 +1,188 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'Instance' +description: | + An `Instance` is the runtime dataplane in Apigee. +references: + guides: + 'Creating a runtime instance': 'https://cloud.google.com/apigee/docs/api-platform/get-started/create-instance' + api: 'https://cloud.google.com/apigee/docs/reference/apis/apigee/rest/v1/organizations.instances/create' +docs: +base_url: 'instances' +self_link: '{{org_id}}/instances/{{name}}' +create_url: '{{org_id}}/instances' +immutable: true +mutex: '{{org_id}}/apigeeInstances' +import_format: + - '{{org_id}}/instances/{{name}}' + - '{{org_id}}/{{name}}' +timeouts: + insert_minutes: 60 + update_minutes: 20 + delete_minutes: 60 +autogen_async: true +async: + actions: ['create', 'delete', 'update'] + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: true + error: + path: 'error' + message: 'message' +custom_code: + constants: 'templates/terraform/constants/go/apigee_instance.go.tmpl' + custom_import: 'templates/terraform/custom_import/go/apigee_instance.go.tmpl' +error_retry_predicates: + + - 'transport_tpg.IsApigeeRetryableError' +examples: + - name: 'apigee_instance_basic' + vars: + instance_name: 'my-instance-name' + skip_test: true + - name: 'apigee_instance_basic_test' + primary_resource_id: 'apigee_instance' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + skip_docs: true + skip_vcr: true + - name: 'apigee_instance_cidr_range' + vars: + instance_name: 'my-instance-name' + skip_test: true + - name: 'apigee_instance_cidr_range_test' + primary_resource_id: 'apigee_instance' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + skip_docs: true + skip_vcr: true + - name: 'apigee_instance_ip_range' + vars: + instance_name: 'my-instance-name' + skip_test: true + - name: 'apigee_instance_ip_range_test' + primary_resource_id: 'apigee_instance' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + skip_docs: true + skip_vcr: true + - name: 'apigee_instance_full' + vars: + instance_name: 'my-instance-name' + skip_test: true + - name: 'apigee_instance_full_test' + primary_resource_id: 'apigee_instance' + min_version: 'beta' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + skip_docs: true + skip_vcr: true + - name: 'apigee_instance_service_attachment_basic_test' + primary_resource_id: 'apigee_instance' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + skip_docs: true + skip_vcr: true +parameters: + - name: 'orgId' + type: String + description: | + The Apigee Organization associated with the Apigee instance, + in the format `organizations/{{org_name}}`. + url_param_only: true + required: true + immutable: true +properties: + - name: 'name' + type: String + description: | + Resource ID of the instance. + required: true + - name: 'location' + type: String + description: | + Required. Compute Engine location where the instance resides. + required: true + - name: 'peeringCidrRange' + type: String + description: | + The size of the CIDR block range that will be reserved by the instance. For valid values, + see [CidrRange](https://cloud.google.com/apigee/docs/reference/apis/apigee/rest/v1/organizations.instances#CidrRange) on the documentation. + default_from_api: true + - name: 'ipRange' + type: String + description: | + IP range represents the customer-provided CIDR block of length 22 that will be used for + the Apigee instance creation. This optional range, if provided, should be freely + available as part of larger named range the customer has allocated to the Service + Networking peering. If this is not provided, Apigee will automatically request for any + available /22 CIDR block from Service Networking. The customer should use this CIDR block + for configuring their firewall needs to allow traffic from Apigee. + Input format: "a.b.c.d/22" + ignore_read: true + - name: 'description' + type: String + description: | + Description of the instance. + - name: 'displayName' + type: String + description: | + Display name of the instance. + - name: 'diskEncryptionKeyName' + type: String + description: | + Customer Managed Encryption Key (CMEK) used for disk and volume encryption. Required for Apigee paid subscriptions only. + Use the following format: `projects/([^/]+)/locations/([^/]+)/keyRings/([^/]+)/cryptoKeys/([^/]+)` + immutable: true + - name: 'host' + type: String + description: | + Output only. Hostname or IP address of the exposed Apigee endpoint used by clients to connect to the service. + output: true + - name: 'port' + type: String + description: | + Output only. Port number of the exposed Apigee endpoint. + output: true + - name: 'consumerAcceptList' + type: Array + description: | + Optional. Customer accept list represents the list of projects (id/number) on customer + side that can privately connect to the service attachment. It is an optional field + which the customers can provide during the instance creation. By default, the customer + project associated with the Apigee organization will be included to the list. + required: false + default_from_api: true + diff_suppress_func: 'projectListDiffSuppress' + item_type: + type: String + - name: 'serviceAttachment' + type: String + description: | + Output only. Resource name of the service attachment created for the instance in + the format: projects/*/regions/*/serviceAttachments/* Apigee customers can privately + forward traffic to this service attachment using the PSC endpoints. + output: true diff --git a/mmv1/products/apigee/go_InstanceAttachment.yaml b/mmv1/products/apigee/go_InstanceAttachment.yaml new file mode 100644 index 000000000000..bf5450c38978 --- /dev/null +++ b/mmv1/products/apigee/go_InstanceAttachment.yaml @@ -0,0 +1,86 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'InstanceAttachment' +description: | + An `Instance attachment` in Apigee. +references: + guides: + 'Creating an environment': 'https://cloud.google.com/apigee/docs/api-platform/get-started/create-environment' + api: 'https://cloud.google.com/apigee/docs/reference/apis/apigee/rest/v1/organizations.instances.attachments/create' +docs: +base_url: '{{instance_id}}/attachments' +self_link: '{{instance_id}}/attachments/{{name}}' +create_url: '{{instance_id}}/attachments' +delete_url: '{{instance_id}}/attachments/{{name}}' +immutable: true +mutex: 'apigeeInstanceAttachments' +import_format: + - '{{instance_id}}/attachments/{{name}}' + - '{{instance_id}}/{{name}}' +timeouts: + insert_minutes: 30 + update_minutes: 20 + delete_minutes: 30 +autogen_async: true +async: + actions: ['create', 'delete', 'update'] + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: true + error: + path: 'error' + message: 'message' +custom_code: + custom_import: 'templates/terraform/custom_import/go/apigee_instance_attachment.go.tmpl' +skip_sweeper: true +examples: + - name: 'apigee_instance_attachment_basic' + vars: + project_id: 'my-project' + instance_name: 'my-instance-name' + environment_name: 'my-environment-name' + skip_test: true + - name: 'apigee_instance_attachment_basic_test' + primary_resource_id: 'apigee_instance_attachment' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + skip_docs: true + skip_vcr: true +parameters: + - name: 'instanceId' + type: String + description: | + The Apigee instance associated with the Apigee environment, + in the format `organizations/{{org_name}}/instances/{{instance_name}}`. + url_param_only: true + required: true +properties: + - name: 'environment' + type: String + description: | + The resource ID of the environment. + required: true + - name: 'name' + type: String + description: | + The name of the newly created attachment (output parameter). + output: true diff --git a/mmv1/products/apigee/go_KeystoresAliasesSelfSignedCert.yaml b/mmv1/products/apigee/go_KeystoresAliasesSelfSignedCert.yaml new file mode 100644 index 000000000000..442341fb4a16 --- /dev/null +++ b/mmv1/products/apigee/go_KeystoresAliasesSelfSignedCert.yaml @@ -0,0 +1,218 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'KeystoresAliasesSelfSignedCert' +description: | + An Environment Keystore Alias for Self Signed Certificate Format in Apigee +references: + guides: + 'Creating an environment': 'https://cloud.google.com/apigee/docs/api-platform/get-started/create-environment' + api: 'https://cloud.google.com/apigee/docs/reference/apis/apigee/rest/v1/organizations.environments.keystores.aliases/create' +docs: +base_url: 'organizations/{{org_id}}/environments/{{environment}}/keystores/{{keystore}}/aliases/{{alias}}' +self_link: 'organizations/{{org_id}}/environments/{{environment}}/keystores/{{keystore}}/aliases/{{alias}}' +create_url: 'organizations/{{org_id}}/environments/{{environment}}/keystores/{{keystore}}/aliases?alias={{alias}}&format=selfsignedcert' +delete_url: 'organizations/{{org_id}}/environments/{{environment}}/keystores/{{keystore}}/aliases/{{alias}}' +immutable: true +import_format: + - 'organizations/{{org_id}}/environments/{{environment}}/keystores/{{keystore}}/aliases/{{alias}}' +timeouts: + insert_minutes: 30 + update_minutes: 20 + delete_minutes: 30 +autogen_async: true +custom_code: + custom_import: 'templates/terraform/custom_import/go/apigee_env_keystore_alias_self_signed_cert.go.tmpl' +skip_sweeper: true +examples: + - name: 'apigee_env_keystore_alias_self_signed_cert' + primary_resource_id: 'apigee_environment_keystore_ss_alias' + vars: + project_id: 'my-project' + environment_name: 'env-name' + keystore_name: 'env-keystore' + keystores_alias: 'alias' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + skip_vcr: true +parameters: + - name: 'orgId' + type: String + description: | + The Apigee Organization name associated with the Apigee environment + url_param_only: true + required: true + immutable: true + - name: 'environment' + type: String + description: | + The Apigee environment name + url_param_only: true + required: true + immutable: true + - name: 'keystore' + type: String + description: | + The Apigee keystore name associated in an Apigee environment + url_param_only: true + required: true + immutable: true + - name: 'alias' + type: String + description: | + Alias for the key/certificate pair. Values must match the regular expression [\w\s-.]{1,255}. + This must be provided for all formats except selfsignedcert; self-signed certs may specify the alias in either + this parameter or the JSON body. + required: true + immutable: true + - name: 'subjectAlternativeDnsNames' + type: NestedObject + description: | + List of alternative host names. Maximum length is 255 characters for each value. + immutable: true + properties: + - name: 'subjectAlternativeName' + type: String + description: Subject Alternative Name + - name: 'keySize' + type: String + description: | + Key size. Default and maximum value is 2048 bits. + ignore_read: true + - name: 'sigAlg' + type: String + description: | + Signature algorithm to generate private key. Valid values are SHA512withRSA, SHA384withRSA, and SHA256withRSA + required: true + immutable: true + ignore_read: true + - name: 'subject' + type: NestedObject + description: Subject details. + required: true + immutable: true + ignore_read: true + properties: + - name: 'countryCode' + type: String + description: + Two-letter country code. Example, IN for India, US for United States + of America. + ignore_read: true + - name: 'state' + type: String + description: State or district name. Maximum length is 128 characters. + ignore_read: true + - name: 'locality' + type: String + description: City or town name. Maximum length is 128 characters. + ignore_read: true + - name: 'org' + type: String + description: Organization name. Maximum length is 64 characters. + ignore_read: true + - name: 'orgUnit' + type: String + description: Organization team name. Maximum length is 64 characters. + ignore_read: true + - name: 'commonName' + type: String + description: | + Common name of the organization. Maximum length is 64 characters. + ignore_read: true + - name: 'email' + type: String + description: Email address. Max 255 characters. + ignore_read: true + - name: 'certValidityInDays' + type: Integer + description: | + Validity duration of certificate, in days. Accepts positive non-zero value. Defaults to 365. + immutable: true + ignore_read: true +properties: + - name: 'certsInfo' + type: NestedObject + description: Chain of certificates under this alias. + output: true + properties: + - name: 'certInfo' + type: Array + description: List of all properties in the object. + output: true + item_type: + type: NestedObject + properties: + - name: 'version' + type: Integer + description: X.509 version. + output: true + - name: 'subject' + type: String + description: X.509 subject. + output: true + - name: 'issuer' + type: String + description: X.509 issuer. + output: true + - name: 'expiryDate' + type: String + description: + X.509 notAfter validity period in milliseconds since epoch. + output: true + - name: 'validFrom' + type: String + description: + X.509 notBefore validity period in milliseconds since epoch. + output: true + - name: 'isValid' + type: String + description: | + Flag that specifies whether the certificate is valid. + Flag is set to Yes if the certificate is valid, No if expired, or Not yet if not yet valid. + output: true + - name: 'subjectAlternativeNames' + type: Array + description: X.509 subject alternative names (SANs) extension. + output: true + item_type: + type: String + - name: 'sigAlgName' + type: String + description: X.509 signatureAlgorithm. + output: true + - name: 'publicKey' + type: String + description: + Public key component of the X.509 subject public key info. + output: true + - name: 'basicConstraints' + type: String + description: X.509 basic constraints extension. + output: true + - name: 'serialNumber' + type: String + description: X.509 serial number. + output: true + - name: 'type' + type: Enum + description: | + Optional.Type of Alias + output: true + enum_values: + - 'ALIAS_TYPE_UNSPECIFIED' + - 'CERT' + - 'KEY_CERT' diff --git a/mmv1/products/apigee/go_NatAddress.yaml b/mmv1/products/apigee/go_NatAddress.yaml new file mode 100644 index 000000000000..e306ed2f6e5e --- /dev/null +++ b/mmv1/products/apigee/go_NatAddress.yaml @@ -0,0 +1,89 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'NatAddress' +description: | + Apigee NAT (network address translation) address. A NAT address is a static external IP address used for Internet egress traffic. This is not avaible for Apigee hybrid. + Apigee NAT addresses are not automatically activated because they might require explicit allow entries on the target systems first. See https://cloud.google.com/apigee/docs/reference/apis/apigee/rest/v1/organizations.instances.natAddresses/activate +references: + guides: + 'Provisioning NAT IPs': 'https://cloud.google.com/apigee/docs/api-platform/security/nat-provisioning' + api: 'https://cloud.google.com/apigee/docs/reference/apis/apigee/rest/v1/organizations.instances.natAddresses' +docs: +base_url: '{{instance_id}}/natAddresses' +self_link: '{{instance_id}}/natAddresses/{{name}}' +create_url: '{{instance_id}}/natAddresses' +delete_url: '{{instance_id}}/natAddresses/{{name}}' +immutable: true +import_format: + - '{{instance_id}}/natAddresses/{{name}}' + - '{{instance_id}}/{{name}}' +timeouts: + insert_minutes: 30 + update_minutes: 20 + delete_minutes: 30 +autogen_async: true +async: + actions: ['create', 'delete', 'update'] + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: true + error: + path: 'error' + message: 'message' +custom_code: + custom_import: 'templates/terraform/custom_import/go/apigee_nat_address.go.tmpl' +skip_sweeper: true +examples: + - name: 'apigee_nat_address_basic' + vars: + nat_address_name: 'my-nat-address' + skip_test: true + - name: 'apigee_nat_address_basic_test' + primary_resource_id: 'apigee_nat_address' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + skip_docs: true + skip_vcr: true +parameters: + - name: 'instanceId' + type: String + description: | + The Apigee instance associated with the Apigee environment, + in the format `organizations/{{org_name}}/instances/{{instance_name}}`. + url_param_only: true + required: true +properties: + - name: 'name' + type: String + description: | + Resource ID of the NAT address. + required: true + - name: 'ipAddress' + type: String + description: | + The allocated NAT IP address. + output: true + - name: 'state' + type: String + description: | + State of the NAT IP address. + output: true diff --git a/mmv1/products/apigee/go_Organization.yaml b/mmv1/products/apigee/go_Organization.yaml new file mode 100644 index 000000000000..69042edd4c8f --- /dev/null +++ b/mmv1/products/apigee/go_Organization.yaml @@ -0,0 +1,245 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'Organization' +description: | + An `Organization` is the top-level container in Apigee. +references: + guides: + 'Creating an API organization': 'https://cloud.google.com/apigee/docs/api-platform/get-started/create-org' + api: 'https://cloud.google.com/apigee/docs/reference/apis/apigee/rest/v1/organizations' +docs: +base_url: 'organizations' +self_link: 'organizations/{{name}}' +create_url: 'organizations?parent=projects/{{project_id}}' +delete_url: 'organizations/{{name}}?retention={{retention}}' +timeouts: + insert_minutes: 45 + update_minutes: 45 + delete_minutes: 45 +autogen_async: true +async: + actions: ['create', 'update'] + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + timeouts: + insert_minutes: 45 + update_minutes: 45 + delete_minutes: 45 + result: + path: 'response' + resource_inside_response: true + error: + path: 'error' + message: 'message' +custom_code: + encoder: 'templates/terraform/encoders/go/apigee_organization.go.tmpl' + custom_import: 'templates/terraform/custom_import/go/apigee_organization.go.tmpl' +examples: + - name: 'apigee_organization_cloud_basic' + skip_test: true + - name: 'apigee_organization_cloud_basic_test' + primary_resource_id: 'org' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + ignore_read_extra: + - 'properties' + skip_docs: true + skip_vcr: true + - name: 'apigee_organization_cloud_basic_disable_vpc_peering' + skip_test: true + - name: 'apigee_organization_cloud_basic_disable_vpc_peering_test' + primary_resource_id: 'org' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + ignore_read_extra: + - 'properties' + skip_docs: true + skip_vcr: true + - name: 'apigee_organization_cloud_full' + skip_test: true + - name: 'apigee_organization_cloud_full_test' + primary_resource_id: 'org' + min_version: 'beta' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + ignore_read_extra: + - 'properties' + skip_docs: true + skip_vcr: true + - name: 'apigee_organization_cloud_full_disable_vpc_peering' + skip_test: true + - name: 'apigee_organization_cloud_full_disable_vpc_peering_test' + primary_resource_id: 'org' + min_version: 'beta' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + ignore_read_extra: + - 'properties' + skip_docs: true + skip_vcr: true + - name: 'apigee_organization_retention_test' + primary_resource_id: 'org' + min_version: 'beta' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + skip_docs: true + skip_vcr: true + - name: 'apigee_organization_drz_test' + primary_resource_id: 'org' + min_version: 'beta' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + skip_docs: true + skip_vcr: true +parameters: + - name: 'projectId' + type: String + description: | + The project ID associated with the Apigee organization. + url_param_only: true + required: true + immutable: true + - name: 'retention' + type: Enum + description: | + Optional. This setting is applicable only for organizations that are soft-deleted (i.e., BillingType + is not EVALUATION). It controls how long Organization data will be retained after the initial delete + operation completes. During this period, the Organization may be restored to its last known state. + After this period, the Organization will no longer be able to be restored. + url_param_only: true + required: false + default_value: "DELETION_RETENTION_UNSPECIFIED" + enum_values: + - 'DELETION_RETENTION_UNSPECIFIED' + - 'MINIMUM' +properties: + - name: 'name' + type: String + description: | + Output only. Name of the Apigee organization. + output: true + - name: 'displayName' + type: String + description: | + The display name of the Apigee organization. + - name: 'description' + type: String + description: | + Description of the Apigee organization. + - name: 'analyticsRegion' + type: String + description: | + Primary GCP region for analytics data storage. For valid values, see [Create an Apigee organization](https://cloud.google.com/apigee/docs/api-platform/get-started/create-org). + immutable: true + - name: 'apiConsumerDataLocation' + type: String + description: | + This field is needed only for customers using non-default data residency regions. + Apigee stores some control plane data only in single region. + This field determines which single region Apigee should use. + immutable: true + - name: 'apiConsumerDataEncryptionKeyName' + type: String + description: | + Cloud KMS key name used for encrypting API consumer data. + immutable: true + - name: 'controlPlaneEncryptionKeyName' + type: String + description: | + Cloud KMS key name used for encrypting control plane data that is stored in a multi region. + Only used for the data residency region "US" or "EU". + immutable: true + - name: 'authorizedNetwork' + type: String + description: | + Compute Engine network used for Service Networking to be peered with Apigee runtime instances. + See [Getting started with the Service Networking API](https://cloud.google.com/service-infrastructure/docs/service-networking/getting-started). + Valid only when `RuntimeType` is set to CLOUD. The value can be updated only when there are no runtime instances. For example: "default". + - name: 'disableVpcPeering' + type: Boolean + description: | + Flag that specifies whether the VPC Peering through Private Google Access should be + disabled between the consumer network and Apigee. Required if an `authorizedNetwork` + on the consumer project is not provided, in which case the flag should be set to `true`. + Valid only when `RuntimeType` is set to CLOUD. The value must be set before the creation + of any Apigee runtime instance and can be updated only when there are no runtime instances. + - name: 'runtimeType' + type: Enum + description: | + Runtime type of the Apigee organization based on the Apigee subscription purchased. + immutable: true + default_value: "CLOUD" + enum_values: + - 'CLOUD' + - 'HYBRID' + - name: 'subscriptionType' + type: String + description: | + Output only. Subscription type of the Apigee organization. + Valid values include trial (free, limited, and for evaluation purposes only) or paid (full subscription has been purchased). + output: true + - name: 'billingType' + type: String + description: | + Billing type of the Apigee organization. See [Apigee pricing](https://cloud.google.com/apigee/pricing). + immutable: true + default_from_api: true + - name: 'caCertificate' + type: String + description: | + Output only. Base64-encoded public certificate for the root CA of the Apigee organization. + Valid only when `RuntimeType` is CLOUD. A base64-encoded string. + output: true + - name: 'runtimeDatabaseEncryptionKeyName' + type: String + description: | + Cloud KMS key name used for encrypting the data that is stored and replicated across runtime instances. + Update is not allowed after the organization is created. + If not specified, a Google-Managed encryption key will be used. + Valid only when `RuntimeType` is CLOUD. For example: `projects/foo/locations/us/keyRings/bar/cryptoKeys/baz`. + immutable: true + - name: 'properties' + type: NestedObject + description: Properties defined in the Apigee organization profile. + default_from_api: true + properties: + - name: 'property' + type: Array + description: List of all properties in the object. + custom_flatten: 'templates/terraform/custom_flatten/go/apigee_organization_property.go.tmpl' + item_type: + type: NestedObject + properties: + - name: 'name' + type: String + description: Name of the property. + - name: 'value' + type: String + description: Value of the property. + - name: 'apigeeProjectId' + type: String + description: | + Output only. Project ID of the Apigee Tenant Project. + output: true diff --git a/mmv1/products/apigee/go_SyncAuthorization.yaml b/mmv1/products/apigee/go_SyncAuthorization.yaml new file mode 100644 index 000000000000..ed84fd5269aa --- /dev/null +++ b/mmv1/products/apigee/go_SyncAuthorization.yaml @@ -0,0 +1,76 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'SyncAuthorization' +description: | + Authorize the Synchronizer to download environment data from the control plane. +references: + guides: + 'Enable Synchronizer access': 'https://cloud.google.com/apigee/docs/hybrid/v1.8/synchronizer-access#enable-synchronizer-access' + api: 'https://cloud.google.com/apigee/docs/reference/apis/apigee/rest/v1/organizations#getsyncauthorization' +docs: +id_format: 'organizations/{{name}}/syncAuthorization' +base_url: '' +self_link: 'organizations/{{name}}:getSyncAuthorization' +create_url: 'organizations/{{name}}:setSyncAuthorization' +update_url: 'organizations/{{name}}:setSyncAuthorization' +update_verb: 'POST' +read_verb: 'POST' +skip_delete: true +import_format: + - 'organizations/{{name}}/syncAuthorization' +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +custom_code: +examples: + - name: 'apigee_sync_authorization_basic_test' + primary_resource_id: 'apigee_sync_authorization' + vars: + account_id: 'my-account' + project_id: 'my-project' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' +parameters: + - name: 'name' + type: String + description: | + Name of the Apigee organization. + url_param_only: true + required: true + immutable: true +properties: + - name: 'identities' + type: Array + description: | + Array of service accounts to grant access to control plane resources, each specified using the following format: `serviceAccount:service-account-name`. + + The `service-account-name` is formatted like an email address. For example: my-synchronizer-manager-serviceAccount@my_project_id.iam.gserviceaccount.com + + You might specify multiple service accounts, for example, if you have multiple environments and wish to assign a unique service account to each one. + + The service accounts must have **Apigee Synchronizer Manager** role. See also [Create service accounts](https://cloud.google.com/apigee/docs/hybrid/v1.8/sa-about#create-the-service-accounts). + required: true + send_empty_value: true + item_type: + type: String + - name: 'etag' + type: Fingerprint + description: | + Entity tag (ETag) used for optimistic concurrency control as a way to help prevent simultaneous updates from overwriting each other. + Used internally during updates. + output: true diff --git a/mmv1/products/apigee/go_TargetServer.yaml b/mmv1/products/apigee/go_TargetServer.yaml new file mode 100644 index 000000000000..1eba3ab6d617 --- /dev/null +++ b/mmv1/products/apigee/go_TargetServer.yaml @@ -0,0 +1,157 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'TargetServer' +description: | + TargetServer configuration. TargetServers are used to decouple a proxy TargetEndpoint HTTPTargetConnections from concrete URLs for backend services. +references: + guides: + 'Load balancing across backend servers': 'https://cloud.google.com/apigee/docs/api-platform/deploy/load-balancing-across-backend-servers' + api: 'https://cloud.google.com/apigee/docs/reference/apis/apigee/rest/v1/organizations.environments.targetservers/create' +docs: +base_url: '{{env_id}}/targetservers' +self_link: '{{env_id}}/targetservers/{{name}}' +create_url: '{{env_id}}/targetservers' +update_url: '{{env_id}}/targetservers/{{name}}' +delete_url: '{{env_id}}/targetservers/{{name}}' +import_format: + - '{{env_id}}/targetservers/{{name}}' + - '{{env_id}}/{{name}}' +timeouts: + insert_minutes: 1 + update_minutes: 1 + delete_minutes: 1 +autogen_async: true +custom_code: + custom_import: 'templates/terraform/custom_import/go/apigee_target_server.go.tmpl' +skip_sweeper: true +examples: + - name: 'apigee_target_server_test_basic' + primary_resource_id: 'apigee_target_server' + vars: + project_id: 'my-project' + environment_name: 'my-environment-name' + target_server: 'my-target-server' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + skip_test: true + - name: 'apigee_target_server_test' + primary_resource_id: 'apigee_target_server' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + skip_docs: true +parameters: + - name: 'envId' + type: String + description: | + The Apigee environment group associated with the Apigee environment, + in the format `organizations/{{org_name}}/environments/{{env_name}}`. + url_param_only: true + required: true + immutable: true +properties: + - name: 'name' + type: String + description: | + The resource id of this reference. Values must match the regular expression [\w\s-.]+. + required: true + immutable: true + - name: 'description' + type: String + description: | + A human-readable description of this TargetServer. + - name: 'host' + type: String + description: | + The host name this target connects to. Value must be a valid hostname as described by RFC-1123. + required: true + - name: 'port' + type: Integer + description: | + The port number this target connects to on the given host. Value must be between 1 and 65535, inclusive. + required: true + - name: 'isEnabled' + type: Boolean + description: | + Enabling/disabling a TargetServer is useful when TargetServers are used in load balancing configurations, and one or more TargetServers need to taken out of rotation periodically. Defaults to true. + default_value: true + - name: 'sSLInfo' + type: NestedObject + description: Specifies TLS configuration info for this TargetServer. The JSON name is sSLInfo for legacy/backwards compatibility reasons -- Edge originally supported SSL, and the name is still used for TLS configuration. + properties: + - name: 'enabled' + type: Boolean + description: | + Enables TLS. If false, neither one-way nor two-way TLS will be enabled. + required: true + - name: 'clientAuthEnabled' + type: Boolean + description: | + Enables two-way TLS. + - name: 'keyStore' + type: String + description: | + Required if clientAuthEnabled is true. The resource ID of the keystore. + - name: 'keyAlias' + type: String + description: | + Required if clientAuthEnabled is true. The resource ID for the alias containing the private key and cert. + - name: 'trustStore' + type: String + description: | + The resource ID of the truststore. + - name: 'ignoreValidationErrors' + type: Boolean + description: | + If true, Edge ignores TLS certificate errors. Valid when configuring TLS for target servers and target endpoints, and when configuring virtual hosts that use 2-way TLS. When used with a target endpoint/target server, if the backend system uses SNI and returns a cert with a subject Distinguished Name (DN) that does not match the hostname, there is no way to ignore the error and the connection fails. + - name: 'protocols' + type: Array + description: | + The TLS versioins to be used. + item_type: + type: String + - name: 'ciphers' + type: Array + description: | + The SSL/TLS cipher suites to be used. For programmable proxies, it must be one of the cipher suite names listed in: http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#ciphersuites. For configurable proxies, it must follow the configuration specified in: https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#Cipher-suite-configuration. This setting has no effect for configurable proxies when negotiating TLS 1.3. + item_type: + type: String + - name: 'commonName' + type: NestedObject + description: The TLS Common Name of the certificate. + properties: + - name: 'value' + type: String + description: | + The TLS Common Name string of the certificate. + - name: 'wildcardMatch' + type: Boolean + description: | + Indicates whether the cert should be matched against as a wildcard cert. + + - name: 'protocol' + type: Enum + description: | + Immutable. The protocol used by this TargetServer. + immutable: true + default_from_api: true + enum_values: + - 'HTTP' + - 'HTTP2' + - 'GRPC_TARGET' + - 'GRPC' + - 'EXTERNAL_CALLOUT' diff --git a/mmv1/products/apigee/go_product.yaml b/mmv1/products/apigee/go_product.yaml new file mode 100644 index 000000000000..944e02301a90 --- /dev/null +++ b/mmv1/products/apigee/go_product.yaml @@ -0,0 +1,22 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'Apigee' +display_name: 'Apigee' +versions: + - name: 'ga' + base_url: 'https://apigee.googleapis.com/v1/' +scopes: + - 'https://www.googleapis.com/auth/cloud-platform' diff --git a/mmv1/products/beyondcorp/AppConnection.yaml b/mmv1/products/beyondcorp/AppConnection.yaml index 1ebf2c7368fb..55bdb8dc1d9b 100644 --- a/mmv1/products/beyondcorp/AppConnection.yaml +++ b/mmv1/products/beyondcorp/AppConnection.yaml @@ -95,8 +95,8 @@ properties: - !ruby/object:Api::Type::String name: 'type' description: | - The type of network connectivity used by the AppConnection. Refer to - https://cloud.google.com/beyondcorp/docs/reference/rest/v1/projects.locations.appConnections#type + The type of network connectivity used by the AppConnection. Refer + to https://cloud.google.com/beyondcorp/docs/reference/rest/v1/projects.locations.appConnections#type for a list of possible values. immutable: true - !ruby/object:Api::Type::NestedObject diff --git a/mmv1/products/bigqueryreservation/Assignment.yaml b/mmv1/products/bigqueryreservation/Assignment.yaml new file mode 100644 index 000000000000..b4d077bdc1ff --- /dev/null +++ b/mmv1/products/bigqueryreservation/Assignment.yaml @@ -0,0 +1,90 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the License); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Api::Resource +name: ReservationAssignment +base_url: 'projects/{{project}}/locations/{{location}}/reservations/{{reservation}}/assignments' +create_url: 'projects/{{project}}/locations/{{location}}/reservations/{{reservation}}/assignments' +self_link: 'projects/{{project}}/locations/{{location}}/reservations/{{reservation}}/assignments' +delete_url: 'projects/{{project}}/locations/{{location}}/reservations/{{reservation}}/assignments/{{name}}' +id_format: 'projects/{{project}}/locations/{{location}}/reservations/{{reservation}}/assignments/{{name}}' +import_format: + [ + 'projects/{{project}}/locations/{{location}}/reservations/{{reservation}}/assignments/{{name}}', + ] +nested_query: !ruby/object:Api::Resource::NestedQuery + keys: + - assignments +references: !ruby/object:Api::Resource::ReferenceLinks + guides: + 'Work with reservation assignments': 'https://cloud.google.com/bigquery/docs/reservations-assignments' + api: 'https://cloud.google.com/bigquery/docs/reference/reservations/rest/v1/projects.locations.reservations.assignments' +legacy_long_form_project: true +description: | + The BigqueryReservation Assignment resource. +immutable: true +custom_code: !ruby/object:Provider::Terraform::CustomCode + pre_create: templates/terraform/pre_create/bigquery_reservation_assignment.go.erb +examples: + - !ruby/object:Provider::Terraform::Examples + name: 'bigquery_reservation_assignment_basic' + primary_resource_id: 'assignment' + vars: + reservation_name: "example-reservation" + test_env_vars: + project: :PROJECT_NAME + - !ruby/object:Provider::Terraform::Examples + name: 'bigquery_reservation_assignment_full' + primary_resource_id: 'assignment' + skip_docs: true + vars: + reservation_name: "example-reservation" + test_env_vars: + project: :PROJECT_NAME +parameters: + - !ruby/object:Api::Type::String + name: location + description: The location for the resource + url_param_only: true + immutable: true + default_from_api: true + - !ruby/object:Api::Type::ResourceRef + name: reservation + resource: reservation + imports: name + description: The reservation for the resource + url_param_only: true + required: true + immutable: true +properties: + - !ruby/object:Api::Type::String + name: name + description: Output only. The resource name of the assignment. + output: true + custom_flatten: 'templates/terraform/custom_flatten/name_from_self_link.erb' + - !ruby/object:Api::Type::String + name: assignee + description: The resource which will use the reservation. E.g. projects/myproject, folders/123, organizations/456. + required: true + diff_suppress_func: 'tpgresource.CompareSelfLinkOrResourceName' + - !ruby/object:Api::Type::String + name: jobType + description: | + Types of job, which could be specified when using the reservation. Possible values: JOB_TYPE_UNSPECIFIED, PIPELINE, QUERY + required: true + - !ruby/object:Api::Type::String + name: state + description: | + Assignment will remain in PENDING state if no active capacity commitment is present. It will become ACTIVE when some capacity commitment becomes active. + Possible values: STATE_UNSPECIFIED, PENDING, ACTIVE + output: true diff --git a/mmv1/products/certificatemanager/TrustConfig.yaml b/mmv1/products/certificatemanager/TrustConfig.yaml index d7395fa22948..414c639bf9da 100644 --- a/mmv1/products/certificatemanager/TrustConfig.yaml +++ b/mmv1/products/certificatemanager/TrustConfig.yaml @@ -50,8 +50,11 @@ examples: primary_resource_id: 'default' vars: trust_config_name: 'trust-config' -custom_code: !ruby/object:Provider::Terraform::CustomCode - pre_update: templates/terraform/pre_update/certificate_manager_trust_config.go.erb + - !ruby/object:Provider::Terraform::Examples + name: 'certificate_manager_trust_config_allowlisted_certificates' + primary_resource_id: 'default' + vars: + trust_config_name: 'trust-config' parameters: - !ruby/object:Api::Type::String name: 'name' @@ -87,7 +90,6 @@ properties: - !ruby/object:Api::Type::KeyValueLabels name: 'labels' description: 'Set of label tags associated with the trust config.' - immutable: true - !ruby/object:Api::Type::String name: 'description' description: | @@ -124,3 +126,15 @@ properties: PEM intermediate certificate used for building up paths for validation. Each certificate provided in PEM format may occupy up to 5kB. sensitive: true + - !ruby/object:Api::Type::Array + name: allowlistedCertificates + description: | + Allowlisted PEM-encoded certificates. A certificate matching an allowlisted certificate is always considered valid as long as + the certificate is parseable, proof of private key possession is established, and constraints on the certificate's SAN field are met. + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::String + name: 'pemCertificate' + description: | + PEM certificate that is allowlisted. The certificate can be up to 5k bytes, and must be a parseable X.509 certificate. + required: true diff --git a/mmv1/products/cloudscheduler/Job.yaml b/mmv1/products/cloudscheduler/Job.yaml index f65975609557..bd22c694415c 100644 --- a/mmv1/products/cloudscheduler/Job.yaml +++ b/mmv1/products/cloudscheduler/Job.yaml @@ -343,7 +343,7 @@ properties: description: | The full URI path that the request will be sent to. required: true - diff_suppress_func: 'tpgresource.LastSlashDiffSuppress' + diff_suppress_func: 'LastSlashDiffSuppress' - !ruby/object:Api::Type::String name: httpMethod description: | diff --git a/mmv1/products/compute/Autoscaler.yaml b/mmv1/products/compute/Autoscaler.yaml index f62d4f9a1b99..f2db0fd9d46f 100644 --- a/mmv1/products/compute/Autoscaler.yaml +++ b/mmv1/products/compute/Autoscaler.yaml @@ -137,6 +137,7 @@ properties: to. This is required when creating or updating an autoscaler. The maximum number of replicas should not be lower than minimal number of replicas. + send_empty_value: true required: true - !ruby/object:Api::Type::Integer name: 'cooldownPeriod' diff --git a/mmv1/products/compute/Disk.yaml b/mmv1/products/compute/Disk.yaml index f8737babfc7d..2f794dbc11e9 100644 --- a/mmv1/products/compute/Disk.yaml +++ b/mmv1/products/compute/Disk.yaml @@ -362,7 +362,7 @@ properties: disk interfaces are automatically determined on attachment. description: | Specifies the disk interface to use for attaching this disk, which is either SCSI or NVME. The default is SCSI. - diff_suppress_func: 'tpgresource.AlwaysDiffSuppress' + diff_suppress_func: AlwaysDiffSuppress - !ruby/object:Api::Type::String name: 'sourceDisk' description: | diff --git a/mmv1/products/compute/ForwardingRule.yaml b/mmv1/products/compute/ForwardingRule.yaml index 4e6f3b6655ab..e3877f30bdb7 100644 --- a/mmv1/products/compute/ForwardingRule.yaml +++ b/mmv1/products/compute/ForwardingRule.yaml @@ -312,7 +312,7 @@ properties: When reading an `IPAddress`, the API always returns the IP address number. default_from_api: true - diff_suppress_func: 'tpgresource.InternalIpDiffSuppress' + diff_suppress_func: InternalIpDiffSuppress - !ruby/object:Api::Type::Enum name: 'IPProtocol' description: | @@ -430,7 +430,7 @@ properties: cannot have overlapping `portRange`s. @pattern: \d+(?:-\d+)? - diff_suppress_func: 'tpgresource.PortRangeDiffSuppress' + diff_suppress_func: PortRangeDiffSuppress default_from_api: true - !ruby/object:Api::Type::Array name: 'ports' @@ -497,6 +497,13 @@ properties: update_url: 'projects/{{project}}/regions/{{region}}/forwardingRules/{{name}}/setTarget' diff_suppress_func: 'tpgresource.CompareSelfLinkRelativePaths' custom_expand: 'templates/terraform/custom_expand/self_link_from_name.erb' + - !ruby/object:Api::Type::Fingerprint + name: 'labelFingerprint' + description: | + The fingerprint used for optimistic locking of this resource. Used + internally during updates. + update_url: 'projects/{{project}}/regions/{{region}}/forwardingRules/{{name}}/setLabels' + update_verb: :POST - !ruby/object:Api::Type::Boolean name: 'allowGlobalAccess' description: | @@ -518,13 +525,6 @@ properties: Labels to apply to this forwarding rule. A list of key->value pairs. update_verb: :POST update_url: 'projects/{{project}}/regions/{{region}}/forwardingRules/{{name}}/setLabels' - - !ruby/object:Api::Type::Fingerprint - name: 'labelFingerprint' - description: | - The fingerprint used for optimistic locking of this resource. Used - internally during updates. - update_url: 'projects/{{project}}/regions/{{region}}/forwardingRules/{{name}}/setLabels' - update_verb: :POST - !ruby/object:Api::Type::Boolean name: 'allPorts' description: | diff --git a/mmv1/products/compute/GlobalForwardingRule.yaml b/mmv1/products/compute/GlobalForwardingRule.yaml index af9a88d4d512..a8c365e59bb6 100644 --- a/mmv1/products/compute/GlobalForwardingRule.yaml +++ b/mmv1/products/compute/GlobalForwardingRule.yaml @@ -25,8 +25,7 @@ description: | balancing. Global forwarding rules can only be used for HTTP load balancing. - For more information, see - https://cloud.google.com/compute/docs/load-balancing/http/ + For more information, see https://cloud.google.com/compute/docs/load-balancing/http/ async: !ruby/object:Api::OpAsync operation: !ruby/object:Api::OpAsync::Operation kind: 'compute#operation' @@ -264,7 +263,7 @@ properties: When reading an `IPAddress`, the API always returns the IP address number. default_from_api: true - diff_suppress_func: 'tpgresource.InternalIpDiffSuppress' + diff_suppress_func: InternalIpDiffSuppress - !ruby/object:Api::Type::Enum name: 'IPProtocol' description: | @@ -441,7 +440,7 @@ properties: cannot have overlapping `portRange`s. @pattern: \d+(?:-\d+)? - diff_suppress_func: 'tpgresource.PortRangeDiffSuppress' + diff_suppress_func: PortRangeDiffSuppress # This is a multi-resource resource reference (TargetHttp(s)Proxy, # TargetSslProxy, TargetTcpProxy, TargetVpnGateway, TargetPool, # TargetInstance) diff --git a/mmv1/products/compute/HealthCheck.yaml b/mmv1/products/compute/HealthCheck.yaml index f0e233c595a2..4f5b45c9b6b0 100644 --- a/mmv1/products/compute/HealthCheck.yaml +++ b/mmv1/products/compute/HealthCheck.yaml @@ -170,6 +170,30 @@ properties: The default value is 5 seconds. It is invalid for timeoutSec to have greater value than checkIntervalSec. default_value: 5 + - !ruby/object:Api::Type::Array + name: 'sourceRegions' + item_type: Api::Type::String + min_size: 3 + max_size: 3 + min_version: beta + description: | + The list of cloud regions from which health checks are performed. If + any regions are specified, then exactly 3 regions should be specified. + The region names must be valid names of Google Cloud regions. This can + only be set for global health check. If this list is non-empty, then + there are restrictions on what other health check fields are supported + and what other resources can use this health check: + + * SSL, HTTP2, and GRPC protocols are not supported. + + * The TCP request field is not supported. + + * The proxyHeader field for HTTP, HTTPS, and TCP is not supported. + + * The checkIntervalSec field must be at least 30. + + * The health check cannot be used with BackendService nor with managed + instance group auto-healing. - !ruby/object:Api::Type::Integer name: 'unhealthyThreshold' description: | diff --git a/mmv1/products/compute/Instance.yaml b/mmv1/products/compute/Instance.yaml index d316af142a77..72d208dcab1e 100644 --- a/mmv1/products/compute/Instance.yaml +++ b/mmv1/products/compute/Instance.yaml @@ -609,9 +609,9 @@ properties: at_least_one_of: - confidential_instance_config.0.enable_confidential_compute - confidential_instance_config.0.confidential_instance_type + deprecation_message: "`enableConfidentialCompute` is deprecated and will be removed in a future major release. Use `confidentialInstanceType: SEV` instead." - !ruby/object:Api::Type::Enum name: 'confidentialInstanceType' - min_version: beta description: | The confidential computing technology the instance uses. SEV is an AMD feature. One of the following values: SEV, SEV_SNP. diff --git a/mmv1/products/compute/ManagedSslCertificate.yaml b/mmv1/products/compute/ManagedSslCertificate.yaml index 1975c56387a5..030079063920 100644 --- a/mmv1/products/compute/ManagedSslCertificate.yaml +++ b/mmv1/products/compute/ManagedSslCertificate.yaml @@ -74,6 +74,8 @@ docs: !ruby/object:Provider::Terraform::Docs certificates may entail some downtime while the certificate provisions. In conclusion: Be extremely cautious. +custom_code: !ruby/object:Provider::Terraform::CustomCode + constants: templates/terraform/constants/compute_managed_ssl_certificate.go.erb examples: - !ruby/object:Provider::Terraform::Examples name: 'managed_ssl_certificate_basic' @@ -131,7 +133,7 @@ properties: there can be up to 100 domains in this list. max_size: 100 required: true - diff_suppress_func: 'tpgresource.AbsoluteDomainSuppress' + diff_suppress_func: 'AbsoluteDomainSuppress' item_type: Api::Type::String - !ruby/object:Api::Type::Enum name: 'type' diff --git a/mmv1/products/compute/NetworkEndpointGroup.yaml b/mmv1/products/compute/NetworkEndpointGroup.yaml index f37a3365d013..dfda899db1d6 100644 --- a/mmv1/products/compute/NetworkEndpointGroup.yaml +++ b/mmv1/products/compute/NetworkEndpointGroup.yaml @@ -55,6 +55,8 @@ async: !ruby/object:Api::OpAsync error: !ruby/object:Api::OpAsync::Error path: 'error/errors' message: 'message' +custom_code: !ruby/object:Provider::Terraform::CustomCode + constants: templates/terraform/constants/compute_network_endpoint_group.go.erb examples: - !ruby/object:Provider::Terraform::Examples name: 'network_endpoint_group' @@ -139,7 +141,7 @@ properties: imports: 'selfLink' description: | Optional subnetwork to which all network endpoints in the NEG belong. - diff_suppress_func: 'tpgresource.CompareOptionalSubnet' + diff_suppress_func: 'compareOptionalSubnet' custom_expand: 'templates/terraform/custom_expand/resourceref_with_validation.go.erb' - !ruby/object:Api::Type::Integer name: 'defaultPort' diff --git a/mmv1/products/compute/RegionAutoscaler.yaml b/mmv1/products/compute/RegionAutoscaler.yaml index fa9991ee61aa..9156b1970aa1 100644 --- a/mmv1/products/compute/RegionAutoscaler.yaml +++ b/mmv1/products/compute/RegionAutoscaler.yaml @@ -117,6 +117,7 @@ properties: to. This is required when creating or updating an autoscaler. The maximum number of replicas should not be lower than minimal number of replicas. + send_empty_value: true required: true - !ruby/object:Api::Type::Integer name: 'cooldownPeriod' diff --git a/mmv1/products/compute/RegionDisk.yaml b/mmv1/products/compute/RegionDisk.yaml index d421a950923f..e03aad8c4431 100644 --- a/mmv1/products/compute/RegionDisk.yaml +++ b/mmv1/products/compute/RegionDisk.yaml @@ -308,7 +308,7 @@ properties: disk interfaces are automatically determined on attachment. description: | Specifies the disk interface to use for attaching this disk, which is either SCSI or NVME. The default is SCSI. - diff_suppress_func: 'tpgresource.AlwaysDiffSuppress' + diff_suppress_func: AlwaysDiffSuppress - !ruby/object:Api::Type::String name: 'sourceDisk' description: | diff --git a/mmv1/products/compute/RegionSslCertificate.yaml b/mmv1/products/compute/RegionSslCertificate.yaml index ba2941812207..c81793ce98ca 100644 --- a/mmv1/products/compute/RegionSslCertificate.yaml +++ b/mmv1/products/compute/RegionSslCertificate.yaml @@ -78,6 +78,7 @@ examples: ignore_read_extra: - 'name_prefix' custom_code: !ruby/object:Provider::Terraform::CustomCode + constants: templates/terraform/constants/compute_certificate.go.erb extra_schema_entry: templates/terraform/extra_schema_entry/ssl_certificate.erb parameters: - !ruby/object:Api::Type::ResourceRef @@ -141,4 +142,4 @@ properties: sensitive: true ignore_read: true custom_flatten: 'templates/terraform/custom_flatten/sha256.erb' - diff_suppress_func: 'tpgresource.Sha256DiffSuppress' + diff_suppress_func: 'sha256DiffSuppress' diff --git a/mmv1/products/compute/RegionUrlMap.yaml b/mmv1/products/compute/RegionUrlMap.yaml index 2d2645f08398..2e31eea20ab9 100644 --- a/mmv1/products/compute/RegionUrlMap.yaml +++ b/mmv1/products/compute/RegionUrlMap.yaml @@ -749,7 +749,7 @@ properties: * 5xx: Loadbalancer will attempt a retry if the backend service responds with any 5xx response code, or if the backend service does not respond at all, - example: disconnects, reset, read timeout, connection failure, and refused + for example: disconnects, reset, read timeout, connection failure, and refused streams. * gateway-error: Similar to 5xx, but only applies to response codes 502, 503 or 504. @@ -1197,7 +1197,7 @@ properties: - 5xx: Loadbalancer will attempt a retry if the backend service responds with any 5xx response code, or if the backend service does not respond at all, - example: disconnects, reset, read timeout, connection failure, and refused + for example: disconnects, reset, read timeout, connection failure, and refused streams. - gateway-error: Similar to 5xx, but only applies to response codes 502, 503 or 504. diff --git a/mmv1/products/compute/ResourcePolicy.yaml b/mmv1/products/compute/ResourcePolicy.yaml index 8e645187a41e..e6ba60cf598c 100644 --- a/mmv1/products/compute/ResourcePolicy.yaml +++ b/mmv1/products/compute/ResourcePolicy.yaml @@ -138,8 +138,7 @@ properties: description: | Time within the window to start the operations. It must be in an hourly format "HH:MM", - where HH : [00-23] and MM : [00] GMT. - eg: 21:00 + where HH : [00-23] and MM : [00] GMT. eg: 21:00 required: true validation: !ruby/object:Provider::Terraform::Validation function: 'verify.ValidateHourlyOnly' diff --git a/mmv1/products/compute/Route.yaml b/mmv1/products/compute/Route.yaml index 9b6b70de1f4d..3c54de91e78f 100644 --- a/mmv1/products/compute/Route.yaml +++ b/mmv1/products/compute/Route.yaml @@ -95,6 +95,7 @@ examples: backend_name: 'compute-backend' route_name: 'route-ilb' custom_code: !ruby/object:Provider::Terraform::CustomCode + constants: templates/terraform/constants/compute_route.go.erb decoder: templates/terraform/decoders/route.erb extra_schema_entry: templates/terraform/extra_schema_entry/route.erb docs: !ruby/object:Provider::Terraform::Docs @@ -252,4 +253,4 @@ properties: - next_hop_ip - next_hop_vpn_tunnel - next_hop_ilb - diff_suppress_func: 'tpgresource.CompareIpAddressOrSelfLinkOrResourceName' + diff_suppress_func: 'CompareIpAddressOrSelfLinkOrResourceName' diff --git a/mmv1/products/compute/SslCertificate.yaml b/mmv1/products/compute/SslCertificate.yaml index f00bbfe1d91c..dc9e40dc16ed 100644 --- a/mmv1/products/compute/SslCertificate.yaml +++ b/mmv1/products/compute/SslCertificate.yaml @@ -128,4 +128,4 @@ properties: sensitive: true ignore_read: true custom_flatten: 'templates/terraform/custom_flatten/sha256.erb' - diff_suppress_func: 'tpgresource.Sha256DiffSuppress' + diff_suppress_func: 'sha256DiffSuppress' diff --git a/mmv1/products/compute/UrlMap.yaml b/mmv1/products/compute/UrlMap.yaml index 5b0cda5141d1..054b6427feb1 100644 --- a/mmv1/products/compute/UrlMap.yaml +++ b/mmv1/products/compute/UrlMap.yaml @@ -703,7 +703,7 @@ properties: * 5xx: Loadbalancer will attempt a retry if the backend service responds with any 5xx response code, or if the backend service does not respond at all, - example: disconnects, reset, read timeout, connection failure, and refused + for example: disconnects, reset, read timeout, connection failure, and refused streams. * gateway-error: Similar to 5xx, but only applies to response codes 502, 503 or 504. @@ -1415,7 +1415,7 @@ properties: * 5xx: Loadbalancer will attempt a retry if the backend service responds with any 5xx response code, or if the backend service does not respond at all, - example: disconnects, reset, read timeout, connection failure, and refused + for example: disconnects, reset, read timeout, connection failure, and refused streams. * gateway-error: Similar to 5xx, but only applies to response codes 502, 503 or 504. diff --git a/mmv1/products/compute/go_Address.yaml b/mmv1/products/compute/go_Address.yaml index beca1b1ebe45..5a280c74e32b 100644 --- a/mmv1/products/compute/go_Address.yaml +++ b/mmv1/products/compute/go_Address.yaml @@ -114,7 +114,7 @@ properties: The type of address to reserve. Note: if you set this argument's value as `INTERNAL` you need to leave the `network_tier` argument unset in that resource block. custom_flatten: 'templates/terraform/custom_flatten/go/default_if_empty.tmpl' - default_value: EXTERNAL + default_value: "EXTERNAL" enum_values: - 'INTERNAL' - 'EXTERNAL' diff --git a/mmv1/products/compute/go_Autoscaler.yaml b/mmv1/products/compute/go_Autoscaler.yaml index 703a28c379a3..86eec2f2de5c 100644 --- a/mmv1/products/compute/go_Autoscaler.yaml +++ b/mmv1/products/compute/go_Autoscaler.yaml @@ -132,6 +132,7 @@ properties: of replicas. api_name: maxNumReplicas required: true + send_empty_value: true - name: 'cooldownPeriod' type: Integer description: | @@ -151,7 +152,7 @@ properties: type: String description: | Defines operating mode for this policy. - default_value: ON + default_value: "ON" - name: 'scaleDownControl' type: NestedObject description: | @@ -262,7 +263,7 @@ properties: - OPTIMIZE_AVAILABILITY. Predictive autoscaling improves availability by monitoring daily and weekly load patterns and scaling out ahead of anticipated demand. custom_flatten: 'templates/terraform/custom_flatten/go/default_if_empty.tmpl' - default_value: NONE + default_value: "NONE" - name: 'metric' type: Array description: | @@ -359,7 +360,7 @@ properties: TimeSeries are returned upon the query execution, the autoscaler will sum their respective values to obtain its scaling value. min_version: 'beta' - default_value: resource.type = gce_instance + default_value: "resource.type = gce_instance" - name: 'loadBalancingUtilization' type: NestedObject description: | @@ -398,7 +399,7 @@ properties: type: String description: | The time zone to be used when interpreting the schedule. The value of this field must be a time zone name from the tz database: http://en.wikipedia.org/wiki/Tz_database. - default_value: UTC + default_value: "UTC" - name: 'durationSec' type: Integer description: | diff --git a/mmv1/products/compute/go_BackendBucketSignedUrlKey.yaml b/mmv1/products/compute/go_BackendBucketSignedUrlKey.yaml index cb6e3777442c..77f911edccf0 100644 --- a/mmv1/products/compute/go_BackendBucketSignedUrlKey.yaml +++ b/mmv1/products/compute/go_BackendBucketSignedUrlKey.yaml @@ -28,7 +28,7 @@ create_url: 'projects/{{project}}/global/backendBuckets/{{backend_bucket}}/addSi delete_url: 'projects/{{project}}/global/backendBuckets/{{backend_bucket}}/deleteSignedUrlKey?keyName={{name}}' delete_verb: 'POST' immutable: true -mutex: signedUrlKey/{{project}}/backendBuckets/{{backend_bucket}}/ +mutex: 'signedUrlKey/{{project}}/backendBuckets/{{backend_bucket}}/' exclude_import: true timeouts: insert_minutes: 20 diff --git a/mmv1/products/compute/go_BackendService.yaml b/mmv1/products/compute/go_BackendService.yaml index c13d11c4ee35..274582b20829 100644 --- a/mmv1/products/compute/go_BackendService.yaml +++ b/mmv1/products/compute/go_BackendService.yaml @@ -150,7 +150,7 @@ properties: for an explanation of load balancing modes. From version 6.0.0 default value will be UTILIZATION to match default GCP value. - default_value: UTILIZATION + default_value: "UTILIZATION" enum_values: - 'UTILIZATION' - 'RATE' @@ -771,7 +771,7 @@ properties: load balancing cannot be used with the other. For more information, refer to [Choosing a load balancer](https://cloud.google.com/load-balancing/docs/backend-service). immutable: true - default_value: EXTERNAL + default_value: "EXTERNAL" enum_values: - 'EXTERNAL' - 'INTERNAL_SELF_MANAGED' diff --git a/mmv1/products/compute/go_BackendServiceSignedUrlKey.yaml b/mmv1/products/compute/go_BackendServiceSignedUrlKey.yaml index b12dcdc74bb0..b03ba69ac476 100644 --- a/mmv1/products/compute/go_BackendServiceSignedUrlKey.yaml +++ b/mmv1/products/compute/go_BackendServiceSignedUrlKey.yaml @@ -28,7 +28,7 @@ create_url: 'projects/{{project}}/global/backendServices/{{backend_service}}/add delete_url: 'projects/{{project}}/global/backendServices/{{backend_service}}/deleteSignedUrlKey?keyName={{name}}' delete_verb: 'POST' immutable: true -mutex: signedUrlKey/{{project}}/backendServices/{{backend_service}}/ +mutex: 'signedUrlKey/{{project}}/backendServices/{{backend_service}}/' exclude_import: true timeouts: insert_minutes: 20 diff --git a/mmv1/products/compute/go_Disk.yaml b/mmv1/products/compute/go_Disk.yaml index 9b0f045ee0fe..08a4bf0ea163 100644 --- a/mmv1/products/compute/go_Disk.yaml +++ b/mmv1/products/compute/go_Disk.yaml @@ -347,8 +347,8 @@ properties: Specifies the disk interface to use for attaching this disk, which is either SCSI or NVME. The default is SCSI. min_version: 'beta' url_param_only: true - diff_suppress_func: 'tpgresource.AlwaysDiffSuppress' - default_value: SCSI + diff_suppress_func: 'AlwaysDiffSuppress' + default_value: "SCSI" deprecation_message: '`interface` is deprecated and will be removed in a future major release. This field is no longer used and can be safely removed from your configurations; disk interfaces are automatically determined on attachment.' - name: 'sourceDisk' type: String @@ -384,7 +384,7 @@ properties: diff_suppress_func: 'tpgresource.CompareResourceNames' custom_flatten: 'templates/terraform/custom_flatten/go/name_from_self_link.tmpl' custom_expand: 'templates/terraform/custom_expand/go/resourceref_with_validation.go.tmpl' - default_value: pd-standard + default_value: "pd-standard" resource: 'DiskType' imports: 'selfLink' - name: 'image' diff --git a/mmv1/products/compute/go_ForwardingRule.yaml b/mmv1/products/compute/go_ForwardingRule.yaml index 2b84b92a45a4..417db5eed9f2 100644 --- a/mmv1/products/compute/go_ForwardingRule.yaml +++ b/mmv1/products/compute/go_ForwardingRule.yaml @@ -299,7 +299,7 @@ properties: When reading an `IPAddress`, the API always returns the IP address number. default_from_api: true - diff_suppress_func: 'tpgresource.InternalIpDiffSuppress' + diff_suppress_func: 'InternalIpDiffSuppress' - name: 'IPProtocol' type: Enum description: | @@ -344,7 +344,7 @@ properties: For more information about forwarding rules, refer to [Forwarding rule concepts](https://cloud.google.com/load-balancing/docs/forwarding-rule-concepts). - default_value: EXTERNAL + default_value: "EXTERNAL" enum_values: - 'EXTERNAL' - 'EXTERNAL_MANAGED' @@ -412,7 +412,7 @@ properties: @pattern: \d+(?:-\d+)? default_from_api: true - diff_suppress_func: 'tpgresource.PortRangeDiffSuppress' + diff_suppress_func: 'PortRangeDiffSuppress' - name: 'ports' type: Array description: | @@ -479,6 +479,15 @@ properties: update_verb: 'POST' diff_suppress_func: 'tpgresource.CompareSelfLinkRelativePaths' custom_expand: 'templates/terraform/custom_expand/go/self_link_from_name.tmpl' + - name: 'labelFingerprint' + type: Fingerprint + description: | + The fingerprint used for optimistic locking of this resource. Used + internally during updates. + output: true + update_url: 'projects/{{project}}/regions/{{region}}/forwardingRules/{{name}}/setLabels' + update_verb: 'POST' + key_expander: '' - name: 'allowGlobalAccess' type: Boolean description: | @@ -501,15 +510,6 @@ properties: immutable: false update_url: 'projects/{{project}}/regions/{{region}}/forwardingRules/{{name}}/setLabels' update_verb: 'POST' - - name: 'labelFingerprint' - type: Fingerprint - description: | - The fingerprint used for optimistic locking of this resource. Used - internally during updates. - output: true - update_url: 'projects/{{project}}/regions/{{region}}/forwardingRules/{{name}}/setLabels' - update_verb: 'POST' - key_expander: '' - name: 'allPorts' type: Boolean description: | diff --git a/mmv1/products/compute/go_GlobalAddress.yaml b/mmv1/products/compute/go_GlobalAddress.yaml index 2ae8cfaa6a10..a53c5b276399 100644 --- a/mmv1/products/compute/go_GlobalAddress.yaml +++ b/mmv1/products/compute/go_GlobalAddress.yaml @@ -130,7 +130,7 @@ properties: * EXTERNAL indicates public/external single IP address. * INTERNAL indicates internal IP ranges belonging to some network. diff_suppress_func: 'tpgresource.EmptyOrDefaultStringSuppress("EXTERNAL")' - default_value: EXTERNAL + default_value: "EXTERNAL" enum_values: - 'EXTERNAL' - 'INTERNAL' diff --git a/mmv1/products/compute/go_GlobalForwardingRule.yaml b/mmv1/products/compute/go_GlobalForwardingRule.yaml index c85cfd9c7525..b9fe030fcdc3 100644 --- a/mmv1/products/compute/go_GlobalForwardingRule.yaml +++ b/mmv1/products/compute/go_GlobalForwardingRule.yaml @@ -21,7 +21,7 @@ description: | balancing. Global forwarding rules can only be used for HTTP load balancing. - For more information, see + For more information, see https://cloud.google.com/compute/docs/load-balancing/http/ docs: base_url: 'projects/{{project}}/global/forwardingRules' has_self_link: true @@ -255,7 +255,7 @@ properties: When reading an `IPAddress`, the API always returns the IP address number. default_from_api: true - diff_suppress_func: 'tpgresource.InternalIpDiffSuppress' + diff_suppress_func: 'InternalIpDiffSuppress' - name: 'IPProtocol' type: Enum description: | @@ -308,7 +308,7 @@ properties: For more information about forwarding rules, refer to [Forwarding rule concepts](https://cloud.google.com/load-balancing/docs/forwarding-rule-concepts). - default_value: EXTERNAL + default_value: "EXTERNAL" enum_values: - 'EXTERNAL' - 'EXTERNAL_MANAGED' @@ -433,7 +433,7 @@ properties: cannot have overlapping `portRange`s. @pattern: \d+(?:-\d+)? - diff_suppress_func: 'tpgresource.PortRangeDiffSuppress' + diff_suppress_func: 'PortRangeDiffSuppress' - name: 'subnetwork' type: ResourceRef description: | diff --git a/mmv1/products/compute/go_GlobalNetworkEndpoint.yaml b/mmv1/products/compute/go_GlobalNetworkEndpoint.yaml index 81d152748bad..fe29d45d0ce4 100644 --- a/mmv1/products/compute/go_GlobalNetworkEndpoint.yaml +++ b/mmv1/products/compute/go_GlobalNetworkEndpoint.yaml @@ -32,7 +32,7 @@ read_verb: 'POST' delete_url: 'projects/{{project}}/global/networkEndpointGroups/{{global_network_endpoint_group}}/detachNetworkEndpoints' delete_verb: 'POST' immutable: true -mutex: networkEndpoint/{{project}}/{{global_network_endpoint_group}} +mutex: 'networkEndpoint/{{project}}/{{global_network_endpoint_group}}' timeouts: insert_minutes: 20 update_minutes: 20 diff --git a/mmv1/products/compute/go_HaVpnGateway.yaml b/mmv1/products/compute/go_HaVpnGateway.yaml index c62e2447443f..4545e037be24 100644 --- a/mmv1/products/compute/go_HaVpnGateway.yaml +++ b/mmv1/products/compute/go_HaVpnGateway.yaml @@ -127,7 +127,7 @@ properties: If not specified, IPV4_ONLY will be used. immutable: true custom_flatten: 'templates/terraform/custom_flatten/go/default_if_empty.tmpl' - default_value: IPV4_ONLY + default_value: "IPV4_ONLY" enum_values: - 'IPV4_ONLY' - 'IPV4_IPV6' diff --git a/mmv1/products/compute/go_HealthCheck.yaml b/mmv1/products/compute/go_HealthCheck.yaml index aa04399b16e3..053c86241be7 100644 --- a/mmv1/products/compute/go_HealthCheck.yaml +++ b/mmv1/products/compute/go_HealthCheck.yaml @@ -158,6 +158,31 @@ properties: The default value is 5 seconds. It is invalid for timeoutSec to have greater value than checkIntervalSec. default_value: 5 + - name: 'sourceRegions' + type: Array + description: | + The list of cloud regions from which health checks are performed. If + any regions are specified, then exactly 3 regions should be specified. + The region names must be valid names of Google Cloud regions. This can + only be set for global health check. If this list is non-empty, then + there are restrictions on what other health check fields are supported + and what other resources can use this health check: + + * SSL, HTTP2, and GRPC protocols are not supported. + + * The TCP request field is not supported. + + * The proxyHeader field for HTTP, HTTPS, and TCP is not supported. + + * The checkIntervalSec field must be at least 30. + + * The health check cannot be used with BackendService nor with managed + instance group auto-healing. + min_version: 'beta' + item_type: + type: String + min_size: 3 + max_size: 3 - name: 'unhealthyThreshold' type: Integer description: | @@ -213,7 +238,7 @@ properties: - 'http_health_check.0.port_name' - 'http_health_check.0.proxy_header' - 'http_health_check.0.port_specification' - default_value: / + default_value: "/" - name: 'response' type: String description: | @@ -267,7 +292,7 @@ properties: - 'http_health_check.0.port_name' - 'http_health_check.0.proxy_header' - 'http_health_check.0.port_specification' - default_value: NONE + default_value: "NONE" enum_values: - 'NONE' - 'PROXY_V1' @@ -338,7 +363,7 @@ properties: - 'https_health_check.0.port_name' - 'https_health_check.0.proxy_header' - 'https_health_check.0.port_specification' - default_value: / + default_value: "/" - name: 'response' type: String description: | @@ -392,7 +417,7 @@ properties: - 'https_health_check.0.port_name' - 'https_health_check.0.proxy_header' - 'https_health_check.0.port_specification' - default_value: NONE + default_value: "NONE" enum_values: - 'NONE' - 'PROXY_V1' @@ -499,7 +524,7 @@ properties: - 'tcp_health_check.0.port_name' - 'tcp_health_check.0.proxy_header' - 'tcp_health_check.0.port_specification' - default_value: NONE + default_value: "NONE" enum_values: - 'NONE' - 'PROXY_V1' @@ -605,7 +630,7 @@ properties: - 'ssl_health_check.0.port_name' - 'ssl_health_check.0.proxy_header' - 'ssl_health_check.0.port_specification' - default_value: NONE + default_value: "NONE" enum_values: - 'NONE' - 'PROXY_V1' @@ -675,7 +700,7 @@ properties: - 'http2_health_check.0.port_name' - 'http2_health_check.0.proxy_header' - 'http2_health_check.0.port_specification' - default_value: / + default_value: "/" - name: 'response' type: String description: | @@ -729,7 +754,7 @@ properties: - 'http2_health_check.0.port_name' - 'http2_health_check.0.proxy_header' - 'http2_health_check.0.port_specification' - default_value: NONE + default_value: "NONE" enum_values: - 'NONE' - 'PROXY_V1' diff --git a/mmv1/products/compute/go_HttpHealthCheck.yaml b/mmv1/products/compute/go_HttpHealthCheck.yaml index dca967b94c9c..0a7780b177a1 100644 --- a/mmv1/products/compute/go_HttpHealthCheck.yaml +++ b/mmv1/products/compute/go_HttpHealthCheck.yaml @@ -107,7 +107,7 @@ properties: description: | The request path of the HTTP health check request. The default value is /. - default_value: / + default_value: "/" - name: 'timeoutSec' type: Integer description: | diff --git a/mmv1/products/compute/go_HttpsHealthCheck.yaml b/mmv1/products/compute/go_HttpsHealthCheck.yaml index a186293003ad..6a29961e22bb 100644 --- a/mmv1/products/compute/go_HttpsHealthCheck.yaml +++ b/mmv1/products/compute/go_HttpsHealthCheck.yaml @@ -107,7 +107,7 @@ properties: description: | The request path of the HTTPS health check request. The default value is /. - default_value: / + default_value: "/" - name: 'timeoutSec' type: Integer description: | diff --git a/mmv1/products/compute/go_Image.yaml b/mmv1/products/compute/go_Image.yaml index 40505e568024..731e4028534d 100644 --- a/mmv1/products/compute/go_Image.yaml +++ b/mmv1/products/compute/go_Image.yaml @@ -219,7 +219,7 @@ properties: should be TAR. This is just a container and transmission format and not a runtime format. Provided by the client when the disk image is created. - default_value: TAR + default_value: "TAR" enum_values: - 'TAR' - name: 'sha1' diff --git a/mmv1/products/compute/go_Instance.yaml b/mmv1/products/compute/go_Instance.yaml index 49eb97d03c47..c8b2b4856e99 100644 --- a/mmv1/products/compute/go_Instance.yaml +++ b/mmv1/products/compute/go_Instance.yaml @@ -593,13 +593,13 @@ properties: at_least_one_of: - 'confidential_instance_config.0.enable_confidential_compute' - 'confidential_instance_config.0.confidential_instance_type' + deprecation_message: '`enableConfidentialCompute` is deprecated and will be removed in a future major release. Use `confidentialInstanceType: SEV` instead.' - name: 'confidentialInstanceType' type: Enum description: | The confidential computing technology the instance uses. SEV is an AMD feature. One of the following values: SEV, SEV_SNP. If SEV_SNP, min_cpu_platform = "AMD Milan" is currently required. - min_version: 'beta' at_least_one_of: - 'confidential_instance_config.0.enable_confidential_compute' - 'confidential_instance_config.0.confidential_instance_type' diff --git a/mmv1/products/compute/go_InstanceGroupMembership.yaml b/mmv1/products/compute/go_InstanceGroupMembership.yaml index d5592ed914cb..80b4bd6e974f 100644 --- a/mmv1/products/compute/go_InstanceGroupMembership.yaml +++ b/mmv1/products/compute/go_InstanceGroupMembership.yaml @@ -41,7 +41,7 @@ read_verb: 'POST' delete_url: 'projects/{{project}}/zones/{{zone}}/instanceGroups/{{instance_group}}/removeInstances' delete_verb: 'POST' immutable: true -mutex: instanceGroups/{{project}}/zones/{{zone}}/{{instance_group}} +mutex: 'instanceGroups/{{project}}/zones/{{zone}}/{{instance_group}}' timeouts: insert_minutes: 20 update_minutes: 20 diff --git a/mmv1/products/compute/go_InstanceGroupNamedPort.yaml b/mmv1/products/compute/go_InstanceGroupNamedPort.yaml index 2a896ad3aad0..0e7e8cc5dde3 100644 --- a/mmv1/products/compute/go_InstanceGroupNamedPort.yaml +++ b/mmv1/products/compute/go_InstanceGroupNamedPort.yaml @@ -31,7 +31,7 @@ create_url: 'projects/{{project}}/zones/{{zone}}/instanceGroups/{{group}}/setNam delete_url: 'projects/{{project}}/zones/{{zone}}/instanceGroups/{{group}}/setNamedPorts' delete_verb: 'POST' immutable: true -mutex: projects/{{project}}/zones/{{zone}}/instanceGroups/{{group}} +mutex: 'projects/{{project}}/zones/{{zone}}/instanceGroups/{{group}}' import_format: - 'projects/{{project}}/zones/{{zone}}/instanceGroups/{{group}}/{{port}}/{{name}}' timeouts: diff --git a/mmv1/products/compute/go_InterconnectAttachment.yaml b/mmv1/products/compute/go_InterconnectAttachment.yaml index b991a7353f58..f422c5b8fbb3 100644 --- a/mmv1/products/compute/go_InterconnectAttachment.yaml +++ b/mmv1/products/compute/go_InterconnectAttachment.yaml @@ -300,7 +300,7 @@ properties: attachment must be created with this option. immutable: true custom_flatten: 'templates/terraform/custom_flatten/go/default_if_empty.tmpl' - default_value: NONE + default_value: "NONE" enum_values: - 'NONE' - 'IPSEC' diff --git a/mmv1/products/compute/go_ManagedSslCertificate.yaml b/mmv1/products/compute/go_ManagedSslCertificate.yaml index 5f1f840af51f..c9d0d3457913 100644 --- a/mmv1/products/compute/go_ManagedSslCertificate.yaml +++ b/mmv1/products/compute/go_ManagedSslCertificate.yaml @@ -66,6 +66,7 @@ async: message: 'message' collection_url_key: 'items' custom_code: + constants: 'templates/terraform/constants/go/compute_managed_ssl_certificate.go.tmpl' examples: - name: 'managed_ssl_certificate_basic' primary_resource_id: 'default' @@ -120,7 +121,7 @@ properties: Domains for which a managed SSL certificate will be valid. Currently, there can be up to 100 domains in this list. required: true - diff_suppress_func: 'tpgresource.AbsoluteDomainSuppress' + diff_suppress_func: 'AbsoluteDomainSuppress' item_type: type: String max_size: 100 @@ -129,7 +130,7 @@ properties: description: | Enum field whose value is always `MANAGED` - used to signal to the API which type this is. - default_value: MANAGED + default_value: "MANAGED" enum_values: - 'MANAGED' - name: 'subjectAlternativeNames' diff --git a/mmv1/products/compute/go_Network.yaml b/mmv1/products/compute/go_Network.yaml index 28cd90a53fe6..9a8704c9fa3d 100644 --- a/mmv1/products/compute/go_Network.yaml +++ b/mmv1/products/compute/go_Network.yaml @@ -175,7 +175,7 @@ properties: Set the order that Firewall Rules and Firewall Policies are evaluated. update_url: 'projects/{{project}}/global/networks/{{name}}' update_verb: 'PATCH' - default_value: AFTER_CLASSIC_FIREWALL + default_value: "AFTER_CLASSIC_FIREWALL" enum_values: - 'BEFORE_CLASSIC_FIREWALL' - 'AFTER_CLASSIC_FIREWALL' diff --git a/mmv1/products/compute/go_NetworkEndpoint.yaml b/mmv1/products/compute/go_NetworkEndpoint.yaml index 4ed2e1b8ed11..626d38e5138d 100644 --- a/mmv1/products/compute/go_NetworkEndpoint.yaml +++ b/mmv1/products/compute/go_NetworkEndpoint.yaml @@ -38,7 +38,7 @@ read_verb: 'POST' delete_url: 'projects/{{project}}/zones/{{zone}}/networkEndpointGroups/{{network_endpoint_group}}/detachNetworkEndpoints' delete_verb: 'POST' immutable: true -mutex: networkEndpoint/{{project}}/{{zone}}/{{network_endpoint_group}} +mutex: 'networkEndpoint/{{project}}/{{zone}}/{{network_endpoint_group}}' timeouts: insert_minutes: 20 update_minutes: 20 diff --git a/mmv1/products/compute/go_NetworkEndpointGroup.yaml b/mmv1/products/compute/go_NetworkEndpointGroup.yaml index 433530c951d0..e4d0610c43c1 100644 --- a/mmv1/products/compute/go_NetworkEndpointGroup.yaml +++ b/mmv1/products/compute/go_NetworkEndpointGroup.yaml @@ -57,6 +57,7 @@ async: message: 'message' collection_url_key: 'items' custom_code: + constants: 'templates/terraform/constants/go/compute_network_endpoint_group.go.tmpl' examples: - name: 'network_endpoint_group' primary_resource_id: 'neg' @@ -111,7 +112,7 @@ properties: CONNECTION balancing modes. Possible values include: GCE_VM_IP, GCE_VM_IP_PORT, NON_GCP_PRIVATE_IP_PORT, INTERNET_IP_PORT, INTERNET_FQDN_PORT, SERVERLESS, and PRIVATE_SERVICE_CONNECT. - default_value: GCE_VM_IP_PORT + default_value: "GCE_VM_IP_PORT" enum_values: - 'GCE_VM_IP' - 'GCE_VM_IP_PORT' @@ -137,7 +138,7 @@ properties: type: ResourceRef description: | Optional subnetwork to which all network endpoints in the NEG belong. - diff_suppress_func: 'tpgresource.CompareOptionalSubnet' + diff_suppress_func: 'compareOptionalSubnet' custom_expand: 'templates/terraform/custom_expand/go/resourceref_with_validation.go.tmpl' resource: 'Subnetwork' imports: 'selfLink' diff --git a/mmv1/products/compute/go_NetworkEndpoints.yaml b/mmv1/products/compute/go_NetworkEndpoints.yaml index d8248ee4c88c..89fc3591b842 100644 --- a/mmv1/products/compute/go_NetworkEndpoints.yaml +++ b/mmv1/products/compute/go_NetworkEndpoints.yaml @@ -42,7 +42,7 @@ update_verb: 'POST' read_verb: 'POST' delete_url: 'projects/{{project}}/zones/{{zone}}/networkEndpointGroups/{{network_endpoint_group}}/detachNetworkEndpoints' delete_verb: 'POST' -mutex: networkEndpoint/{{project}}/{{zone}}/{{network_endpoint_group}} +mutex: 'networkEndpoint/{{project}}/{{zone}}/{{network_endpoint_group}}' import_format: - 'projects/{{project}}/zones/{{zone}}/networkEndpointGroups/{{network_endpoint_group}}' timeouts: diff --git a/mmv1/products/compute/go_NetworkPeeringRoutesConfig.yaml b/mmv1/products/compute/go_NetworkPeeringRoutesConfig.yaml index b1e00931aeb0..ff78cb57b720 100644 --- a/mmv1/products/compute/go_NetworkPeeringRoutesConfig.yaml +++ b/mmv1/products/compute/go_NetworkPeeringRoutesConfig.yaml @@ -32,7 +32,7 @@ create_verb: 'PATCH' update_url: 'projects/{{project}}/global/networks/{{network}}/updatePeering' update_verb: 'PATCH' skip_delete: true -mutex: projects/{{project}}/global/networks/{{network}}/peerings +mutex: 'projects/{{project}}/global/networks/{{network}}/peerings' import_format: - 'projects/{{project}}/global/networks/{{network}}/networkPeerings/{{peering}}' timeouts: diff --git a/mmv1/products/compute/go_NodeGroup.yaml b/mmv1/products/compute/go_NodeGroup.yaml index 3e46390a3e91..afef7468796c 100644 --- a/mmv1/products/compute/go_NodeGroup.yaml +++ b/mmv1/products/compute/go_NodeGroup.yaml @@ -122,7 +122,7 @@ properties: type: String description: | Specifies how to handle instances when a node in the group undergoes maintenance. Set to one of: DEFAULT, RESTART_IN_PLACE, or MIGRATE_WITHIN_NODE_GROUP. The default value is DEFAULT. - default_value: DEFAULT + default_value: "DEFAULT" - name: 'maintenanceWindow' type: NestedObject description: | diff --git a/mmv1/products/compute/go_NodeTemplate.yaml b/mmv1/products/compute/go_NodeTemplate.yaml index d14bff5502ae..2b09bff673b5 100644 --- a/mmv1/products/compute/go_NodeTemplate.yaml +++ b/mmv1/products/compute/go_NodeTemplate.yaml @@ -149,7 +149,7 @@ properties: type: Enum description: | CPU overcommit. - default_value: NONE + default_value: "NONE" enum_values: - 'ENABLED' - 'NONE' diff --git a/mmv1/products/compute/go_OrganizationSecurityPolicy.yaml b/mmv1/products/compute/go_OrganizationSecurityPolicy.yaml index 644b09c48415..c9033fe12929 100644 --- a/mmv1/products/compute/go_OrganizationSecurityPolicy.yaml +++ b/mmv1/products/compute/go_OrganizationSecurityPolicy.yaml @@ -88,6 +88,6 @@ properties: is "FIREWALL". min_version: 'beta' immutable: true - default_value: FIREWALL + default_value: "FIREWALL" enum_values: - 'FIREWALL' diff --git a/mmv1/products/compute/go_OrganizationSecurityPolicyRule.yaml b/mmv1/products/compute/go_OrganizationSecurityPolicyRule.yaml index 0b02fc55bfc2..54fa3324ecf7 100644 --- a/mmv1/products/compute/go_OrganizationSecurityPolicyRule.yaml +++ b/mmv1/products/compute/go_OrganizationSecurityPolicyRule.yaml @@ -90,7 +90,7 @@ properties: Preconfigured versioned expression. For organization security policy rules, the only supported type is "FIREWALL". min_version: 'beta' - default_value: FIREWALL + default_value: "FIREWALL" enum_values: - 'FIREWALL' - name: 'config' diff --git a/mmv1/products/compute/go_PacketMirroring.yaml b/mmv1/products/compute/go_PacketMirroring.yaml index 04554d993989..065c5979be0c 100644 --- a/mmv1/products/compute/go_PacketMirroring.yaml +++ b/mmv1/products/compute/go_PacketMirroring.yaml @@ -138,7 +138,7 @@ properties: - name: 'direction' type: Enum description: Direction of traffic to mirror. - default_value: BOTH + default_value: "BOTH" enum_values: - 'INGRESS' - 'EGRESS' diff --git a/mmv1/products/compute/go_PerInstanceConfig.yaml b/mmv1/products/compute/go_PerInstanceConfig.yaml index 4db8981e8176..7134e2e74f1c 100644 --- a/mmv1/products/compute/go_PerInstanceConfig.yaml +++ b/mmv1/products/compute/go_PerInstanceConfig.yaml @@ -31,7 +31,7 @@ update_verb: 'POST' read_verb: 'POST' delete_url: 'projects/{{project}}/zones/{{zone}}/instanceGroupManagers/{{instance_group_manager}}/deletePerInstanceConfigs' delete_verb: 'POST' -mutex: instanceGroupManager/{{project}}/{{zone}}/{{instance_group_manager}} +mutex: 'instanceGroupManager/{{project}}/{{zone}}/{{instance_group_manager}}' timeouts: insert_minutes: 20 update_minutes: 20 @@ -166,7 +166,7 @@ properties: type: Enum description: | The mode of the disk. - default_value: READ_WRITE + default_value: "READ_WRITE" enum_values: - 'READ_ONLY' - 'READ_WRITE' @@ -178,7 +178,7 @@ properties: `NEVER` - detach the disk when the VM is deleted, but do not delete the disk. `ON_PERMANENT_INSTANCE_DELETION` will delete the stateful disk when the VM is permanently deleted from the instance group. - default_value: NEVER + default_value: "NEVER" enum_values: - 'NEVER' - 'ON_PERMANENT_INSTANCE_DELETION' @@ -195,7 +195,7 @@ properties: type: Enum description: | These stateful IPs will never be released during autohealing, update or VM instance recreate operations. This flag is used to configure if the IP reservation should be deleted after it is no longer used by the group, e.g. when the given instance or the whole group is deleted. - default_value: NEVER + default_value: "NEVER" enum_values: - 'NEVER' - 'ON_PERMANENT_INSTANCE_DELETION' @@ -224,7 +224,7 @@ properties: type: Enum description: | These stateful IPs will never be released during autohealing, update or VM instance recreate operations. This flag is used to configure if the IP reservation should be deleted after it is no longer used by the group, e.g. when the given instance or the whole group is deleted. - default_value: NEVER + default_value: "NEVER" enum_values: - 'NEVER' - 'ON_PERMANENT_INSTANCE_DELETION' diff --git a/mmv1/products/compute/go_RegionAutoscaler.yaml b/mmv1/products/compute/go_RegionAutoscaler.yaml index 0bc0b9cfcb30..43c28318b020 100644 --- a/mmv1/products/compute/go_RegionAutoscaler.yaml +++ b/mmv1/products/compute/go_RegionAutoscaler.yaml @@ -119,6 +119,7 @@ properties: of replicas. api_name: maxNumReplicas required: true + send_empty_value: true - name: 'cooldownPeriod' type: Integer description: | @@ -138,7 +139,7 @@ properties: type: String description: | Defines operating mode for this policy. - default_value: ON + default_value: "ON" - name: 'scaleDownControl' type: NestedObject description: | @@ -247,7 +248,7 @@ properties: - OPTIMIZE_AVAILABILITY. Predictive autoscaling improves availability by monitoring daily and weekly load patterns and scaling out ahead of anticipated demand. custom_flatten: 'templates/terraform/custom_flatten/go/default_if_empty.tmpl' - default_value: NONE + default_value: "NONE" - name: 'metric' type: Array description: | @@ -380,7 +381,7 @@ properties: type: String description: | The time zone to be used when interpreting the schedule. The value of this field must be a time zone name from the tz database: http://en.wikipedia.org/wiki/Tz_database. - default_value: UTC + default_value: "UTC" - name: 'durationSec' type: Integer description: | diff --git a/mmv1/products/compute/go_RegionBackendService.yaml b/mmv1/products/compute/go_RegionBackendService.yaml index cab122136a1b..f01a23bc1d77 100644 --- a/mmv1/products/compute/go_RegionBackendService.yaml +++ b/mmv1/products/compute/go_RegionBackendService.yaml @@ -149,7 +149,7 @@ properties: for an explanation of load balancing modes. From version 6.0.0 default value will be UTILIZATION to match default GCP value. - default_value: CONNECTION + default_value: "CONNECTION" enum_values: - 'UTILIZATION' - 'RATE' @@ -777,7 +777,7 @@ properties: balancing cannot be used with the other(s). For more information, refer to [Choosing a load balancer](https://cloud.google.com/load-balancing/docs/backend-service). immutable: true - default_value: INTERNAL + default_value: "INTERNAL" enum_values: - 'EXTERNAL' - 'EXTERNAL_MANAGED' @@ -1188,7 +1188,7 @@ properties: `PER_SESSION`: The Connection Tracking is performed as per the configured Session Affinity. It matches the configured Session Affinity. - default_value: PER_CONNECTION + default_value: "PER_CONNECTION" enum_values: - 'PER_CONNECTION' - 'PER_SESSION' @@ -1211,7 +1211,7 @@ properties: If set to `ALWAYS_PERSIST`, existing connections always persist on unhealthy backends regardless of protocol and session affinity. It is generally not recommended to use this mode overriding the default. - default_value: DEFAULT_FOR_PROTOCOL + default_value: "DEFAULT_FOR_PROTOCOL" enum_values: - 'DEFAULT_FOR_PROTOCOL' - 'NEVER_PERSIST' diff --git a/mmv1/products/compute/go_RegionDisk.yaml b/mmv1/products/compute/go_RegionDisk.yaml index 3a4d7e04b8af..15c02435d62c 100644 --- a/mmv1/products/compute/go_RegionDisk.yaml +++ b/mmv1/products/compute/go_RegionDisk.yaml @@ -286,7 +286,7 @@ properties: create the disk. Provide this when creating the disk. custom_flatten: 'templates/terraform/custom_flatten/go/name_from_self_link.tmpl' custom_expand: 'templates/terraform/custom_expand/go/resourceref_with_validation.go.tmpl' - default_value: pd-standard + default_value: "pd-standard" resource: 'RegionDiskType' imports: 'selfLink' - name: 'interface' @@ -295,8 +295,8 @@ properties: Specifies the disk interface to use for attaching this disk, which is either SCSI or NVME. The default is SCSI. min_version: 'beta' url_param_only: true - diff_suppress_func: 'tpgresource.AlwaysDiffSuppress' - default_value: SCSI + diff_suppress_func: 'AlwaysDiffSuppress' + default_value: "SCSI" deprecation_message: '`interface` is deprecated and will be removed in a future major release. This field is no longer used and can be safely removed from your configurations; disk interfaces are automatically determined on attachment.' - name: 'sourceDisk' type: String diff --git a/mmv1/products/compute/go_RegionHealthCheck.yaml b/mmv1/products/compute/go_RegionHealthCheck.yaml index 3249ecc45d3d..061556ba80b1 100644 --- a/mmv1/products/compute/go_RegionHealthCheck.yaml +++ b/mmv1/products/compute/go_RegionHealthCheck.yaml @@ -220,7 +220,7 @@ properties: - 'http_health_check.0.port_name' - 'http_health_check.0.proxy_header' - 'http_health_check.0.port_specification' - default_value: / + default_value: "/" - name: 'response' type: String description: | @@ -274,7 +274,7 @@ properties: - 'http_health_check.0.port_name' - 'http_health_check.0.proxy_header' - 'http_health_check.0.port_specification' - default_value: NONE + default_value: "NONE" enum_values: - 'NONE' - 'PROXY_V1' @@ -345,7 +345,7 @@ properties: - 'https_health_check.0.port_name' - 'https_health_check.0.proxy_header' - 'https_health_check.0.port_specification' - default_value: / + default_value: "/" - name: 'response' type: String description: | @@ -399,7 +399,7 @@ properties: - 'https_health_check.0.port_name' - 'https_health_check.0.proxy_header' - 'https_health_check.0.port_specification' - default_value: NONE + default_value: "NONE" enum_values: - 'NONE' - 'PROXY_V1' @@ -506,7 +506,7 @@ properties: - 'tcp_health_check.0.port_name' - 'tcp_health_check.0.proxy_header' - 'tcp_health_check.0.port_specification' - default_value: NONE + default_value: "NONE" enum_values: - 'NONE' - 'PROXY_V1' @@ -612,7 +612,7 @@ properties: - 'ssl_health_check.0.port_name' - 'ssl_health_check.0.proxy_header' - 'ssl_health_check.0.port_specification' - default_value: NONE + default_value: "NONE" enum_values: - 'NONE' - 'PROXY_V1' @@ -682,7 +682,7 @@ properties: - 'http2_health_check.0.port_name' - 'http2_health_check.0.proxy_header' - 'http2_health_check.0.port_specification' - default_value: / + default_value: "/" - name: 'response' type: String description: | @@ -736,7 +736,7 @@ properties: - 'http2_health_check.0.port_name' - 'http2_health_check.0.proxy_header' - 'http2_health_check.0.port_specification' - default_value: NONE + default_value: "NONE" enum_values: - 'NONE' - 'PROXY_V1' diff --git a/mmv1/products/compute/go_RegionNetworkEndpoint.yaml b/mmv1/products/compute/go_RegionNetworkEndpoint.yaml index a98401a6ddf5..b74ab976f8f6 100644 --- a/mmv1/products/compute/go_RegionNetworkEndpoint.yaml +++ b/mmv1/products/compute/go_RegionNetworkEndpoint.yaml @@ -34,7 +34,7 @@ read_verb: 'POST' delete_url: 'projects/{{project}}/regions/{{region}}/networkEndpointGroups/{{region_network_endpoint_group}}/detachNetworkEndpoints' delete_verb: 'POST' immutable: true -mutex: networkEndpoint/{{project}}/{{region}}/{{region_network_endpoint_group}} +mutex: 'networkEndpoint/{{project}}/{{region}}/{{region_network_endpoint_group}}' timeouts: insert_minutes: 20 update_minutes: 20 diff --git a/mmv1/products/compute/go_RegionNetworkEndpointGroup.yaml b/mmv1/products/compute/go_RegionNetworkEndpointGroup.yaml index ba12edad1066..57d6aa26deda 100644 --- a/mmv1/products/compute/go_RegionNetworkEndpointGroup.yaml +++ b/mmv1/products/compute/go_RegionNetworkEndpointGroup.yaml @@ -136,7 +136,7 @@ properties: type: Enum description: | Type of network endpoints in this network endpoint group. Defaults to SERVERLESS. - default_value: SERVERLESS + default_value: "SERVERLESS" enum_values: - 'SERVERLESS' - 'PRIVATE_SERVICE_CONNECT' diff --git a/mmv1/products/compute/go_RegionPerInstanceConfig.yaml b/mmv1/products/compute/go_RegionPerInstanceConfig.yaml index da471721b90c..c8c275451f3f 100644 --- a/mmv1/products/compute/go_RegionPerInstanceConfig.yaml +++ b/mmv1/products/compute/go_RegionPerInstanceConfig.yaml @@ -32,7 +32,7 @@ update_verb: 'POST' read_verb: 'POST' delete_url: 'projects/{{project}}/regions/{{region}}/instanceGroupManagers/{{region_instance_group_manager}}/deletePerInstanceConfigs' delete_verb: 'POST' -mutex: instanceGroupManager/{{project}}/{{region}}/{{region_instance_group_manager}} +mutex: 'instanceGroupManager/{{project}}/{{region}}/{{region_instance_group_manager}}' timeouts: insert_minutes: 20 update_minutes: 20 @@ -167,7 +167,7 @@ properties: type: Enum description: | The mode of the disk. - default_value: READ_WRITE + default_value: "READ_WRITE" enum_values: - 'READ_ONLY' - 'READ_WRITE' @@ -179,7 +179,7 @@ properties: `NEVER` - detach the disk when the VM is deleted, but do not delete the disk. `ON_PERMANENT_INSTANCE_DELETION` will delete the stateful disk when the VM is permanently deleted from the instance group. - default_value: NEVER + default_value: "NEVER" enum_values: - 'NEVER' - 'ON_PERMANENT_INSTANCE_DELETION' @@ -196,7 +196,7 @@ properties: type: Enum description: | These stateful IPs will never be released during autohealing, update or VM instance recreate operations. This flag is used to configure if the IP reservation should be deleted after it is no longer used by the group, e.g. when the given instance or the whole group is deleted. - default_value: NEVER + default_value: "NEVER" enum_values: - 'NEVER' - 'ON_PERMANENT_INSTANCE_DELETION' @@ -225,7 +225,7 @@ properties: type: Enum description: | These stateful IPs will never be released during autohealing, update or VM instance recreate operations. This flag is used to configure if the IP reservation should be deleted after it is no longer used by the group, e.g. when the given instance or the whole group is deleted. - default_value: NEVER + default_value: "NEVER" enum_values: - 'NEVER' - 'ON_PERMANENT_INSTANCE_DELETION' diff --git a/mmv1/products/compute/go_RegionSslCertificate.yaml b/mmv1/products/compute/go_RegionSslCertificate.yaml index a365f1cacf13..0d16bbc8dd59 100644 --- a/mmv1/products/compute/go_RegionSslCertificate.yaml +++ b/mmv1/products/compute/go_RegionSslCertificate.yaml @@ -50,6 +50,7 @@ async: collection_url_key: 'items' custom_code: extra_schema_entry: 'templates/terraform/extra_schema_entry/go/ssl_certificate.tmpl' + constants: 'templates/terraform/constants/go/compute_certificate.go.tmpl' examples: - name: 'region_ssl_certificate_basic' primary_resource_id: 'default' @@ -131,5 +132,5 @@ properties: immutable: true ignore_read: true sensitive: true - diff_suppress_func: 'tpgresource.Sha256DiffSuppress' + diff_suppress_func: 'sha256DiffSuppress' custom_flatten: 'templates/terraform/custom_flatten/go/sha256.tmpl' diff --git a/mmv1/products/compute/go_RegionSslPolicy.yaml b/mmv1/products/compute/go_RegionSslPolicy.yaml index 4a4bab982358..c19fa6181e55 100644 --- a/mmv1/products/compute/go_RegionSslPolicy.yaml +++ b/mmv1/products/compute/go_RegionSslPolicy.yaml @@ -91,7 +91,7 @@ properties: See the [official documentation](https://cloud.google.com/compute/docs/load-balancing/ssl-policies#profilefeaturesupport) for information on what cipher suites each profile provides. If `CUSTOM` is used, the `custom_features` attribute **must be set**. - default_value: COMPATIBLE + default_value: "COMPATIBLE" enum_values: - 'COMPATIBLE' - 'MODERN' @@ -102,7 +102,7 @@ properties: description: | The minimum version of SSL protocol that can be used by the clients to establish a connection with the load balancer. - default_value: TLS_1_0 + default_value: "TLS_1_0" enum_values: - 'TLS_1_0' - 'TLS_1_1' diff --git a/mmv1/products/compute/go_RegionTargetTcpProxy.yaml b/mmv1/products/compute/go_RegionTargetTcpProxy.yaml index 81901cf43476..f1c24ee41313 100644 --- a/mmv1/products/compute/go_RegionTargetTcpProxy.yaml +++ b/mmv1/products/compute/go_RegionTargetTcpProxy.yaml @@ -95,7 +95,7 @@ properties: description: | Specifies the type of proxy header to append before sending data to the backend. - default_value: NONE + default_value: "NONE" enum_values: - 'NONE' - 'PROXY_V1' diff --git a/mmv1/products/compute/go_RegionUrlMap.yaml b/mmv1/products/compute/go_RegionUrlMap.yaml index f1f796d6c24c..0094adf584ad 100644 --- a/mmv1/products/compute/go_RegionUrlMap.yaml +++ b/mmv1/products/compute/go_RegionUrlMap.yaml @@ -753,6 +753,24 @@ properties: * 5xx: Loadbalancer will attempt a retry if the backend service responds with any 5xx response code, or if the backend service does not respond at all, + for example: disconnects, reset, read timeout, connection failure, and refused + streams. + * gateway-error: Similar to 5xx, but only applies to response codes + 502, 503 or 504. + * connect-failure: Loadbalancer will retry on failures + connecting to backend services, for example due to connection timeouts. + * retriable-4xx: Loadbalancer will retry for retriable 4xx response codes. + Currently the only retriable error supported is 409. + * refused-stream: Loadbalancer will retry if the backend service resets the stream with a + REFUSED_STREAM error code. This reset type indicates that it is safe to retry. + * cancelled: Loadbalancer will retry if the gRPC status code in the response + header is set to cancelled + * deadline-exceeded: Loadbalancer will retry if the + gRPC status code in the response header is set to deadline-exceeded + * resource-exhausted: Loadbalancer will retry if the gRPC status code in the response + header is set to resource-exhausted + * unavailable: Loadbalancer will retry if the gRPC status code in + the response header is set to unavailable item_type: type: String - name: 'timeout' @@ -1196,6 +1214,24 @@ properties: - 5xx: Loadbalancer will attempt a retry if the backend service responds with any 5xx response code, or if the backend service does not respond at all, + for example: disconnects, reset, read timeout, connection failure, and refused + streams. + - gateway-error: Similar to 5xx, but only applies to response codes + 502, 503 or 504. + - connect-failure: Loadbalancer will retry on failures + connecting to backend services, for example due to connection timeouts. + - retriable-4xx: Loadbalancer will retry for retriable 4xx response codes. + Currently the only retriable error supported is 409. + - refused-stream: Loadbalancer will retry if the backend service resets the stream with a + REFUSED_STREAM error code. This reset type indicates that it is safe to retry. + - cancelled: Loadbalancer will retry if the gRPC status code in the response + header is set to cancelled + - deadline-exceeded: Loadbalancer will retry if the + gRPC status code in the response header is set to deadline-exceeded + - resource-exhausted: Loadbalancer will retry if the gRPC status code in the response + header is set to resource-exhausted + - unavailable: Loadbalancer will retry if + the gRPC status code in the response header is set to unavailable item_type: type: String - name: 'timeout' diff --git a/mmv1/products/compute/go_Reservation.yaml b/mmv1/products/compute/go_Reservation.yaml index 518522620377..1ae6cf037ade 100644 --- a/mmv1/products/compute/go_Reservation.yaml +++ b/mmv1/products/compute/go_Reservation.yaml @@ -232,7 +232,7 @@ properties: description: | The disk interface to use for attaching this disk. immutable: true - default_value: SCSI + default_value: "SCSI" enum_values: - 'SCSI' - 'NVME' diff --git a/mmv1/products/compute/go_ResourcePolicy.yaml b/mmv1/products/compute/go_ResourcePolicy.yaml index 1e689c492b05..cec8edde5c7e 100644 --- a/mmv1/products/compute/go_ResourcePolicy.yaml +++ b/mmv1/products/compute/go_ResourcePolicy.yaml @@ -134,7 +134,7 @@ properties: description: | Time within the window to start the operations. It must be in an hourly format "HH:MM", - where HH : [00-23] and MM : [00] GMT. + where HH : [00-23] and MM : [00] GMT. eg: 21:00 required: true validation: function: 'verify.ValidateHourlyOnly' @@ -215,7 +215,7 @@ properties: description: | Specifies the behavior to apply to scheduled snapshots when the source disk is deleted. - default_value: KEEP_AUTO_SNAPSHOTS + default_value: "KEEP_AUTO_SNAPSHOTS" enum_values: - 'KEEP_AUTO_SNAPSHOTS' - 'APPLY_RETENTION_POLICY' diff --git a/mmv1/products/compute/go_Route.yaml b/mmv1/products/compute/go_Route.yaml index ef5b1a44c013..5d6cd4498bf3 100644 --- a/mmv1/products/compute/go_Route.yaml +++ b/mmv1/products/compute/go_Route.yaml @@ -52,7 +52,7 @@ docs: base_url: 'projects/{{project}}/global/routes' has_self_link: true immutable: true -mutex: projects/{{project}}/global/networks/{{network}}/peerings +mutex: 'projects/{{project}}/global/networks/{{network}}/peerings' timeouts: insert_minutes: 20 update_minutes: 20 @@ -73,6 +73,7 @@ async: collection_url_key: 'items' custom_code: extra_schema_entry: 'templates/terraform/extra_schema_entry/go/route.tmpl' + constants: 'templates/terraform/constants/go/compute_route.go.tmpl' decoder: 'templates/terraform/decoders/go/route.tmpl' error_retry_predicates: @@ -253,4 +254,4 @@ properties: - 'next_hop_ip' - 'next_hop_vpn_tunnel' - 'next_hop_ilb' - diff_suppress_func: 'tpgresource.CompareIpAddressOrSelfLinkOrResourceName' + diff_suppress_func: 'CompareIpAddressOrSelfLinkOrResourceName' diff --git a/mmv1/products/compute/go_Router.yaml b/mmv1/products/compute/go_Router.yaml index b41f54f27de3..f0ab6e85ebef 100644 --- a/mmv1/products/compute/go_Router.yaml +++ b/mmv1/products/compute/go_Router.yaml @@ -25,7 +25,7 @@ docs: base_url: 'projects/{{project}}/regions/{{region}}/routers' has_self_link: true update_verb: 'PATCH' -mutex: router/{{region}}/{{name}} +mutex: 'router/{{region}}/{{name}}' timeouts: insert_minutes: 20 update_minutes: 20 @@ -126,7 +126,7 @@ properties: type: Enum description: | User-specified flag to indicate which mode to use for advertisement. - default_value: DEFAULT + default_value: "DEFAULT" enum_values: - 'DEFAULT' - 'CUSTOM' diff --git a/mmv1/products/compute/go_RouterNat.yaml b/mmv1/products/compute/go_RouterNat.yaml index 8e6afb9e5415..d53602cf5d13 100644 --- a/mmv1/products/compute/go_RouterNat.yaml +++ b/mmv1/products/compute/go_RouterNat.yaml @@ -30,7 +30,7 @@ update_url: 'projects/{{project}}/regions/{{region}}/routers/{{router}}' update_verb: 'PATCH' delete_url: 'projects/{{project}}/regions/{{region}}/routers/{{router}}' delete_verb: 'PATCH' -mutex: router/{{region}}/{{router}} +mutex: 'router/{{region}}/{{router}}' timeouts: insert_minutes: 20 update_minutes: 20 @@ -448,7 +448,7 @@ properties: If `PRIVATE` NAT used for private IP translation. min_version: 'beta' immutable: true - default_value: PUBLIC + default_value: "PUBLIC" enum_values: - 'PUBLIC' - 'PRIVATE' diff --git a/mmv1/products/compute/go_SslCertificate.yaml b/mmv1/products/compute/go_SslCertificate.yaml index 97bc3263f0a8..d57ea5e11265 100644 --- a/mmv1/products/compute/go_SslCertificate.yaml +++ b/mmv1/products/compute/go_SslCertificate.yaml @@ -119,5 +119,5 @@ properties: immutable: true ignore_read: true sensitive: true - diff_suppress_func: 'tpgresource.Sha256DiffSuppress' + diff_suppress_func: 'sha256DiffSuppress' custom_flatten: 'templates/terraform/custom_flatten/go/sha256.tmpl' diff --git a/mmv1/products/compute/go_SslPolicy.yaml b/mmv1/products/compute/go_SslPolicy.yaml index 6d2ecd29f45d..9641c0832a70 100644 --- a/mmv1/products/compute/go_SslPolicy.yaml +++ b/mmv1/products/compute/go_SslPolicy.yaml @@ -89,7 +89,7 @@ properties: See the [official documentation](https://cloud.google.com/compute/docs/load-balancing/ssl-policies#profilefeaturesupport) for information on what cipher suites each profile provides. If `CUSTOM` is used, the `custom_features` attribute **must be set**. - default_value: COMPATIBLE + default_value: "COMPATIBLE" enum_values: - 'COMPATIBLE' - 'MODERN' @@ -100,7 +100,7 @@ properties: description: | The minimum version of SSL protocol that can be used by the clients to establish a connection with the load balancer. - default_value: TLS_1_0 + default_value: "TLS_1_0" enum_values: - 'TLS_1_0' - 'TLS_1_1' diff --git a/mmv1/products/compute/go_Subnetwork.yaml b/mmv1/products/compute/go_Subnetwork.yaml index 4184e5a30d5a..b1e6108aed1b 100644 --- a/mmv1/products/compute/go_Subnetwork.yaml +++ b/mmv1/products/compute/go_Subnetwork.yaml @@ -292,7 +292,7 @@ properties: - 'log_config.0.flow_sampling' - 'log_config.0.metadata' - 'log_config.0.filterExpr' - default_value: INTERVAL_5_SEC + default_value: "INTERVAL_5_SEC" enum_values: - 'INTERVAL_5_SEC' - 'INTERVAL_30_SEC' @@ -325,7 +325,7 @@ properties: - 'log_config.0.flow_sampling' - 'log_config.0.metadata' - 'log_config.0.filterExpr' - default_value: INCLUDE_ALL_METADATA + default_value: "INCLUDE_ALL_METADATA" enum_values: - 'EXCLUDE_ALL_METADATA' - 'INCLUDE_ALL_METADATA' @@ -342,12 +342,14 @@ properties: type: String description: | Export filter used to define which VPC flow logs should be logged, as as CEL expression. See + https://cloud.google.com/vpc/docs/flow-logs#filtering for details on how to format this field. + The default value is 'true', which evaluates to include everything. at_least_one_of: - 'log_config.0.aggregation_interval' - 'log_config.0.flow_sampling' - 'log_config.0.metadata' - 'log_config.0.filterExpr' - default_value: true + default_value: "true" - name: 'stackType' type: Enum description: | diff --git a/mmv1/products/compute/go_TargetHttpsProxy.yaml b/mmv1/products/compute/go_TargetHttpsProxy.yaml index 0b758b752c8c..296503b25d2b 100644 --- a/mmv1/products/compute/go_TargetHttpsProxy.yaml +++ b/mmv1/products/compute/go_TargetHttpsProxy.yaml @@ -120,7 +120,7 @@ properties: update_url: 'projects/{{project}}/global/targetHttpsProxies/{{name}}/setQuicOverride' update_verb: 'POST' custom_flatten: 'templates/terraform/custom_flatten/go/default_if_empty.tmpl' - default_value: NONE + default_value: "NONE" enum_values: - 'NONE' - 'ENABLE' diff --git a/mmv1/products/compute/go_TargetInstance.yaml b/mmv1/products/compute/go_TargetInstance.yaml index 99d92de1ee4d..c61038f4b112 100644 --- a/mmv1/products/compute/go_TargetInstance.yaml +++ b/mmv1/products/compute/go_TargetInstance.yaml @@ -130,7 +130,7 @@ properties: NAT option controlling how IPs are NAT'ed to the instance. Currently only NO_NAT (default value) is supported. immutable: true - default_value: NO_NAT + default_value: "NO_NAT" enum_values: - 'NO_NAT' - name: 'securityPolicy' diff --git a/mmv1/products/compute/go_TargetSslProxy.yaml b/mmv1/products/compute/go_TargetSslProxy.yaml index 1f294f259f28..e5dba61992b5 100644 --- a/mmv1/products/compute/go_TargetSslProxy.yaml +++ b/mmv1/products/compute/go_TargetSslProxy.yaml @@ -88,7 +88,7 @@ properties: the backend. update_url: 'projects/{{project}}/global/targetSslProxies/{{name}}/setProxyHeader' update_verb: 'POST' - default_value: NONE + default_value: "NONE" enum_values: - 'NONE' - 'PROXY_V1' diff --git a/mmv1/products/compute/go_TargetTcpProxy.yaml b/mmv1/products/compute/go_TargetTcpProxy.yaml index b75fff706297..bc8d89f4d45c 100644 --- a/mmv1/products/compute/go_TargetTcpProxy.yaml +++ b/mmv1/products/compute/go_TargetTcpProxy.yaml @@ -87,7 +87,7 @@ properties: the backend. update_url: 'projects/{{project}}/global/targetTcpProxies/{{name}}/setProxyHeader' update_verb: 'POST' - default_value: NONE + default_value: "NONE" enum_values: - 'NONE' - 'PROXY_V1' diff --git a/mmv1/products/compute/go_UrlMap.yaml b/mmv1/products/compute/go_UrlMap.yaml index dee0cd5963de..1890eafba34a 100644 --- a/mmv1/products/compute/go_UrlMap.yaml +++ b/mmv1/products/compute/go_UrlMap.yaml @@ -365,6 +365,11 @@ properties: type: ResourceRef description: | The full or partial URL to the BackendBucket resource that contains the custom error content. Examples are: + https://www.googleapis.com/compute/v1/projects/project/global/backendBuckets/myBackendBucket + compute/v1/projects/project/global/backendBuckets/myBackendBucket + global/backendBuckets/myBackendBucket + If errorService is not specified at lower levels like pathMatcher, pathRule and routeRule, an errorService specified at a higher level in the UrlMap will be used. If UrlMap.defaultCustomErrorResponsePolicy contains one or more errorResponseRules[], it must specify errorService. + If load balancer cannot reach the backendBucket, a simple Not Found Error will be returned, with the original response code (or overrideResponseCode if configured). resource: 'BackendBucket' imports: 'selfLink' - name: 'headerAction' @@ -524,6 +529,12 @@ properties: description: | The full or partial URL to the BackendBucket resource that contains the custom error content. Examples are: + https://www.googleapis.com/compute/v1/projects/project/global/backendBuckets/myBackendBucket + compute/v1/projects/project/global/backendBuckets/myBackendBucket + global/backendBuckets/myBackendBucket + + If errorService is not specified at lower levels like pathMatcher, pathRule and routeRule, an errorService specified at a higher level in the UrlMap will be used. If UrlMap.defaultCustomErrorResponsePolicy contains one or more errorResponseRules[], it must specify errorService. + If load balancer cannot reach the backendBucket, a simple Not Found Error will be returned, with the original response code (or overrideResponseCode if configured). resource: 'BackendBucket' imports: 'selfLink' - name: 'routeAction' @@ -702,6 +713,24 @@ properties: * 5xx: Loadbalancer will attempt a retry if the backend service responds with any 5xx response code, or if the backend service does not respond at all, + for example: disconnects, reset, read timeout, connection failure, and refused + streams. + * gateway-error: Similar to 5xx, but only applies to response codes + 502, 503 or 504. + * connect-failure: Loadbalancer will retry on failures + connecting to backend services, for example due to connection timeouts. + * retriable-4xx: Loadbalancer will retry for retriable 4xx response codes. + Currently the only retriable error supported is 409. + * refused-stream: Loadbalancer will retry if the backend service resets the stream with a + REFUSED_STREAM error code. This reset type indicates that it is safe to retry. + * cancelled: Loadbalancer will retry if the gRPC status code in the response + header is set to cancelled + * deadline-exceeded: Loadbalancer will retry if the + gRPC status code in the response header is set to deadline-exceeded + * resource-exhausted: Loadbalancer will retry if the gRPC status code in the response + header is set to resource-exhausted + * unavailable: Loadbalancer will retry if + the gRPC status code in the response header is set to unavailable item_type: type: String - name: 'timeout' @@ -1417,6 +1446,24 @@ properties: * 5xx: Loadbalancer will attempt a retry if the backend service responds with any 5xx response code, or if the backend service does not respond at all, + for example: disconnects, reset, read timeout, connection failure, and refused + streams. + * gateway-error: Similar to 5xx, but only applies to response codes + 502, 503 or 504. + * connect-failure: Loadbalancer will retry on failures + connecting to backend services, for example due to connection timeouts. + * retriable-4xx: Loadbalancer will retry for retriable 4xx response codes. + Currently the only retriable error supported is 409. + * refused-stream: Loadbalancer will retry if the backend service resets the stream with a + REFUSED_STREAM error code. This reset type indicates that it is safe to retry. + * cancelled: Loadbalancer will retry if the gRPC status code in the response + header is set to cancelled + * deadline-exceeded: Loadbalancer will retry if the + gRPC status code in the response header is set to deadline-exceeded + * resource-exhausted: Loadbalancer will retry if the gRPC status code in the response + header is set to resource-exhausted + * unavailable: Loadbalancer will retry if the gRPC status code in + the response header is set to unavailable item_type: type: String - name: 'timeout' @@ -2086,6 +2133,12 @@ properties: description: | The full or partial URL to the BackendBucket resource that contains the custom error content. Examples are: + https://www.googleapis.com/compute/v1/projects/project/global/backendBuckets/myBackendBucket + compute/v1/projects/project/global/backendBuckets/myBackendBucket + global/backendBuckets/myBackendBucket + + If errorService is not specified at lower levels like pathMatcher, pathRule and routeRule, an errorService specified at a higher level in the UrlMap will be used. If UrlMap.defaultCustomErrorResponsePolicy contains one or more errorResponseRules[], it must specify errorService. + If load balancer cannot reach the backendBucket, a simple Not Found Error will be returned, with the original response code (or overrideResponseCode if configured). resource: 'BackendBucket' imports: 'selfLink' - name: 'test' diff --git a/mmv1/products/datafusion/go_Instance.yaml b/mmv1/products/datafusion/go_Instance.yaml index f76d7bc4950f..a4db1f7fef0f 100644 --- a/mmv1/products/datafusion/go_Instance.yaml +++ b/mmv1/products/datafusion/go_Instance.yaml @@ -68,6 +68,16 @@ examples: prober_test_run: '' test_vars_overrides: 'prober_test_run': '`options = { prober_test_run = "true" }`' + - name: 'data_fusion_instance_psc' + primary_resource_id: 'psc_instance' + vars: + instance_name: 'psc-instance' + network_name: 'datafusion-psc-network' + subnet_name: 'datafusion-psc-subnet' + attachment_name: 'datafusion-psc-attachment' + prober_test_run: '' + test_vars_overrides: + 'prober_test_run': '`options = { prober_test_run = "true" }`' - name: 'data_fusion_instance_cmek' primary_resource_id: 'cmek' vars: @@ -238,7 +248,6 @@ properties: description: | The IP range in CIDR notation to use for the managed Data Fusion instance nodes. This range must not overlap with any other ranges used in the Data Fusion instance network. - required: true immutable: true - name: 'network' type: String @@ -246,8 +255,46 @@ properties: Name of the network in the project with which the tenant project will be peered for executing pipelines. In case of shared VPC where the network resides in another host project the network should specified in the form of projects/{host-project-id}/global/networks/{network} - required: true immutable: true + - name: 'connectionType' + type: Enum + description: | + Optional. Type of connection for establishing private IP connectivity between the Data Fusion customer project VPC and + the corresponding tenant project from a predefined list of available connection modes. + If this field is unspecified for a private instance, VPC peering is used. + immutable: true + enum_values: + - 'VPC_PEERING' + - 'PRIVATE_SERVICE_CONNECT_INTERFACES' + - name: 'privateServiceConnectConfig' + type: NestedObject + description: | + Optional. Configuration for Private Service Connect. + This is required only when using connection type PRIVATE_SERVICE_CONNECT_INTERFACES. + immutable: true + properties: + - name: 'networkAttachment' + type: String + description: | + Optional. The reference to the network attachment used to establish private connectivity. + It will be of the form projects/{project-id}/regions/{region}/networkAttachments/{network-attachment-id}. + This is required only when using connection type PRIVATE_SERVICE_CONNECT_INTERFACES. + immutable: true + - name: 'unreachableCidrBlock' + type: String + description: | + Optional. Input only. The CIDR block to which the CDF instance can't route traffic to in the consumer project VPC. + The size of this block should be at least /25. This range should not overlap with the primary address range of any subnetwork used by the network attachment. + This range can be used for other purposes in the consumer VPC as long as there is no requirement for CDF to reach destinations using these addresses. + If this value is not provided, the server chooses a non RFC 1918 address range. The format of this field is governed by RFC 4632. + immutable: true + ignore_read: true + - name: 'effectiveUnreachableCidrBlock' + type: String + description: | + Output only. The CIDR block to which the CDF instance can't route traffic to in the consumer project VPC. + The size of this block is /25. The format of this field is governed by RFC 4632. + output: true - name: 'zone' type: String description: | diff --git a/mmv1/products/dataplex/Datascan.yaml b/mmv1/products/dataplex/Datascan.yaml index c3900509e163..c1c71bed0dc2 100644 --- a/mmv1/products/dataplex/Datascan.yaml +++ b/mmv1/products/dataplex/Datascan.yaml @@ -428,6 +428,16 @@ properties: required: true description: | The SQL expression. + - !ruby/object:Api::Type::NestedObject + name: 'sqlAssertion' + description: | + Table rule which evaluates whether any row matches invalid state. + properties: + - !ruby/object:Api::Type::String + name: 'sqlStatement' + required: true + description: | + The SQL statement. - !ruby/object:Api::Type::NestedObject name: 'dataProfileSpec' allow_empty_object: true diff --git a/mmv1/products/gkehub2/Feature.yaml b/mmv1/products/gkehub2/Feature.yaml index 0dd00735c910..86a76db27cfb 100644 --- a/mmv1/products/gkehub2/Feature.yaml +++ b/mmv1/products/gkehub2/Feature.yaml @@ -286,6 +286,9 @@ properties: - !ruby/object:Api::Type::String name: sourceFormat description: 'Specifies whether the Config Sync Repo is in hierarchical or unstructured mode' + - !ruby/object:Api::Type::Boolean + name: enabled + description: 'Enables the installation of ConfigSync. If set to true, ConfigSync resources will be created and the other ConfigSync fields will be applied if exist. If set to false, all other ConfigSync fields will be ignored, ConfigSync resources will be deleted. If omitted, ConfigSync resources will be managed depends on the presence of the git or oci field.' - !ruby/object:Api::Type::Boolean name: preventDrift description: 'Set to true to enable the Config Sync admission webhook to prevent drifts. If set to `false`, disables the Config Sync admission webhook and does not prevent drifts.' diff --git a/mmv1/products/kms/CryptoKey.yaml b/mmv1/products/kms/CryptoKey.yaml index 3bb03dcf7eb6..b1b6fc705338 100644 --- a/mmv1/products/kms/CryptoKey.yaml +++ b/mmv1/products/kms/CryptoKey.yaml @@ -149,7 +149,7 @@ properties: immutable: true description: | The period of time that versions of this key spend in the DESTROY_SCHEDULED state before transitioning to DESTROYED. - If not specified at creation time, the default duration is 24 hours. + If not specified at creation time, the default duration is 30 days. default_from_api: true - !ruby/object:Api::Type::Boolean name: 'importOnly' diff --git a/mmv1/products/monitoring/AlertPolicy.yaml b/mmv1/products/monitoring/AlertPolicy.yaml index 52e00415e3c2..ca0bf80921e5 100644 --- a/mmv1/products/monitoring/AlertPolicy.yaml +++ b/mmv1/products/monitoring/AlertPolicy.yaml @@ -983,6 +983,7 @@ properties: - documentation.0.content - documentation.0.mime_type - documentation.0.subject + - documentation.0.links description: | The text of the documentation, interpreted according to mimeType. The content may not exceed 8,192 Unicode characters and may not @@ -994,6 +995,7 @@ properties: - documentation.0.content - documentation.0.mime_type - documentation.0.subject + - documentation.0.links default_value: text/markdown description: | The format of the content field. Presently, only the value @@ -1004,8 +1006,28 @@ properties: - documentation.0.content - documentation.0.mime_type - documentation.0.subject + - documentation.0.links description: | The subject line of the notification. The subject line may not exceed 10,240 bytes. In notifications generated by this policy the contents of the subject line after variable expansion will be truncated to 255 bytes or shorter at the latest UTF-8 character boundary. + - !ruby/object:Api::Type::Array + name: links + at_least_one_of: + - documentation.0.content + - documentation.0.mime_type + - documentation.0.subject + - documentation.0.links + description: | + Links to content such as playbooks, repositories, and other resources. This field can contain up to 3 entries. + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::String + name: displayName + description: | + A short display name for the link. The display name must not be empty or exceed 63 characters. Example: "playbook". + - !ruby/object:Api::Type::String + name: url + description: | + The url of a webpage. A url can be templatized by using variables in the path or the query parameters. The total length of a URL should not exceed 2083 characters before and after variable expansion. Example: "https://my_domain.com/playbook?name=${resource.name}". diff --git a/mmv1/products/netapp/storagePool.yaml b/mmv1/products/netapp/storagePool.yaml index 529d2ea5aaae..aafe3d74deb2 100644 --- a/mmv1/products/netapp/storagePool.yaml +++ b/mmv1/products/netapp/storagePool.yaml @@ -25,9 +25,22 @@ description: | The capacity of the pool can be split up and assigned to volumes within the pool. Storage pools are a billable component of NetApp Volumes. Billing is based on the location, service level, and capacity allocated to a pool independent of consumption at the volume level. + + Storage pools of service level Flex are available as zonal (single zone) or regional (two zones in same region) pools. + Zonal and regional pools are high-available within the zone. On top of that, regional pools have `replica_zone` as + hot standby zone. All volume access is served from the `zone`. If `zone` fails, `replica_zone` + automatically becomes the active zone. This will cause state drift in your configuration. + If a zone switch (manual or automatic) is triggered outside of Terraform, you need to adjust the `zone` + and `replica_zone` values to reflect the current state, or Terraform will initiate a zone switch when running + the next apply. You can trigger a manual + [zone switch](https://cloud.google.com/netapp/volumes/docs/configure-and-use/storage-pools/edit-or-delete-storage-pool#switch_active_and_replica_zones) + via Terraform by swapping the value of the `zone` and `replica_zone` parameters in your HCL code. + Note : Regional FLEX storage pool are supported in beta provider currently. + references: !ruby/object:Api::Resource::ReferenceLinks guides: 'Quickstart documentation': 'https://cloud.google.com/netapp/volumes/docs/get-started/quickstarts/create-storage-pool' + 'Regional Flex zone switch': 'https://cloud.google.com/netapp/volumes/docs/configure-and-use/storage-pools/edit-or-delete-storage-pool#switch_active_and_replica_zones' api: 'https://cloud.google.com/netapp/volumes/docs/reference/rest/v1/projects.locations.storagePools' base_url: projects/{{project}}/locations/{{location}}/storagePools self_link: projects/{{project}}/locations/{{location}}/storagePools/{{name}} @@ -49,11 +62,11 @@ parameters: immutable: true url_param_only: true description: | - Name of the location. Usually a region name, expect for some FLEX service level pools which require a zone name. + Name of the location. For zonal Flex pools specify a zone name, in all other cases a region name. - !ruby/object:Api::Type::String name: 'name' description: - The resource name of the storage pool. Needs to be unique per location. + The resource name of the storage pool. Needs to be unique per location/region. required: true immutable: true url_param_only: true @@ -133,3 +146,18 @@ properties: - :SERVICE_MANAGED - :CLOUD_KMS output: true + - !ruby/object:Api::Type::String + name: 'zone' + description: | + Specifies the active zone for regional Flex pools. `zone` and `replica_zone` values can be swapped to initiate a + [zone switch](https://cloud.google.com/netapp/volumes/docs/configure-and-use/storage-pools/edit-or-delete-storage-pool#switch_active_and_replica_zones). + If you want to create a zonal Flex pool, specify a zone name for `location` and omit `zone`. + min_version: beta + - !ruby/object:Api::Type::String + name: 'replicaZone' + description: | + Specifies the replica zone for regional Flex pools. `zone` and `replica_zone` values can be swapped to initiate a + [zone switch](https://cloud.google.com/netapp/volumes/docs/configure-and-use/storage-pools/edit-or-delete-storage-pool#switch_active_and_replica_zones). + min_version: beta +custom_code: !ruby/object:Provider::Terraform::CustomCode + pre_update: templates/terraform/pre_update/netapp_storagepool.go.erb diff --git a/mmv1/products/netapp/volume.yaml b/mmv1/products/netapp/volume.yaml index 1073e7525c17..3ac6a167d60b 100644 --- a/mmv1/products/netapp/volume.yaml +++ b/mmv1/products/netapp/volume.yaml @@ -475,6 +475,18 @@ properties: name: 'scheduledBackupEnabled' description: |- When set to true, scheduled backup is enabled on the volume. Omit if no backup_policy is specified. + - !ruby/object:Api::Type::String + name: 'zone' + description: | + Specifies the active zone for regional volume. + output: true + min_version: beta + - !ruby/object:Api::Type::String + name: 'replicaZone' + description: | + Specifies the replica zone for regional volume. + output: true + min_version: beta virtual_fields: - !ruby/object:Api::Type::Enum name: 'deletion_policy' diff --git a/mmv1/products/pubsub/Subscription.yaml b/mmv1/products/pubsub/Subscription.yaml index d70884b88cfa..8ce56cc77c90 100644 --- a/mmv1/products/pubsub/Subscription.yaml +++ b/mmv1/products/pubsub/Subscription.yaml @@ -140,7 +140,7 @@ properties: - !ruby/object:Api::Type::String name: 'table' description: | - The name of the table to which to write data, of the form {projectId}:{datasetId}.{tableId} + The name of the table to which to write data, of the form {projectId}.{datasetId}.{tableId} required: true - !ruby/object:Api::Type::Boolean name: 'useTopicSchema' @@ -302,7 +302,7 @@ properties: - v1beta1: uses the push format defined in the v1beta1 Pub/Sub API. - v1 or v1beta2: uses the push format defined in the v1 Pub/Sub API. - diff_suppress_func: 'tpgresource.IgnoreMissingKeyInMap("x-goog-version")' + diff_suppress_func: 'IgnoreMissingKeyInMap("x-goog-version")' - !ruby/object:Api::Type::NestedObject name: 'noWrapper' custom_flatten: 'templates/terraform/custom_flatten/pubsub_no_wrapper_write_metadata_flatten.go.erb' diff --git a/mmv1/products/pubsub/go_Schema.yaml b/mmv1/products/pubsub/go_Schema.yaml index 46ee0533179b..c16b3ab2f43b 100644 --- a/mmv1/products/pubsub/go_Schema.yaml +++ b/mmv1/products/pubsub/go_Schema.yaml @@ -70,7 +70,7 @@ properties: - name: 'type' type: Enum description: The type of the schema definition - default_value: TYPE_UNSPECIFIED + default_value: "TYPE_UNSPECIFIED" enum_values: - 'TYPE_UNSPECIFIED' - 'PROTOCOL_BUFFER' diff --git a/mmv1/products/pubsub/go_Subscription.yaml b/mmv1/products/pubsub/go_Subscription.yaml index 02ab2a488f57..8665583d28f8 100644 --- a/mmv1/products/pubsub/go_Subscription.yaml +++ b/mmv1/products/pubsub/go_Subscription.yaml @@ -141,7 +141,7 @@ properties: - name: 'table' type: String description: | - The name of the table to which to write data, of the form {projectId}:{datasetId}.{tableId} + The name of the table to which to write data, of the form {projectId}.{datasetId}.{tableId} required: true - name: 'useTopicSchema' type: Boolean @@ -207,7 +207,7 @@ properties: The maximum duration that can elapse before a new Cloud Storage file is created. Min 1 minute, max 10 minutes, default 5 minutes. May not exceed the subscription's acknowledgement deadline. A duration in seconds with up to nine fractional digits, ending with 's'. Example: "3.5s". - default_value: 300s + default_value: "300s" - name: 'maxBytes' type: Integer description: | @@ -303,7 +303,7 @@ properties: - v1beta1: uses the push format defined in the v1beta1 Pub/Sub API. - v1 or v1beta2: uses the push format defined in the v1 Pub/Sub API. - diff_suppress_func: 'tpgresource.IgnoreMissingKeyInMap("x-goog-version")' + diff_suppress_func: 'IgnoreMissingKeyInMap("x-goog-version")' - name: 'noWrapper' type: NestedObject description: | @@ -353,7 +353,7 @@ properties: A duration in seconds with up to nine fractional digits, terminated by 's'. Example: `"600.5s"`. - default_value: 604800s + default_value: "604800s" - name: 'retainAckedMessages' type: Boolean description: | diff --git a/mmv1/products/pubsub/go_Topic.yaml b/mmv1/products/pubsub/go_Topic.yaml index 32c9ce54bce2..807462055e78 100644 --- a/mmv1/products/pubsub/go_Topic.yaml +++ b/mmv1/products/pubsub/go_Topic.yaml @@ -143,7 +143,7 @@ properties: - name: 'encoding' type: Enum description: The encoding of messages validated against schema. - default_value: ENCODING_UNSPECIFIED + default_value: "ENCODING_UNSPECIFIED" enum_values: - 'ENCODING_UNSPECIFIED' - 'JSON' diff --git a/mmv1/products/securesourcemanager/Repository.yaml b/mmv1/products/securesourcemanager/Repository.yaml new file mode 100644 index 000000000000..5756b37c8d73 --- /dev/null +++ b/mmv1/products/securesourcemanager/Repository.yaml @@ -0,0 +1,156 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Api::Resource +name: 'Repository' +base_url: 'projects/{{project}}/locations/{{location}}/repositories?repository_id={{repository_id}}' +self_link: 'projects/{{project}}/locations/{{location}}/repositories/{{repository_id}}' +immutable: true +description: 'Repositories store source code. It supports all Git SCM client commands and has built-in pull requests and issue tracking. Both HTTPS and SSH authentication are supported.' +references: !ruby/object:Api::Resource::ReferenceLinks + guides: + 'Official Documentation': 'https://cloud.google.com/secure-source-manager/docs/overview' +import_format: ['projects/{{project}}/locations/{{location}}/repositories/{{repository_id}}', '{{repository_id}}'] +autogen_async: true +async: !ruby/object:Api::OpAsync + actions: ['create', 'delete'] + operation: !ruby/object:Api::OpAsync::Operation + path: 'name' + base_url: '{{op_id}}' + wait_ms: 1000 + result: !ruby/object:Api::OpAsync::Result + path: 'response' + status: !ruby/object:Api::OpAsync::Status + path: 'done' + complete: true + allowed: + - true + - false + error: !ruby/object:Api::OpAsync::Error + path: 'error' + message: 'message' +iam_policy: !ruby/object:Api::Resource::IamPolicy + parent_resource_attribute: 'repository_id' + method_name_separator: ':' + allowed_iam_role: 'roles/securesourcemanager.repoAdmin' + import_format: ['projects/{{project}}/locations/{{location}}/repositories/{{repository_id}}', '{{repository_id}}'] +examples: + - !ruby/object:Provider::Terraform::Examples + name: 'secure_source_manager_repository_basic' + primary_resource_id: 'default' + primary_resource_name: "fmt.Sprintf(\"tf-test-my-repository%s\", + context[\"random_suffix\"\ + ])" + vars: + repository_id: 'my-repository' + instance_id: 'my-instance' + - !ruby/object:Provider::Terraform::Examples + name: 'secure_source_manager_repository_initial_config' + primary_resource_id: 'default' + primary_resource_name: "fmt.Sprintf(\"tf-test-my-repository%s\", + context[\"random_suffix\"\ + ])" + vars: + repository_id: 'my-repository' + instance_id: 'my-instance' +parameters: + - !ruby/object:Api::Type::String + name: 'location' + description: | + The location for the Repository. + required: true + url_param_only: true + - !ruby/object:Api::Type::String + name: 'repository_id' + description: | + The ID for the Repository. + required: true + url_param_only: true +properties: + - !ruby/object:Api::Type::String + name: 'name' + description: | + The resource name for the Repository. + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: | + Description of the repository, which cannot exceed 500 characters. + - !ruby/object:Api::Type::String + name: 'instance' + description: | + The name of the instance in which the repository is hosted. + required: true + diff_suppress_func: 'tpgresource.ProjectNumberDiffSuppress' + - !ruby/object:Api::Type::String + name: 'uid' + description: | + Unique identifier of the repository. + output: true + - !ruby/object:Api::Type::Time + name: 'createTime' + description: | + Time the repository was created in UTC. + output: true + - !ruby/object:Api::Type::Time + name: 'updateTime' + description: | + Time the repository was updated in UTC. + output: true + - !ruby/object:Api::Type::NestedObject + name: 'uris' + description: | + URIs for the repository. + output: true + properties: + - !ruby/object:Api::Type::String + name: 'html' + description: | + HTML is the URI for the user to view the repository in a browser. + output: true + - !ruby/object:Api::Type::String + name: 'gitHttps' + description: + git_https is the git HTTPS URI for git operations. + output: true + - !ruby/object:Api::Type::String + name: 'api' + description: | + API is the URI for API access. + output: true + - !ruby/object:Api::Type::NestedObject + name: 'initialConfig' + description: | + Initial configurations for the repository. + ignore_read: true + properties: + - !ruby/object:Api::Type::String + name: 'defaultBranch' + description: | + Default branch name of the repository. + - !ruby/object:Api::Type::Array + name: 'gitignores' + description: | + List of gitignore template names user can choose from. + Valid values can be viewed at https://cloud.google.com/secure-source-manager/docs/reference/rest/v1/projects.locations.repositories#initialconfig. + item_type: Api::Type::String + - !ruby/object:Api::Type::String + name: 'license' + description: | + License template name user can choose from. + Valid values can be viewed at https://cloud.google.com/secure-source-manager/docs/reference/rest/v1/projects.locations.repositories#initialconfig. + - !ruby/object:Api::Type::String + name: 'readme' + description: | + README template name. + Valid values can be viewed at https://cloud.google.com/secure-source-manager/docs/reference/rest/v1/projects.locations.repositories#initialconfig. diff --git a/mmv1/products/securitycenterv2/OrganizationNotificationConfig.yaml b/mmv1/products/securitycenterv2/OrganizationNotificationConfig.yaml new file mode 100644 index 000000000000..1026cf8d27be --- /dev/null +++ b/mmv1/products/securitycenterv2/OrganizationNotificationConfig.yaml @@ -0,0 +1,129 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Api::Resource +name: 'OrganizationNotificationConfig' +base_url: organizations/{{organization}}/locations/{{location}}/notificationConfigs +self_link: '{{name}}' +create_url: organizations/{{organization}}/locations/{{location}}/notificationConfigs?configId={{config_id}} +update_verb: :PATCH +update_mask: true +description: | + A Cloud Security Command Center (Cloud SCC) notification configs. A + notification config is a Cloud SCC resource that contains the + configuration to send notifications for create/update events of + findings, assets and etc. + ~> **Note:** In order to use Cloud SCC resources, your organization must be enrolled + in [SCC Standard/Premium](https://cloud.google.com/security-command-center/docs/quickstart-security-command-center). + Without doing so, you may run into errors during resource creation. +references: !ruby/object:Api::Resource::ReferenceLinks + guides: + 'Official Documentation': 'https://cloud.google.com/security-command-center/docs' + api: 'https://cloud.google.com/security-command-center/docs/reference/rest/v2/organizations.locations.notificationConfigs' +examples: + - !ruby/object:Provider::Terraform::Examples + name: 'scc_v2_organization_notification_config_basic' + primary_resource_id: 'custom_organization_notification_config' + vars: + topic_name: 'my-topic' + config_id: 'my-config' + test_env_vars: + org_id: :ORG_ID +custom_code: !ruby/object:Provider::Terraform::CustomCode + custom_import: templates/terraform/custom_import/scc_v2_source_self_link_as_name_set_organization.go.erb + post_create: templates/terraform/post_create/set_computed_name.erb +parameters: + - !ruby/object:Api::Type::String + name: organization + required: true + immutable: true + url_param_only: true + description: | + The organization whose Cloud Security Command Center the Notification + Config lives in. + - !ruby/object:Api::Type::String + name: configId + required: true + immutable: true + url_param_only: true + description: | + This must be unique within the organization. + - !ruby/object:Api::Type::String + name: location + immutable: true + url_param_only: true + default_value: global + description: | + location Id is provided by organization. If not provided, Use global as default. +properties: + - !ruby/object:Api::Type::String + name: name + output: true + description: | + The resource name of this notification config, in the format + `organizations/{{organization}}/notificationConfigs/{{config_id}}`. + - !ruby/object:Api::Type::String + name: description + description: | + The description of the notification config (max of 1024 characters). + validation: !ruby/object:Provider::Terraform::Validation + function: 'validation.StringLenBetween(0, 1024)' + - !ruby/object:Api::Type::String + name: pubsubTopic + required: true + description: | + The Pub/Sub topic to send notifications to. Its format is + "projects/[project_id]/topics/[topic]". + - !ruby/object:Api::Type::String + name: serviceAccount + output: true + description: | + The service account that needs "pubsub.topics.publish" permission to + publish to the Pub/Sub topic. + - !ruby/object:Api::Type::NestedObject + name: streamingConfig + required: true + description: | + The config for triggering streaming-based notifications. + update_mask_fields: + - 'streamingConfig.filter' + properties: + - !ruby/object:Api::Type::String + name: filter + required: true + description: | + Expression that defines the filter to apply across create/update + events of assets or findings as specified by the event type. The + expression is a list of zero or more restrictions combined via + logical operators AND and OR. Parentheses are supported, and OR + has higher precedence than AND. + + Restrictions have the form and may have + a - character in front of them to indicate negation. The fields + map to those defined in the corresponding resource. + + The supported operators are: + + * = for all value types. + * >, <, >=, <= for integer values. + * :, meaning substring matching, for strings. + + The supported value types are: + + * string literals in quotes. + * integer literals without quotes. + * boolean literals true and false without quotes. + + See + [Filtering notifications](https://cloud.google.com/security-command-center/docs/how-to-api-filter-notifications) + for information on how to write a filter. diff --git a/mmv1/products/securitycenterv2/product.yaml b/mmv1/products/securitycenterv2/product.yaml new file mode 100644 index 000000000000..beddce3f145d --- /dev/null +++ b/mmv1/products/securitycenterv2/product.yaml @@ -0,0 +1,23 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Api::Product +name: SecurityCenterV2 +display_name: Security Command Center (SCC)v2 API +legacy_name: scc_v2 +versions: + - !ruby/object:Api::Product::Version + name: ga + base_url: https://securitycenter.googleapis.com/v2/ +scopes: + - https://www.googleapis.com/auth/cloud-platform diff --git a/mmv1/products/securityposture/PostureDeployment.yaml b/mmv1/products/securityposture/PostureDeployment.yaml index 0df1f718a997..327d55cf5cc0 100644 --- a/mmv1/products/securityposture/PostureDeployment.yaml +++ b/mmv1/products/securityposture/PostureDeployment.yaml @@ -32,16 +32,7 @@ autogen_async: true async: !ruby/object:Api::OpAsync operation: !ruby/object:Api::OpAsync::Operation base_url: '{{op_id}}' -examples: - - !ruby/object:Provider::Terraform::Examples - name: 'securityposture_posture_deployment_basic' - primary_resource_id: 'postureDeployment' - vars: - posture_id: "posture_1" - deployment_id: "posture_deployment_1" - test_env_vars: - org_id: :ORG_ID - project_number: :PROJECT_NUMBER + parameters: - !ruby/object:Api::Type::String name: parent diff --git a/mmv1/products/storage/ManagedFolder.yaml b/mmv1/products/storage/ManagedFolder.yaml new file mode 100644 index 000000000000..41bde5e0855e --- /dev/null +++ b/mmv1/products/storage/ManagedFolder.yaml @@ -0,0 +1,83 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Api::Resource +name: 'ManagedFolder' +kind: 'storage#managedFolder' +base_url: 'b/{{bucket}}/managedFolders' +self_link: 'b/{{bucket}}/managedFolders/{{%name}}' +id_format: '{{bucket}}/{{name}}' +delete_url: 'b/{{bucket}}/managedFolders/{{%name}}' +has_self_link: true +immutable: true +skip_sweeper: true # Skipping sweeper since this is a child resource. +description: | + A Google Cloud Storage Managed Folder. + + You can apply Identity and Access Management (IAM) policies to + managed folders to grant principals access only to the objects + within the managed folder, which lets you more finely control access + for specific data sets and tables within a bucket. You can nest + managed folders up to 15 levels deep, including the parent managed + folder. + + Managed folders can only be created in buckets that have uniform + bucket-level access enabled. +references: !ruby/object:Api::Resource::ReferenceLinks + guides: + 'Official Documentation': 'https://cloud.google.com/storage/docs/managed-folders' + api: 'https://cloud.google.com/storage/docs/json_api/v1/managedFolder' +# iam_policy: handwritten in mmv1/third_party/terraform/services/storage/iam_storage_managed_folder.go +import_format: + - '{{bucket}}/managedFolders/{{%name}}' + - '{{bucket}}/{{%name}}' +examples: + - !ruby/object:Provider::Terraform::Examples + name: 'storage_managed_folder_basic' + primary_resource_id: 'folder' + vars: + bucket_name: 'my-bucket' +parameters: + - !ruby/object:Api::Type::ResourceRef + name: 'bucket' + resource: 'Bucket' + imports: 'name' + description: 'The name of the bucket that contains the managed folder.' + required: true + - !ruby/object:Api::Type::String + name: 'name' + description: | + The name of the managed folder expressed as a path. Must include + trailing '/'. For example, `example_dir/example_dir2/`. + required: true + # The API returns values with trailing slashes, even if not + # provided. Enforcing trailing slashes prevents diffs and ensures + # consistent output. + validation: !ruby/object:Provider::Terraform::Validation + regex: '/$' +properties: + - !ruby/object:Api::Type::String + name: createTime + description: | + The timestamp at which this managed folder was created. + output: true + - !ruby/object:Api::Type::String + name: updateTime + description: | + The timestamp at which this managed folder was most recently updated. + output: true + - !ruby/object:Api::Type::String + name: metageneration + description: | + The metadata generation of the managed folder. + output: true diff --git a/mmv1/products/vmwareengine/Cluster.yaml b/mmv1/products/vmwareengine/Cluster.yaml index e2fa5c8053c3..6112c8298d0d 100644 --- a/mmv1/products/vmwareengine/Cluster.yaml +++ b/mmv1/products/vmwareengine/Cluster.yaml @@ -49,6 +49,8 @@ async: !ruby/object:Api::OpAsync import_format: ["{{%parent}}/clusters/{{name}}"] id_format: "{{parent}}/clusters/{{name}}" autogen_async: true +# There is a handwritten sweeper that provides a list of locations to sweep +skip_sweeper: true examples: - !ruby/object:Provider::Terraform::Examples name: "vmware_engine_cluster_basic" diff --git a/mmv1/products/workstations/WorkstationConfig.yaml b/mmv1/products/workstations/WorkstationConfig.yaml index ff575c7801f5..da235ea6df81 100644 --- a/mmv1/products/workstations/WorkstationConfig.yaml +++ b/mmv1/products/workstations/WorkstationConfig.yaml @@ -73,6 +73,13 @@ examples: vars: workstation_cluster_name: 'workstation-cluster' workstation_config_name: 'workstation-config' + key_short_name: 'keyname' + value_short_name: 'valuename' + org_id: '123456789' + test_vars_overrides: + key_short_name: '"tf-test-key-" + acctest.RandString(t, 10)' + value_short_name: '"tf-test-value-" + acctest.RandString(t, 10)' + org_id: 'envvar.GetTestOrgFromEnv(t)' - !ruby/object:Provider::Terraform::Examples name: 'workstation_config_container' min_version: beta @@ -222,6 +229,7 @@ properties: - 'host.gceInstance.accelerators' - 'host.gceInstance.boostConfigs' - 'host.gceInstance.disableSsh' + - 'host.gceInstance.vmTags' properties: - !ruby/object:Api::Type::NestedObject name: 'gceInstance' @@ -376,6 +384,14 @@ properties: description: | Number of accelerator cards exposed to the instance. required: true + - !ruby/object:Api::Type::KeyValuePairs + name: 'vmTags' + description: | + Resource manager tags to be bound to the VM instances backing the Workstations. + Tag keys and values have the same definition as + https://cloud.google.com/resource-manager/docs/tags/tags-overview + Keys must be in the format `tagKeys/{tag_key_id}`, and + values are in the format `tagValues/456`. - !ruby/object:Api::Type::Array name: 'persistentDirectories' description: | diff --git a/mmv1/provider/terraform.go b/mmv1/provider/terraform.go index ff16e3614ab8..e1dc95237850 100644 --- a/mmv1/provider/terraform.go +++ b/mmv1/provider/terraform.go @@ -858,39 +858,6 @@ func (t Terraform) GetMmv1ServicesInVersion(products []*api.Product) []string { // // 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 -// // # Capitalize the first letter of a property name. // # E.g. "creationTimestamp" becomes "CreationTimestamp". // def titlelize_property(property) diff --git a/mmv1/provider/terraform_tgc.rb b/mmv1/provider/terraform_tgc.rb index 075888bfb87d..034a35d4665d 100644 --- a/mmv1/provider/terraform_tgc.rb +++ b/mmv1/provider/terraform_tgc.rb @@ -336,7 +336,9 @@ def copy_common_files(output_folder, generate_code, _generate_docs) ['converters/google/resources/logging_project_bucket_config.go', 'third_party/tgc/logging_project_bucket_config.go'], ['converters/google/resources/logging_billing_account_bucket_config.go', - 'third_party/tgc/logging_billing_account_bucket_config.go'] + 'third_party/tgc/logging_billing_account_bucket_config.go'], + ['converters/google/resources/appengine_standard_version.go', + 'third_party/tgc/appengine_standard_version.go'] ]) end diff --git a/mmv1/template-converter.go b/mmv1/template-converter.go index 16d06276cc19..b6809faad156 100644 --- a/mmv1/template-converter.go +++ b/mmv1/template-converter.go @@ -689,6 +689,7 @@ func checkExceptionList(filePath string) bool { "custom_flatten/bigquery_table_ref_copy_destinationtable.go", "custom_flatten/bigquery_table_ref_extract_sourcetable.go", "custom_flatten/bigquery_table_ref_query_destinationtable.go", + "constants/router_nat_validate_action_active_range.go", "unordered_list_customize_diff", "default_if_empty", diff --git a/mmv1/templates/terraform/constants/compute_certificate.go.erb b/mmv1/templates/terraform/constants/compute_certificate.go.erb new file mode 100644 index 000000000000..f5f6aae3c09d --- /dev/null +++ b/mmv1/templates/terraform/constants/compute_certificate.go.erb @@ -0,0 +1,5 @@ +// sha256DiffSuppress +// if old is the hex-encoded sha256 sum of new, treat them as equal +func sha256DiffSuppress(_, old, new string, _ *schema.ResourceData) bool { + return hex.EncodeToString(sha256.New().Sum([]byte(old))) == new +} \ No newline at end of file diff --git a/mmv1/templates/terraform/constants/compute_forwarding_rule.go.erb b/mmv1/templates/terraform/constants/compute_forwarding_rule.go.erb index 92afd4d61d0a..19580c9b79b3 100644 --- a/mmv1/templates/terraform/constants/compute_forwarding_rule.go.erb +++ b/mmv1/templates/terraform/constants/compute_forwarding_rule.go.erb @@ -14,3 +14,54 @@ func forwardingRuleCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, v } return nil } + +// Port range '80' and '80-80' is equivalent. +// `old` is read from the server and always has the full range format (e.g. '80-80', '1024-2048'). +// `new` can be either a single port or a port range. +func PortRangeDiffSuppress(k, old, new string, d *schema.ResourceData) bool { + return old == new+"-"+new +} + +// Suppresses diff for IPv4 and IPv6 different formats. +// It also suppresses diffs if an IP is changing to a reference. +func InternalIpDiffSuppress(_, old, new string, _ *schema.ResourceData) bool { + addr_equality := false + netmask_equality := false + + addr_netmask_old := strings.Split(old, "/") + addr_netmask_new := strings.Split(new, "/") + + // Check if old or new are IPs (with or without netmask) + var addr_old net.IP + if net.ParseIP(addr_netmask_old[0]) == nil { + addr_old = net.ParseIP(old) + } else { + addr_old = net.ParseIP(addr_netmask_old[0]) + } + var addr_new net.IP + if net.ParseIP(addr_netmask_new[0]) == nil { + addr_new = net.ParseIP(new) + } else { + addr_new = net.ParseIP(addr_netmask_new[0]) + } + + if addr_old != nil { + if addr_new == nil { + // old is an IP and new is a reference + addr_equality = true + } else { + // old and new are IP addresses + addr_equality = bytes.Equal(addr_old, addr_new) + } + } + + // If old and new both have a netmask compare them, otherwise suppress + // This is not technically correct but prevents the permadiff described in https://github.com/hashicorp/terraform-provider-google/issues/16400 + if (len(addr_netmask_old)) == 2 && (len(addr_netmask_new) == 2) { + netmask_equality = addr_netmask_old[1] == addr_netmask_new[1] + } else { + netmask_equality = true + } + + return addr_equality && netmask_equality +} \ No newline at end of file diff --git a/mmv1/templates/terraform/constants/compute_instance.go.erb b/mmv1/templates/terraform/constants/compute_instance.go.erb new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/mmv1/templates/terraform/constants/compute_managed_ssl_certificate.go.erb b/mmv1/templates/terraform/constants/compute_managed_ssl_certificate.go.erb new file mode 100644 index 000000000000..e80b7c1f9075 --- /dev/null +++ b/mmv1/templates/terraform/constants/compute_managed_ssl_certificate.go.erb @@ -0,0 +1,7 @@ +// For managed SSL certs, if new is an absolute FQDN (trailing '.') but old isn't, treat them as equals. +func AbsoluteDomainSuppress(k, old, new string, _ *schema.ResourceData) bool { + if strings.HasPrefix(k, "managed.0.domains.") { + return old == strings.TrimRight(new, ".") || new == strings.TrimRight(old, ".") + } + return false +} \ No newline at end of file diff --git a/mmv1/templates/terraform/constants/compute_network_endpoint_group.go.erb b/mmv1/templates/terraform/constants/compute_network_endpoint_group.go.erb new file mode 100644 index 000000000000..ad1828dfe457 --- /dev/null +++ b/mmv1/templates/terraform/constants/compute_network_endpoint_group.go.erb @@ -0,0 +1,9 @@ +// Use this method when subnet is optioanl and auto_create_subnetworks = true +// API sometimes choose a subnet so the diff needs to be ignored +func compareOptionalSubnet(_, old, new string, _ *schema.ResourceData) bool { + if tpgresource.IsEmptyValue(reflect.ValueOf(new)) { + return true + } + // otherwise compare as self links + return tpgresource.CompareSelfLinkOrResourceName("", old, new, nil) +} \ No newline at end of file diff --git a/mmv1/templates/terraform/constants/compute_route.go.erb b/mmv1/templates/terraform/constants/compute_route.go.erb new file mode 100644 index 000000000000..915d204a137f --- /dev/null +++ b/mmv1/templates/terraform/constants/compute_route.go.erb @@ -0,0 +1,12 @@ +// Use this method when the field accepts either an IP address or a +// self_link referencing a resource (such as google_compute_route's +// next_hop_ilb) +func CompareIpAddressOrSelfLinkOrResourceName(_, old, new string, _ *schema.ResourceData) bool { + // if we can parse `new` as an IP address, then compare as strings + if net.ParseIP(new) != nil { + return new == old + } + + // otherwise compare as self links + return tpgresource.CompareSelfLinkOrResourceName("", old, new, nil) +} \ No newline at end of file diff --git a/mmv1/templates/terraform/constants/dataproc_cluster.go.erb b/mmv1/templates/terraform/constants/dataproc_cluster.go.erb new file mode 100644 index 000000000000..2761e8c4a5e2 --- /dev/null +++ b/mmv1/templates/terraform/constants/dataproc_cluster.go.erb @@ -0,0 +1,10 @@ +// Suppress diffs for values that are equivalent except for their use of the words "location" +// compared to "region" or "zone" +func locationDiffSuppress(k, old, new string, d *schema.ResourceData) bool { + return locationDiffSuppressHelper(old, new) || locationDiffSuppressHelper(new, old) +} + +func locationDiffSuppressHelper(a, b string) bool { + return strings.Replace(a, "/locations/", "/regions/", 1) == b || + strings.Replace(a, "/locations/", "/zones/", 1) == b +} \ No newline at end of file diff --git a/mmv1/templates/terraform/constants/disk.erb b/mmv1/templates/terraform/constants/disk.erb index 52cc7aecd070..409d59f3fb99 100644 --- a/mmv1/templates/terraform/constants/disk.erb +++ b/mmv1/templates/terraform/constants/disk.erb @@ -17,6 +17,13 @@ func hyperDiskIopsUpdateDiffSupress(_ context.Context, d *schema.ResourceDiff, m } <% end -%> +<% unless version == "ga" -%> +// Suppress all diffs, used for Disk.Interface which is a nonfunctional field +func AlwaysDiffSuppress(_, _, _ string, _ *schema.ResourceData) bool { + return true +} +<% end -%> + // diffsupress for beta and to check change in source_disk attribute func sourceDiskDiffSupress(_, old, new string, _ *schema.ResourceData) bool { s1 := strings.TrimPrefix(old, "https://www.googleapis.com/compute/beta") diff --git a/mmv1/templates/terraform/constants/go/compute_certificate.go.tmpl b/mmv1/templates/terraform/constants/go/compute_certificate.go.tmpl new file mode 100644 index 000000000000..f5f6aae3c09d --- /dev/null +++ b/mmv1/templates/terraform/constants/go/compute_certificate.go.tmpl @@ -0,0 +1,5 @@ +// sha256DiffSuppress +// if old is the hex-encoded sha256 sum of new, treat them as equal +func sha256DiffSuppress(_, old, new string, _ *schema.ResourceData) bool { + return hex.EncodeToString(sha256.New().Sum([]byte(old))) == new +} \ No newline at end of file diff --git a/mmv1/templates/terraform/constants/go/compute_forwarding_rule.go.tmpl b/mmv1/templates/terraform/constants/go/compute_forwarding_rule.go.tmpl index 92afd4d61d0a..19580c9b79b3 100644 --- a/mmv1/templates/terraform/constants/go/compute_forwarding_rule.go.tmpl +++ b/mmv1/templates/terraform/constants/go/compute_forwarding_rule.go.tmpl @@ -14,3 +14,54 @@ func forwardingRuleCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, v } return nil } + +// Port range '80' and '80-80' is equivalent. +// `old` is read from the server and always has the full range format (e.g. '80-80', '1024-2048'). +// `new` can be either a single port or a port range. +func PortRangeDiffSuppress(k, old, new string, d *schema.ResourceData) bool { + return old == new+"-"+new +} + +// Suppresses diff for IPv4 and IPv6 different formats. +// It also suppresses diffs if an IP is changing to a reference. +func InternalIpDiffSuppress(_, old, new string, _ *schema.ResourceData) bool { + addr_equality := false + netmask_equality := false + + addr_netmask_old := strings.Split(old, "/") + addr_netmask_new := strings.Split(new, "/") + + // Check if old or new are IPs (with or without netmask) + var addr_old net.IP + if net.ParseIP(addr_netmask_old[0]) == nil { + addr_old = net.ParseIP(old) + } else { + addr_old = net.ParseIP(addr_netmask_old[0]) + } + var addr_new net.IP + if net.ParseIP(addr_netmask_new[0]) == nil { + addr_new = net.ParseIP(new) + } else { + addr_new = net.ParseIP(addr_netmask_new[0]) + } + + if addr_old != nil { + if addr_new == nil { + // old is an IP and new is a reference + addr_equality = true + } else { + // old and new are IP addresses + addr_equality = bytes.Equal(addr_old, addr_new) + } + } + + // If old and new both have a netmask compare them, otherwise suppress + // This is not technically correct but prevents the permadiff described in https://github.com/hashicorp/terraform-provider-google/issues/16400 + if (len(addr_netmask_old)) == 2 && (len(addr_netmask_new) == 2) { + netmask_equality = addr_netmask_old[1] == addr_netmask_new[1] + } else { + netmask_equality = true + } + + return addr_equality && netmask_equality +} \ No newline at end of file diff --git a/mmv1/templates/terraform/constants/go/compute_instance.go.tmpl b/mmv1/templates/terraform/constants/go/compute_instance.go.tmpl new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/mmv1/templates/terraform/constants/go/compute_managed_ssl_certificate.go.tmpl b/mmv1/templates/terraform/constants/go/compute_managed_ssl_certificate.go.tmpl new file mode 100644 index 000000000000..e80b7c1f9075 --- /dev/null +++ b/mmv1/templates/terraform/constants/go/compute_managed_ssl_certificate.go.tmpl @@ -0,0 +1,7 @@ +// For managed SSL certs, if new is an absolute FQDN (trailing '.') but old isn't, treat them as equals. +func AbsoluteDomainSuppress(k, old, new string, _ *schema.ResourceData) bool { + if strings.HasPrefix(k, "managed.0.domains.") { + return old == strings.TrimRight(new, ".") || new == strings.TrimRight(old, ".") + } + return false +} \ No newline at end of file diff --git a/mmv1/templates/terraform/constants/go/compute_network_endpoint_group.go.tmpl b/mmv1/templates/terraform/constants/go/compute_network_endpoint_group.go.tmpl new file mode 100644 index 000000000000..ad1828dfe457 --- /dev/null +++ b/mmv1/templates/terraform/constants/go/compute_network_endpoint_group.go.tmpl @@ -0,0 +1,9 @@ +// Use this method when subnet is optioanl and auto_create_subnetworks = true +// API sometimes choose a subnet so the diff needs to be ignored +func compareOptionalSubnet(_, old, new string, _ *schema.ResourceData) bool { + if tpgresource.IsEmptyValue(reflect.ValueOf(new)) { + return true + } + // otherwise compare as self links + return tpgresource.CompareSelfLinkOrResourceName("", old, new, nil) +} \ No newline at end of file diff --git a/mmv1/templates/terraform/constants/go/compute_route.go.tmpl b/mmv1/templates/terraform/constants/go/compute_route.go.tmpl new file mode 100644 index 000000000000..915d204a137f --- /dev/null +++ b/mmv1/templates/terraform/constants/go/compute_route.go.tmpl @@ -0,0 +1,12 @@ +// Use this method when the field accepts either an IP address or a +// self_link referencing a resource (such as google_compute_route's +// next_hop_ilb) +func CompareIpAddressOrSelfLinkOrResourceName(_, old, new string, _ *schema.ResourceData) bool { + // if we can parse `new` as an IP address, then compare as strings + if net.ParseIP(new) != nil { + return new == old + } + + // otherwise compare as self links + return tpgresource.CompareSelfLinkOrResourceName("", old, new, nil) +} \ No newline at end of file diff --git a/mmv1/templates/terraform/constants/go/dataproc_cluster.go.tmpl b/mmv1/templates/terraform/constants/go/dataproc_cluster.go.tmpl new file mode 100644 index 000000000000..2761e8c4a5e2 --- /dev/null +++ b/mmv1/templates/terraform/constants/go/dataproc_cluster.go.tmpl @@ -0,0 +1,10 @@ +// Suppress diffs for values that are equivalent except for their use of the words "location" +// compared to "region" or "zone" +func locationDiffSuppress(k, old, new string, d *schema.ResourceData) bool { + return locationDiffSuppressHelper(old, new) || locationDiffSuppressHelper(new, old) +} + +func locationDiffSuppressHelper(a, b string) bool { + return strings.Replace(a, "/locations/", "/regions/", 1) == b || + strings.Replace(a, "/locations/", "/zones/", 1) == b +} \ No newline at end of file diff --git a/mmv1/templates/terraform/constants/go/disk.tmpl b/mmv1/templates/terraform/constants/go/disk.tmpl index 0c554b930b78..a3947c725250 100644 --- a/mmv1/templates/terraform/constants/go/disk.tmpl +++ b/mmv1/templates/terraform/constants/go/disk.tmpl @@ -17,6 +17,13 @@ func hyperDiskIopsUpdateDiffSupress(_ context.Context, d *schema.ResourceDiff, m } {{- end }} +{{ if ne $.TargetVersionName `ga` -}} +// Suppress all diffs, used for Disk.Interface which is a nonfunctional field +func AlwaysDiffSuppress(_, _, _ string, _ *schema.ResourceData) bool { + return true +} +{{- end }} + // diffsupress for beta and to check change in source_disk attribute func sourceDiskDiffSupress(_, old, new string, _ *schema.ResourceData) bool { s1 := strings.TrimPrefix(old, "https://www.googleapis.com/compute/beta") diff --git a/mmv1/templates/terraform/constants/go/pubsub_subscription.go.tmpl b/mmv1/templates/terraform/constants/go/pubsub_subscription.go.tmpl new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/mmv1/templates/terraform/constants/go/router_nat_validate_action_active_range.go.tmpl b/mmv1/templates/terraform/constants/go/router_nat_validate_action_active_range.go.tmpl index b84559fd5420..b1dbba3c8a53 100644 --- a/mmv1/templates/terraform/constants/go/router_nat_validate_action_active_range.go.tmpl +++ b/mmv1/templates/terraform/constants/go/router_nat_validate_action_active_range.go.tmpl @@ -1,5 +1,4 @@ -{{- if ne $.TargetVersionName "ga" }} -// validates if the field action.source_nat_active_ranges is filled when the type is PRIVATE. +{{- if ne $.TargetVersionName "ga" -}} natType := d.Get("type").(string) if natType == "PRIVATE" { rules := d.Get("rules").(*schema.Set) diff --git a/mmv1/templates/terraform/constants/go/scheduler.tmpl b/mmv1/templates/terraform/constants/go/scheduler.tmpl index e509161d7110..0764f1dfd8e4 100644 --- a/mmv1/templates/terraform/constants/go/scheduler.tmpl +++ b/mmv1/templates/terraform/constants/go/scheduler.tmpl @@ -16,7 +16,19 @@ func validateAuthHeaders(_ context.Context, diff *schema.ResourceDiff, v interfa return nil } +// Suppress diffs in below cases +// "https://hello-rehvs75zla-uc.a.run.app/" -> "https://hello-rehvs75zla-uc.a.run.app" +// "https://hello-rehvs75zla-uc.a.run.app" -> "https://hello-rehvs75zla-uc.a.run.app/" +func LastSlashDiffSuppress(_, old, new string, _ *schema.ResourceData) bool { + if last := len(new) - 1; last >= 0 && new[last] == '/' { + new = new[:last] + } + if last := len(old) - 1; last >= 0 && old[last] == '/' { + old = old[:last] + } + return new == old +} func authHeaderDiffSuppress(k, old, new string, d *schema.ResourceData) bool { // If generating an `oauth_token` and `scope` is not provided in the configuration, diff --git a/mmv1/templates/terraform/constants/go/subscription.go.tmpl b/mmv1/templates/terraform/constants/go/subscription.go.tmpl index 1fb3cf747795..336517ee07a7 100644 --- a/mmv1/templates/terraform/constants/go/subscription.go.tmpl +++ b/mmv1/templates/terraform/constants/go/subscription.go.tmpl @@ -22,3 +22,25 @@ func comparePubsubSubscriptionExpirationPolicy(_, old, new string, _ *schema.Res } return trimmedNew == trimmedOld } + +func IgnoreMissingKeyInMap(key string) schema.SchemaDiffSuppressFunc { + return func(k, old, new string, d *schema.ResourceData) bool { + log.Printf("[DEBUG] - suppressing diff %q with old %q, new %q", k, old, new) + if strings.HasSuffix(k, ".%") { + oldNum, err := strconv.Atoi(old) + if err != nil { + log.Printf("[ERROR] could not parse %q as number, no longer attempting diff suppress", old) + return false + } + newNum, err := strconv.Atoi(new) + if err != nil { + log.Printf("[ERROR] could not parse %q as number, no longer attempting diff suppress", new) + return false + } + return oldNum+1 == newNum + } else if strings.HasSuffix(k, "." + key) { + return old == "" + } + return false + } +} \ No newline at end of file diff --git a/mmv1/templates/terraform/constants/pubsub_subscription.go.erb b/mmv1/templates/terraform/constants/pubsub_subscription.go.erb new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/mmv1/templates/terraform/constants/router_nat_validate_action_active_range.go.erb b/mmv1/templates/terraform/constants/router_nat_validate_action_active_range.go.erb index 635e90f85472..0437b25d316f 100644 --- a/mmv1/templates/terraform/constants/router_nat_validate_action_active_range.go.erb +++ b/mmv1/templates/terraform/constants/router_nat_validate_action_active_range.go.erb @@ -1,5 +1,4 @@ <% unless version == 'ga' -%> -// validates if the field action.source_nat_active_ranges is filled when the type is PRIVATE. natType := d.Get("type").(string) if natType == "PRIVATE" { rules := d.Get("rules").(*schema.Set) diff --git a/mmv1/templates/terraform/constants/scheduler.erb b/mmv1/templates/terraform/constants/scheduler.erb index e509161d7110..0764f1dfd8e4 100644 --- a/mmv1/templates/terraform/constants/scheduler.erb +++ b/mmv1/templates/terraform/constants/scheduler.erb @@ -16,7 +16,19 @@ func validateAuthHeaders(_ context.Context, diff *schema.ResourceDiff, v interfa return nil } +// Suppress diffs in below cases +// "https://hello-rehvs75zla-uc.a.run.app/" -> "https://hello-rehvs75zla-uc.a.run.app" +// "https://hello-rehvs75zla-uc.a.run.app" -> "https://hello-rehvs75zla-uc.a.run.app/" +func LastSlashDiffSuppress(_, old, new string, _ *schema.ResourceData) bool { + if last := len(new) - 1; last >= 0 && new[last] == '/' { + new = new[:last] + } + if last := len(old) - 1; last >= 0 && old[last] == '/' { + old = old[:last] + } + return new == old +} func authHeaderDiffSuppress(k, old, new string, d *schema.ResourceData) bool { // If generating an `oauth_token` and `scope` is not provided in the configuration, diff --git a/mmv1/templates/terraform/constants/subscription.go.erb b/mmv1/templates/terraform/constants/subscription.go.erb index 187ca2e22d69..4b40b1748bd7 100644 --- a/mmv1/templates/terraform/constants/subscription.go.erb +++ b/mmv1/templates/terraform/constants/subscription.go.erb @@ -24,3 +24,25 @@ func comparePubsubSubscriptionExpirationPolicy(_, old, new string, _ *schema.Res } return trimmedNew == trimmedOld } + +func IgnoreMissingKeyInMap(key string) schema.SchemaDiffSuppressFunc { + return func(k, old, new string, d *schema.ResourceData) bool { + log.Printf("[DEBUG] - suppressing diff %q with old %q, new %q", k, old, new) + if strings.HasSuffix(k, ".%") { + oldNum, err := strconv.Atoi(old) + if err != nil { + log.Printf("[ERROR] could not parse %q as number, no longer attempting diff suppress", old) + return false + } + newNum, err := strconv.Atoi(new) + if err != nil { + log.Printf("[ERROR] could not parse %q as number, no longer attempting diff suppress", new) + return false + } + return oldNum+1 == newNum + } else if strings.HasSuffix(k, "." + key) { + return old == "" + } + return false + } +} \ No newline at end of file diff --git a/mmv1/templates/terraform/custom_flatten/go/default_if_empty.tmpl b/mmv1/templates/terraform/custom_flatten/go/default_if_empty.tmpl index d381d195cfe9..f68ee25c1e23 100644 --- a/mmv1/templates/terraform/custom_flatten/go/default_if_empty.tmpl +++ b/mmv1/templates/terraform/custom_flatten/go/default_if_empty.tmpl @@ -9,12 +9,12 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -*/}} +*/ -}} func flatten{{$.GetPrefix}}{{$.TitlelizeProperty}}(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { if v == nil || tpgresource.IsEmptyValue(reflect.ValueOf(v)) { return {{$.GoLiteral $.DefaultValue}} } -{{ if $.IsA "Integer" }} +{{ if $.IsA "Integer" -}} // Handles the string fixed64 format if strVal, ok := v.(string); ok { if intVal, err := tpgresource.StringToFixed64(strVal); err == nil { diff --git a/mmv1/templates/terraform/custom_import/apigee_endpoint_attachment.go.erb b/mmv1/templates/terraform/custom_import/apigee_endpoint_attachment.go.erb index f9cc71e0283e..9bfc3aeab667 100644 --- a/mmv1/templates/terraform/custom_import/apigee_endpoint_attachment.go.erb +++ b/mmv1/templates/terraform/custom_import/apigee_endpoint_attachment.go.erb @@ -2,32 +2,31 @@ config := meta.(*transport_tpg.Config) // current import_formats cannot import fields with forward slashes in their value if err := tpgresource.ParseImportId([]string{"(?P.+)"}, d, config); err != nil { - return nil, err + return nil, err } nameParts := strings.Split(d.Get("name").(string), "/") if len(nameParts) == 4 { - // `organizations/{{org_name}}/endpointAttachment/{{endpoint_attachment_id}}` - orgId := fmt.Sprintf("organizations/%s", nameParts[1]) - if err := d.Set("org_id", orgId); err != nil { - return nil, fmt.Errorf("Error setting org_id: %s", err) - } - if err := d.Set("endpoint_attachment_id", nameParts[3]); err != nil { - return nil, fmt.Errorf("Error setting endpoint_attachment_id: %s", err) - } + // `organizations/{{org_name}}/endpointAttachment/{{endpoint_attachment_id}}` + orgId := fmt.Sprintf("organizations/%s", nameParts[1]) + if err := d.Set("org_id", orgId); err != nil { + return nil, fmt.Errorf("Error setting org_id: %s", err) + } + if err := d.Set("endpoint_attachment_id", nameParts[3]); err != nil { + return nil, fmt.Errorf("Error setting endpoint_attachment_id: %s", err) + } } else { - return nil, fmt.Errorf( - "Saw %s when the name is expected to have shape %s", - d.Get("name"), - "organizations/{{org_name}}/environments/{{name}}") + return nil, fmt.Errorf( + "Saw %s when the name is expected to have shape %s", + d.Get("name"), + "organizations/{{org_name}}/environments/{{name}}") } // Replace import id for the resource id id, err := tpgresource.ReplaceVars(d, config, "{{name}}") if err != nil { - return nil, fmt.Errorf("Error constructing id: %s", err) + return nil, fmt.Errorf("Error constructing id: %s", err) } d.SetId(id) return []*schema.ResourceData{d}, nil - diff --git a/mmv1/templates/terraform/custom_import/go/apigee_endpoint_attachment.go.tmpl b/mmv1/templates/terraform/custom_import/go/apigee_endpoint_attachment.go.tmpl index 2e0afd971f9d..6685ea3bd72b 100644 --- a/mmv1/templates/terraform/custom_import/go/apigee_endpoint_attachment.go.tmpl +++ b/mmv1/templates/terraform/custom_import/go/apigee_endpoint_attachment.go.tmpl @@ -2,32 +2,31 @@ config := meta.(*transport_tpg.Config) // current import_formats cannot import fields with forward slashes in their value if err := tpgresource.ParseImportId([]string{"(?P.+)"}, d, config); err != nil { - return nil, err + return nil, err } nameParts := strings.Split(d.Get("name").(string), "/") if len(nameParts) == 4 { - // `organizations/{{"{{"}}org_name{{"}}"}}/endpointAttachment/{{"{{"}}endpoint_attachment_id{{"}}"}}` - orgId := fmt.Sprintf("organizations/%s", nameParts[1]) - if err := d.Set("org_id", orgId); err != nil { - return nil, fmt.Errorf("Error setting org_id: %s", err) - } - if err := d.Set("endpoint_attachment_id", nameParts[3]); err != nil { - return nil, fmt.Errorf("Error setting endpoint_attachment_id: %s", err) - } + // `organizations/{{"{{"}}org_name{{"}}"}}/endpointAttachment/{{"{{"}}endpoint_attachment_id{{"}}"}}` + orgId := fmt.Sprintf("organizations/%s", nameParts[1]) + if err := d.Set("org_id", orgId); err != nil { + return nil, fmt.Errorf("Error setting org_id: %s", err) + } + if err := d.Set("endpoint_attachment_id", nameParts[3]); err != nil { + return nil, fmt.Errorf("Error setting endpoint_attachment_id: %s", err) + } } else { - return nil, fmt.Errorf( - "Saw %s when the name is expected to have shape %s", - d.Get("name"), - "organizations/{{"{{"}}org_name{{"}}"}}/environments/{{"{{"}}name{{"}}"}}") + return nil, fmt.Errorf( + "Saw %s when the name is expected to have shape %s", + d.Get("name"), + "organizations/{{"{{"}}org_name{{"}}"}}/environments/{{"{{"}}name{{"}}"}}") } // Replace import id for the resource id id, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}name{{"}}"}}") if err != nil { - return nil, fmt.Errorf("Error constructing id: %s", err) + return nil, fmt.Errorf("Error constructing id: %s", err) } d.SetId(id) return []*schema.ResourceData{d}, nil - diff --git a/mmv1/templates/terraform/custom_import/go/scc_v2_source_self_link_as_name_set_organization.go.tmpl b/mmv1/templates/terraform/custom_import/go/scc_v2_source_self_link_as_name_set_organization.go.tmpl new file mode 100644 index 000000000000..71dee0f9828a --- /dev/null +++ b/mmv1/templates/terraform/custom_import/go/scc_v2_source_self_link_as_name_set_organization.go.tmpl @@ -0,0 +1,26 @@ +config := meta.(*transport_tpg.Config) + +// current import_formats can't import fields with forward slashes in their value +if err := tpgresource.ParseImportId([]string{"(?P.+)"}, d, config); err != nil { + return nil, err +} + +stringParts := strings.Split(d.Get("name").(string), "/") +if len(stringParts) != 6 { + return nil, fmt.Errorf( + "Saw %s when the name is expected to have shape %s", + d.Get("name"), + "organizations/{{"{{"}}organization{{"}}"}}/locations/{{"{{"}}location{{"}}"}}/notificationConfigs/{{"{{"}}config_id{{"}}"}}", + ) +} + +if err := d.Set("organization", stringParts[1]); err != nil { + return nil, fmt.Errorf("Error setting organization: %s", err) +} +if err := d.Set("location", stringParts[3]); err != nil { + return nil, fmt.Errorf("Error setting location: %s", err) +} +if err := d.Set("config_id", stringParts[5]); err != nil { + return nil, fmt.Errorf("Error setting config_id: %s", err) +} +return []*schema.ResourceData{d}, nil diff --git a/mmv1/templates/terraform/custom_import/scc_v2_source_self_link_as_name_set_organization.go.erb b/mmv1/templates/terraform/custom_import/scc_v2_source_self_link_as_name_set_organization.go.erb new file mode 100644 index 000000000000..b74ffa3fd24d --- /dev/null +++ b/mmv1/templates/terraform/custom_import/scc_v2_source_self_link_as_name_set_organization.go.erb @@ -0,0 +1,26 @@ +config := meta.(*transport_tpg.Config) + +// current import_formats can't import fields with forward slashes in their value +if err := tpgresource.ParseImportId([]string{"(?P.+)"}, d, config); err != nil { + return nil, err +} + +stringParts := strings.Split(d.Get("name").(string), "/") +if len(stringParts) != 6 { + return nil, fmt.Errorf( + "Saw %s when the name is expected to have shape %s", + d.Get("name"), + "organizations/{{organization}}/locations/{{location}}/notificationConfigs/{{config_id}}", + ) +} + +if err := d.Set("organization", stringParts[1]); err != nil { + return nil, fmt.Errorf("Error setting organization: %s", err) +} +if err := d.Set("location", stringParts[3]); err != nil { + return nil, fmt.Errorf("Error setting location: %s", err) +} +if err := d.Set("config_id", stringParts[5]); err != nil { + return nil, fmt.Errorf("Error setting config_id: %s", err) +} +return []*schema.ResourceData{d}, nil diff --git a/mmv1/templates/terraform/encoders/go/cloudfunctions2_runtime_update_policy.go.tmpl b/mmv1/templates/terraform/encoders/go/cloudfunctions2_runtime_update_policy.go.tmpl new file mode 100644 index 000000000000..db4ef3e273ee --- /dev/null +++ b/mmv1/templates/terraform/encoders/go/cloudfunctions2_runtime_update_policy.go.tmpl @@ -0,0 +1,15 @@ +if obj == nil || obj["buildConfig"] == nil { + return obj, nil +} + +build_config := obj["buildConfig"].(map[string]interface{}) + +// Automatic Update policy is the default from API, unset it if the data +// contains the on-deploy policy. +if build_config["onDeployUpdatePolicy"] != nil { + delete(build_config, "automaticUpdatePolicy") +} + +obj["buildConfig"] = build_config + +return obj, nil diff --git a/mmv1/templates/terraform/examples/base_configs/iam_test_file.go.tmpl b/mmv1/templates/terraform/examples/base_configs/iam_test_file.go.tmpl index 2af462ec3716..64cbf701b89f 100644 --- a/mmv1/templates/terraform/examples/base_configs/iam_test_file.go.tmpl +++ b/mmv1/templates/terraform/examples/base_configs/iam_test_file.go.tmpl @@ -55,7 +55,7 @@ func TestAcc{{ $.ResourceName }}IamBindingGenerated(t *testing.T) { {{- if not $.IamPolicy.SkipImportTest }} { ResourceName: "{{ $.IamTerraformName }}_binding.foo", - ImportStateId: fmt.Sprintf("{{ $.IamResourceUriFormat }} {{ $.IamPolicy.AllowedIamRole }}", {{ $.IamImportQualifiersForTest }}, {{ $example.PrimaryResourceName }}), + ImportStateId: fmt.Sprintf("{{ $.IamResourceUriFormat }} {{ $.IamPolicy.AllowedIamRole }}", {{ if $.IamImportQualifiersForTest }}{{ $.IamImportQualifiersForTest }}, {{ end }}{{ $example.PrimaryResourceName }}), ImportState: true, ImportStateVerify: true, }, @@ -67,7 +67,7 @@ func TestAcc{{ $.ResourceName }}IamBindingGenerated(t *testing.T) { {{- if not $.IamPolicy.SkipImportTest }} { ResourceName: "{{ $.IamTerraformName }}_binding.foo", - ImportStateId: fmt.Sprintf("{{ $.IamResourceUriFormat }} {{ $.IamPolicy.AllowedIamRole }}", {{ $.IamImportQualifiersForTest }}, {{ $example.PrimaryResourceName }}), + ImportStateId: fmt.Sprintf("{{ $.IamResourceUriFormat }} {{ $.IamPolicy.AllowedIamRole }}", {{ if $.IamImportQualifiersForTest }}{{ $.IamImportQualifiersForTest }}, {{ end }}{{ $example.PrimaryResourceName }}), ImportState: true, ImportStateVerify: true, }, @@ -102,7 +102,7 @@ func TestAcc{{ $.ResourceName }}IamMemberGenerated(t *testing.T) { {{- if not $.IamPolicy.SkipImportTest }} { ResourceName: "{{ $.IamTerraformName }}_member.foo", - ImportStateId: fmt.Sprintf("{{ $.IamResourceUriFormat }} {{ $.IamPolicy.AllowedIamRole }} user:admin@hashicorptest.com", {{ $.IamImportQualifiersForTest }}, {{ $example.PrimaryResourceName }}), + ImportStateId: fmt.Sprintf("{{ $.IamResourceUriFormat }} {{ $.IamPolicy.AllowedIamRole }} user:admin@hashicorptest.com", {{ if $.IamImportQualifiersForTest }}{{ $.IamImportQualifiersForTest }}, {{ end }}{{ $example.PrimaryResourceName }}), ImportState: true, ImportStateVerify: true, }, @@ -145,7 +145,7 @@ func TestAcc{{ $.ResourceName }}IamPolicyGenerated(t *testing.T) { {{- if not $.IamPolicy.SkipImportTest }} { ResourceName: "{{ $.IamTerraformName }}_policy.foo", - ImportStateId: fmt.Sprintf("{{ $.IamResourceUriFormat }}", {{ $.IamImportQualifiersForTest }}, {{ $example.PrimaryResourceName }}), + ImportStateId: fmt.Sprintf("{{ $.IamResourceUriFormat }}", {{ if $.IamImportQualifiersForTest }}{{ $.IamImportQualifiersForTest }}, {{ end }}{{ $example.PrimaryResourceName }}), ImportState: true, ImportStateVerify: true, }, @@ -156,7 +156,7 @@ func TestAcc{{ $.ResourceName }}IamPolicyGenerated(t *testing.T) { {{- if not $.IamPolicy.SkipImportTest }} { ResourceName: "{{ $.IamTerraformName }}_policy.foo", - ImportStateId: fmt.Sprintf("{{ $.IamResourceUriFormat }}", {{ $.IamImportQualifiersForTest }}, {{ $example.PrimaryResourceName }}), + ImportStateId: fmt.Sprintf("{{ $.IamResourceUriFormat }}", {{ if $.IamImportQualifiersForTest }}{{ $.IamImportQualifiersForTest }}, {{ end }}{{ $example.PrimaryResourceName }}), ImportState: true, ImportStateVerify: true, }, diff --git a/mmv1/templates/terraform/examples/bigquery_reservation_assignment_basic.tf.erb b/mmv1/templates/terraform/examples/bigquery_reservation_assignment_basic.tf.erb new file mode 100644 index 000000000000..0aac775c6040 --- /dev/null +++ b/mmv1/templates/terraform/examples/bigquery_reservation_assignment_basic.tf.erb @@ -0,0 +1,13 @@ +resource "google_bigquery_reservation" "basic" { + name = "<%= ctx[:vars]['reservation_name'] %>" + project = "<%= ctx[:test_env_vars]['project'] %>" + location = "us-central1" + slot_capacity = 0 + ignore_idle_slots = false +} + +resource "google_bigquery_reservation_assignment" "<%= ctx[:primary_resource_id] %>" { + assignee = "projects/<%= ctx[:test_env_vars]['project'] %>" + job_type = "PIPELINE" + reservation = google_bigquery_reservation.basic.id +} \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/bigquery_reservation_assignment_full.tf.erb b/mmv1/templates/terraform/examples/bigquery_reservation_assignment_full.tf.erb new file mode 100644 index 000000000000..7944dc71ea68 --- /dev/null +++ b/mmv1/templates/terraform/examples/bigquery_reservation_assignment_full.tf.erb @@ -0,0 +1,14 @@ +resource "google_bigquery_reservation" "basic" { + name = "<%= ctx[:vars]['reservation_name'] %>" + project = "<%= ctx[:test_env_vars]['project'] %>" + location = "us-central1" + slot_capacity = 0 + ignore_idle_slots = false +} + +resource "google_bigquery_reservation_assignment" "<%= ctx[:primary_resource_id] %>" { + assignee = "projects/<%= ctx[:test_env_vars]['project'] %>" + job_type = "QUERY" + location = "us-central1" + reservation = google_bigquery_reservation.basic.id +} \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/certificate_manager_trust_config_allowlisted_certificates.tf.erb b/mmv1/templates/terraform/examples/certificate_manager_trust_config_allowlisted_certificates.tf.erb new file mode 100644 index 000000000000..5b9890dcf018 --- /dev/null +++ b/mmv1/templates/terraform/examples/certificate_manager_trust_config_allowlisted_certificates.tf.erb @@ -0,0 +1,16 @@ +resource "google_certificate_manager_trust_config" "<%= ctx[:primary_resource_id] %>" { + name = "<%= ctx[:vars]["trust_config_name"] %>" + description = "A sample trust config resource with allowlisted certificates" + location = "global" + + allowlisted_certificates { + pem_certificate = file("test-fixtures/cert.pem") + } + allowlisted_certificates { + pem_certificate = file("test-fixtures/cert2.pem") + } + + labels = { + foo = "bar" + } +} \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/compute_health_check_http_source_regions.tf.erb b/mmv1/templates/terraform/examples/compute_health_check_http_source_regions.tf.erb new file mode 100644 index 000000000000..84e955e6d6dc --- /dev/null +++ b/mmv1/templates/terraform/examples/compute_health_check_http_source_regions.tf.erb @@ -0,0 +1,12 @@ +resource "google_compute_health_check" "<%= ctx[:primary_resource_id] %>" { + provider = "google-beta" + name = "<%= ctx[:vars]['health_check_name'] %>" + check_interval_sec = 30 + + http_health_check { + port = 80 + port_specification = "USE_FIXED_PORT" + } + + source_regions = ["us-west1", "us-central1", "us-east5"] +} diff --git a/mmv1/templates/terraform/examples/compute_health_check_https_source_regions.tf.erb b/mmv1/templates/terraform/examples/compute_health_check_https_source_regions.tf.erb new file mode 100644 index 000000000000..44747f0ba7b3 --- /dev/null +++ b/mmv1/templates/terraform/examples/compute_health_check_https_source_regions.tf.erb @@ -0,0 +1,12 @@ +resource "google_compute_health_check" "<%= ctx[:primary_resource_id] %>" { + provider = "google-beta" + name = "<%= ctx[:vars]['health_check_name'] %>" + check_interval_sec = 30 + + https_health_check { + port = 80 + port_specification = "USE_FIXED_PORT" + } + + source_regions = ["us-west1", "us-central1", "us-east5"] +} \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/compute_health_check_tcp_source_regions.tf.erb b/mmv1/templates/terraform/examples/compute_health_check_tcp_source_regions.tf.erb new file mode 100644 index 000000000000..9f1da3a2e2e8 --- /dev/null +++ b/mmv1/templates/terraform/examples/compute_health_check_tcp_source_regions.tf.erb @@ -0,0 +1,12 @@ +resource "google_compute_health_check" "<%= ctx[:primary_resource_id] %>" { + provider = "google-beta" + name = "<%= ctx[:vars]['health_check_name'] %>" + check_interval_sec = 30 + + tcp_health_check { + port = 80 + port_specification = "USE_FIXED_PORT" + } + + source_regions = ["us-west1", "us-central1", "us-east5"] +} \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/dataplex_datascan_full_quality.tf.erb b/mmv1/templates/terraform/examples/dataplex_datascan_full_quality.tf.erb index a9088a138411..54a73f986c39 100644 --- a/mmv1/templates/terraform/examples/dataplex_datascan_full_quality.tf.erb +++ b/mmv1/templates/terraform/examples/dataplex_datascan_full_quality.tf.erb @@ -94,6 +94,13 @@ resource "google_dataplex_datascan" "<%= ctx[:primary_resource_id] %>" { sql_expression = "COUNT(*) > 0" } } + + rules { + dimension = "VALIDITY" + sql_assertion { + sql_statement = "select * from bigquery-public-data.austin_bikeshare.bikeshare_stations where station_id is null" + } + } } diff --git a/mmv1/templates/terraform/examples/go/bigquery_reservation_assignment_basic.tf.tmpl b/mmv1/templates/terraform/examples/go/bigquery_reservation_assignment_basic.tf.tmpl new file mode 100644 index 000000000000..5961e131f26f --- /dev/null +++ b/mmv1/templates/terraform/examples/go/bigquery_reservation_assignment_basic.tf.tmpl @@ -0,0 +1,13 @@ +resource "google_bigquery_reservation" "basic" { + name = "{{index $.Vars "reservation_name"}}" + project = "{{index $.TestEnvVars "project"}}" + location = "us-central1" + slot_capacity = 0 + ignore_idle_slots = false +} + +resource "google_bigquery_reservation_assignment" "{{$.PrimaryResourceId}}" { + assignee = "projects/{{index $.TestEnvVars "project"}}" + job_type = "PIPELINE" + reservation = google_bigquery_reservation.basic.id +} \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/go/bigquery_reservation_assignment_full.tf.tmpl b/mmv1/templates/terraform/examples/go/bigquery_reservation_assignment_full.tf.tmpl new file mode 100644 index 000000000000..c13722a098b4 --- /dev/null +++ b/mmv1/templates/terraform/examples/go/bigquery_reservation_assignment_full.tf.tmpl @@ -0,0 +1,14 @@ +resource "google_bigquery_reservation" "basic" { + name = "{{index $.Vars "reservation_name"}}" + project = "{{index $.TestEnvVars "project"}}" + location = "us-central1" + slot_capacity = 0 + ignore_idle_slots = false +} + +resource "google_bigquery_reservation_assignment" "{{$.PrimaryResourceId}}" { + assignee = "projects/{{index $.TestEnvVars "project"}}" + job_type = "QUERY" + location = "us-central1" + reservation = google_bigquery_reservation.basic.id +} \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/go/certificate_manager_trust_config_allowlisted_certificates.tf.tmpl b/mmv1/templates/terraform/examples/go/certificate_manager_trust_config_allowlisted_certificates.tf.tmpl new file mode 100644 index 000000000000..90dff10f6d3f --- /dev/null +++ b/mmv1/templates/terraform/examples/go/certificate_manager_trust_config_allowlisted_certificates.tf.tmpl @@ -0,0 +1,16 @@ +resource "google_certificate_manager_trust_config" "{{$.PrimaryResourceId}}" { + name = "{{index $.Vars "trust_config_name"}}" + description = "A sample trust config resource with allowlisted certificates" + location = "global" + + allowlisted_certificates { + pem_certificate = file("test-fixtures/cert.pem") + } + allowlisted_certificates { + pem_certificate = file("test-fixtures/cert2.pem") + } + + labels = { + foo = "bar" + } +} \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/go/cloudfunctions2_abiu.tf.tmpl b/mmv1/templates/terraform/examples/go/cloudfunctions2_abiu.tf.tmpl new file mode 100644 index 000000000000..b850585bc62c --- /dev/null +++ b/mmv1/templates/terraform/examples/go/cloudfunctions2_abiu.tf.tmpl @@ -0,0 +1,72 @@ +locals { + project = "{{index $.TestEnvVars "project"}}" # Google Cloud Platform Project ID +} + +resource "google_service_account" "account" { + provider = google-beta + account_id = "{{index $.Vars "service_account"}}" + display_name = "Test Service Account" +} + +resource "google_pubsub_topic" "topic" { + provider = google-beta + name = "{{index $.Vars "topic"}}" +} + +resource "google_storage_bucket" "bucket" { + provider = google-beta + name = "${local.project}-{{index $.Vars "bucket_name"}}" # Every bucket name must be globally unique + location = "US" + uniform_bucket_level_access = true +} + +resource "google_storage_bucket_object" "object" { + provider = google-beta + name = "function-source.zip" + bucket = google_storage_bucket.bucket.name + source = "{{index $.Vars "zip_path"}}" # Add path to the zipped function source code +} + +resource "google_cloudfunctions2_function" "{{$.PrimaryResourceId}}" { + provider = google-beta + name = "{{index $.Vars "function"}}" + location = "europe-west6" + description = "a new function" + + build_config { + runtime = "nodejs16" + entry_point = "helloPubSub" # Set the entry point + environment_variables = { + BUILD_CONFIG_TEST = "build_test" + } + source { + storage_source { + bucket = google_storage_bucket.bucket.name + object = google_storage_bucket_object.object.name + } + } + automatic_update_policy {} + } + + service_config { + max_instance_count = 3 + min_instance_count = 1 + available_memory = "4Gi" + timeout_seconds = 60 + max_instance_request_concurrency = 80 + available_cpu = "4" + environment_variables = { + SERVICE_CONFIG_TEST = "config_test" + } + ingress_settings = "ALLOW_INTERNAL_ONLY" + all_traffic_on_latest_revision = true + service_account_email = google_service_account.account.email + } + + event_trigger { + trigger_region = "us-central1" + event_type = "google.cloud.pubsub.topic.v1.messagePublished" + pubsub_topic = google_pubsub_topic.topic.id + retry_policy = "RETRY_POLICY_RETRY" + } +} diff --git a/mmv1/templates/terraform/examples/go/cloudfunctions2_abiu_on_deploy.tf.tmpl b/mmv1/templates/terraform/examples/go/cloudfunctions2_abiu_on_deploy.tf.tmpl new file mode 100644 index 000000000000..f90f726cfb65 --- /dev/null +++ b/mmv1/templates/terraform/examples/go/cloudfunctions2_abiu_on_deploy.tf.tmpl @@ -0,0 +1,72 @@ +locals { + project = "{{index $.TestEnvVars "project"}}" # Google Cloud Platform Project ID +} + +resource "google_service_account" "account" { + provider = google-beta + account_id = "{{index $.Vars "service_account"}}" + display_name = "Test Service Account" +} + +resource "google_pubsub_topic" "topic" { + provider = google-beta + name = "{{index $.Vars "topic"}}" +} + +resource "google_storage_bucket" "bucket" { + provider = google-beta + name = "${local.project}-{{index $.Vars "bucket_name"}}" # Every bucket name must be globally unique + location = "US" + uniform_bucket_level_access = true +} + +resource "google_storage_bucket_object" "object" { + provider = google-beta + name = "function-source.zip" + bucket = google_storage_bucket.bucket.name + source = "{{index $.Vars "zip_path"}}" # Add path to the zipped function source code +} + +resource "google_cloudfunctions2_function" "{{$.PrimaryResourceId}}" { + provider = google-beta + name = "{{index $.Vars "function"}}" + location = "europe-west6" + description = "a new function" + + build_config { + runtime = "nodejs16" + entry_point = "helloPubSub" # Set the entry point + environment_variables = { + BUILD_CONFIG_TEST = "build_test" + } + source { + storage_source { + bucket = google_storage_bucket.bucket.name + object = google_storage_bucket_object.object.name + } + } + on_deploy_update_policy {} + } + + service_config { + max_instance_count = 3 + min_instance_count = 1 + available_memory = "4Gi" + timeout_seconds = 60 + max_instance_request_concurrency = 80 + available_cpu = "4" + environment_variables = { + SERVICE_CONFIG_TEST = "config_test" + } + ingress_settings = "ALLOW_INTERNAL_ONLY" + all_traffic_on_latest_revision = true + service_account_email = google_service_account.account.email + } + + event_trigger { + trigger_region = "us-central1" + event_type = "google.cloud.pubsub.topic.v1.messagePublished" + pubsub_topic = google_pubsub_topic.topic.id + retry_policy = "RETRY_POLICY_RETRY" + } +} diff --git a/mmv1/templates/terraform/examples/go/compute_health_check_http_source_regions.tf.tmpl b/mmv1/templates/terraform/examples/go/compute_health_check_http_source_regions.tf.tmpl new file mode 100644 index 000000000000..14480abe2365 --- /dev/null +++ b/mmv1/templates/terraform/examples/go/compute_health_check_http_source_regions.tf.tmpl @@ -0,0 +1,12 @@ +resource "google_compute_health_check" "{{$.PrimaryResourceId}}" { + provider = "google-beta" + name = "{{index $.Vars "health_check_name"}}" + check_interval_sec = 30 + + http_health_check { + port = 80 + port_specification = "USE_FIXED_PORT" + } + + source_regions = ["us-west1", "us-central1", "us-east5"] +} diff --git a/mmv1/templates/terraform/examples/go/compute_health_check_https_source_regions.tf.tmpl b/mmv1/templates/terraform/examples/go/compute_health_check_https_source_regions.tf.tmpl new file mode 100644 index 000000000000..d274bf06b9cb --- /dev/null +++ b/mmv1/templates/terraform/examples/go/compute_health_check_https_source_regions.tf.tmpl @@ -0,0 +1,12 @@ +resource "google_compute_health_check" "{{$.PrimaryResourceId}}" { + provider = "google-beta" + name = "{{index $.Vars "health_check_name"}}" + check_interval_sec = 30 + + https_health_check { + port = 80 + port_specification = "USE_FIXED_PORT" + } + + source_regions = ["us-west1", "us-central1", "us-east5"] +} \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/go/compute_health_check_tcp_source_regions.tf.tmpl b/mmv1/templates/terraform/examples/go/compute_health_check_tcp_source_regions.tf.tmpl new file mode 100644 index 000000000000..4bd4b7bd4465 --- /dev/null +++ b/mmv1/templates/terraform/examples/go/compute_health_check_tcp_source_regions.tf.tmpl @@ -0,0 +1,12 @@ +resource "google_compute_health_check" "{{$.PrimaryResourceId}}" { + provider = "google-beta" + name = "{{index $.Vars "health_check_name"}}" + check_interval_sec = 30 + + tcp_health_check { + port = 80 + port_specification = "USE_FIXED_PORT" + } + + source_regions = ["us-west1", "us-central1", "us-east5"] +} \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/go/data_fusion_instance_psc.tf.tmpl b/mmv1/templates/terraform/examples/go/data_fusion_instance_psc.tf.tmpl new file mode 100644 index 000000000000..222d89b2f0f9 --- /dev/null +++ b/mmv1/templates/terraform/examples/go/data_fusion_instance_psc.tf.tmpl @@ -0,0 +1,39 @@ +resource "google_data_fusion_instance" "{{$.PrimaryResourceId}}" { + name = "{{index $.Vars "instance_name"}}" + region = "us-central1" + type = "BASIC" + private_instance = true + + network_config { + connection_type = "PRIVATE_SERVICE_CONNECT_INTERFACES" + private_service_connect_config { + network_attachment = google_compute_network_attachment.psc.id + unreachable_cidr_block = "192.168.0.0/25" + } + } + + {{index $.Vars "prober_test_run"}} +} + +resource "google_compute_network" "psc" { + name = "{{index $.Vars "network_name"}}" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "psc" { + name = "{{index $.Vars "subnet_name"}}" + region = "us-central1" + + network = google_compute_network.psc.id + ip_cidr_range = "10.0.0.0/16" +} + +resource "google_compute_network_attachment" "psc" { + name = "{{index $.Vars "attachment_name"}}" + region = "us-central1" + connection_preference = "ACCEPT_AUTOMATIC" + + subnetworks = [ + google_compute_subnetwork.psc.self_link + ] +} diff --git a/mmv1/templates/terraform/examples/go/dataplex_datascan_full_quality.tf.tmpl b/mmv1/templates/terraform/examples/go/dataplex_datascan_full_quality.tf.tmpl index 9640959c3742..5fa08827f4bd 100644 --- a/mmv1/templates/terraform/examples/go/dataplex_datascan_full_quality.tf.tmpl +++ b/mmv1/templates/terraform/examples/go/dataplex_datascan_full_quality.tf.tmpl @@ -94,6 +94,13 @@ resource "google_dataplex_datascan" "{{$.PrimaryResourceId}}" { sql_expression = "COUNT(*) > 0" } } + + rules { + dimension = "VALIDITY" + sql_assertion { + sql_statement = "select * from bigquery-public-data.austin_bikeshare.bikeshare_stations where station_id is null" + } + } } diff --git a/mmv1/templates/terraform/examples/go/healthcare_dataset_cmek.tf.tmpl b/mmv1/templates/terraform/examples/go/healthcare_dataset_cmek.tf.tmpl new file mode 100644 index 000000000000..a7f2a6244c99 --- /dev/null +++ b/mmv1/templates/terraform/examples/go/healthcare_dataset_cmek.tf.tmpl @@ -0,0 +1,36 @@ +data "google_project" "project" {} + +resource "google_healthcare_dataset" "default" { + name = "{{index $.Vars "dataset_name"}}" + location = "us-central1" + time_zone = "UTC" + + encryption_spec { + kms_key_name = google_kms_crypto_key.crypto_key.id + } + + depends_on = [ + google_kms_crypto_key_iam_binding.healthcare_cmek_keyuser + ] +} + +resource "google_kms_crypto_key" "crypto_key" { + name = "{{index $.Vars "key_name"}}" + key_ring = google_kms_key_ring.key_ring.id + purpose = "ENCRYPT_DECRYPT" +} + +resource "google_kms_key_ring" "key_ring" { + name = "{{index $.Vars "keyring_name"}}" + location = "us-central1" +} + +resource "google_kms_crypto_key_iam_binding" "healthcare_cmek_keyuser" { + crypto_key_id = google_kms_crypto_key.crypto_key.id + role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" + members = [ + "serviceAccount:service-${data.google_project.project.number}@gcp-sa-healthcare.iam.gserviceaccount.com", + ] +} + + diff --git a/mmv1/templates/terraform/examples/go/region_network_endpoint_group_appengine.tf.tmpl b/mmv1/templates/terraform/examples/go/region_network_endpoint_group_appengine.tf.tmpl index fd1feccc8d47..457a35337e4a 100644 --- a/mmv1/templates/terraform/examples/go/region_network_endpoint_group_appengine.tf.tmpl +++ b/mmv1/templates/terraform/examples/go/region_network_endpoint_group_appengine.tf.tmpl @@ -67,4 +67,4 @@ resource "google_storage_bucket_object" "{{$.PrimaryResourceId}}" { name = "hello-world.zip" bucket = google_storage_bucket.{{$.PrimaryResourceId}}.name source = "./test-fixtures/hello-world.zip" -} +} \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/go/region_network_endpoint_group_appengine_empty.tf.tmpl b/mmv1/templates/terraform/examples/go/region_network_endpoint_group_appengine_empty.tf.tmpl index f05b51e45174..0a221538ac12 100644 --- a/mmv1/templates/terraform/examples/go/region_network_endpoint_group_appengine_empty.tf.tmpl +++ b/mmv1/templates/terraform/examples/go/region_network_endpoint_group_appengine_empty.tf.tmpl @@ -5,4 +5,4 @@ resource "google_compute_region_network_endpoint_group" "{{$.PrimaryResourceId}} region = "us-central1" app_engine { } -} +} \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/go/region_network_endpoint_group_cloudrun.tf.tmpl b/mmv1/templates/terraform/examples/go/region_network_endpoint_group_cloudrun.tf.tmpl index 14af0ae70d32..e6c5c2b58715 100644 --- a/mmv1/templates/terraform/examples/go/region_network_endpoint_group_cloudrun.tf.tmpl +++ b/mmv1/templates/terraform/examples/go/region_network_endpoint_group_cloudrun.tf.tmpl @@ -24,4 +24,4 @@ resource "google_cloud_run_service" "{{$.PrimaryResourceId}}" { percent = 100 latest_revision = true } -} +} \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/go/scc_v2_organization_notification_config_basic.tf.tmpl b/mmv1/templates/terraform/examples/go/scc_v2_organization_notification_config_basic.tf.tmpl new file mode 100644 index 000000000000..1d90cb7a579e --- /dev/null +++ b/mmv1/templates/terraform/examples/go/scc_v2_organization_notification_config_basic.tf.tmpl @@ -0,0 +1,15 @@ +resource "google_pubsub_topic" "scc_v2_organization_notification_config" { + name = "{{index $.Vars "topic_name"}}" +} + +resource "google_scc_v2_organization_notification_config" "{{$.PrimaryResourceId}}" { + config_id = "{{index $.Vars "config_id"}}" + organization = "{{index $.TestEnvVars "org_id"}}" + location = "global" + description = "My custom Cloud Security Command Center Finding Organization Notification Configuration" + pubsub_topic = google_pubsub_topic.scc_v2_organization_notification_config.id + + streaming_config { + filter = "category = \"OPEN_FIREWALL\" AND state = \"ACTIVE\"" + } +} diff --git a/mmv1/templates/terraform/examples/go/secure_source_manager_repository_basic.tf.tmpl b/mmv1/templates/terraform/examples/go/secure_source_manager_repository_basic.tf.tmpl new file mode 100644 index 000000000000..fc410d2d0d58 --- /dev/null +++ b/mmv1/templates/terraform/examples/go/secure_source_manager_repository_basic.tf.tmpl @@ -0,0 +1,10 @@ +resource "google_secure_source_manager_instance" "instance" { + location = "us-central1" + instance_id = "{{index $.Vars "instance_id"}}" +} + +resource "google_secure_source_manager_repository" "{{$.PrimaryResourceId}}" { + location = "us-central1" + repository_id = "{{index $.Vars "repository_id"}}" + instance = google_secure_source_manager_instance.instance.name +} diff --git a/mmv1/templates/terraform/examples/go/secure_source_manager_repository_initial_config.tf.tmpl b/mmv1/templates/terraform/examples/go/secure_source_manager_repository_initial_config.tf.tmpl new file mode 100644 index 000000000000..969a25833002 --- /dev/null +++ b/mmv1/templates/terraform/examples/go/secure_source_manager_repository_initial_config.tf.tmpl @@ -0,0 +1,18 @@ +resource "google_secure_source_manager_instance" "instance" { + location = "us-central1" + instance_id = "{{index $.Vars "instance_id"}}" +} + +resource "google_secure_source_manager_repository" "{{$.PrimaryResourceId}}" { + location = "us-central1" + repository_id = "{{index $.Vars "repository_id"}}" + instance = google_secure_source_manager_instance.instance.name + + description = "This is a test repository" + initial_config { + default_branch = "main" + gitignores = ["python"] + license = "mit" + readme = "default" + } +} diff --git a/mmv1/templates/terraform/examples/go/storage_managed_folder_basic.tf.tmpl b/mmv1/templates/terraform/examples/go/storage_managed_folder_basic.tf.tmpl new file mode 100644 index 000000000000..940ed198c082 --- /dev/null +++ b/mmv1/templates/terraform/examples/go/storage_managed_folder_basic.tf.tmpl @@ -0,0 +1,10 @@ +resource "google_storage_bucket" "bucket" { + name = "{{index $.Vars "bucket_name"}}" + location = "EU" + uniform_bucket_level_access = true +} + +resource "google_storage_managed_folder" "{{$.PrimaryResourceId}}" { + bucket = google_storage_bucket.bucket.name + name = "managed/folder/name/" +} diff --git a/mmv1/templates/terraform/examples/go/workstation_config_basic.tf.tmpl b/mmv1/templates/terraform/examples/go/workstation_config_basic.tf.tmpl index 4c293b9abb1e..2b34032e64fa 100644 --- a/mmv1/templates/terraform/examples/go/workstation_config_basic.tf.tmpl +++ b/mmv1/templates/terraform/examples/go/workstation_config_basic.tf.tmpl @@ -1,3 +1,21 @@ +resource "google_project" "project" { + project_id = "{{index $.Vars "project_id"}}" + name = "{{index $.Vars "project_id"}}" + org_id = "{{index $.TestEnvVars "org_id"}}" +} + +resource "google_tags_tag_key" "tag_key1" { + provider = "google-beta" + parent = "organizations/{{index $.TestEnvVars "org_id"}}" + short_name = "{{index $.Vars "tag_key1"}}" +} + +resource "google_tags_tag_value" "tag_value1" { + provider = "google-beta" + parent = "tagKeys/${google_tags_tag_key.tag_key1.name}" + short_name = "{{index $.Vars "tag_value1"}}" +} + resource "google_compute_network" "default" { provider = google-beta name = "{{index $.Vars "workstation_cluster_name"}}" @@ -52,6 +70,9 @@ resource "google_workstations_workstation_config" "{{$.PrimaryResourceId}}" { boot_disk_size_gb = 35 disable_public_ip_addresses = true disable_ssh = false + vm_tags = { + "tagKeys/${google_tags_tag_key.tag_key1.short_name}" = "tagValues/${google_tags_tag_value.tag_value1.short_name}" + } } } } diff --git a/mmv1/templates/terraform/examples/region_network_endpoint_group_appengine.tf.erb b/mmv1/templates/terraform/examples/region_network_endpoint_group_appengine.tf.erb index 848721eea4a5..2dea773b58e7 100644 --- a/mmv1/templates/terraform/examples/region_network_endpoint_group_appengine.tf.erb +++ b/mmv1/templates/terraform/examples/region_network_endpoint_group_appengine.tf.erb @@ -67,4 +67,4 @@ resource "google_storage_bucket_object" "<%= ctx[:primary_resource_id] %>" { name = "hello-world.zip" bucket = google_storage_bucket.<%= ctx[:primary_resource_id] %>.name source = "./test-fixtures/hello-world.zip" -} +} \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/region_network_endpoint_group_appengine_empty.tf.erb b/mmv1/templates/terraform/examples/region_network_endpoint_group_appengine_empty.tf.erb index fe794ed86e7b..3bcdffc62c20 100644 --- a/mmv1/templates/terraform/examples/region_network_endpoint_group_appengine_empty.tf.erb +++ b/mmv1/templates/terraform/examples/region_network_endpoint_group_appengine_empty.tf.erb @@ -5,4 +5,4 @@ resource "google_compute_region_network_endpoint_group" "<%= ctx[:primary_resour region = "us-central1" app_engine { } -} +} \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/region_network_endpoint_group_cloudrun.tf.erb b/mmv1/templates/terraform/examples/region_network_endpoint_group_cloudrun.tf.erb index 7dd4bad620a7..9198102b398e 100644 --- a/mmv1/templates/terraform/examples/region_network_endpoint_group_cloudrun.tf.erb +++ b/mmv1/templates/terraform/examples/region_network_endpoint_group_cloudrun.tf.erb @@ -24,4 +24,4 @@ resource "google_cloud_run_service" "<%= ctx[:primary_resource_id] %>" { percent = 100 latest_revision = true } -} +} \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/scc_v2_organization_notification_config_basic.tf.erb b/mmv1/templates/terraform/examples/scc_v2_organization_notification_config_basic.tf.erb new file mode 100644 index 000000000000..c186e028d4ee --- /dev/null +++ b/mmv1/templates/terraform/examples/scc_v2_organization_notification_config_basic.tf.erb @@ -0,0 +1,15 @@ +resource "google_pubsub_topic" "scc_v2_organization_notification_config" { + name = "<%= ctx[:vars]['topic_name'] %>" +} + +resource "google_scc_v2_organization_notification_config" "<%= ctx[:primary_resource_id] %>" { + config_id = "<%= ctx[:vars]['config_id'] %>" + organization = "<%= ctx[:test_env_vars]['org_id'] %>" + location = "global" + description = "My custom Cloud Security Command Center Finding Organization Notification Configuration" + pubsub_topic = google_pubsub_topic.scc_v2_organization_notification_config.id + + streaming_config { + filter = "category = \"OPEN_FIREWALL\" AND state = \"ACTIVE\"" + } +} diff --git a/mmv1/templates/terraform/examples/secure_source_manager_repository_basic.tf.erb b/mmv1/templates/terraform/examples/secure_source_manager_repository_basic.tf.erb new file mode 100644 index 000000000000..6cc59eeacc15 --- /dev/null +++ b/mmv1/templates/terraform/examples/secure_source_manager_repository_basic.tf.erb @@ -0,0 +1,10 @@ +resource "google_secure_source_manager_instance" "instance" { + location = "us-central1" + instance_id = "<%= ctx[:vars]['instance_id'] %>" +} + +resource "google_secure_source_manager_repository" "<%= ctx[:primary_resource_id] %>" { + location = "us-central1" + repository_id = "<%= ctx[:vars]['repository_id'] %>" + instance = google_secure_source_manager_instance.instance.name +} diff --git a/mmv1/templates/terraform/examples/secure_source_manager_repository_initial_config.tf.erb b/mmv1/templates/terraform/examples/secure_source_manager_repository_initial_config.tf.erb new file mode 100644 index 000000000000..b8264bb9240f --- /dev/null +++ b/mmv1/templates/terraform/examples/secure_source_manager_repository_initial_config.tf.erb @@ -0,0 +1,18 @@ +resource "google_secure_source_manager_instance" "instance" { + location = "us-central1" + instance_id = "<%= ctx[:vars]['instance_id'] %>" +} + +resource "google_secure_source_manager_repository" "<%= ctx[:primary_resource_id] %>" { + location = "us-central1" + repository_id = "<%= ctx[:vars]['repository_id'] %>" + instance = google_secure_source_manager_instance.instance.name + + description = "This is a test repository" + initial_config { + default_branch = "main" + gitignores = ["python"] + license = "mit" + readme = "default" + } +} diff --git a/mmv1/templates/terraform/examples/securityposture_posture_deployment_basic.tf.erb b/mmv1/templates/terraform/examples/securityposture_posture_deployment_basic.tf.erb deleted file mode 100644 index af6d2ea842cf..000000000000 --- a/mmv1/templates/terraform/examples/securityposture_posture_deployment_basic.tf.erb +++ /dev/null @@ -1,32 +0,0 @@ -resource "google_securityposture_posture" "posture_1" { - posture_id = "<%= ctx[:vars]['posture_id'] %>" - parent = "organizations/<%= ctx[:test_env_vars]['org_id'] %>" - location = "global" - state = "ACTIVE" - description = "a new posture" - policy_sets { - policy_set_id = "org_policy_set" - description = "set of org policies" - policies { - policy_id = "policy_1" - constraint { - org_policy_constraint { - canned_constraint_id = "storage.uniformBucketLevelAccess" - policy_rules { - enforce = true - } - } - } - } - } -} - -resource "google_securityposture_posture_deployment" "<%= ctx[:primary_resource_id] %>" { - posture_deployment_id = "<%= ctx[:vars]['deployment_id'] %>" - parent = "organizations/<%= ctx[:test_env_vars]['org_id'] %>" - location = "global" - description = "a new posture deployment" - target_resource = "projects/<%= ctx[:test_env_vars]['project_number'] %>" - posture_id = google_securityposture_posture.posture_1.name - posture_revision_id = google_securityposture_posture.posture_1.revision_id -} diff --git a/mmv1/templates/terraform/examples/storage_managed_folder_basic.tf.erb b/mmv1/templates/terraform/examples/storage_managed_folder_basic.tf.erb new file mode 100644 index 000000000000..4f349c62e0ab --- /dev/null +++ b/mmv1/templates/terraform/examples/storage_managed_folder_basic.tf.erb @@ -0,0 +1,10 @@ +resource "google_storage_bucket" "bucket" { + name = "<%= ctx[:vars]['bucket_name'] %>" + location = "EU" + uniform_bucket_level_access = true +} + +resource "google_storage_managed_folder" "<%= ctx[:primary_resource_id] %>" { + bucket = google_storage_bucket.bucket.name + name = "managed/folder/name/" +} diff --git a/mmv1/templates/terraform/examples/workstation_config_basic.tf.erb b/mmv1/templates/terraform/examples/workstation_config_basic.tf.erb index 1925d7b016b6..a70cc2c0a054 100644 --- a/mmv1/templates/terraform/examples/workstation_config_basic.tf.erb +++ b/mmv1/templates/terraform/examples/workstation_config_basic.tf.erb @@ -1,3 +1,15 @@ +resource "google_tags_tag_key" "tag_key1" { + provider = google-beta + parent = "organizations/<%= ctx[:vars]['org_id'] %>" + short_name = "<%= ctx[:vars]['key_short_name'] %>" +} + +resource "google_tags_tag_value" "tag_value1" { + provider = google-beta + parent = "tagKeys/${google_tags_tag_key.tag_key1.name}" + short_name = "<%= ctx[:vars]['value_short_name'] %>" +} + resource "google_compute_network" "default" { provider = google-beta name = "<%= ctx[:vars]['workstation_cluster_name'] %>" @@ -52,6 +64,9 @@ resource "google_workstations_workstation_config" "<%= ctx[:primary_resource_id] boot_disk_size_gb = 35 disable_public_ip_addresses = true disable_ssh = false + vm_tags = { + "tagKeys/${google_tags_tag_key.tag_key1.name}" = "tagValues/${google_tags_tag_value.tag_value1.name}" + } } } } diff --git a/mmv1/templates/terraform/extra_schema_entry/go/route.tmpl b/mmv1/templates/terraform/extra_schema_entry/go/route.tmpl index e395a1edcdf3..703d043d4696 100644 --- a/mmv1/templates/terraform/extra_schema_entry/go/route.tmpl +++ b/mmv1/templates/terraform/extra_schema_entry/go/route.tmpl @@ -1,4 +1,4 @@ -"next_hop_instance_zone": &schema.Schema{ +"next_hop_instance_zone": { Type: schema.TypeString, Optional: true, ForceNew: true, diff --git a/mmv1/templates/terraform/extra_schema_entry/route.erb b/mmv1/templates/terraform/extra_schema_entry/route.erb index e395a1edcdf3..703d043d4696 100644 --- a/mmv1/templates/terraform/extra_schema_entry/route.erb +++ b/mmv1/templates/terraform/extra_schema_entry/route.erb @@ -1,4 +1,4 @@ -"next_hop_instance_zone": &schema.Schema{ +"next_hop_instance_zone": { Type: schema.TypeString, Optional: true, ForceNew: true, diff --git a/mmv1/templates/terraform/iam/example_config_body/go/storage_managed_folder.tf.tmpl b/mmv1/templates/terraform/iam/example_config_body/go/storage_managed_folder.tf.tmpl new file mode 100644 index 000000000000..2c5d96e844fb --- /dev/null +++ b/mmv1/templates/terraform/iam/example_config_body/go/storage_managed_folder.tf.tmpl @@ -0,0 +1,2 @@ + bucket = google_storage_managed_folder.folder.bucket + managed_folder = google_storage_managed_folder.folder.name diff --git a/mmv1/templates/terraform/iam/example_config_body/storage_managed_folder.tf.erb b/mmv1/templates/terraform/iam/example_config_body/storage_managed_folder.tf.erb new file mode 100644 index 000000000000..2c5d96e844fb --- /dev/null +++ b/mmv1/templates/terraform/iam/example_config_body/storage_managed_folder.tf.erb @@ -0,0 +1,2 @@ + bucket = google_storage_managed_folder.folder.bucket + managed_folder = google_storage_managed_folder.folder.name diff --git a/mmv1/templates/terraform/iam_policy.go.tmpl b/mmv1/templates/terraform/iam_policy.go.tmpl index 1c476b0631c6..fc06518313b5 100644 --- a/mmv1/templates/terraform/iam_policy.go.tmpl +++ b/mmv1/templates/terraform/iam_policy.go.tmpl @@ -97,7 +97,7 @@ func {{ $.ResourceName }}IamUpdaterProducer(d tpgresource.TerraformResourceData, values["{{ $param }}"] = v.(string) } -{{- end }} +{{ end }} {{- end }}{{- /* range $param := $.IamResourceParams */}} // We may have gotten either a long or short name, so attempt to parse long name if possible diff --git a/mmv1/templates/terraform/nested_property_documentation.html.markdown.tmpl b/mmv1/templates/terraform/nested_property_documentation.html.markdown.tmpl index f451327fe851..4b4979bc6193 100644 --- a/mmv1/templates/terraform/nested_property_documentation.html.markdown.tmpl +++ b/mmv1/templates/terraform/nested_property_documentation.html.markdown.tmpl @@ -1,18 +1,34 @@ -{{- define "nestedPropertyDocumentation" }} - {{- if $.FlattenObject }} - {{- range $np := $.NestedProperties }} -{{- template "nestedPropertyDocumentation" $np -}} - {{- end}} - {{- else if $.NestedProperties }} +{{ "" }} +{{- if $.FlattenObject }} + {{- range $np := $.NestedProperties }} +{{- trimTemplate "nested_property_documentation.html.markdown.tmpl" $np -}} + {{- end -}} +{{- else if $.NestedProperties }} The `{{ underscore $.Name }}` block {{ if $.Output }}contains{{ else }}supports{{ end }}: -{{ if $.IsA "Map" }} - * `{{ underscore $.KeyName }}` - (Required) The identifier for this object. Format specified above. - {{- end}} +{{ "" }} + {{- if $.IsA "Map" }} +* `{{ underscore $.KeyName }}` - (Required) The identifier for this object. Format specified above. +{{ "" }} + {{- end -}} + {{- if $.NestedProperties }} {{- range $np := $.NestedProperties }} -{{- template "propertyDocumentation" $np }} +{{- trimTemplate "property_documentation.html.markdown.tmpl" $np -}} + {{- end -}} +{{ "" }} + {{- $innerNested := false }} + {{- range $np := $.NestedProperties }} + {{- if $np.NestedProperties }} + {{- $innerNested = true }} + {{- end }} + {{- end }} + {{- if $innerNested}} +{{ "" }} {{- end }} {{- range $np := $.NestedProperties }} -{{- template "nestedPropertyDocumentation" $np -}} - {{- end}} - {{- end}} -{{- end}} \ No newline at end of file + {{- if $np.NestedProperties }} +{{- trimTemplate "nested_property_documentation.html.markdown.tmpl" $np -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} +{{ "" }} \ No newline at end of file diff --git a/mmv1/templates/terraform/nested_query.go.tmpl b/mmv1/templates/terraform/nested_query.go.tmpl index 73af9ee3db31..3c268d6eda82 100644 --- a/mmv1/templates/terraform/nested_query.go.tmpl +++ b/mmv1/templates/terraform/nested_query.go.tmpl @@ -102,31 +102,13 @@ func resource{{ $.ResourceName }}PatchCreateEncoder(d *schema.ResourceData, meta // Return list with the resource to create appended {{- if $.NestedQuery.IsListOfIds }} res := map[string]interface{}{ - "{{ $.LastNestedQueryKey }}": append(currItems, obj["{{ $.FirstIdentity.ApiName }}"]), + "{{ $.LastNestedQueryKey }}": append(currItems, obj["{{ $.FirstIdentityProp.ApiName }}"]), } {{- else }} res := map[string]interface{}{ "{{ $.LastNestedQueryKey }}": append(currItems, obj), } {{- end }} - {{/* - Reconstruct the full nested object. For example, if nested_query.keys is: - - nested_item - - more_nested_item - Then the code above will build: - { - "more_nested_item": [...] - } - Add back the other keys so we get: - { - "nested_item": { - "more_nested_item": [...] - } - } - Note that this assumes that we can safely have "more_nested_item" be the only element - in the "nested_item" map, which only works if the patch request takes an update mask - (or if the rest of the map would have been empty anyway). - */}} {{- range $i, $k := $.NestedQuery.Keys }} {{- if ne $i 0 }} wrapped := map[string]interface{}{ @@ -203,9 +185,8 @@ func resource{{ $.ResourceName }}PatchDeleteEncoder(d *schema.ResourceData, meta res := map[string]interface{}{ "{{ $.LastNestedQueryKey }}": updatedItems, } - {{/* see comments in PatchCreateEncoder for details */}} {{- range $i, $k := $.NestedQuery.Keys }} - {{- if ne $i 0 }} + {{- if ne $i (sub (len $.NestedQuery.Keys) 1) }} wrapped := map[string]interface{}{ "{{ $k }}": res, } @@ -218,7 +199,7 @@ func resource{{ $.ResourceName }}PatchDeleteEncoder(d *schema.ResourceData, meta // ListForPatch handles making API request to get parent resource and // extracting list of objects. -{{/* This function is similar to flattenNested...() but +{{- /* This function is similar to flattenNested...() but # 1) does an API request to read the parent resource from API (flatten takes in list from top-level Read() method, whereas this method is called in Create/Update/Delete) @@ -227,7 +208,7 @@ func resource{{ $.ResourceName }}PatchDeleteEncoder(d *schema.ResourceData, meta */}} func resource{{ $.ResourceName }}ListForPatch(d *schema.ResourceData, meta interface{}) ([]interface{}, error) { config := meta.(*transport_tpg.Config) - url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}{{$.ProductMetadata.Name}}BasePath{{"}}{{"}}$.SelfLinkUri{{"}}"}}") + url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}{{$.ProductMetadata.Name}}BasePath{{"}}"}}{{$.SelfLinkUri}}") if err != nil { return nil, err } @@ -264,12 +245,14 @@ func resource{{ $.ResourceName }}ListForPatch(d *schema.ResourceData, meta inter var v interface{} var ok bool -{{- range $k := $.NestedQuery.Keys }} +{{- range $i, $k := $.NestedQuery.Keys }} + {{- if not (eq $i (sub (len $.NestedQuery.Keys) 1)) }} if v, ok = res["{{ $k }}"]; ok && v != nil { res = v.(map[string]interface{}) } else { return nil, nil } + {{- end }} {{- end }} v, ok = res["{{ $.LastNestedQueryKey }}"] diff --git a/mmv1/templates/terraform/pre_create/bigquery_reservation_assignment.go.erb b/mmv1/templates/terraform/pre_create/bigquery_reservation_assignment.go.erb new file mode 100644 index 000000000000..c4a0cdfda0f2 --- /dev/null +++ b/mmv1/templates/terraform/pre_create/bigquery_reservation_assignment.go.erb @@ -0,0 +1,20 @@ + if _, ok := d.GetOkExists("location"); !ok { + // Extract location from parent reservation. + reservation := d.Get("reservation").(string) + + tableRef := regexp.MustCompile("projects/(.+)/locations/(.+)/reservations/(.+)") + if parts := tableRef.FindStringSubmatch(reservation); parts != nil { + err := d.Set("location", parts[2]) + if err != nil { + return err + } + } + + if strings.Contains(url, "locations//") { + // re-compute url now that location must be set + url = strings.ReplaceAll(url, "/locations//", "/locations/"+d.Get("location").(string)+"/") + if err != nil { + return err + } + } + } diff --git a/mmv1/templates/terraform/pre_create/compute_snapshot_precreate_url.go.erb b/mmv1/templates/terraform/pre_create/compute_snapshot_precreate_url.go.erb index 32227c290959..58a6aba3800c 100644 --- a/mmv1/templates/terraform/pre_create/compute_snapshot_precreate_url.go.erb +++ b/mmv1/templates/terraform/pre_create/compute_snapshot_precreate_url.go.erb @@ -1,3 +1,2 @@ url = regexp.MustCompile("PRE_CREATE_REPLACE_ME").ReplaceAllLiteralString(url, sourceDiskProp.(string)) - diff --git a/mmv1/templates/terraform/pre_create/go/bigquery_reservation_assignment.go.tmpl b/mmv1/templates/terraform/pre_create/go/bigquery_reservation_assignment.go.tmpl new file mode 100644 index 000000000000..c4a0cdfda0f2 --- /dev/null +++ b/mmv1/templates/terraform/pre_create/go/bigquery_reservation_assignment.go.tmpl @@ -0,0 +1,20 @@ + if _, ok := d.GetOkExists("location"); !ok { + // Extract location from parent reservation. + reservation := d.Get("reservation").(string) + + tableRef := regexp.MustCompile("projects/(.+)/locations/(.+)/reservations/(.+)") + if parts := tableRef.FindStringSubmatch(reservation); parts != nil { + err := d.Set("location", parts[2]) + if err != nil { + return err + } + } + + if strings.Contains(url, "locations//") { + // re-compute url now that location must be set + url = strings.ReplaceAll(url, "/locations//", "/locations/"+d.Get("location").(string)+"/") + if err != nil { + return err + } + } + } diff --git a/mmv1/templates/terraform/pre_create/go/compute_snapshot_precreate_url.go.tmpl b/mmv1/templates/terraform/pre_create/go/compute_snapshot_precreate_url.go.tmpl index 32227c290959..58a6aba3800c 100644 --- a/mmv1/templates/terraform/pre_create/go/compute_snapshot_precreate_url.go.tmpl +++ b/mmv1/templates/terraform/pre_create/go/compute_snapshot_precreate_url.go.tmpl @@ -1,3 +1,2 @@ url = regexp.MustCompile("PRE_CREATE_REPLACE_ME").ReplaceAllLiteralString(url, sourceDiskProp.(string)) - diff --git a/mmv1/templates/terraform/pre_update/go/netapp_storagepool.go.tmpl b/mmv1/templates/terraform/pre_update/go/netapp_storagepool.go.tmpl new file mode 100644 index 000000000000..f2a44072f39d --- /dev/null +++ b/mmv1/templates/terraform/pre_update/go/netapp_storagepool.go.tmpl @@ -0,0 +1,65 @@ +// detect manual zone switches for service level FLEX + +if d.Get("service_level").(string) == "FLEX" { + // Check if this is zonal or regional Flex. Only continue for regional pool + _, hasZone := d.GetOk("zone") + _, hasReplicaZone := d.GetOk("replica_zone") + if hasZone && hasReplicaZone { + // For a zone switch, user needs to swap zone and replica_zone. Other changes are not allowed + if d.HasChange("zone") && d.HasChange("replica_zone") { + oldZone, newZone := d.GetChange("zone") + oldReplicaZone, newReplicaZone := d.GetChange("replica_zone") + if newZone == oldReplicaZone && newReplicaZone == oldZone { + rawurl, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}NetappBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/locations/{{"{{"}}location{{"}}"}}/storagePools/{{"{{"}}name{{"}}"}}:switch") + if err != nil { + return err + } + + reso, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "POST", + Project: billingProject, + RawURL: rawurl, + UserAgent: userAgent, + Timeout: d.Timeout(schema.TimeoutUpdate), + }) + if err != nil { + return fmt.Errorf("Error switching active zone for pool: %s, %v", d.Id(), err) + } + + err = NetappOperationWaitTime( + config, reso, project, "Switching active pool zone", userAgent, + d.Timeout(schema.TimeoutUpdate)) + if err != nil { + return err + } + + //remove zone and replicaZone from updateMask + n := 0 + for _, v := range updateMask { + if v != "zone" && v != "replicaZone" { + updateMask[n] = v + n++ + } + } + updateMask = updateMask[:n] + + // delete from payload too + delete(obj, "zone") + delete(obj, "replicaZone") + + // PATCH URL was already build prior to this code. We need to rebuild it to catch our changes + url, err = tpgresource.ReplaceVars(d, config, "{{"{{"}}NetappBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/locations/{{"{{"}}location{{"}}"}}/storagePools/{{"{{"}}name{{"}}"}}") + if err != nil { + return err + } + url, err = transport_tpg.AddQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) + if err != nil { + return err + } + } else { + return fmt.Errorf("Incorrect zone change for pool: %s. Supported zone, replica_zone are : %s, %s", d.Id(), oldZone, oldReplicaZone) + } + } + } +} diff --git a/mmv1/templates/terraform/pre_update/netapp_storagepool.go.erb b/mmv1/templates/terraform/pre_update/netapp_storagepool.go.erb new file mode 100644 index 000000000000..86e47f1349e2 --- /dev/null +++ b/mmv1/templates/terraform/pre_update/netapp_storagepool.go.erb @@ -0,0 +1,65 @@ +// detect manual zone switches for service level FLEX + +if d.Get("service_level").(string) == "FLEX" { + // Check if this is zonal or regional Flex. Only continue for regional pool + _, hasZone := d.GetOk("zone") + _, hasReplicaZone := d.GetOk("replica_zone") + if hasZone && hasReplicaZone { + // For a zone switch, user needs to swap zone and replica_zone. Other changes are not allowed + if d.HasChange("zone") && d.HasChange("replica_zone") { + oldZone, newZone := d.GetChange("zone") + oldReplicaZone, newReplicaZone := d.GetChange("replica_zone") + if newZone == oldReplicaZone && newReplicaZone == oldZone { + rawurl, err := tpgresource.ReplaceVars(d, config, "{{NetappBasePath}}projects/{{project}}/locations/{{location}}/storagePools/{{name}}:switch") + if err != nil { + return err + } + + reso, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "POST", + Project: billingProject, + RawURL: rawurl, + UserAgent: userAgent, + Timeout: d.Timeout(schema.TimeoutUpdate), + }) + if err != nil { + return fmt.Errorf("Error switching active zone for pool: %s, %v", d.Id(), err) + } + + err = NetappOperationWaitTime( + config, reso, project, "Switching active pool zone", userAgent, + d.Timeout(schema.TimeoutUpdate)) + if err != nil { + return err + } + + //remove zone and replicaZone from updateMask + n := 0 + for _, v := range updateMask { + if v != "zone" && v != "replicaZone" { + updateMask[n] = v + n++ + } + } + updateMask = updateMask[:n] + + // delete from payload too + delete(obj, "zone") + delete(obj, "replicaZone") + + // PATCH URL was already build prior to this code. We need to rebuild it to catch our changes + url, err = tpgresource.ReplaceVars(d, config, "{{NetappBasePath}}projects/{{project}}/locations/{{location}}/storagePools/{{name}}") + if err != nil { + return err + } + url, err = transport_tpg.AddQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) + if err != nil { + return err + } + } else { + return fmt.Errorf("Incorrect zone change for pool: %s. Supported zone, replica_zone are : %s, %s", d.Id(), oldZone, oldReplicaZone) + } + } + } +} diff --git a/mmv1/templates/terraform/property_documentation.html.markdown.tmpl b/mmv1/templates/terraform/property_documentation.html.markdown.tmpl index 6b08df4ada86..d7cb4c524fee 100644 --- a/mmv1/templates/terraform/property_documentation.html.markdown.tmpl +++ b/mmv1/templates/terraform/property_documentation.html.markdown.tmpl @@ -1,4 +1,4 @@ -{{- define "propertyDocumentation" }} +{{ "" }} * `{{ underscore $.Name }}` - {{- if and (eq $.MinVersion "beta") (not (eq $.ResourceMetadata.MinVersion "beta")) }} {{- if $.Required }} @@ -21,13 +21,13 @@ (Deprecated) {{- end}} {{- end }} - {{ $.ResourceMetadata.FormatDocDescription $.Description true -}} + {{- $.ResourceMetadata.FormatDocDescription $.Description true -}} {{- if and (and ($.IsA "Array") ($.ItemType.IsA "Enum")) (and (not $.Output) (not $.ItemType.SkipDocsValues))}} {{- if $.ItemType.DefaultValue }} Default value is `{{ $.ItemType.DefaultValue }}`. {{- end }} - Each value may be one of: {{ $.ItemType.EnumValuesToString "`" false }}. - {{- else if and ($.IsA "Enum") (and (not $.Output) (not (and $.ItemType $.ItemType.SkipDocsValues)))}} + Each value may be one of: {{ $.ItemType.EnumValuesToString "`" false }}. + {{- else if and ($.IsA "Enum") (and (not $.Output) (not $.SkipDocsValues))}} {{- if $.DefaultValue }} Default value is `{{ $.DefaultValue }}`. {{- end }} @@ -42,5 +42,5 @@ {{- if $.DeprecationMessage }} ~> **Warning:** {{ $.DeprecationMessage }} - {{- end }} -{{ end }} \ No newline at end of file + {{- end -}} +{{ "" }} \ No newline at end of file diff --git a/mmv1/templates/terraform/resource.go.tmpl b/mmv1/templates/terraform/resource.go.tmpl index f1fce957a967..c63c3336f388 100644 --- a/mmv1/templates/terraform/resource.go.tmpl +++ b/mmv1/templates/terraform/resource.go.tmpl @@ -98,10 +98,10 @@ func Resource{{ $.ResourceName -}}() *schema.Resource { {{- if $.StateUpgraders }} StateUpgraders: []schema.StateUpgrader{ -{{- range $v := $.SchemaVersions }} +{{- range $v := $.StateUpgradersCount }} { - Type: resource{{$.PathType}}ResourceV{{$v}}().CoreConfigSchema().ImpliedType(), - Upgrade: Resource{{$.PathType}}UpgradeV{{$v}}, + Type: resource{{$.ResourceName}}ResourceV{{$v}}().CoreConfigSchema().ImpliedType(), + Upgrade: Resource{{$.ResourceName}}UpgradeV{{$v}}, Version: {{$v}}, }, {{- end }} @@ -109,6 +109,11 @@ func Resource{{ $.ResourceName -}}() *schema.Resource { {{- end }} {{- if or (and (or $.HasProject $.HasRegion $.HasZone) (not $.SkipDefaultCdiff)) $.CustomDiff }} CustomizeDiff: customdiff.All( +{{- if $.UnorderedListProperties }} +{{- range $prop := $.UnorderedListProperties }} + resource{{ $.ResourceName }}{{ camelize $prop.Name "upper" }}SetStyleDiff, +{{- end}} +{{- end}} {{- if $.CustomDiff -}} {{- range $cdiff := $.CustomDiff }} {{ $cdiff }}, @@ -149,8 +154,10 @@ func Resource{{ $.ResourceName -}}() *schema.Resource { }, {{- end}} {{- end}} -{{/* TODO Q2 function to compile custom code lines ($.CustomCode.extra_schema_entry) */}} -{{- if $.HasProject -}} +{{- if $.CustomCode.ExtraSchemaEntry }} + {{ $.CustomTemplate $.CustomCode.ExtraSchemaEntry false -}} +{{- end}} +{{ if $.HasProject -}} "project": { Type: schema.TypeString, Optional: true, @@ -170,9 +177,7 @@ func Resource{{ $.ResourceName -}}() *schema.Resource { } {{- range $prop := $.AllUserProperties }} -{{if and (eq $prop.Type "Array") ($prop.IsSet) (eq $prop.ItemType.Type "NestedObject")}} {{template "SchemaSubResource" $prop}} -{{end}} {{- end}} {{- range $prop := $.UnorderedListProperties }} @@ -240,7 +245,7 @@ func resource{{ $.ResourceName -}}Create(d *schema.ResourceData, meta interface{ if err != nil { return err } -{{if $.UpdateMask -}} +{{- if $.UpdateMask }} url, err = transport_tpg.AddQueryParams(url, map[string]string{"updateMask": "{{ join $.NestedQuery.Keys "." -}}"}) if err != nil { return err @@ -274,7 +279,7 @@ func resource{{ $.ResourceName -}}Create(d *schema.ResourceData, meta interface{ } headers := make(http.Header) -{{- if $.CustomCode.PreCreate }} +{{- if $.CustomCode.PreCreate }} {{ $.CustomTemplate $.CustomCode.PreCreate false -}} {{- end}} res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ @@ -299,10 +304,10 @@ func resource{{ $.ResourceName -}}Create(d *schema.ResourceData, meta interface{ {{- end}} return fmt.Errorf("Error creating {{ $.Name -}}: %s", err) } -{{/* # Set resource properties from create API response (unless it returns an Operation) */}} -{{if and ($.GetAsync) (not ($.GetAsync.IsA "OpAsync")) }} +{{- /* # Set resource properties from create API response (unless it returns an Operation) */}} +{{- if not (and $.GetAsync ($.GetAsync.IsA "OpAsync")) }} {{- range $prop := $.GettableProperties }} -{{ if and ($.IsInIdentity $prop) $prop.Output }} +{{- if and ($.IsInIdentity $prop) $prop.Output }} if err := d.Set("{{ underscore $prop.Name -}}", flatten{{ if $.NestedQuery -}}Nested{{end}}{{ $.ResourceName -}}{{ camelize $prop.Name "upper" -}}(res["{{ $prop.ApiName -}}"], d, config)); err != nil { return fmt.Errorf(`Error setting computed identity field "{{ underscore $prop.Name }}": %s`, err) } @@ -403,10 +408,12 @@ func resource{{ $.ResourceName -}}Create(d *schema.ResourceData, meta interface{ {{if $.GetAsync.IsA "PollAsync" -}} err = transport_tpg.PollingWaitTime(resource{{ $.ResourceName -}}PollRead(d, meta), {{ $.GetAsync.CheckResponseFuncExistence -}}, "Creating {{ $.Name -}}", d.Timeout(schema.TimeoutCreate), {{ $.GetAsync.TargetOccurrences -}}) if err != nil { -{{if $.GetAsync.SuppressError -}} +{{- if $.GetAsync.SuppressError -}} + log.Printf("[ERROR] Unable to confirm eventually consistent {{ $.Name }} %q finished updating: %q", d.Id(), err) -{{- else -}} -{{if $.CustomCode.PostCreateFailure -}} + +{{- else }} +{{- if $.CustomCode.PostCreateFailure -}} resource{{ $.ResourceName -}}PostCreateFailure(d, meta) {{- end}} return fmt.Errorf("Error waiting to create {{ $.Name -}}: %s", err) @@ -483,7 +490,10 @@ func resource{{ $.ResourceName -}}PollRead(d *schema.ResourceData, meta interfac if err != nil { return res, err } -{{if $.NestedQuery -}} +{{- if $.CustomCode.Decoder }} +{{""}} +{{- end }} +{{- if $.NestedQuery }} res, err = flattenNested{{ $.ResourceName -}}(d, meta, res) if err != nil { return nil, err @@ -492,7 +502,9 @@ func resource{{ $.ResourceName -}}PollRead(d *schema.ResourceData, meta interfac if res == nil { return nil, tpgresource.Fake404("nested", "{{ $.ResourceName }}") } - +{{- if not $.CustomCode.Decoder }} +{{""}} +{{- end }} {{- end -}} {{- if $.CustomCode.Decoder -}} res, err = resource{{ $.ResourceName -}}Decoder(d, meta, res) @@ -502,7 +514,7 @@ func resource{{ $.ResourceName -}}PollRead(d *schema.ResourceData, meta interfac if res == nil { return nil, tpgresource.Fake404("decoded", "{{ $.ResourceName }}") } -{{- end -}} +{{- end }} return res, nil {{ end -}} } @@ -568,9 +580,9 @@ func resource{{ $.ResourceName -}}Read(d *schema.ResourceData, meta interface{}) {{- end}} }) if err != nil { -{{if $.ReadErrorTransform -}} +{{- if $.ReadErrorTransform -}} return transport_tpg.HandleNotFoundError({{ $.ReadErrorTransform }}(err), d, fmt.Sprintf("{{ $.ResourceName }} %q", d.Id())) -{{ else -}} +{{- else }} return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("{{ $.ResourceName }} %q", d.Id())) {{- end}} } @@ -603,21 +615,20 @@ func resource{{ $.ResourceName -}}Read(d *schema.ResourceData, meta interface{}) return nil } {{ end}} -{{- if $.VirtualFields -}} +{{- if $.VirtualFields }} + // Explicitly set virtual fields to default values if unset {{- range $prop := $.VirtualFields }} {{ if not (eq $prop.DefaultValue nil) -}} if _, ok := d.GetOkExists("{{ $prop.Name -}}"); !ok { - if err := d.Set("{{ $prop.Name -}}", {{ $prop.DefaultValue -}}); err != nil { + if err := d.Set("{{ $prop.Name -}}", {{ $prop.GoLiteral $prop.DefaultValue -}}); err != nil { return fmt.Errorf("Error setting {{ $prop.Name -}}: %s", err) } } {{- end}} {{- end}} {{- end}} -{{ if $.HasProject }} - - +{{- if $.HasProject }} if err := d.Set("project", project); err != nil { return fmt.Errorf("Error reading {{ $.Name -}}: %s", err) } @@ -752,9 +763,9 @@ func resource{{ $.ResourceName -}}Update(d *schema.ResourceData, meta interface{ log.Printf("[DEBUG] Updating {{ $.Name }} %q: %#v", d.Id(), obj) headers := make(http.Header) {{- if $.UpdateMask }} -{{ template "UpdateMask" $ -}} +{{ $.CustomTemplate "templates/terraform/update_mask.go.tmpl" false -}} {{ end}} -{{ if $.CustomCode.PreUpdate -}} +{{- if $.CustomCode.PreUpdate -}}{{""}} {{ $.CustomTemplate $.CustomCode.PreUpdate true -}} {{ end}} {{ if $.NestedQuery -}} @@ -813,7 +824,10 @@ if len(updateMask) > 0 { if err != nil { return err } -{{ else if $.GetAsync.IsA "PollAsync" -}} +{{- if not $.FieldSpecificUpdateMethods }} +{{""}} +{{- end}} +{{- else if $.GetAsync.IsA "PollAsync" -}} err = transport_tpg.PollingWaitTime(resource{{ $.ResourceName -}}PollRead(d, meta), {{ $.GetAsync.CheckResponseFuncExistence -}}, "Updating {{ $.Name -}}", d.Timeout(schema.TimeoutUpdate), {{ $.GetAsync.TargetOccurrences -}}) if err != nil { {{ if $.GetAsync.SuppressError -}} @@ -828,9 +842,9 @@ if len(updateMask) > 0 { } {{- end}} {{- end}}{{/*if not immutable*/}} -{{ if $.FieldSpecificUpdateMethods }} +{{- if $.FieldSpecificUpdateMethods }} d.Partial(true) -{{ $CustomUpdateProps := $.PropertiesByCustomUpdate }} +{{ $CustomUpdateProps := $.PropertiesByCustomUpdate $.RootProperties }} {{ range $group := $.PropertiesByCustomUpdateGroups }} if d.HasChange("{{ join ($.PropertyNamesToStrings (index $CustomUpdateProps $group)) "\") || d.HasChange(\""}}") { obj := make(map[string]interface{}) @@ -864,13 +878,13 @@ if d.HasChange("{{ join ($.PropertyNamesToStrings (index $CustomUpdateProps $gro {{- end}} }) if err != nil { - return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("{{ $.ResourceName -}} %q", d.Id())) + return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("{{ $.ResourceName }} %q", d.Id())) } obj["{{ $group.FingerprintName }}"] = getRes["{{ $group.FingerprintName }}"] {{ end }}{{/*if FingerprintName*/}} -{{ range $propsByKey := $.CustomUpdatePropertiesByKey $group.UpdateUrl $group.UpdateId $group.FingerprintName $group.UpdateVerb }} +{{ range $propsByKey := $.CustomUpdatePropertiesByKey $.AllUserProperties $group.UpdateUrl $group.UpdateId $group.FingerprintName $group.UpdateVerb }} {{ $propsByKey.ApiName -}}Prop, err := expand{{ if $.NestedQuery -}}Nested{{ end }}{{ $.ResourceName -}}{{ camelize $propsByKey.Name "upper" -}}({{ if $propsByKey.FlattenObject }}nil{{else}}d.Get("{{underscore $propsByKey.Name}}"){{ end }}, d, config) if err != nil { return err @@ -980,7 +994,8 @@ if d.HasChange("{{ join ($.PropertyNamesToStrings (index $CustomUpdateProps $gro {{- end}} {{- end}} } -{{ end }}{{/*range PropertiesByCustomUpdate*/}} +{{- end }}{{/*range PropertiesByCustomUpdate*/}} +{{ "" }} d.Partial(false) {{- end }}{{/*if FieldSpecificUpdateMethods*/}} @@ -1053,7 +1068,7 @@ func resource{{ $.ResourceName }}Delete(d *schema.ResourceData, meta interface{} return transport_tpg.HandleNotFoundError(err, d, "{{ $.Name }}") } {{- if $.UpdateMask }} - url, err = transport_tpg.AddQueryParams(url, map[string]string{"updateMask": "{{- join $.NestedQuery.Keys "," -}}"}) + url, err = transport_tpg.AddQueryParams(url, map[string]string{"updateMask": "{{- join $.NestedQuery.Keys "." -}}"}) if err != nil { return err } @@ -1150,7 +1165,7 @@ func resource{{ $.ResourceName }}Import(d *schema.ResourceData, meta interface{} // Explicitly set virtual fields to default values on import {{- range $vf := $.VirtualFields }} {{- if not (eq $vf.DefaultValue nil) }} - if err := d.Set("{{ $vf.Name }}", {{ $vf.DefaultValue }}); err != nil { + if err := d.Set("{{ $vf.Name }}", {{ $vf.GoLiteral $vf.DefaultValue }}); err != nil { return nil, fmt.Errorf("Error setting {{ $vf.Name }}: %s", err) } {{- end }} @@ -1187,6 +1202,9 @@ func resource{{ $.ResourceName -}}UpdateEncoder(d *schema.ResourceData, meta int {{ template "NestedQuery" $ }} {{- end }} {{- if $.CustomCode.Decoder }} +{{- if and $.CustomCode.UpdateEncoder (not $.NestedQuery ) }} +{{ "" }} +{{- end }} func resource{{ $.ResourceName -}}Decoder(d *schema.ResourceData, meta interface{}, res map[string]interface{}) (map[string]interface{}, error) { {{ $.CustomTemplate $.CustomCode.Decoder false -}} } @@ -1196,4 +1214,6 @@ func resource{{ $.ResourceName -}}PostCreateFailure(d *schema.ResourceData, meta {{- $.CustomTemplate $.CustomCode.PostCreateFailure false -}} } {{- end }} -{{/* TODO state upgraders */}} +{{- if and $.SchemaVersion $.StateUpgraders }} + {{ $.CustomTemplate $.StateMigrationFile false -}} +{{- end }} diff --git a/mmv1/templates/terraform/resource.html.markdown.erb b/mmv1/templates/terraform/resource.html.markdown.erb index cefd14b6a765..e13e38760a62 100644 --- a/mmv1/templates/terraform/resource.html.markdown.erb +++ b/mmv1/templates/terraform/resource.html.markdown.erb @@ -73,7 +73,7 @@ To get more information about <%= object.name -%>, see: <% end # object...api.nil? -%> <% if !object.references.guides.empty? -%> * How-to Guides -<% object.references.guides.each do |title, link| -%> +<% object.references.guides.sort.each do |title, link| -%> * [<%= title -%>](<%= link -%>) <% end # object...guides.each -%> <% end # object...guides.empty? -%> diff --git a/mmv1/templates/terraform/resource.html.markdown.tmpl b/mmv1/templates/terraform/resource.html.markdown.tmpl index cf13c07f9e3e..68c0fd3ad6ec 100644 --- a/mmv1/templates/terraform/resource.html.markdown.tmpl +++ b/mmv1/templates/terraform/resource.html.markdown.tmpl @@ -27,7 +27,7 @@ # ---------------------------------------------------------------------------- subcategory: "{{$.ProductMetadata.DisplayName}}" description: |- - {{ $.FormatDocDescription (firstSentence $.Description) true }} + {{- $.FormatDocDescription (firstSentence $.Description) true }} --- # {{$.TerraformName}} @@ -40,7 +40,7 @@ description: |- ~> **Warning:** This resource is in beta, and should be used with the terraform-provider-google-beta provider. See [Provider Versions](https://terraform.io/docs/providers/google/guides/provider_versions.html) for more details on beta resources. {{- end }} -{{ if $.References}} +{{ if or $.References.Api $.References.Guides }} To get more information about {{$.Name}}, see: {{- if $.References.Api}} @@ -53,9 +53,13 @@ To get more information about {{$.Name}}, see: * [{{$title}}]({{$link}}) {{- end }} {{- end }} -{{ end }} + {{- if gt (len $.Examples) 0}} +{{ "" }} + {{- end }} +{{- else }} +{{ "" }} +{{- end }} {{- if $.Docs.Warning}} - ~> **Warning:** {{$.Docs.Warning}} {{- end }} {{- if $.Docs.Note}} @@ -76,54 +80,60 @@ values will be stored in the raw state as plain text: {{ $.SensitivePropsToStrin {{- end }} - {{- end }} ## Example Usage - {{ title (camelize $e.Name "upper" )}} ```hcl {{ $e.DocumentationHCLText -}} ``` + {{- end }} {{- end }} {{- end }} ## Argument Reference The following arguments are supported: - -{{ range $p := $.RootProperties }} +{{ "" }} +{{ "" }} +{{- range $p := $.RootProperties }} {{- if $p.Required }} -{{- template "propertyDocumentation" $p }} +{{- trimTemplate "property_documentation.html.markdown.tmpl" $p -}} {{- end }} {{- end }} - +{{ "" }} {{- range $p := $.AllUserProperties }} {{- if $p.Required }} -{{- template "nestedPropertyDocumentation" $p}} +{{- trimTemplate "nested_property_documentation.html.markdown.tmpl" $p -}} {{- end}} {{- end }} - - - - - -{{ range $p := $.RootProperties }} +{{ "" }} +{{ "" }} +{{- range $p := $.RootProperties }} {{- if and (not $p.Required) (not $p.Output) }} -{{- template "propertyDocumentation" $p -}} +{{- trimTemplate "property_documentation.html.markdown.tmpl" $p -}} {{- end }} {{- end }} {{- if or (contains $.BaseUrl "{{project}}") (contains $.CreateUrl "{{project}}")}} * `project` - (Optional) The ID of the project in which the resource belongs. If it is not provided, the provider project is used. +{{ "" }} {{- end }} -{{ range $f := $.VirtualFields }} +{{- range $f := $.VirtualFields }} * `{{$f.Name}}` - (Optional) {{$f.Description}} {{- end }} -{{ if $.Docs.OptionalProperties }} +{{- if not $.Docs.OptionalProperties }} +{{ "" }} +{{- end }} +{{- if $.Docs.OptionalProperties }} {{ $.Docs.OptionalProperties }} {{- end }} {{- range $p := $.AllUserProperties }} {{- if and (not $p.Required) (not $p.Output) }} -{{- template "nestedPropertyDocumentation" $p -}} -{{ end}} +{{- trimTemplate "nested_property_documentation.html.markdown.tmpl" $p -}} + {{- end}} {{- end }} +{{- "" }} ## Attributes Reference In addition to the arguments listed above, the following computed attributes are exported: @@ -131,15 +141,16 @@ In addition to the arguments listed above, the following computed attributes are * `id` - an identifier for the resource with format `{{$.IdFormat}}` {{ range $p := $.RootProperties }} {{- if $p.Output }} -{{- template "propertyDocumentation" $p -}} +{{- trimTemplate "property_documentation.html.markdown.tmpl" $p }} {{- end}} {{- end }} -{{- if $.HasSelfLink }} +{{- if $.HasSelfLink -}} * `self_link` - The URI of the created resource. +{{ "" }} {{- end }} {{ range $p := $.AllUserProperties }} {{- if $p.Output }} -{{- template "nestedPropertyDocumentation" $p -}} +{{- trimTemplate "nested_property_documentation.html.markdown.tmpl" $p }} {{- end }} {{- end }} ## Timeouts @@ -148,7 +159,9 @@ This resource provides the following [Timeouts](https://developer.hashicorp.com/terraform/plugin/sdkv2/resources/retries-and-customizable-timeouts) configuration options: - `create` - Default is {{$.Timeouts.InsertMinutes}} minutes. -- `update` - Default is {{$.Timeouts.UpdateMinutes}} minutes.{{/*TODO Q2: <% if updatable?(object, properties) || object.root_labels? -%> */}} +{{- if or $.Updatable $.RootLabels }} +- `update` - Default is {{$.Timeouts.UpdateMinutes}} minutes. +{{- end }} - `delete` - Default is {{$.Timeouts.DeleteMinutes}} minutes. ## Import @@ -160,7 +173,7 @@ This resource does not support import. {{$.Name}} can be imported using any of these accepted formats: {{ range $idFormat := $.ImportIdFormatsFromResource }} -* `{{$idFormat}}` +* `{{replaceAll $idFormat "%" "" }}` {{- end }} @@ -168,7 +181,7 @@ In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashico ```tf import { - id = "{{ index $.ImportIdFormatsFromResource 0 }}" + id = "{{replaceAll (index $.ImportIdFormatsFromResource 0) "%" "" }}" to = {{$.TerraformName}}.default } ``` @@ -177,7 +190,7 @@ When using the [`terraform import` command](https://developer.hashicorp.com/terr ``` {{- range $idFormat := $.ImportIdFormatsFromResource }} -$ terraform import {{$.TerraformName}}.default {{$idFormat}} +$ terraform import {{$.TerraformName}}.default {{replaceAll $idFormat "%" "" }} {{- end }} ``` {{ end }} diff --git a/mmv1/templates/terraform/resource_iam.html.markdown.tmpl b/mmv1/templates/terraform/resource_iam.html.markdown.tmpl index 88bd230b9359..312e02f6b37a 100644 --- a/mmv1/templates/terraform/resource_iam.html.markdown.tmpl +++ b/mmv1/templates/terraform/resource_iam.html.markdown.tmpl @@ -103,7 +103,7 @@ With IAM Conditions: ```hcl data "google_iam_policy" "admin" { -{{ if eq $.MinVersionObj.Name "beta" }} +{{- if eq $.MinVersionObj.Name "beta" }} provider = google-beta {{- end }} binding { @@ -135,7 +135,7 @@ resource "{{ $.IamTerraformName }}_policy" "policy" { resource "{{ $.IamTerraformName }}_binding" "binding" { {{- if eq $.MinVersionObj.Name "beta" }} provider = google-beta -{{- end -}} +{{- end }} {{- $.CustomTemplate $.IamPolicy.ExampleConfigBody false }} role = "{{if $.IamPolicy.AdminIamRole}}{{$.IamPolicy.AdminIamRole }}{{else}}{{$.IamPolicy.AllowedIamRole}}{{end}}" members = [ @@ -169,22 +169,22 @@ resource "{{ $.IamTerraformName }}_binding" "binding" { ```hcl resource "{{ $.IamTerraformName }}_member" "member" { -{{- if eq $.MinVersionObj.Name "beta" -}} +{{- if eq $.MinVersionObj.Name "beta" }} provider = google-beta -{{- end -}} +{{- end }} {{- $.CustomTemplate $.IamPolicy.ExampleConfigBody false }} role = "{{if $.IamPolicy.AdminIamRole}}{{$.IamPolicy.AdminIamRole }}{{else}}{{$.IamPolicy.AllowedIamRole}}{{end}}" member = "user:jane@example.com" } ``` -{{ if $.IamPolicy.IamConditionsRequestType -}} +{{ if $.IamPolicy.IamConditionsRequestType }} With IAM Conditions: ```hcl resource "{{ $.IamTerraformName }}_member" "member" { {{- if eq $.MinVersionObj.Name "beta" }} provider = google-beta -{{- end -}} +{{- end }} {{- $.CustomTemplate $.IamPolicy.ExampleConfigBody false }} role = "{{if $.IamPolicy.AdminIamRole}}{{$.IamPolicy.AdminIamRole}}{{else}}{{$.IamPolicy.AllowedIamRole}}{{end}}" member = "user:jane@example.com" @@ -245,8 +245,7 @@ The following arguments are supported: * `policy_data` - (Required only by `{{ $.IamTerraformName }}_policy`) The policy data generated by a `google_iam_policy` data source. - -{{ if $.IamPolicy.IamConditionsRequestType -}} +{{ if $.IamPolicy.IamConditionsRequestType }} * `condition` - (Optional) An [IAM Condition](https://cloud.google.com/iam/docs/conditions-overview) for a given binding. Structure is documented below. @@ -263,7 +262,7 @@ The `condition` block supports: ~> **Warning:** Terraform considers the `role` and condition contents (`title`+`description`+`expression`) as the identifier for the binding. This means that if any part of the condition is changed out-of-band, Terraform will consider it to be an entirely different resource and will treat it as such. -{{- end -}} +{{- end }} ## Attributes Reference In addition to the arguments listed above, the following computed attributes are @@ -299,8 +298,8 @@ $ terraform import {{ $.IamTerraformName }}_policy.editor {{ $.FirstIamImportIdF -> **Custom Roles**: If you're importing a IAM resource with a custom role, make sure to use the full name of the custom role, e.g. `[projects/my-project|organizations/my-org]/roles/my-custom-role`. +{{- if contains $.BaseUrl "{{project}}" }} -{{ if contains $.BaseUrl "{{project}}" -}} ## User Project Overrides This resource supports [User Project Overrides](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/provider_reference#user_project_override). diff --git a/mmv1/templates/terraform/schema_property.go.tmpl b/mmv1/templates/terraform/schema_property.go.tmpl index 44f21449b2c7..57bde43a980b 100644 --- a/mmv1/templates/terraform/schema_property.go.tmpl +++ b/mmv1/templates/terraform/schema_property.go.tmpl @@ -122,7 +122,7 @@ Default value: {{ .ItemType.DefaultValue -}} DiffSuppressFunc: tpgresource.CompareSelfLinkOrResourceName, {{- end }} {{ else -}} - Type: schema.Type{{ .ItemTypeClass -}}, + Type: {{ .TFType .ItemType.Type }}, {{ end -}} }, {{ end -}} @@ -166,16 +166,16 @@ Default value: {{ .ItemType.DefaultValue -}} Default: {{ .GoLiteral .DefaultValue -}}, {{ end -}} {{ if or .Conflicting .Conflicts -}} - ConflictsWith: {{ .GoLiteral .Conflicting -}}, + ConflictsWith: {{ .GoLiteral (.GetPropertySchemaPathList .Conflicting) -}}, {{ end -}} {{ if or .AtLeastOneOfList .AtLeastOneOf -}} - AtLeastOneOf: {{ .GoLiteral .AtLeastOneOfList -}}, + AtLeastOneOf: {{ .GoLiteral (.GetPropertySchemaPathList .AtLeastOneOfList) -}}, {{ end -}} {{ if or .ExactlyOneOfList .ExactlyOneOf -}} - ExactlyOneOf: {{ .GoLiteral .ExactlyOneOfList -}}, + ExactlyOneOf: {{ .GoLiteral (.GetPropertySchemaPathList .ExactlyOneOfList) -}}, {{ end -}} {{ if or .RequiredWithList .RequiredWith -}} - RequiredWith: {{ .GoLiteral .RequiredWithList -}}, + RequiredWith: {{ .GoLiteral (.GetPropertySchemaPathList .RequiredWithList) -}}, {{ end -}} }, {{- end -}} diff --git a/mmv1/templates/terraform/schema_subresource.go.tmpl b/mmv1/templates/terraform/schema_subresource.go.tmpl index 86e1f7a1c869..485f1425ebdb 100644 --- a/mmv1/templates/terraform/schema_subresource.go.tmpl +++ b/mmv1/templates/terraform/schema_subresource.go.tmpl @@ -13,6 +13,7 @@ # limitations under the License. */}} {{define "SchemaSubResource"}} +{{if and (eq .Type "Array") (.IsSet) (eq .ItemType.Type "NestedObject")}} {{ if and (.IsSet) (eq .Type "Array") (eq .ItemType.Type "NestedObject") -}} func {{ .NamespaceProperty }}Schema() *schema.Resource { return &schema.Resource{ @@ -24,7 +25,7 @@ func {{ .NamespaceProperty }}Schema() *schema.Resource { } } {{ end -}} - +{{end}} {{ if .NestedProperties }} {{- range $prop := $.NestedProperties }} {{template "SchemaSubResource" $prop}} diff --git a/mmv1/templates/terraform/unordered_list_customize_diff.go.tmpl b/mmv1/templates/terraform/unordered_list_customize_diff.go.tmpl index f05a76bf00fe..e80c7d5d1b10 100644 --- a/mmv1/templates/terraform/unordered_list_customize_diff.go.tmpl +++ b/mmv1/templates/terraform/unordered_list_customize_diff.go.tmpl @@ -1,5 +1,5 @@ -{{- define "UnorderedListCustomizeDiff" }} -keys := diff.GetChangedKeysPrefix({{ underscore $.Name }}) +{{- define "UnorderedListCustomizeDiff" -}} +keys := diff.GetChangedKeysPrefix("{{ underscore $.Name }}") if len(keys) == 0 { return nil } @@ -28,11 +28,11 @@ for i := 0; i < count; i++ { } } -oldSet := schema.NewSet(schema.HashResource(Resource{{ $.ResourceMetadata.ResourceName }}().Schema[{{ underscore $.Name }}].Elem.(*schema.Resource)), old) -newSet := schema.NewSet(schema.HashResource(Resource{{ $.ResourceMetadata.ResourceName }}().Schema[{{ underscore $.Name }}].Elem.(*schema.Resource)), new) +oldSet := schema.NewSet(schema.HashResource(Resource{{ $.ResourceMetadata.ResourceName }}().Schema["{{ underscore $.Name }}"].Elem.(*schema.Resource)), old) +newSet := schema.NewSet(schema.HashResource(Resource{{ $.ResourceMetadata.ResourceName }}().Schema["{{ underscore $.Name }}"].Elem.(*schema.Resource)), new) if oldSet.Equal(newSet) { - if err := diff.Clear({{ underscore $.Name }}); err != nil { + if err := diff.Clear("{{ underscore $.Name }}"); err != nil { return err } } diff --git a/mmv1/templates/terraform/update_encoder/compute_service_attachment.go.erb b/mmv1/templates/terraform/update_encoder/compute_service_attachment.go.erb index 03b7dbdb169e..cc3abdc946a6 100644 --- a/mmv1/templates/terraform/update_encoder/compute_service_attachment.go.erb +++ b/mmv1/templates/terraform/update_encoder/compute_service_attachment.go.erb @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -%> - // need to send value in PATCH due to validation bug on api b/198329756 nameProp := d.Get("name") if v, ok := d.GetOkExists("name"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, nameProp)) { diff --git a/mmv1/templates/terraform/update_encoder/go/compute_service_attachment.go.tmpl b/mmv1/templates/terraform/update_encoder/go/compute_service_attachment.go.tmpl index c4c547e72fa0..aca47912a356 100644 --- a/mmv1/templates/terraform/update_encoder/go/compute_service_attachment.go.tmpl +++ b/mmv1/templates/terraform/update_encoder/go/compute_service_attachment.go.tmpl @@ -10,7 +10,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -}} - // need to send value in PATCH due to validation bug on api b/198329756 nameProp := d.Get("name") if v, ok := d.GetOkExists("name"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, nameProp)) { diff --git a/mmv1/templates/terraform/update_mask.go.tmpl b/mmv1/templates/terraform/update_mask.go.tmpl index 6702943ade2e..515db8f04da6 100644 --- a/mmv1/templates/terraform/update_mask.go.tmpl +++ b/mmv1/templates/terraform/update_mask.go.tmpl @@ -9,14 +9,13 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -*/}} -{{- define "UpdateMask" -}} +*/ -}} updateMask := []string{} {{- $maskGroups := $.GetPropertyUpdateMasksGroups $.UpdateBodyProperties "" }} {{- range $key := $.GetPropertyUpdateMasksGroupKeys $.UpdateBodyProperties }} if d.HasChange("{{ $key }}") { - updateMask = append(updateMask, "{{ join (index $maskGroups $key) "\",\""}}") + updateMask = append(updateMask, "{{ join (index $maskGroups $key) "\",\n\""}}") } {{- end }} // updateMask is a URL parameter but not present in the schema, so ReplaceVars @@ -25,4 +24,3 @@ url, err = transport_tpg.AddQueryParams(url, map[string]string{"updateMask": str if err != nil { return err } -{{- end }}{{/* define */}} diff --git a/mmv1/templates/terraform/yaml_conversion.erb b/mmv1/templates/terraform/yaml_conversion.erb index e2f06c8688ff..4a944e2a281c 100644 --- a/mmv1/templates/terraform/yaml_conversion.erb +++ b/mmv1/templates/terraform/yaml_conversion.erb @@ -130,7 +130,7 @@ skip_delete: <%= object.skip_delete %> immutable: <%= object.immutable %> <% end -%> <% unless object.mutex.nil? -%> -mutex: <%= object.mutex %> +mutex: '<%= object.mutex %>' <% end -%> <% #import @@ -184,8 +184,8 @@ async: <% unless object.async.target_occurrences.nil? -%> target_occurrences: <%= object.async.target_occurrences %> <% end -%> - actions: ['<%= object.async.actions.join('\',\'') %>'] <% end -%> + actions: ['<%= object.async.actions.join('\', \'') %>'] <% if object.async.is_a? Api::OpAsync -%> type: 'OpAsync' <% #async.operation %> diff --git a/mmv1/templates/terraform/yaml_conversion_field.erb b/mmv1/templates/terraform/yaml_conversion_field.erb index 3d9b5d39cf1a..ef751e24e860 100644 --- a/mmv1/templates/terraform/yaml_conversion_field.erb +++ b/mmv1/templates/terraform/yaml_conversion_field.erb @@ -151,7 +151,7 @@ <% end -%> <% end -%> <% unless property.default_value.nil? -%> - default_value: <%= property.default_value %> + default_value: <%= go_literal(property.default_value) %> <% end -%> <% unless property.deprecation_message.nil? -%> deprecation_message: '<%= property.deprecation_message %>' diff --git a/mmv1/templates/tgc/resource_converters.go.erb b/mmv1/templates/tgc/resource_converters.go.erb index 7b27f8edc4ff..e0a3f62a3496 100644 --- a/mmv1/templates/tgc/resource_converters.go.erb +++ b/mmv1/templates/tgc/resource_converters.go.erb @@ -115,6 +115,7 @@ func ResourceConverters() map[string][]cai.ResourceConverter { "google_storage_bucket_iam_member": {resourceConverterStorageBucketIamMember()}, "google_compute_node_group": {compute.ResourceConverterComputeNodeGroup()}, "google_logging_folder_bucket_config": {resourceConverterLogFolderBucket()}, + "google_app_engine_standard_app_version": {resourceAppEngineStandardAppVersion()}, "google_logging_organization_bucket_config": {resourceConverterLogOrganizationBucket()}, "google_logging_project_bucket_config": {resourceConverterLogProjectBucket()}, "google_logging_billing_account_bucket_config": {resourceConverterLogBillingAccountBucket()}, diff --git a/mmv1/third_party/terraform/.teamcity/components/inputs/services_beta.kt b/mmv1/third_party/terraform/.teamcity/components/inputs/services_beta.kt index 7804a4a11ad7..92b46f197d84 100644 --- a/mmv1/third_party/terraform/.teamcity/components/inputs/services_beta.kt +++ b/mmv1/third_party/terraform/.teamcity/components/inputs/services_beta.kt @@ -626,6 +626,11 @@ var ServicesListBeta = mapOf( "displayName" to "Securitycentermanagement", "path" to "./google-beta/services/securitycentermanagement" ), + "securitycenterv2" to mapOf( + "name" to "securitycenterv2", + "displayName" to "securitycenterv2", + "path" to "./google-beta/services/securitycenterv2" + ), "securityposture" to mapOf( "name" to "securityposture", "displayName" to "Securityposture", diff --git a/mmv1/third_party/terraform/.teamcity/components/inputs/services_ga.kt b/mmv1/third_party/terraform/.teamcity/components/inputs/services_ga.kt index 76cf658afd9b..c0d118fdc1b6 100644 --- a/mmv1/third_party/terraform/.teamcity/components/inputs/services_ga.kt +++ b/mmv1/third_party/terraform/.teamcity/components/inputs/services_ga.kt @@ -621,6 +621,11 @@ var ServicesListGa = mapOf( "displayName" to "Securitycentermanagement", "path" to "./google/services/securitycentermanagement" ), + "securitycenterv2" to mapOf( + "name" to "securitycenterv2", + "displayName" to "securitycenterv2", + "path" to "./google/services/securitycenterv2" + ), "securityposture" to mapOf( "name" to "securityposture", "displayName" to "Securityposture", diff --git a/mmv1/third_party/terraform/go.mod.erb b/mmv1/third_party/terraform/go.mod.erb index eca77fa58285..20470932ebef 100644 --- a/mmv1/third_party/terraform/go.mod.erb +++ b/mmv1/third_party/terraform/go.mod.erb @@ -28,18 +28,18 @@ require ( github.com/sirupsen/logrus v1.8.1 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 - golang.org/x/net v0.25.0 - golang.org/x/oauth2 v0.20.0 - google.golang.org/api v0.180.0 - google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 - google.golang.org/grpc v1.63.2 - google.golang.org/protobuf v1.34.1 + golang.org/x/net v0.26.0 + golang.org/x/oauth2 v0.21.0 + google.golang.org/api v0.185.0 + google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 + google.golang.org/grpc v1.64.0 + google.golang.org/protobuf v1.34.2 ) require ( bitbucket.org/creachadair/stringset v0.0.8 // indirect - cloud.google.com/go v0.113.0 // indirect - cloud.google.com/go/auth v0.4.1 // indirect + cloud.google.com/go v0.115.0 // indirect + cloud.google.com/go/auth v0.5.1 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect cloud.google.com/go/compute/metadata v0.3.0 // indirect cloud.google.com/go/iam v1.1.8 // indirect @@ -51,7 +51,7 @@ require ( 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/xds/go v0.0.0-20231128003011-0fa0005c9caa // indirect + github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 // 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 @@ -99,14 +99,14 @@ require ( 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.23.0 // indirect + golang.org/x/crypto v0.24.0 // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.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-20240429193739-8cf5692501f6 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae // indirect + google.golang.org/genproto v0.0.0-20240617180043-68d350f18fd4 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/mmv1/third_party/terraform/go.sum b/mmv1/third_party/terraform/go.sum index 30534d3e3b1c..04ec71650cc1 100644 --- a/mmv1/third_party/terraform/go.sum +++ b/mmv1/third_party/terraform/go.sum @@ -1,10 +1,10 @@ bitbucket.org/creachadair/stringset v0.0.8 h1:gQqe4vs8XWgMyijfyKE6K8o4TcyGGrRXe0JvHgx5H+M= bitbucket.org/creachadair/stringset v0.0.8/go.mod h1:AgthVMyMxC/6FK1KBJ2ALdqkZObGN8hOetgpwXyMn34= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.113.0 h1:g3C70mn3lWfckKBiCVsAshabrDg01pQ0pnX1MNtnMkA= -cloud.google.com/go v0.113.0/go.mod h1:glEqlogERKYeePz6ZdkcLJ28Q2I6aERgDDErBg9GzO8= -cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg= -cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro= +cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= +cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= +cloud.google.com/go/auth v0.5.1 h1:0QNO7VThG54LUzKiQxv8C6x1YX7lUrzlAa1nVLF8CIw= +cloud.google.com/go/auth v0.5.1/go.mod h1:vbZT8GjzDf3AVqCcQmqeeM32U9HBFc32vVVAbwDsa6s= cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= cloud.google.com/go/bigtable v1.24.0 h1:RtBERIoZZsQm3LUExDGFWgOwMEHCO04O9/pDA0KoAZI= @@ -44,8 +44,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= -github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= +github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 h1:DBmgJDC9dTfkVyGgipamEh2BpGYxScCH1TOF1LL1cXc= +github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50/go.mod h1:5e1+Vvlzido69INQaVO6d87Qn543Xr6nooe9Kz7oBFM= github.com/creachadair/staticfile v0.1.2/go.mod h1:a3qySzCIXEprDGxk6tSxSI+dBBdLzqeBOMhZ+o2d3pM= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= @@ -293,8 +293,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc= golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= @@ -317,11 +317,11 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= -golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -347,19 +347,19 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -371,14 +371,14 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= -golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4= -google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE= +google.golang.org/api v0.185.0 h1:ENEKk1k4jW8SmmaT6RE+ZasxmxezCrD5Vw4npvr+pAU= +google.golang.org/api v0.185.0/go.mod h1:HNfvIkJGlgrIlrbYkAm9W9IdkmKZjOTVh33YltygGbg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= @@ -387,20 +387,20 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240429193739-8cf5692501f6 h1:MTmrc2F5TZKDKXigcZetYkH04YwqtOPEQJwh4PPOgfk= -google.golang.org/genproto v0.0.0-20240429193739-8cf5692501f6/go.mod h1:2ROWwqCIx97Y7CSyp11xB8fori0wzvD6+gbacaf5c8I= -google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae h1:AH34z6WAGVNkllnKs5raNq3yRq93VnjBG6rpfub/jYk= -google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:FfiGhwUm6CJviekPrc0oJ+7h29e+DmWU6UtjX0ZvI7Y= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 h1:mxSlqyb8ZAHsYDCfiXN1EDdNTdvjUJSLY+OnAUtYNYA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM= +google.golang.org/genproto v0.0.0-20240617180043-68d350f18fd4 h1:CUiCqkPw1nNrNQzCCG4WA65m0nAmQiwXHpub3dNyruU= +google.golang.org/genproto v0.0.0-20240617180043-68d350f18fd4/go.mod h1:EvuUDCulqGgV80RvP1BHuom+smhX4qtlhnNatHuroGQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3 h1:QW9+G6Fir4VcRXVH8x3LilNAb6cxBGLa6+GM4hRwexE= +google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3/go.mod h1:kdrSS/OiLkPrNUpzD4aHgCq2rVuC/YRxok32HXZ4vRE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 h1:Di6ANFilr+S60a4S61ZM00vLdw0IrQOSMS2/6mrnOU0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -412,8 +412,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/mmv1/third_party/terraform/go/go.mod b/mmv1/third_party/terraform/go/go.mod index 9aeb1b3967d5..5fa8af5fb7ca 100644 --- a/mmv1/third_party/terraform/go/go.mod +++ b/mmv1/third_party/terraform/go/go.mod @@ -27,18 +27,18 @@ require ( github.com/sirupsen/logrus v1.8.1 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 - golang.org/x/net v0.25.0 - golang.org/x/oauth2 v0.20.0 - google.golang.org/api v0.180.0 - google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 - google.golang.org/grpc v1.63.2 - google.golang.org/protobuf v1.34.1 + golang.org/x/net v0.26.0 + golang.org/x/oauth2 v0.21.0 + google.golang.org/api v0.185.0 + google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 + google.golang.org/grpc v1.64.0 + google.golang.org/protobuf v1.34.2 ) require ( bitbucket.org/creachadair/stringset v0.0.8 // indirect - cloud.google.com/go v0.113.0 // indirect - cloud.google.com/go/auth v0.4.1 // indirect + cloud.google.com/go v0.115.0 // indirect + cloud.google.com/go/auth v0.5.1 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect cloud.google.com/go/compute/metadata v0.3.0 // indirect cloud.google.com/go/iam v1.1.8 // indirect @@ -50,7 +50,7 @@ require ( 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/xds/go v0.0.0-20231128003011-0fa0005c9caa // indirect + github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 // 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 @@ -98,14 +98,14 @@ require ( 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.23.0 // indirect + golang.org/x/crypto v0.24.0 // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.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-20240429193739-8cf5692501f6 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae // indirect + google.golang.org/genproto v0.0.0-20240617180043-68d350f18fd4 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.erb b/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.erb index 9e26302c84c0..73fa0d83fe75 100644 --- a/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.erb +++ b/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.erb @@ -133,8 +133,10 @@ var handwrittenDatasources = map[string]*schema.Resource{ <% end -%> "google_iap_client": iap.DataSourceGoogleIapClient(), "google_kms_crypto_key": kms.DataSourceGoogleKmsCryptoKey(), + "google_kms_crypto_keys": kms.DataSourceGoogleKmsCryptoKeys(), "google_kms_crypto_key_version": kms.DataSourceGoogleKmsCryptoKeyVersion(), "google_kms_key_ring": kms.DataSourceGoogleKmsKeyRing(), + "google_kms_key_rings": kms.DataSourceGoogleKmsKeyRings(), "google_kms_secret": kms.DataSourceGoogleKmsSecret(), "google_kms_secret_ciphertext": kms.DataSourceGoogleKmsSecretCiphertext(), <% unless version == 'ga' -%> @@ -252,6 +254,7 @@ var handwrittenIAMDatasources = map[string]*schema.Resource{ "google_kms_crypto_key_iam_policy": tpgiamresource.DataSourceIamPolicy(kms.IamKmsCryptoKeySchema, kms.NewKmsCryptoKeyIamUpdater), "google_spanner_instance_iam_policy": tpgiamresource.DataSourceIamPolicy(spanner.IamSpannerInstanceSchema, spanner.NewSpannerInstanceIamUpdater), "google_spanner_database_iam_policy": tpgiamresource.DataSourceIamPolicy(spanner.IamSpannerDatabaseSchema, spanner.NewSpannerDatabaseIamUpdater), + "google_storage_managed_folder_iam_policy": tpgiamresource.DataSourceIamPolicy(storage.StorageManagedFolderIamSchema, storage.StorageManagedFolderIamUpdaterProducer), "google_organization_iam_policy": tpgiamresource.DataSourceIamPolicy(resourcemanager.IamOrganizationSchema, resourcemanager.NewOrganizationIamUpdater), "google_project_iam_policy": tpgiamresource.DataSourceIamPolicy(resourcemanager.IamProjectSchema, resourcemanager.NewProjectIamUpdater), "google_pubsub_subscription_iam_policy": tpgiamresource.DataSourceIamPolicy(pubsub.IamPubsubSubscriptionSchema, pubsub.NewPubsubSubscriptionIamUpdater), @@ -426,6 +429,9 @@ var handwrittenIAMResources = map[string]*schema.Resource{ "google_spanner_database_iam_binding": tpgiamresource.ResourceIamBinding(spanner.IamSpannerDatabaseSchema, spanner.NewSpannerDatabaseIamUpdater, spanner.SpannerDatabaseIdParseFunc), "google_spanner_database_iam_member": tpgiamresource.ResourceIamMember(spanner.IamSpannerDatabaseSchema, spanner.NewSpannerDatabaseIamUpdater, spanner.SpannerDatabaseIdParseFunc), "google_spanner_database_iam_policy": tpgiamresource.ResourceIamPolicy(spanner.IamSpannerDatabaseSchema, spanner.NewSpannerDatabaseIamUpdater, spanner.SpannerDatabaseIdParseFunc), + "google_storage_managed_folder_iam_binding": tpgiamresource.ResourceIamBinding(storage.StorageManagedFolderIamSchema, storage.StorageManagedFolderIamUpdaterProducer, storage.StorageManagedFolderIdParseFunc), + "google_storage_managed_folder_iam_member": tpgiamresource.ResourceIamMember(storage.StorageManagedFolderIamSchema, storage.StorageManagedFolderIamUpdaterProducer, storage.StorageManagedFolderIdParseFunc), + "google_storage_managed_folder_iam_policy": tpgiamresource.ResourceIamPolicy(storage.StorageManagedFolderIamSchema, storage.StorageManagedFolderIamUpdaterProducer, storage.StorageManagedFolderIdParseFunc), "google_organization_iam_binding": tpgiamresource.ResourceIamBinding(resourcemanager.IamOrganizationSchema, resourcemanager.NewOrganizationIamUpdater, resourcemanager.OrgIdParseFunc), "google_organization_iam_member": tpgiamresource.ResourceIamMember(resourcemanager.IamOrganizationSchema, resourcemanager.NewOrganizationIamUpdater, resourcemanager.OrgIdParseFunc), "google_organization_iam_policy": tpgiamresource.ResourceIamPolicy(resourcemanager.IamOrganizationSchema, resourcemanager.NewOrganizationIamUpdater, resourcemanager.OrgIdParseFunc), diff --git a/mmv1/third_party/terraform/services/certificatemanager/resource_certificate_manager_trust_config_test.go b/mmv1/third_party/terraform/services/certificatemanager/resource_certificate_manager_trust_config_test.go index b84cbb0530f6..d976cb10fa39 100644 --- a/mmv1/third_party/terraform/services/certificatemanager/resource_certificate_manager_trust_config_test.go +++ b/mmv1/third_party/terraform/services/certificatemanager/resource_certificate_manager_trust_config_test.go @@ -46,7 +46,7 @@ func testAccCertificateManagerTrustConfig_update0(context map[string]interface{} resource "google_certificate_manager_trust_config" "default" { name = "tf-test-trust-config%{random_suffix}" description = "sample description for the trust config" - location = "us-central1" + location = "global" trust_stores { trust_anchors { @@ -57,6 +57,10 @@ resource "google_certificate_manager_trust_config" "default" { } } + allowlisted_certificates { + pem_certificate = file("test-fixtures/cert.pem") + } + labels = { "foo" = "bar" } @@ -69,7 +73,7 @@ func testAccCertificateManagerTrustConfig_update1(context map[string]interface{} resource "google_certificate_manager_trust_config" "default" { name = "tf-test-trust-config%{random_suffix}" description = "sample description for the trust config 2" - location = "us-central1" + location = "global" trust_stores { trust_anchors { @@ -80,6 +84,10 @@ resource "google_certificate_manager_trust_config" "default" { } } + allowlisted_certificates { + pem_certificate = file("test-fixtures/cert.pem") + } + labels = { "bar" = "foo" } diff --git a/mmv1/third_party/terraform/services/cloudscheduler/resource_cloud_scheduler_job_test.go b/mmv1/third_party/terraform/services/cloudscheduler/resource_cloud_scheduler_job_test.go index afc24a42df70..26711bb51297 100644 --- a/mmv1/third_party/terraform/services/cloudscheduler/resource_cloud_scheduler_job_test.go +++ b/mmv1/third_party/terraform/services/cloudscheduler/resource_cloud_scheduler_job_test.go @@ -5,6 +5,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/services/cloudscheduler" ) func TestAccCloudSchedulerJob_schedulerPausedExample(t *testing.T) { @@ -37,6 +38,45 @@ func TestAccCloudSchedulerJob_schedulerPausedExample(t *testing.T) { }) } +func TestUnitCloudSchedulerJob_LastSlashDiffSuppress(t *testing.T) { + cases := map[string]struct { + Old, New string + ExpectDiffSuppress bool + }{ + "slash to no slash": { + Old: "https://hello-rehvs75zla-uc.a.run.app/", + New: "https://hello-rehvs75zla-uc.a.run.app", + ExpectDiffSuppress: true, + }, + "no slash to slash": { + Old: "https://hello-rehvs75zla-uc.a.run.app", + New: "https://hello-rehvs75zla-uc.a.run.app/", + ExpectDiffSuppress: true, + }, + "slash to slash": { + Old: "https://hello-rehvs75zla-uc.a.run.app/", + New: "https://hello-rehvs75zla-uc.a.run.app/", + ExpectDiffSuppress: true, + }, + "no slash to no slash": { + Old: "https://hello-rehvs75zla-uc.a.run.app", + New: "https://hello-rehvs75zla-uc.a.run.app", + ExpectDiffSuppress: true, + }, + "different domains": { + Old: "https://x.a.run.app/", + New: "https://y.a.run.app", + ExpectDiffSuppress: false, + }, + } + + for tn, tc := range cases { + if cloudscheduler.LastSlashDiffSuppress("uri", tc.Old, tc.New, nil) != tc.ExpectDiffSuppress { + t.Fatalf("bad: %s, '%s' => '%s' expect %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress) + } + } +} + func testAccCloudSchedulerJob_schedulerPaused(context map[string]interface{}) string { return acctest.Nprintf(` resource "google_cloud_scheduler_job" "job" { diff --git a/mmv1/third_party/terraform/services/compute/compute_instance_helpers.go.erb b/mmv1/third_party/terraform/services/compute/compute_instance_helpers.go.erb index 297a10389cda..b0a01710a046 100644 --- a/mmv1/third_party/terraform/services/compute/compute_instance_helpers.go.erb +++ b/mmv1/third_party/terraform/services/compute/compute_instance_helpers.go.erb @@ -613,9 +613,7 @@ func expandConfidentialInstanceConfig(d tpgresource.TerraformResourceData) *comp prefix := "confidential_instance_config.0" return &compute.ConfidentialInstanceConfig{ EnableConfidentialCompute: d.Get(prefix + ".enable_confidential_compute").(bool), - <% unless version == "ga" -%> ConfidentialInstanceType: d.Get(prefix + ".confidential_instance_type").(string), - <% end -%> } } @@ -626,9 +624,7 @@ func flattenConfidentialInstanceConfig(ConfidentialInstanceConfig *compute.Confi return []map[string]interface{}{{ "enable_confidential_compute": ConfidentialInstanceConfig.EnableConfidentialCompute, - <% unless version == "ga" -%> "confidential_instance_type": ConfidentialInstanceConfig.ConfidentialInstanceType, - <% end -%> }} } diff --git a/mmv1/third_party/terraform/services/compute/go/compute_instance_helpers.go.tmpl b/mmv1/third_party/terraform/services/compute/go/compute_instance_helpers.go.tmpl index 1b6d42dafc33..d6bced2b7fee 100644 --- a/mmv1/third_party/terraform/services/compute/go/compute_instance_helpers.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/go/compute_instance_helpers.go.tmpl @@ -612,9 +612,7 @@ func expandConfidentialInstanceConfig(d tpgresource.TerraformResourceData) *comp prefix := "confidential_instance_config.0" return &compute.ConfidentialInstanceConfig{ EnableConfidentialCompute: d.Get(prefix + ".enable_confidential_compute").(bool), - {{- if ne $.TargetVersionName "ga" }} ConfidentialInstanceType: d.Get(prefix + ".confidential_instance_type").(string), - {{- end }} } } @@ -625,9 +623,7 @@ func flattenConfidentialInstanceConfig(ConfidentialInstanceConfig *compute.Confi return []map[string]interface{}{{"{{"}} "enable_confidential_compute": ConfidentialInstanceConfig.EnableConfidentialCompute, - {{- if ne $.TargetVersionName "ga" }} "confidential_instance_type": ConfidentialInstanceConfig.ConfidentialInstanceType, - {{- end }} {{"}}"}} } diff --git a/mmv1/third_party/terraform/services/compute/go/resource_compute_global_forwarding_rule_test.go.tmpl b/mmv1/third_party/terraform/services/compute/go/resource_compute_global_forwarding_rule_test.go.tmpl index 8e0191a8b4e8..8d837b9c2062 100644 --- a/mmv1/third_party/terraform/services/compute/go/resource_compute_global_forwarding_rule_test.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/go/resource_compute_global_forwarding_rule_test.go.tmpl @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/services/compute" ) func TestAccComputeGlobalForwardingRule_updateTarget(t *testing.T) { @@ -163,6 +164,144 @@ func TestAccComputeGlobalForwardingRule_internalLoadBalancing(t *testing.T) { } {{- end }} +func TestUnitComputeGlobalForwardingRule_PortRangeDiffSuppress(t *testing.T) { + cases := map[string]struct { + Old, New string + ExpectDiffSuppress bool + }{ + "different single values": { + Old: "80-80", + New: "443", + ExpectDiffSuppress: false, + }, + "different ranges": { + Old: "80-80", + New: "443-444", + ExpectDiffSuppress: false, + }, + "same single values": { + Old: "80-80", + New: "80", + ExpectDiffSuppress: true, + }, + "same ranges": { + Old: "80-80", + New: "80-80", + ExpectDiffSuppress: false, + }, + } + + for tn, tc := range cases { + if compute.PortRangeDiffSuppress("ports", tc.Old, tc.New, nil) != tc.ExpectDiffSuppress { + t.Fatalf("bad: %s, '%s' => '%s' expect %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress) + } + } +} + +func TestUnitComputeGlobalForwardingRule_InternalIpDiffSuppress(t *testing.T) { + cases := map[string]struct { + Old, New string + ExpectDiffSuppress bool + }{ + "suppress - same long and short ipv6 IPs without netmask": { + Old: "2600:1900:4020:31cd:8000:0:0:0", + New: "2600:1900:4020:31cd:8000::", + ExpectDiffSuppress: true, + }, + "suppress - long and short ipv6 IPs with netmask": { + Old: "2600:1900:4020:31cd:8000:0:0:0/96", + New: "2600:1900:4020:31cd:8000::/96", + ExpectDiffSuppress: true, + }, + "suppress - long ipv6 IP with netmask and short ipv6 IP without netmask": { + Old: "2600:1900:4020:31cd:8000:0:0:0/96", + New: "2600:1900:4020:31cd:8000::", + ExpectDiffSuppress: true, + }, + "suppress - long ipv6 IP without netmask and short ipv6 IP with netmask": { + Old: "2600:1900:4020:31cd:8000:0:0:0", + New: "2600:1900:4020:31cd:8000::/96", + ExpectDiffSuppress: true, + }, + "suppress - long ipv6 IP with netmask and reference": { + Old: "2600:1900:4020:31cd:8000:0:0:0/96", + New: "projects/project_id/regions/region/addresses/address-name", + ExpectDiffSuppress: true, + }, + "suppress - long ipv6 IP without netmask and reference": { + Old: "2600:1900:4020:31cd:8000:0:0:0", + New: "projects/project_id/regions/region/addresses/address-name", + ExpectDiffSuppress: true, + }, + "do not suppress - ipv6 IPs different netmask": { + Old: "2600:1900:4020:31cd:8000:0:0:0/96", + New: "2600:1900:4020:31cd:8000:0:0:0/95", + ExpectDiffSuppress: false, + }, + "do not suppress - reference and ipv6 IP with netmask": { + Old: "projects/project_id/regions/region/addresses/address-name", + New: "2600:1900:4020:31cd:8000:0:0:0/96", + ExpectDiffSuppress: false, + }, + "do not suppress - ipv6 IPs - 1": { + Old: "2600:1900:4020:31cd:8000:0:0:0", + New: "2600:1900:4020:31cd:8001::", + ExpectDiffSuppress: false, + }, + "do not suppress - ipv6 IPs - 2": { + Old: "2600:1900:4020:31cd:8000:0:0:0", + New: "2600:1900:4020:31cd:8000:0:0:8000", + ExpectDiffSuppress: false, + }, + "suppress - ipv4 IPs": { + Old: "1.2.3.4", + New: "1.2.3.4", + ExpectDiffSuppress: true, + }, + "suppress - ipv4 IP without netmask and ipv4 IP with netmask": { + Old: "1.2.3.4", + New: "1.2.3.4/24", + ExpectDiffSuppress: true, + }, + "suppress - ipv4 IP without netmask and reference": { + Old: "1.2.3.4", + New: "projects/project_id/regions/region/addresses/address-name", + ExpectDiffSuppress: true, + }, + "do not suppress - reference and ipv4 IP without netmask": { + Old: "projects/project_id/regions/region/addresses/address-name", + New: "1.2.3.4", + ExpectDiffSuppress: false, + }, + "do not suppress - different ipv4 IPs": { + Old: "1.2.3.4", + New: "1.2.3.5", + ExpectDiffSuppress: false, + }, + "do not suppress - ipv4 IPs different netmask": { + Old: "1.2.3.4/24", + New: "1.2.3.5/25", + ExpectDiffSuppress: false, + }, + "do not suppress - different references": { + Old: "projects/project_id/regions/region/addresses/address-name", + New: "projects/project_id/regions/region/addresses/address-name-1", + ExpectDiffSuppress: false, + }, + "do not suppress - same references": { + Old: "projects/project_id/regions/region/addresses/address-name", + New: "projects/project_id/regions/region/addresses/address-name", + ExpectDiffSuppress: false, + }, + } + + for tn, tc := range cases { + if compute.InternalIpDiffSuppress("ipv4/v6_compare", tc.Old, tc.New, nil) != tc.ExpectDiffSuppress { + t.Fatalf("bad: %s, '%s' => '%s' expect %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress) + } + } +} + func testAccComputeGlobalForwardingRule_httpProxy(fr, targetProxy, proxy, proxy2, backend, hc, urlmap string) string { return fmt.Sprintf(` resource "google_compute_global_forwarding_rule" "forwarding_rule" { diff --git a/mmv1/third_party/terraform/services/compute/go/resource_compute_health_check_test.go.tmpl b/mmv1/third_party/terraform/services/compute/go/resource_compute_health_check_test.go.tmpl index e0b18dab24a6..d762af049515 100644 --- a/mmv1/third_party/terraform/services/compute/go/resource_compute_health_check_test.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/go/resource_compute_health_check_test.go.tmpl @@ -382,3 +382,68 @@ resource "google_compute_health_check" "foobar" { `, hckName) } {{- end }} + +{{ if ne $.TargetVersionName `ga` -}} + +func TestAccComputeHealthCheck_srcRegions_update(t *testing.T) { + t.Parallel() + + hckName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), + CheckDestroy: testAccCheckComputeHealthCheckDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComputeHealthCheck_srcRegions(hckName), + }, + { + ResourceName: "google_compute_health_check.src_region", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeHealthCheck_srcRegions_update(hckName), + }, + { + ResourceName: "google_compute_health_check.src_region", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + + +func testAccComputeHealthCheck_srcRegions(hckName string) string { + return fmt.Sprintf(` +resource "google_compute_health_check" "src_region" { + provider = "google-beta" + name = "%s" + description = "Resource created for Terraform acceptance testing" + check_interval_sec = 30 + source_regions = ["us-central1", "us-east1", "asia-south1"] + http_health_check { + port = "80" + } +} +`, hckName) +} + +func testAccComputeHealthCheck_srcRegions_update(hckName string) string { + return fmt.Sprintf(` +resource "google_compute_health_check" "src_region" { + provider = "google-beta" + name = "%s" + description = "Resource updated for Terraform acceptance testing" + check_interval_sec = 30 + source_regions = ["us-west1", "europe-north1", "asia-south1"] + http_health_check { + port = "80" + } +} +`, hckName) +} + +{{ end }} diff --git a/mmv1/third_party/terraform/services/compute/go/resource_compute_instance.go.tmpl b/mmv1/third_party/terraform/services/compute/go/resource_compute_instance.go.tmpl index 045b0be451fa..5789b30f00ef 100644 --- a/mmv1/third_party/terraform/services/compute/go/resource_compute_instance.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/go/resource_compute_instance.go.tmpl @@ -28,6 +28,30 @@ import ( {{- end }} ) +func IpCidrRangeDiffSuppress(k, old, new string, d *schema.ResourceData) bool { + // The range may be a: + // A) single IP address (e.g. 10.2.3.4) + // B) CIDR format string (e.g. 10.1.2.0/24) + // C) netmask (e.g. /24) + // + // For A) and B), no diff to suppress, they have to match completely. + // For C), The API picks a network IP address and this creates a diff of the form: + // network_interface.0.alias_ip_range.0.ip_cidr_range: "10.128.1.0/24" => "/24" + // We should only compare the mask portion for this case. + if len(new) > 0 && new[0] == '/' { + oldNetmaskStartPos := strings.LastIndex(old, "/") + + if oldNetmaskStartPos != -1 { + oldNetmask := old[strings.LastIndex(old, "/"):] + if oldNetmask == new { + return true + } + } + } + + return false +} + var ( bootDiskKeys = []string{ "boot_disk.0.auto_delete", @@ -417,7 +441,7 @@ func ResourceComputeInstance() *schema.Resource { "ip_cidr_range": { Type: schema.TypeString, Required: true, - DiffSuppressFunc: tpgresource.IpCidrRangeDiffSuppress, + DiffSuppressFunc: IpCidrRangeDiffSuppress, Description: `The IP CIDR range represented by this alias IP range.`, }, "subnetwork_range_name": { @@ -1024,13 +1048,6 @@ be from 0 to 999,999,999 inclusive.`, Description: `The Confidential VM config being used by the instance. on_host_maintenance has to be set to TERMINATE or this will fail to create.`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - {{- if eq $.TargetVersionName "ga" }} - "enable_confidential_compute": { - Type: schema.TypeBool, - Required: true, - Description: `Defines whether the instance should have confidential compute enabled.`, - }, - {{- else }} "enable_confidential_compute": { Type: schema.TypeBool, Optional: true, @@ -1046,7 +1063,6 @@ be from 0 to 999,999,999 inclusive.`, If SEV_SNP, min_cpu_platform = "AMD Milan" is currently required.`, AtLeastOneOf: []string{"confidential_instance_config.0.enable_confidential_compute", "confidential_instance_config.0.confidential_instance_type"}, }, - {{- end }} }, }, }, diff --git a/mmv1/third_party/terraform/services/compute/go/resource_compute_instance_template.go.tmpl b/mmv1/third_party/terraform/services/compute/go/resource_compute_instance_template.go.tmpl index cd2c9d8bfaf9..5937612209e1 100644 --- a/mmv1/third_party/terraform/services/compute/go/resource_compute_instance_template.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/go/resource_compute_instance_template.go.tmpl @@ -523,7 +523,7 @@ Google Cloud KMS.`, Type: schema.TypeString, Required: true, ForceNew: true, - DiffSuppressFunc: tpgresource.IpCidrRangeDiffSuppress, + DiffSuppressFunc: IpCidrRangeDiffSuppress, Description: `The IP CIDR range represented by this alias IP range. This IP CIDR range must belong to the specified subnetwork and cannot contain IP addresses reserved by system or used by other network interfaces. At the time of writing only a netmask (e.g. /24) may be supplied, with a CIDR format resulting in an API error.`, }, "subnetwork_range_name": { @@ -898,14 +898,6 @@ be from 0 to 999,999,999 inclusive.`, Description: `The Confidential VM config being used by the instance. on_host_maintenance has to be set to TERMINATE or this will fail to create.`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - {{- if eq $.TargetVersionName "ga" }} - "enable_confidential_compute": { - Type: schema.TypeBool, - Required: true, - ForceNew: true, - Description: `Defines whether the instance should have confidential compute enabled.`, - }, - {{- else }} "enable_confidential_compute": { Type: schema.TypeBool, Optional: true, @@ -923,7 +915,6 @@ be from 0 to 999,999,999 inclusive.`, If SEV_SNP, min_cpu_platform = "AMD Milan" is currently required.`, AtLeastOneOf: []string{"confidential_instance_config.0.enable_confidential_compute", "confidential_instance_config.0.confidential_instance_type"}, }, - {{- end }} }, }, }, diff --git a/mmv1/third_party/terraform/services/compute/go/resource_compute_instance_template_test.go.tmpl b/mmv1/third_party/terraform/services/compute/go/resource_compute_instance_template_test.go.tmpl index a2c8876a0639..9d4d7dfae60d 100644 --- a/mmv1/third_party/terraform/services/compute/go/resource_compute_instance_template_test.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/go/resource_compute_instance_template_test.go.tmpl @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-google/google/acctest" "github.com/hashicorp/terraform-provider-google/google/envvar" + tpgcompute "github.com/hashicorp/terraform-provider-google/google/services/compute" "github.com/hashicorp/terraform-provider-google/google/tpgresource" {{ if eq $.TargetVersionName `ga` }} @@ -778,9 +779,7 @@ func TestAccComputeInstanceTemplate_ConfidentialInstanceConfigMain(t *testing.T) t.Parallel() var instanceTemplate compute.InstanceTemplate - {{- if ne $.TargetVersionName "ga" }} var instanceTemplate2 compute.InstanceTemplate - {{- end }} acctest.VcrTest(t, resource.TestCase{ PreCheck: func() { acctest.AccTestPreCheck(t) }, @@ -792,13 +791,10 @@ func TestAccComputeInstanceTemplate_ConfidentialInstanceConfigMain(t *testing.T) Check: resource.ComposeTestCheckFunc( testAccCheckComputeInstanceTemplateExists(t, "google_compute_instance_template.foobar", &instanceTemplate), testAccCheckComputeInstanceTemplateHasConfidentialInstanceConfig(&instanceTemplate, true, "SEV"), - {{- if ne $.TargetVersionName "ga" }} testAccCheckComputeInstanceTemplateExists(t, "google_compute_instance_template.foobar2", &instanceTemplate2), testAccCheckComputeInstanceTemplateHasConfidentialInstanceConfig(&instanceTemplate2, true, ""), - {{- end }} ), }, - {{- if ne $.TargetVersionName "ga" }} { Config: testAccComputeInstanceTemplateConfidentialInstanceConfigNoEnable(acctest.RandString(t, 10), "AMD Milan", "SEV_SNP"), Check: resource.ComposeTestCheckFunc( @@ -808,7 +804,6 @@ func TestAccComputeInstanceTemplate_ConfidentialInstanceConfigMain(t *testing.T) testAccCheckComputeInstanceTemplateHasConfidentialInstanceConfig(&instanceTemplate2, false, "SEV_SNP"), ), }, - {{- end }} }, }) } @@ -1533,6 +1528,50 @@ func TestAccComputeInstanceTemplate_resourceManagerTags(t *testing.T) { }) } +func TestUnitComputeInstanceTemplate_IpCidrRangeDiffSuppress(t *testing.T) { + cases := map[string]struct { + Old, New string + ExpectDiffSuppress bool + }{ + "single ip address": { + Old: "10.2.3.4", + New: "10.2.3.5", + ExpectDiffSuppress: false, + }, + "cidr format string": { + Old: "10.1.2.0/24", + New: "10.1.3.0/24", + ExpectDiffSuppress: false, + }, + "netmask same mask": { + Old: "10.1.2.0/24", + New: "/24", + ExpectDiffSuppress: true, + }, + "netmask different mask": { + Old: "10.1.2.0/24", + New: "/32", + ExpectDiffSuppress: false, + }, + "add netmask": { + Old: "", + New: "/24", + ExpectDiffSuppress: false, + }, + "remove netmask": { + Old: "/24", + New: "", + ExpectDiffSuppress: false, + }, + } + + for tn, tc := range cases { + if tpgcompute.IpCidrRangeDiffSuppress("ip_cidr_range", tc.Old, tc.New, nil) != tc.ExpectDiffSuppress { + t.Fatalf("bad: %s, '%s' => '%s' expect %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress) + } + } +} + func testAccCheckComputeInstanceTemplateDestroyProducer(t *testing.T) func(s *terraform.State) error { return func(s *terraform.State) error { config := acctest.GoogleProviderConfig(t) @@ -1859,7 +1898,7 @@ func testAccCheckComputeInstanceTemplateHasAliasIpRange(instanceTemplate *comput return func(s *terraform.State) error { for _, networkInterface := range instanceTemplate.Properties.NetworkInterfaces { for _, aliasIpRange := range networkInterface.AliasIpRanges { - if aliasIpRange.SubnetworkRangeName == subnetworkRangeName && (aliasIpRange.IpCidrRange == iPCidrRange || tpgresource.IpCidrRangeDiffSuppress("ip_cidr_range", aliasIpRange.IpCidrRange, iPCidrRange, nil)) { + if aliasIpRange.SubnetworkRangeName == subnetworkRangeName && (aliasIpRange.IpCidrRange == iPCidrRange || tpgcompute.IpCidrRangeDiffSuppress("ip_cidr_range", aliasIpRange.IpCidrRange, iPCidrRange, nil)) { return nil } } @@ -1986,11 +2025,9 @@ func testAccCheckComputeInstanceTemplateHasConfidentialInstanceConfig(instanceTe if instanceTemplate.Properties.ConfidentialInstanceConfig.EnableConfidentialCompute != EnableConfidentialCompute { return fmt.Errorf("Wrong ConfidentialInstanceConfig EnableConfidentialCompute: expected %t, got, %t", EnableConfidentialCompute, instanceTemplate.Properties.ConfidentialInstanceConfig.EnableConfidentialCompute) } - {{- if ne $.TargetVersionName "ga" }} if instanceTemplate.Properties.ConfidentialInstanceConfig.ConfidentialInstanceType != ConfidentialInstanceType { return fmt.Errorf("Wrong ConfidentialInstanceConfig ConfidentialInstanceType: expected %s, got, %s", ConfidentialInstanceType, instanceTemplate.Properties.ConfidentialInstanceConfig.ConfidentialInstanceType) } - {{- end }} return nil } @@ -3311,9 +3348,7 @@ resource "google_compute_instance_template" "foobar" { confidential_instance_config { enable_confidential_compute = true -{{- if ne $.TargetVersionName "ga" }} confidential_instance_type = %q -{{- end }} } scheduling { @@ -3322,7 +3357,6 @@ resource "google_compute_instance_template" "foobar" { } -{{ if ne $.TargetVersionName `ga` -}} resource "google_compute_instance_template" "foobar2" { name = "tf-test-instance2-template-%s" machine_type = "n2d-standard-2" @@ -3346,15 +3380,9 @@ resource "google_compute_instance_template" "foobar2" { } } -{{- end }} -{{- if eq $.TargetVersionName "ga" }} -`, suffix) -{{- else }} `, suffix, confidentialInstanceType, suffix) -{{- end }} } -{{ if ne $.TargetVersionName `ga` -}} func testAccComputeInstanceTemplateConfidentialInstanceConfigNoEnable(suffix string, minCpuPlatform, confidentialInstanceType string) string { return fmt.Sprintf(` data "google_compute_image" "my_image2" { @@ -3415,7 +3443,6 @@ resource "google_compute_instance_template" "foobar4" { } `, suffix, minCpuPlatform, confidentialInstanceType, suffix, minCpuPlatform, confidentialInstanceType) } -{{- end }} func testAccComputeInstanceTemplateAdvancedMachineFeatures(suffix string) string { return fmt.Sprintf(` diff --git a/mmv1/third_party/terraform/services/compute/go/resource_compute_instance_test.go.tmpl b/mmv1/third_party/terraform/services/compute/go/resource_compute_instance_test.go.tmpl index 27e65e9281ec..3e71b164f462 100644 --- a/mmv1/third_party/terraform/services/compute/go/resource_compute_instance_test.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/go/resource_compute_instance_test.go.tmpl @@ -1889,9 +1889,7 @@ func TestAccComputeInstanceConfidentialInstanceConfigMain(t *testing.T) { t.Parallel() var instance compute.Instance - {{- if ne $.TargetVersionName "ga" }} var instance2 compute.Instance - {{- end }} instanceName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) acctest.VcrTest(t, resource.TestCase{ @@ -1904,13 +1902,10 @@ func TestAccComputeInstanceConfidentialInstanceConfigMain(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckComputeInstanceExists(t, "google_compute_instance.foobar", &instance), testAccCheckComputeInstanceHasConfidentialInstanceConfig(&instance, true, "SEV"), - {{- if ne $.TargetVersionName "ga" }} testAccCheckComputeInstanceExists(t, "google_compute_instance.foobar2", &instance2), testAccCheckComputeInstanceHasConfidentialInstanceConfig(&instance2, true, ""), - {{- end }} ), }, - {{- if ne $.TargetVersionName "ga" }} { Config: testAccComputeInstanceConfidentialInstanceConfigNoEnable(instanceName, "AMD Milan", "SEV_SNP"), Check: resource.ComposeTestCheckFunc( @@ -1920,7 +1915,6 @@ func TestAccComputeInstanceConfidentialInstanceConfigMain(t *testing.T) { testAccCheckComputeInstanceHasConfidentialInstanceConfig(&instance2, false, "SEV_SNP"), ), }, - {{- end }} }, }) } @@ -4310,7 +4304,7 @@ func testAccCheckComputeInstanceHasAliasIpRange(instance *compute.Instance, subn return func(s *terraform.State) error { for _, networkInterface := range instance.NetworkInterfaces { for _, aliasIpRange := range networkInterface.AliasIpRanges { - if aliasIpRange.SubnetworkRangeName == subnetworkRangeName && (aliasIpRange.IpCidrRange == iPCidrRange || tpgresource.IpCidrRangeDiffSuppress("ip_cidr_range", aliasIpRange.IpCidrRange, iPCidrRange, nil)) { + if aliasIpRange.SubnetworkRangeName == subnetworkRangeName && (aliasIpRange.IpCidrRange == iPCidrRange || tpgcompute.IpCidrRangeDiffSuppress("ip_cidr_range", aliasIpRange.IpCidrRange, iPCidrRange, nil)) { return nil } } @@ -4395,11 +4389,9 @@ func testAccCheckComputeInstanceHasConfidentialInstanceConfig(instance *compute. if instance.ConfidentialInstanceConfig.EnableConfidentialCompute != EnableConfidentialCompute { return fmt.Errorf("Wrong ConfidentialInstanceConfig EnableConfidentialCompute: expected %t, got, %t", EnableConfidentialCompute, instance.ConfidentialInstanceConfig.EnableConfidentialCompute) } - {{- if ne $.TargetVersionName "ga" }} if instance.ConfidentialInstanceConfig.ConfidentialInstanceType != ConfidentialInstanceType { return fmt.Errorf("Wrong ConfidentialInstanceConfig ConfidentialInstanceType: expected %s, got, %s", ConfidentialInstanceType, instance.ConfidentialInstanceConfig.ConfidentialInstanceType) } - {{- end }} return nil } @@ -7681,9 +7673,7 @@ resource "google_compute_instance" "foobar" { confidential_instance_config { enable_confidential_compute = true -{{- if ne $.TargetVersionName "ga" }} confidential_instance_type = %q -{{- end }} } scheduling { @@ -7692,7 +7682,6 @@ resource "google_compute_instance" "foobar" { } -{{ if ne $.TargetVersionName `ga` -}} resource "google_compute_instance" "foobar2" { name = "%s2" machine_type = "n2d-standard-2" @@ -7717,15 +7706,9 @@ resource "google_compute_instance" "foobar2" { } } -{{- end }} -{{- if eq $.TargetVersionName "ga" }} -`, instance) -{{- else }} `, instance, confidentialInstanceType, instance) -{{- end }} } -{{ if ne $.TargetVersionName `ga` -}} func testAccComputeInstanceConfidentialInstanceConfigNoEnable(instance string, minCpuPlatform, confidentialInstanceType string) string { return fmt.Sprintf(` data "google_compute_image" "my_image2" { @@ -7788,7 +7771,6 @@ resource "google_compute_instance" "foobar4" { } `, instance, minCpuPlatform, confidentialInstanceType, instance, minCpuPlatform, confidentialInstanceType) } -{{- end }} func testAccComputeInstance_attributionLabelCreate(instance, add, strategy string) string { return fmt.Sprintf(` diff --git a/mmv1/third_party/terraform/services/compute/go/resource_compute_region_instance_template.go.tmpl b/mmv1/third_party/terraform/services/compute/go/resource_compute_region_instance_template.go.tmpl index 68df06e180b7..6ffd6a99546d 100644 --- a/mmv1/third_party/terraform/services/compute/go/resource_compute_region_instance_template.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/go/resource_compute_region_instance_template.go.tmpl @@ -493,7 +493,7 @@ Google Cloud KMS.`, Type: schema.TypeString, Required: true, ForceNew: true, - DiffSuppressFunc: tpgresource.IpCidrRangeDiffSuppress, + DiffSuppressFunc: IpCidrRangeDiffSuppress, Description: `The IP CIDR range represented by this alias IP range. This IP CIDR range must belong to the specified subnetwork and cannot contain IP addresses reserved by system or used by other network interfaces. At the time of writing only a netmask (e.g. /24) may be supplied, with a CIDR format resulting in an API error.`, }, "subnetwork_range_name": { @@ -851,14 +851,6 @@ be from 0 to 999,999,999 inclusive.`, Description: `The Confidential VM config being used by the instance. on_host_maintenance has to be set to TERMINATE or this will fail to create.`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - {{- if eq $.TargetVersionName "ga" }} - "enable_confidential_compute": { - Type: schema.TypeBool, - Required: true, - ForceNew: true, - Description: `Defines whether the instance should have confidential compute enabled.`, - }, - {{- else }} "enable_confidential_compute": { Type: schema.TypeBool, Optional: true, @@ -876,7 +868,6 @@ be from 0 to 999,999,999 inclusive.`, If SEV_SNP, min_cpu_platform = "AMD Milan" is currently required.`, AtLeastOneOf: []string{"confidential_instance_config.0.enable_confidential_compute", "confidential_instance_config.0.confidential_instance_type"}, }, - {{- end }} }, }, }, diff --git a/mmv1/third_party/terraform/services/compute/go/resource_compute_region_instance_template_test.go.tmpl b/mmv1/third_party/terraform/services/compute/go/resource_compute_region_instance_template_test.go.tmpl index f66690696fcb..ddbcb20d5f58 100644 --- a/mmv1/third_party/terraform/services/compute/go/resource_compute_region_instance_template_test.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/go/resource_compute_region_instance_template_test.go.tmpl @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/terraform-provider-google/google/acctest" "github.com/hashicorp/terraform-provider-google/google/envvar" "github.com/hashicorp/terraform-provider-google/google/tpgresource" + tpgcompute "github.com/hashicorp/terraform-provider-google/google/services/compute" transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" {{ if eq $.TargetVersionName `ga` }} @@ -688,9 +689,7 @@ func TestAccComputeRegionInstanceTemplate_ConfidentialInstanceConfigMain(t *test t.Parallel() var instanceTemplate compute.InstanceTemplate - {{- if ne $.TargetVersionName "ga" }} var instanceTemplate2 compute.InstanceTemplate - {{- end }} acctest.VcrTest(t, resource.TestCase{ PreCheck: func() { acctest.AccTestPreCheck(t) }, @@ -702,13 +701,10 @@ func TestAccComputeRegionInstanceTemplate_ConfidentialInstanceConfigMain(t *test Check: resource.ComposeTestCheckFunc( testAccCheckComputeRegionInstanceTemplateExists(t, "google_compute_region_instance_template.foobar", &instanceTemplate), testAccCheckComputeRegionInstanceTemplateHasConfidentialInstanceConfig(&instanceTemplate, true, "SEV"), - {{- if ne $.TargetVersionName "ga" }} testAccCheckComputeRegionInstanceTemplateExists(t, "google_compute_region_instance_template.foobar2", &instanceTemplate2), testAccCheckComputeRegionInstanceTemplateHasConfidentialInstanceConfig(&instanceTemplate2, true, ""), - {{- end }} ), }, - {{- if ne $.TargetVersionName "ga" }} { Config: testAccComputeRegionInstanceTemplateConfidentialInstanceConfigNoEnable(acctest.RandString(t, 10), "AMD Milan", "SEV_SNP"), Check: resource.ComposeTestCheckFunc( @@ -718,7 +714,6 @@ func TestAccComputeRegionInstanceTemplate_ConfidentialInstanceConfigMain(t *test testAccCheckComputeRegionInstanceTemplateHasConfidentialInstanceConfig(&instanceTemplate2, false, "SEV_SNP"), ), }, - {{- end }} }, }) } @@ -1618,7 +1613,7 @@ func testAccCheckComputeRegionInstanceTemplateHasAliasIpRange(instanceTemplate * return func(s *terraform.State) error { for _, networkInterface := range instanceTemplate.Properties.NetworkInterfaces { for _, aliasIpRange := range networkInterface.AliasIpRanges { - if aliasIpRange.SubnetworkRangeName == subnetworkRangeName && (aliasIpRange.IpCidrRange == iPCidrRange || tpgresource.IpCidrRangeDiffSuppress("ip_cidr_range", aliasIpRange.IpCidrRange, iPCidrRange, nil)) { + if aliasIpRange.SubnetworkRangeName == subnetworkRangeName && (aliasIpRange.IpCidrRange == iPCidrRange || tpgcompute.IpCidrRangeDiffSuppress("ip_cidr_range", aliasIpRange.IpCidrRange, iPCidrRange, nil)) { return nil } } @@ -1732,11 +1727,9 @@ func testAccCheckComputeRegionInstanceTemplateHasConfidentialInstanceConfig(inst if instanceTemplate.Properties.ConfidentialInstanceConfig.EnableConfidentialCompute != EnableConfidentialCompute { return fmt.Errorf("Wrong ConfidentialInstanceConfig EnableConfidentialCompute: expected %t, got, %t", EnableConfidentialCompute, instanceTemplate.Properties.ConfidentialInstanceConfig.EnableConfidentialCompute) } - {{- if ne $.TargetVersionName "ga" }} if instanceTemplate.Properties.ConfidentialInstanceConfig.ConfidentialInstanceType != ConfidentialInstanceType { return fmt.Errorf("Wrong ConfidentialInstanceConfig ConfidentialInstanceType: expected %s, got, %s", ConfidentialInstanceType, instanceTemplate.Properties.ConfidentialInstanceConfig.ConfidentialInstanceType) } - {{- end }} return nil } @@ -2883,9 +2876,7 @@ resource "google_compute_region_instance_template" "foobar" { confidential_instance_config { enable_confidential_compute = true -{{- if ne $.TargetVersionName "ga" }} confidential_instance_type = %q -{{- end }} } scheduling { @@ -2894,7 +2885,6 @@ resource "google_compute_region_instance_template" "foobar" { } -{{ if ne $.TargetVersionName `ga` -}} resource "google_compute_region_instance_template" "foobar2" { name = "tf-test-instance2-template-%s" machine_type = "n2d-standard-2" @@ -2919,15 +2909,9 @@ resource "google_compute_region_instance_template" "foobar2" { } } -{{- end }} -{{- if eq $.TargetVersionName "ga" }} -`, suffix) -{{- else }} `, suffix, confidentialInstanceType, suffix) -{{- end }} } -{{ if ne $.TargetVersionName `ga` -}} func testAccComputeRegionInstanceTemplateConfidentialInstanceConfigNoEnable(suffix string, minCpuPlatform, confidentialInstanceType string) string { return fmt.Sprintf(` data "google_compute_image" "my_image2" { @@ -2990,7 +2974,6 @@ resource "google_compute_region_instance_template" "foobar4" { } `, suffix, minCpuPlatform, confidentialInstanceType, suffix, minCpuPlatform, confidentialInstanceType) } -{{- end }} func testAccComputeRegionInstanceTemplateAdvancedMachineFeatures(suffix string) string { return fmt.Sprintf(` diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_global_forwarding_rule_test.go.erb b/mmv1/third_party/terraform/services/compute/resource_compute_global_forwarding_rule_test.go.erb index e4f0036ae180..cbf00c941c03 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_global_forwarding_rule_test.go.erb +++ b/mmv1/third_party/terraform/services/compute/resource_compute_global_forwarding_rule_test.go.erb @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/services/compute" ) func TestAccComputeGlobalForwardingRule_updateTarget(t *testing.T) { @@ -164,6 +165,144 @@ func TestAccComputeGlobalForwardingRule_internalLoadBalancing(t *testing.T) { } <% end -%> +func TestUnitComputeGlobalForwardingRule_PortRangeDiffSuppress(t *testing.T) { + cases := map[string]struct { + Old, New string + ExpectDiffSuppress bool + }{ + "different single values": { + Old: "80-80", + New: "443", + ExpectDiffSuppress: false, + }, + "different ranges": { + Old: "80-80", + New: "443-444", + ExpectDiffSuppress: false, + }, + "same single values": { + Old: "80-80", + New: "80", + ExpectDiffSuppress: true, + }, + "same ranges": { + Old: "80-80", + New: "80-80", + ExpectDiffSuppress: false, + }, + } + + for tn, tc := range cases { + if compute.PortRangeDiffSuppress("ports", tc.Old, tc.New, nil) != tc.ExpectDiffSuppress { + t.Fatalf("bad: %s, '%s' => '%s' expect %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress) + } + } +} + +func TestUnitComputeGlobalForwardingRule_InternalIpDiffSuppress(t *testing.T) { + cases := map[string]struct { + Old, New string + ExpectDiffSuppress bool + }{ + "suppress - same long and short ipv6 IPs without netmask": { + Old: "2600:1900:4020:31cd:8000:0:0:0", + New: "2600:1900:4020:31cd:8000::", + ExpectDiffSuppress: true, + }, + "suppress - long and short ipv6 IPs with netmask": { + Old: "2600:1900:4020:31cd:8000:0:0:0/96", + New: "2600:1900:4020:31cd:8000::/96", + ExpectDiffSuppress: true, + }, + "suppress - long ipv6 IP with netmask and short ipv6 IP without netmask": { + Old: "2600:1900:4020:31cd:8000:0:0:0/96", + New: "2600:1900:4020:31cd:8000::", + ExpectDiffSuppress: true, + }, + "suppress - long ipv6 IP without netmask and short ipv6 IP with netmask": { + Old: "2600:1900:4020:31cd:8000:0:0:0", + New: "2600:1900:4020:31cd:8000::/96", + ExpectDiffSuppress: true, + }, + "suppress - long ipv6 IP with netmask and reference": { + Old: "2600:1900:4020:31cd:8000:0:0:0/96", + New: "projects/project_id/regions/region/addresses/address-name", + ExpectDiffSuppress: true, + }, + "suppress - long ipv6 IP without netmask and reference": { + Old: "2600:1900:4020:31cd:8000:0:0:0", + New: "projects/project_id/regions/region/addresses/address-name", + ExpectDiffSuppress: true, + }, + "do not suppress - ipv6 IPs different netmask": { + Old: "2600:1900:4020:31cd:8000:0:0:0/96", + New: "2600:1900:4020:31cd:8000:0:0:0/95", + ExpectDiffSuppress: false, + }, + "do not suppress - reference and ipv6 IP with netmask": { + Old: "projects/project_id/regions/region/addresses/address-name", + New: "2600:1900:4020:31cd:8000:0:0:0/96", + ExpectDiffSuppress: false, + }, + "do not suppress - ipv6 IPs - 1": { + Old: "2600:1900:4020:31cd:8000:0:0:0", + New: "2600:1900:4020:31cd:8001::", + ExpectDiffSuppress: false, + }, + "do not suppress - ipv6 IPs - 2": { + Old: "2600:1900:4020:31cd:8000:0:0:0", + New: "2600:1900:4020:31cd:8000:0:0:8000", + ExpectDiffSuppress: false, + }, + "suppress - ipv4 IPs": { + Old: "1.2.3.4", + New: "1.2.3.4", + ExpectDiffSuppress: true, + }, + "suppress - ipv4 IP without netmask and ipv4 IP with netmask": { + Old: "1.2.3.4", + New: "1.2.3.4/24", + ExpectDiffSuppress: true, + }, + "suppress - ipv4 IP without netmask and reference": { + Old: "1.2.3.4", + New: "projects/project_id/regions/region/addresses/address-name", + ExpectDiffSuppress: true, + }, + "do not suppress - reference and ipv4 IP without netmask": { + Old: "projects/project_id/regions/region/addresses/address-name", + New: "1.2.3.4", + ExpectDiffSuppress: false, + }, + "do not suppress - different ipv4 IPs": { + Old: "1.2.3.4", + New: "1.2.3.5", + ExpectDiffSuppress: false, + }, + "do not suppress - ipv4 IPs different netmask": { + Old: "1.2.3.4/24", + New: "1.2.3.5/25", + ExpectDiffSuppress: false, + }, + "do not suppress - different references": { + Old: "projects/project_id/regions/region/addresses/address-name", + New: "projects/project_id/regions/region/addresses/address-name-1", + ExpectDiffSuppress: false, + }, + "do not suppress - same references": { + Old: "projects/project_id/regions/region/addresses/address-name", + New: "projects/project_id/regions/region/addresses/address-name", + ExpectDiffSuppress: false, + }, + } + + for tn, tc := range cases { + if compute.InternalIpDiffSuppress("ipv4/v6_compare", tc.Old, tc.New, nil) != tc.ExpectDiffSuppress { + t.Fatalf("bad: %s, '%s' => '%s' expect %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress) + } + } +} + func testAccComputeGlobalForwardingRule_httpProxy(fr, targetProxy, proxy, proxy2, backend, hc, urlmap string) string { return fmt.Sprintf(` resource "google_compute_global_forwarding_rule" "forwarding_rule" { diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_health_check_test.go.erb b/mmv1/third_party/terraform/services/compute/resource_compute_health_check_test.go.erb index b6630fd7e94e..45adf7269b1e 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_health_check_test.go.erb +++ b/mmv1/third_party/terraform/services/compute/resource_compute_health_check_test.go.erb @@ -383,3 +383,68 @@ resource "google_compute_health_check" "foobar" { `, hckName) } <% end -%> + +<% unless version == 'ga' -%> + +func TestAccComputeHealthCheck_srcRegions_update(t *testing.T) { + t.Parallel() + + hckName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), + CheckDestroy: testAccCheckComputeHealthCheckDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComputeHealthCheck_srcRegions(hckName), + }, + { + ResourceName: "google_compute_health_check.src_region", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeHealthCheck_srcRegions_update(hckName), + }, + { + ResourceName: "google_compute_health_check.src_region", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + + +func testAccComputeHealthCheck_srcRegions(hckName string) string { + return fmt.Sprintf(` +resource "google_compute_health_check" "src_region" { + provider = "google-beta" + name = "%s" + description = "Resource created for Terraform acceptance testing" + check_interval_sec = 30 + source_regions = ["us-central1", "us-east1", "asia-south1"] + http_health_check { + port = "80" + } +} +`, hckName) +} + +func testAccComputeHealthCheck_srcRegions_update(hckName string) string { + return fmt.Sprintf(` +resource "google_compute_health_check" "src_region" { + provider = "google-beta" + name = "%s" + description = "Resource updated for Terraform acceptance testing" + check_interval_sec = 30 + source_regions = ["us-west1", "europe-north1", "asia-south1"] + http_health_check { + port = "80" + } +} +`, hckName) +} + +<% end -%> diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_instance.go.erb b/mmv1/third_party/terraform/services/compute/resource_compute_instance.go.erb index f292b032a776..68f26d8eaa4c 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_instance.go.erb +++ b/mmv1/third_party/terraform/services/compute/resource_compute_instance.go.erb @@ -29,6 +29,30 @@ import ( <% end -%> ) +func IpCidrRangeDiffSuppress(k, old, new string, d *schema.ResourceData) bool { + // The range may be a: + // A) single IP address (e.g. 10.2.3.4) + // B) CIDR format string (e.g. 10.1.2.0/24) + // C) netmask (e.g. /24) + // + // For A) and B), no diff to suppress, they have to match completely. + // For C), The API picks a network IP address and this creates a diff of the form: + // network_interface.0.alias_ip_range.0.ip_cidr_range: "10.128.1.0/24" => "/24" + // We should only compare the mask portion for this case. + if len(new) > 0 && new[0] == '/' { + oldNetmaskStartPos := strings.LastIndex(old, "/") + + if oldNetmaskStartPos != -1 { + oldNetmask := old[strings.LastIndex(old, "/"):] + if oldNetmask == new { + return true + } + } + } + + return false +} + var ( bootDiskKeys = []string{ "boot_disk.0.auto_delete", @@ -418,7 +442,7 @@ func ResourceComputeInstance() *schema.Resource { "ip_cidr_range": { Type: schema.TypeString, Required: true, - DiffSuppressFunc: tpgresource.IpCidrRangeDiffSuppress, + DiffSuppressFunc: IpCidrRangeDiffSuppress, Description: `The IP CIDR range represented by this alias IP range.`, }, "subnetwork_range_name": { @@ -1025,13 +1049,6 @@ be from 0 to 999,999,999 inclusive.`, Description: `The Confidential VM config being used by the instance. on_host_maintenance has to be set to TERMINATE or this will fail to create.`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - <% if version == "ga" -%> - "enable_confidential_compute": { - Type: schema.TypeBool, - Required: true, - Description: `Defines whether the instance should have confidential compute enabled.`, - }, - <% else -%> "enable_confidential_compute": { Type: schema.TypeBool, Optional: true, @@ -1047,7 +1064,6 @@ be from 0 to 999,999,999 inclusive.`, If SEV_SNP, min_cpu_platform = "AMD Milan" is currently required.`, AtLeastOneOf: []string{"confidential_instance_config.0.enable_confidential_compute", "confidential_instance_config.0.confidential_instance_type"}, }, - <% end -%> }, }, }, diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_instance_template.go.erb b/mmv1/third_party/terraform/services/compute/resource_compute_instance_template.go.erb index a76fdd9f75aa..29496540192c 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_instance_template.go.erb +++ b/mmv1/third_party/terraform/services/compute/resource_compute_instance_template.go.erb @@ -524,7 +524,7 @@ Google Cloud KMS.`, Type: schema.TypeString, Required: true, ForceNew: true, - DiffSuppressFunc: tpgresource.IpCidrRangeDiffSuppress, + DiffSuppressFunc: IpCidrRangeDiffSuppress, Description: `The IP CIDR range represented by this alias IP range. This IP CIDR range must belong to the specified subnetwork and cannot contain IP addresses reserved by system or used by other network interfaces. At the time of writing only a netmask (e.g. /24) may be supplied, with a CIDR format resulting in an API error.`, }, "subnetwork_range_name": { @@ -899,14 +899,6 @@ be from 0 to 999,999,999 inclusive.`, Description: `The Confidential VM config being used by the instance. on_host_maintenance has to be set to TERMINATE or this will fail to create.`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - <% if version == "ga" -%> - "enable_confidential_compute": { - Type: schema.TypeBool, - Required: true, - ForceNew: true, - Description: `Defines whether the instance should have confidential compute enabled.`, - }, - <% else -%> "enable_confidential_compute": { Type: schema.TypeBool, Optional: true, @@ -924,7 +916,6 @@ be from 0 to 999,999,999 inclusive.`, If SEV_SNP, min_cpu_platform = "AMD Milan" is currently required.`, AtLeastOneOf: []string{"confidential_instance_config.0.enable_confidential_compute", "confidential_instance_config.0.confidential_instance_type"}, }, - <% end -%> }, }, }, diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_instance_template_test.go.erb b/mmv1/third_party/terraform/services/compute/resource_compute_instance_template_test.go.erb index c7adb60d0e11..f1a511161174 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_instance_template_test.go.erb +++ b/mmv1/third_party/terraform/services/compute/resource_compute_instance_template_test.go.erb @@ -18,6 +18,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-google/google/acctest" "github.com/hashicorp/terraform-provider-google/google/envvar" + tpgcompute "github.com/hashicorp/terraform-provider-google/google/services/compute" "github.com/hashicorp/terraform-provider-google/google/tpgresource" <% if version == "ga" -%> @@ -779,9 +780,7 @@ func TestAccComputeInstanceTemplate_ConfidentialInstanceConfigMain(t *testing.T) t.Parallel() var instanceTemplate compute.InstanceTemplate - <% unless version == "ga" -%> var instanceTemplate2 compute.InstanceTemplate - <% end -%> acctest.VcrTest(t, resource.TestCase{ PreCheck: func() { acctest.AccTestPreCheck(t) }, @@ -793,13 +792,10 @@ func TestAccComputeInstanceTemplate_ConfidentialInstanceConfigMain(t *testing.T) Check: resource.ComposeTestCheckFunc( testAccCheckComputeInstanceTemplateExists(t, "google_compute_instance_template.foobar", &instanceTemplate), testAccCheckComputeInstanceTemplateHasConfidentialInstanceConfig(&instanceTemplate, true, "SEV"), - <% unless version == "ga" -%> testAccCheckComputeInstanceTemplateExists(t, "google_compute_instance_template.foobar2", &instanceTemplate2), testAccCheckComputeInstanceTemplateHasConfidentialInstanceConfig(&instanceTemplate2, true, ""), - <% end -%> ), }, - <% unless version == "ga" -%> { Config: testAccComputeInstanceTemplateConfidentialInstanceConfigNoEnable(acctest.RandString(t, 10), "AMD Milan", "SEV_SNP"), Check: resource.ComposeTestCheckFunc( @@ -809,7 +805,6 @@ func TestAccComputeInstanceTemplate_ConfidentialInstanceConfigMain(t *testing.T) testAccCheckComputeInstanceTemplateHasConfidentialInstanceConfig(&instanceTemplate2, false, "SEV_SNP"), ), }, - <% end -%> }, }) } @@ -1534,6 +1529,50 @@ func TestAccComputeInstanceTemplate_resourceManagerTags(t *testing.T) { }) } +func TestUnitComputeInstanceTemplate_IpCidrRangeDiffSuppress(t *testing.T) { + cases := map[string]struct { + Old, New string + ExpectDiffSuppress bool + }{ + "single ip address": { + Old: "10.2.3.4", + New: "10.2.3.5", + ExpectDiffSuppress: false, + }, + "cidr format string": { + Old: "10.1.2.0/24", + New: "10.1.3.0/24", + ExpectDiffSuppress: false, + }, + "netmask same mask": { + Old: "10.1.2.0/24", + New: "/24", + ExpectDiffSuppress: true, + }, + "netmask different mask": { + Old: "10.1.2.0/24", + New: "/32", + ExpectDiffSuppress: false, + }, + "add netmask": { + Old: "", + New: "/24", + ExpectDiffSuppress: false, + }, + "remove netmask": { + Old: "/24", + New: "", + ExpectDiffSuppress: false, + }, + } + + for tn, tc := range cases { + if tpgcompute.IpCidrRangeDiffSuppress("ip_cidr_range", tc.Old, tc.New, nil) != tc.ExpectDiffSuppress { + t.Fatalf("bad: %s, '%s' => '%s' expect %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress) + } + } +} + func testAccCheckComputeInstanceTemplateDestroyProducer(t *testing.T) func(s *terraform.State) error { return func(s *terraform.State) error { config := acctest.GoogleProviderConfig(t) @@ -1860,7 +1899,7 @@ func testAccCheckComputeInstanceTemplateHasAliasIpRange(instanceTemplate *comput return func(s *terraform.State) error { for _, networkInterface := range instanceTemplate.Properties.NetworkInterfaces { for _, aliasIpRange := range networkInterface.AliasIpRanges { - if aliasIpRange.SubnetworkRangeName == subnetworkRangeName && (aliasIpRange.IpCidrRange == iPCidrRange || tpgresource.IpCidrRangeDiffSuppress("ip_cidr_range", aliasIpRange.IpCidrRange, iPCidrRange, nil)) { + if aliasIpRange.SubnetworkRangeName == subnetworkRangeName && (aliasIpRange.IpCidrRange == iPCidrRange || tpgcompute.IpCidrRangeDiffSuppress("ip_cidr_range", aliasIpRange.IpCidrRange, iPCidrRange, nil)) { return nil } } @@ -1987,11 +2026,9 @@ func testAccCheckComputeInstanceTemplateHasConfidentialInstanceConfig(instanceTe if instanceTemplate.Properties.ConfidentialInstanceConfig.EnableConfidentialCompute != EnableConfidentialCompute { return fmt.Errorf("Wrong ConfidentialInstanceConfig EnableConfidentialCompute: expected %t, got, %t", EnableConfidentialCompute, instanceTemplate.Properties.ConfidentialInstanceConfig.EnableConfidentialCompute) } - <% unless version == "ga" -%> if instanceTemplate.Properties.ConfidentialInstanceConfig.ConfidentialInstanceType != ConfidentialInstanceType { return fmt.Errorf("Wrong ConfidentialInstanceConfig ConfidentialInstanceType: expected %s, got, %s", ConfidentialInstanceType, instanceTemplate.Properties.ConfidentialInstanceConfig.ConfidentialInstanceType) } - <% end -%> return nil } @@ -3312,9 +3349,7 @@ resource "google_compute_instance_template" "foobar" { confidential_instance_config { enable_confidential_compute = true -<% unless version == "ga" -%> confidential_instance_type = %q -<% end -%> } scheduling { @@ -3323,7 +3358,6 @@ resource "google_compute_instance_template" "foobar" { } -<% unless version == "ga" -%> resource "google_compute_instance_template" "foobar2" { name = "tf-test-instance2-template-%s" machine_type = "n2d-standard-2" @@ -3347,15 +3381,9 @@ resource "google_compute_instance_template" "foobar2" { } } -<% end -%> -<% if version == "ga" -%> -`, suffix) -<% else -%> `, suffix, confidentialInstanceType, suffix) -<% end -%> } -<% unless version == "ga" -%> func testAccComputeInstanceTemplateConfidentialInstanceConfigNoEnable(suffix string, minCpuPlatform, confidentialInstanceType string) string { return fmt.Sprintf(` data "google_compute_image" "my_image2" { @@ -3416,7 +3444,6 @@ resource "google_compute_instance_template" "foobar4" { } `, suffix, minCpuPlatform, confidentialInstanceType, suffix, minCpuPlatform, confidentialInstanceType) } -<% end -%> func testAccComputeInstanceTemplateAdvancedMachineFeatures(suffix string) string { return fmt.Sprintf(` diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_instance_test.go.erb b/mmv1/third_party/terraform/services/compute/resource_compute_instance_test.go.erb index dde0a9397a76..682c7c6e8ace 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_instance_test.go.erb +++ b/mmv1/third_party/terraform/services/compute/resource_compute_instance_test.go.erb @@ -1890,9 +1890,7 @@ func TestAccComputeInstanceConfidentialInstanceConfigMain(t *testing.T) { t.Parallel() var instance compute.Instance - <% unless version == "ga" -%> var instance2 compute.Instance - <% end -%> instanceName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) acctest.VcrTest(t, resource.TestCase{ @@ -1905,13 +1903,10 @@ func TestAccComputeInstanceConfidentialInstanceConfigMain(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckComputeInstanceExists(t, "google_compute_instance.foobar", &instance), testAccCheckComputeInstanceHasConfidentialInstanceConfig(&instance, true, "SEV"), - <% unless version == "ga" -%> testAccCheckComputeInstanceExists(t, "google_compute_instance.foobar2", &instance2), testAccCheckComputeInstanceHasConfidentialInstanceConfig(&instance2, true, ""), - <% end -%> ), }, - <% unless version == "ga" -%> { Config: testAccComputeInstanceConfidentialInstanceConfigNoEnable(instanceName, "AMD Milan", "SEV_SNP"), Check: resource.ComposeTestCheckFunc( @@ -1921,7 +1916,6 @@ func TestAccComputeInstanceConfidentialInstanceConfigMain(t *testing.T) { testAccCheckComputeInstanceHasConfidentialInstanceConfig(&instance2, false, "SEV_SNP"), ), }, - <% end -%> }, }) } @@ -4311,7 +4305,7 @@ func testAccCheckComputeInstanceHasAliasIpRange(instance *compute.Instance, subn return func(s *terraform.State) error { for _, networkInterface := range instance.NetworkInterfaces { for _, aliasIpRange := range networkInterface.AliasIpRanges { - if aliasIpRange.SubnetworkRangeName == subnetworkRangeName && (aliasIpRange.IpCidrRange == iPCidrRange || tpgresource.IpCidrRangeDiffSuppress("ip_cidr_range", aliasIpRange.IpCidrRange, iPCidrRange, nil)) { + if aliasIpRange.SubnetworkRangeName == subnetworkRangeName && (aliasIpRange.IpCidrRange == iPCidrRange || tpgcompute.IpCidrRangeDiffSuppress("ip_cidr_range", aliasIpRange.IpCidrRange, iPCidrRange, nil)) { return nil } } @@ -4396,11 +4390,9 @@ func testAccCheckComputeInstanceHasConfidentialInstanceConfig(instance *compute. if instance.ConfidentialInstanceConfig.EnableConfidentialCompute != EnableConfidentialCompute { return fmt.Errorf("Wrong ConfidentialInstanceConfig EnableConfidentialCompute: expected %t, got, %t", EnableConfidentialCompute, instance.ConfidentialInstanceConfig.EnableConfidentialCompute) } - <% unless version == "ga" -%> if instance.ConfidentialInstanceConfig.ConfidentialInstanceType != ConfidentialInstanceType { return fmt.Errorf("Wrong ConfidentialInstanceConfig ConfidentialInstanceType: expected %s, got, %s", ConfidentialInstanceType, instance.ConfidentialInstanceConfig.ConfidentialInstanceType) } - <% end -%> return nil } @@ -7682,9 +7674,7 @@ resource "google_compute_instance" "foobar" { confidential_instance_config { enable_confidential_compute = true -<% unless version == "ga" -%> confidential_instance_type = %q -<% end -%> } scheduling { @@ -7693,7 +7683,6 @@ resource "google_compute_instance" "foobar" { } -<% unless version == "ga" -%> resource "google_compute_instance" "foobar2" { name = "%s2" machine_type = "n2d-standard-2" @@ -7718,15 +7707,9 @@ resource "google_compute_instance" "foobar2" { } } -<% end -%> -<% if version == "ga" -%> -`, instance) -<% else -%> `, instance, confidentialInstanceType, instance) -<% end -%> } -<% unless version == "ga" -%> func testAccComputeInstanceConfidentialInstanceConfigNoEnable(instance string, minCpuPlatform, confidentialInstanceType string) string { return fmt.Sprintf(` data "google_compute_image" "my_image2" { @@ -7789,7 +7772,6 @@ resource "google_compute_instance" "foobar4" { } `, instance, minCpuPlatform, confidentialInstanceType, instance, minCpuPlatform, confidentialInstanceType) } -<% end -%> func testAccComputeInstance_attributionLabelCreate(instance, add, strategy string) string { return fmt.Sprintf(` diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_region_instance_template.go.erb b/mmv1/third_party/terraform/services/compute/resource_compute_region_instance_template.go.erb index 46817b0944e8..99de89c3dd39 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_region_instance_template.go.erb +++ b/mmv1/third_party/terraform/services/compute/resource_compute_region_instance_template.go.erb @@ -494,7 +494,7 @@ Google Cloud KMS.`, Type: schema.TypeString, Required: true, ForceNew: true, - DiffSuppressFunc: tpgresource.IpCidrRangeDiffSuppress, + DiffSuppressFunc: IpCidrRangeDiffSuppress, Description: `The IP CIDR range represented by this alias IP range. This IP CIDR range must belong to the specified subnetwork and cannot contain IP addresses reserved by system or used by other network interfaces. At the time of writing only a netmask (e.g. /24) may be supplied, with a CIDR format resulting in an API error.`, }, "subnetwork_range_name": { @@ -852,14 +852,6 @@ be from 0 to 999,999,999 inclusive.`, Description: `The Confidential VM config being used by the instance. on_host_maintenance has to be set to TERMINATE or this will fail to create.`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - <% if version == "ga" -%> - "enable_confidential_compute": { - Type: schema.TypeBool, - Required: true, - ForceNew: true, - Description: `Defines whether the instance should have confidential compute enabled.`, - }, - <% else -%> "enable_confidential_compute": { Type: schema.TypeBool, Optional: true, @@ -877,7 +869,6 @@ be from 0 to 999,999,999 inclusive.`, If SEV_SNP, min_cpu_platform = "AMD Milan" is currently required.`, AtLeastOneOf: []string{"confidential_instance_config.0.enable_confidential_compute", "confidential_instance_config.0.confidential_instance_type"}, }, - <% end -%> }, }, }, diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_region_instance_template_test.go.erb b/mmv1/third_party/terraform/services/compute/resource_compute_region_instance_template_test.go.erb index bbba450d3d5a..2947f3ccb095 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_region_instance_template_test.go.erb +++ b/mmv1/third_party/terraform/services/compute/resource_compute_region_instance_template_test.go.erb @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/terraform-provider-google/google/acctest" "github.com/hashicorp/terraform-provider-google/google/envvar" "github.com/hashicorp/terraform-provider-google/google/tpgresource" + tpgcompute "github.com/hashicorp/terraform-provider-google/google/services/compute" transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" <% if version == "ga" -%> @@ -689,9 +690,7 @@ func TestAccComputeRegionInstanceTemplate_ConfidentialInstanceConfigMain(t *test t.Parallel() var instanceTemplate compute.InstanceTemplate - <% unless version == "ga" -%> var instanceTemplate2 compute.InstanceTemplate - <% end -%> acctest.VcrTest(t, resource.TestCase{ PreCheck: func() { acctest.AccTestPreCheck(t) }, @@ -703,13 +702,10 @@ func TestAccComputeRegionInstanceTemplate_ConfidentialInstanceConfigMain(t *test Check: resource.ComposeTestCheckFunc( testAccCheckComputeRegionInstanceTemplateExists(t, "google_compute_region_instance_template.foobar", &instanceTemplate), testAccCheckComputeRegionInstanceTemplateHasConfidentialInstanceConfig(&instanceTemplate, true, "SEV"), - <% unless version == "ga" -%> testAccCheckComputeRegionInstanceTemplateExists(t, "google_compute_region_instance_template.foobar2", &instanceTemplate2), testAccCheckComputeRegionInstanceTemplateHasConfidentialInstanceConfig(&instanceTemplate2, true, ""), - <% end -%> ), }, - <% unless version == "ga" -%> { Config: testAccComputeRegionInstanceTemplateConfidentialInstanceConfigNoEnable(acctest.RandString(t, 10), "AMD Milan", "SEV_SNP"), Check: resource.ComposeTestCheckFunc( @@ -719,7 +715,6 @@ func TestAccComputeRegionInstanceTemplate_ConfidentialInstanceConfigMain(t *test testAccCheckComputeRegionInstanceTemplateHasConfidentialInstanceConfig(&instanceTemplate2, false, "SEV_SNP"), ), }, - <% end -%> }, }) } @@ -1619,7 +1614,7 @@ func testAccCheckComputeRegionInstanceTemplateHasAliasIpRange(instanceTemplate * return func(s *terraform.State) error { for _, networkInterface := range instanceTemplate.Properties.NetworkInterfaces { for _, aliasIpRange := range networkInterface.AliasIpRanges { - if aliasIpRange.SubnetworkRangeName == subnetworkRangeName && (aliasIpRange.IpCidrRange == iPCidrRange || tpgresource.IpCidrRangeDiffSuppress("ip_cidr_range", aliasIpRange.IpCidrRange, iPCidrRange, nil)) { + if aliasIpRange.SubnetworkRangeName == subnetworkRangeName && (aliasIpRange.IpCidrRange == iPCidrRange || tpgcompute.IpCidrRangeDiffSuppress("ip_cidr_range", aliasIpRange.IpCidrRange, iPCidrRange, nil)) { return nil } } @@ -1733,11 +1728,9 @@ func testAccCheckComputeRegionInstanceTemplateHasConfidentialInstanceConfig(inst if instanceTemplate.Properties.ConfidentialInstanceConfig.EnableConfidentialCompute != EnableConfidentialCompute { return fmt.Errorf("Wrong ConfidentialInstanceConfig EnableConfidentialCompute: expected %t, got, %t", EnableConfidentialCompute, instanceTemplate.Properties.ConfidentialInstanceConfig.EnableConfidentialCompute) } - <% unless version == "ga" -%> if instanceTemplate.Properties.ConfidentialInstanceConfig.ConfidentialInstanceType != ConfidentialInstanceType { return fmt.Errorf("Wrong ConfidentialInstanceConfig ConfidentialInstanceType: expected %s, got, %s", ConfidentialInstanceType, instanceTemplate.Properties.ConfidentialInstanceConfig.ConfidentialInstanceType) } - <% end -%> return nil } @@ -2884,9 +2877,7 @@ resource "google_compute_region_instance_template" "foobar" { confidential_instance_config { enable_confidential_compute = true -<% unless version == "ga" -%> confidential_instance_type = %q -<% end -%> } scheduling { @@ -2895,7 +2886,6 @@ resource "google_compute_region_instance_template" "foobar" { } -<% unless version == "ga" -%> resource "google_compute_region_instance_template" "foobar2" { name = "tf-test-instance2-template-%s" machine_type = "n2d-standard-2" @@ -2920,15 +2910,9 @@ resource "google_compute_region_instance_template" "foobar2" { } } -<% end -%> -<% if version == "ga" -%> -`, suffix) -<% else -%> `, suffix, confidentialInstanceType, suffix) -<% end -%> } -<% unless version == "ga" -%> func testAccComputeRegionInstanceTemplateConfidentialInstanceConfigNoEnable(suffix string, minCpuPlatform, confidentialInstanceType string) string { return fmt.Sprintf(` data "google_compute_image" "my_image2" { @@ -2991,7 +2975,6 @@ resource "google_compute_region_instance_template" "foobar4" { } `, suffix, minCpuPlatform, confidentialInstanceType, suffix, minCpuPlatform, confidentialInstanceType) } -<% end -%> func testAccComputeRegionInstanceTemplateAdvancedMachineFeatures(suffix string) string { return fmt.Sprintf(` diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_ssl_certificate_test.go b/mmv1/third_party/terraform/services/compute/resource_compute_ssl_certificate_test.go index 84031c22e724..2df10cc9c932 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_ssl_certificate_test.go +++ b/mmv1/third_party/terraform/services/compute/resource_compute_ssl_certificate_test.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/services/compute" ) func TestAccComputeSslCertificate_no_name(t *testing.T) { @@ -36,6 +37,45 @@ func TestAccComputeSslCertificate_no_name(t *testing.T) { }) } +func TestUnitComputeManagedSslCertificate_AbsoluteDomainSuppress(t *testing.T) { + cases := map[string]struct { + Old, New string + ExpectDiffSuppress bool + }{ + "new trailing dot": { + Old: "sslcert.tf-test.club", + New: "sslcert.tf-test.club.", + ExpectDiffSuppress: true, + }, + "old trailing dot": { + Old: "sslcert.tf-test.club.", + New: "sslcert.tf-test.club", + ExpectDiffSuppress: true, + }, + "same trailing dot": { + Old: "sslcert.tf-test.club.", + New: "sslcert.tf-test.club.", + ExpectDiffSuppress: false, + }, + "different trailing dot": { + Old: "sslcert.tf-test.club.", + New: "sslcert.tf-test.clubs.", + ExpectDiffSuppress: false, + }, + "different no trailing dot": { + Old: "sslcert.tf-test.club", + New: "sslcert.tf-test.clubs", + ExpectDiffSuppress: false, + }, + } + + for tn, tc := range cases { + if compute.AbsoluteDomainSuppress("managed.0.domains.", tc.Old, tc.New, nil) != tc.ExpectDiffSuppress { + t.Fatalf("bad: %s, '%s' => '%s' expect %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress) + } + } +} + func testAccCheckComputeSslCertificateExists(t *testing.T, n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] diff --git a/mmv1/third_party/terraform/services/container/go/resource_container_cluster.go.tmpl b/mmv1/third_party/terraform/services/container/go/resource_container_cluster.go.tmpl index 8abea04b38ca..79480fe95545 100644 --- a/mmv1/third_party/terraform/services/container/go/resource_container_cluster.go.tmpl +++ b/mmv1/third_party/terraform/services/container/go/resource_container_cluster.go.tmpl @@ -27,6 +27,15 @@ import ( {{- end }} ) +// Single-digit hour is equivalent to hour with leading zero e.g. suppress diff 1:00 => 01:00. +// Assume either value could be in either format. +func Rfc3339TimeDiffSuppress(k, old, new string, d *schema.ResourceData) bool { + if (len(old) == 4 && "0"+old == new) || (len(new) == 4 && "0"+new == old) { + return true + } + return false +} + var ( instanceGroupManagerURL = regexp.MustCompile(fmt.Sprintf("projects/(%s)/zones/([a-z0-9-]*)/instanceGroupManagers/([^/]*)", verify.ProjectRegex)) @@ -1022,7 +1031,7 @@ func ResourceContainerCluster() *schema.Resource { Type: schema.TypeString, Required: true, ValidateFunc: verify.ValidateRFC3339Time, - DiffSuppressFunc: tpgresource.Rfc3339TimeDiffSuppress, + DiffSuppressFunc: Rfc3339TimeDiffSuppress, }, "duration": { Type: schema.TypeString, @@ -1188,9 +1197,9 @@ func ResourceContainerCluster() *schema.Resource { Optional: true, Computed: true, {{- if eq $.TargetVersionName "ga" }} - Description: `GKE components exposing metrics. Valid values include SYSTEM_COMPONENTS, APISERVER, SCHEDULER, CONTROLLER_MANAGER, STORAGE, HPA, POD, DAEMONSET, DEPLOYMENT, STATEFULSET, KUBELET and CADVISOR.`, + Description: `GKE components exposing metrics. Valid values include SYSTEM_COMPONENTS, APISERVER, SCHEDULER, CONTROLLER_MANAGER, STORAGE, HPA, POD, DAEMONSET, DEPLOYMENT, STATEFULSET, KUBELET, CADVISOR and DCGM.`, {{- else }} - Description: `GKE components exposing metrics. Valid values include SYSTEM_COMPONENTS, APISERVER, SCHEDULER, CONTROLLER_MANAGER, STORAGE, HPA, POD, DAEMONSET, DEPLOYMENT, STATEFULSET, WORKLOADS, KUBELET and CADVISOR.`, + Description: `GKE components exposing metrics. Valid values include SYSTEM_COMPONENTS, APISERVER, SCHEDULER, CONTROLLER_MANAGER, STORAGE, HPA, POD, DAEMONSET, DEPLOYMENT, STATEFULSET, WORKLOADS, KUBELET, CADVISOR and DCGM.`, {{- end }} Elem: &schema.Schema{ Type: schema.TypeString, diff --git a/mmv1/third_party/terraform/services/container/go/resource_container_cluster_migratev1.go.tmpl b/mmv1/third_party/terraform/services/container/go/resource_container_cluster_migratev1.go.tmpl index e61fc211f072..e823197d5234 100644 --- a/mmv1/third_party/terraform/services/container/go/resource_container_cluster_migratev1.go.tmpl +++ b/mmv1/third_party/terraform/services/container/go/resource_container_cluster_migratev1.go.tmpl @@ -792,7 +792,7 @@ func resourceContainerClusterResourceV1() *schema.Resource { Type: schema.TypeString, Required: true, ValidateFunc: verify.ValidateRFC3339Time, - DiffSuppressFunc: tpgresource.Rfc3339TimeDiffSuppress, + DiffSuppressFunc: Rfc3339TimeDiffSuppress, }, "duration": { Type: schema.TypeString, @@ -958,9 +958,9 @@ func resourceContainerClusterResourceV1() *schema.Resource { Optional: true, Computed: true, {{- if eq $.TargetVersionName "ga" }} - Description: `GKE components exposing metrics. Valid values include SYSTEM_COMPONENTS, APISERVER, SCHEDULER, CONTROLLER_MANAGER, STORAGE, HPA, POD, DAEMONSET, DEPLOYMENT and STATEFULSET.`, + Description: `GKE components exposing metrics. Valid values include SYSTEM_COMPONENTS, APISERVER, SCHEDULER, CONTROLLER_MANAGER, STORAGE, HPA, POD, DAEMONSET, DEPLOYMENT, STATEFULSET and DCGM.`, {{- else }} - Description: `GKE components exposing metrics. Valid values include SYSTEM_COMPONENTS, APISERVER, SCHEDULER, CONTROLLER_MANAGER, STORAGE, HPA, POD, DAEMONSET, DEPLOYMENT, STATEFULSET and WORKLOADS.`, + Description: `GKE components exposing metrics. Valid values include SYSTEM_COMPONENTS, APISERVER, SCHEDULER, CONTROLLER_MANAGER, STORAGE, HPA, POD, DAEMONSET, DEPLOYMENT, STATEFULSET, WORKLOADS and DCGM.`, {{- end }} Elem: &schema.Schema{ Type: schema.TypeString, diff --git a/mmv1/third_party/terraform/services/container/go/resource_container_cluster_test.go.tmpl b/mmv1/third_party/terraform/services/container/go/resource_container_cluster_test.go.tmpl index 8f4d6808b5a9..5b289f0027f4 100644 --- a/mmv1/third_party/terraform/services/container/go/resource_container_cluster_test.go.tmpl +++ b/mmv1/third_party/terraform/services/container/go/resource_container_cluster_test.go.tmpl @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-google/google/acctest" "github.com/hashicorp/terraform-provider-google/google/envvar" + "github.com/hashicorp/terraform-provider-google/google/services/container" ) func TestAccContainerCluster_basic(t *testing.T) { @@ -596,6 +597,49 @@ func TestAccContainerCluster_withAuthenticatorGroupsConfig(t *testing.T) { }) } +func TestUnitContainerCluster_Rfc3339TimeDiffSuppress(t *testing.T) { + cases := map[string]struct { + Old, New string + ExpectDiffSuppress bool + }{ + "same time, format changed to have leading zero": { + Old: "2:00", + New: "02:00", + ExpectDiffSuppress: true, + }, + "same time, format changed not to have leading zero": { + Old: "02:00", + New: "2:00", + ExpectDiffSuppress: true, + }, + "different time, both without leading zero": { + Old: "2:00", + New: "3:00", + ExpectDiffSuppress: false, + }, + "different time, old with leading zero, new without": { + Old: "02:00", + New: "3:00", + ExpectDiffSuppress: false, + }, + "different time, new with leading zero, oldwithout": { + Old: "2:00", + New: "03:00", + ExpectDiffSuppress: false, + }, + "different time, both with leading zero": { + Old: "02:00", + New: "03:00", + ExpectDiffSuppress: false, + }, + } + for tn, tc := range cases { + if container.Rfc3339TimeDiffSuppress("time", tc.Old, tc.New, nil) != tc.ExpectDiffSuppress { + t.Errorf("bad: %s, '%s' => '%s' expect DiffSuppress to return %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress) + } + } +} + {{ if ne $.TargetVersionName `ga` -}} func testAccContainerCluster_enableMultiNetworking(clusterName string) string { return fmt.Sprintf(` diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster.go.erb b/mmv1/third_party/terraform/services/container/resource_container_cluster.go.erb index c128e2604441..f8107b751894 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster.go.erb +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster.go.erb @@ -28,6 +28,15 @@ import ( <% end -%> ) +// Single-digit hour is equivalent to hour with leading zero e.g. suppress diff 1:00 => 01:00. +// Assume either value could be in either format. +func Rfc3339TimeDiffSuppress(k, old, new string, d *schema.ResourceData) bool { + if (len(old) == 4 && "0"+old == new) || (len(new) == 4 && "0"+new == old) { + return true + } + return false +} + var ( instanceGroupManagerURL = regexp.MustCompile(fmt.Sprintf("projects/(%s)/zones/([a-z0-9-]*)/instanceGroupManagers/([^/]*)", verify.ProjectRegex)) @@ -1023,7 +1032,7 @@ func ResourceContainerCluster() *schema.Resource { Type: schema.TypeString, Required: true, ValidateFunc: verify.ValidateRFC3339Time, - DiffSuppressFunc: tpgresource.Rfc3339TimeDiffSuppress, + DiffSuppressFunc: Rfc3339TimeDiffSuppress, }, "duration": { Type: schema.TypeString, @@ -1189,9 +1198,9 @@ func ResourceContainerCluster() *schema.Resource { Optional: true, Computed: true, <% if version == "ga" -%> - Description: `GKE components exposing metrics. Valid values include SYSTEM_COMPONENTS, APISERVER, SCHEDULER, CONTROLLER_MANAGER, STORAGE, HPA, POD, DAEMONSET, DEPLOYMENT, STATEFULSET, KUBELET and CADVISOR.`, + Description: `GKE components exposing metrics. Valid values include SYSTEM_COMPONENTS, APISERVER, SCHEDULER, CONTROLLER_MANAGER, STORAGE, HPA, POD, DAEMONSET, DEPLOYMENT, STATEFULSET, KUBELET, CADVISOR and DCGM.`, <% else -%> - Description: `GKE components exposing metrics. Valid values include SYSTEM_COMPONENTS, APISERVER, SCHEDULER, CONTROLLER_MANAGER, STORAGE, HPA, POD, DAEMONSET, DEPLOYMENT, STATEFULSET, WORKLOADS, KUBELET and CADVISOR.`, + Description: `GKE components exposing metrics. Valid values include SYSTEM_COMPONENTS, APISERVER, SCHEDULER, CONTROLLER_MANAGER, STORAGE, HPA, POD, DAEMONSET, DEPLOYMENT, STATEFULSET, WORKLOADS, KUBELET, CADVISOR and DCGM.`, <% end -%> Elem: &schema.Schema{ Type: schema.TypeString, diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster_migratev1.go.erb b/mmv1/third_party/terraform/services/container/resource_container_cluster_migratev1.go.erb index 5e73abe2175f..87e86456b2f5 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster_migratev1.go.erb +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster_migratev1.go.erb @@ -793,7 +793,7 @@ func resourceContainerClusterResourceV1() *schema.Resource { Type: schema.TypeString, Required: true, ValidateFunc: verify.ValidateRFC3339Time, - DiffSuppressFunc: tpgresource.Rfc3339TimeDiffSuppress, + DiffSuppressFunc: Rfc3339TimeDiffSuppress, }, "duration": { Type: schema.TypeString, @@ -959,9 +959,9 @@ func resourceContainerClusterResourceV1() *schema.Resource { Optional: true, Computed: true, <% if version == "ga" -%> - Description: `GKE components exposing metrics. Valid values include SYSTEM_COMPONENTS, APISERVER, SCHEDULER, CONTROLLER_MANAGER, STORAGE, HPA, POD, DAEMONSET, DEPLOYMENT and STATEFULSET.`, + Description: `GKE components exposing metrics. Valid values include SYSTEM_COMPONENTS, APISERVER, SCHEDULER, CONTROLLER_MANAGER, STORAGE, HPA, POD, DAEMONSET, DEPLOYMENT, STATEFULSET and DCGM.`, <% else -%> - Description: `GKE components exposing metrics. Valid values include SYSTEM_COMPONENTS, APISERVER, SCHEDULER, CONTROLLER_MANAGER, STORAGE, HPA, POD, DAEMONSET, DEPLOYMENT, STATEFULSET and WORKLOADS.`, + Description: `GKE components exposing metrics. Valid values include SYSTEM_COMPONENTS, APISERVER, SCHEDULER, CONTROLLER_MANAGER, STORAGE, HPA, POD, DAEMONSET, DEPLOYMENT, STATEFULSET, WORKLOADS and DCGM.`, <% end -%> Elem: &schema.Schema{ Type: schema.TypeString, diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.erb b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.erb index 5a917f5cf280..1f018f2a937a 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.erb +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.erb @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-google/google/acctest" "github.com/hashicorp/terraform-provider-google/google/envvar" + "github.com/hashicorp/terraform-provider-google/google/services/container" ) func TestAccContainerCluster_basic(t *testing.T) { @@ -597,6 +598,49 @@ func TestAccContainerCluster_withAuthenticatorGroupsConfig(t *testing.T) { }) } +func TestUnitContainerCluster_Rfc3339TimeDiffSuppress(t *testing.T) { + cases := map[string]struct { + Old, New string + ExpectDiffSuppress bool + }{ + "same time, format changed to have leading zero": { + Old: "2:00", + New: "02:00", + ExpectDiffSuppress: true, + }, + "same time, format changed not to have leading zero": { + Old: "02:00", + New: "2:00", + ExpectDiffSuppress: true, + }, + "different time, both without leading zero": { + Old: "2:00", + New: "3:00", + ExpectDiffSuppress: false, + }, + "different time, old with leading zero, new without": { + Old: "02:00", + New: "3:00", + ExpectDiffSuppress: false, + }, + "different time, new with leading zero, oldwithout": { + Old: "2:00", + New: "03:00", + ExpectDiffSuppress: false, + }, + "different time, both with leading zero": { + Old: "02:00", + New: "03:00", + ExpectDiffSuppress: false, + }, + } + for tn, tc := range cases { + if container.Rfc3339TimeDiffSuppress("time", tc.Old, tc.New, nil) != tc.ExpectDiffSuppress { + t.Errorf("bad: %s, '%s' => '%s' expect DiffSuppress to return %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress) + } + } +} + <% unless version == 'ga' -%> func testAccContainerCluster_enableMultiNetworking(clusterName string) string { return fmt.Sprintf(` diff --git a/mmv1/third_party/terraform/services/dataproc/resource_dataproc_cluster.go b/mmv1/third_party/terraform/services/dataproc/resource_dataproc_cluster.go index 0b3d660053d1..0abed2ab6332 100644 --- a/mmv1/third_party/terraform/services/dataproc/resource_dataproc_cluster.go +++ b/mmv1/third_party/terraform/services/dataproc/resource_dataproc_cluster.go @@ -130,6 +130,17 @@ func diskConfigKeys(configName string) []string { } } +// Suppress diffs for values that are equivalent except for their use of the words "location" +// compared to "region" or "zone" +func LocationDiffSuppress(k, old, new string, d *schema.ResourceData) bool { + return LocationDiffSuppressHelper(old, new) || LocationDiffSuppressHelper(new, old) +} + +func LocationDiffSuppressHelper(a, b string) bool { + return strings.Replace(a, "/locations/", "/regions/", 1) == b || + strings.Replace(a, "/locations/", "/zones/", 1) == b +} + func resourceDataprocLabelDiffSuppress(k, old, new string, d *schema.ResourceData) bool { if strings.HasPrefix(k, resourceDataprocGoogleProvidedLabelPrefix) && new == "" { return true @@ -1430,7 +1441,7 @@ by Dataproc`, Type: schema.TypeString, Required: true, Description: `The autoscaling policy used by the cluster.`, - DiffSuppressFunc: tpgresource.LocationDiffSuppress, + DiffSuppressFunc: LocationDiffSuppress, }, }, }, diff --git a/mmv1/third_party/terraform/services/dataproc/resource_dataproc_cluster_migrate.go b/mmv1/third_party/terraform/services/dataproc/resource_dataproc_cluster_migrate.go index 768d639642b1..0e503027fbc5 100644 --- a/mmv1/third_party/terraform/services/dataproc/resource_dataproc_cluster_migrate.go +++ b/mmv1/third_party/terraform/services/dataproc/resource_dataproc_cluster_migrate.go @@ -1241,7 +1241,7 @@ by Dataproc`, Type: schema.TypeString, Required: true, Description: `The autoscaling policy used by the cluster.`, - DiffSuppressFunc: tpgresource.LocationDiffSuppress, + DiffSuppressFunc: LocationDiffSuppress, }, }, }, diff --git a/mmv1/third_party/terraform/services/dataproc/resource_dataproc_cluster_sweeper.go b/mmv1/third_party/terraform/services/dataproc/resource_dataproc_cluster_sweeper.go index ad4927e28a19..e66dfb7fafba 100644 --- a/mmv1/third_party/terraform/services/dataproc/resource_dataproc_cluster_sweeper.go +++ b/mmv1/third_party/terraform/services/dataproc/resource_dataproc_cluster_sweeper.go @@ -66,7 +66,7 @@ func testSweepDataprocCluster(region string) error { return nil } - resourceList, ok := res["policies"] + resourceList, ok := res["clusters"] if !ok { log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.") return nil diff --git a/mmv1/third_party/terraform/services/gkehub2/go/resource_gke_hub_feature_test.go.tmpl b/mmv1/third_party/terraform/services/gkehub2/go/resource_gke_hub_feature_test.go.tmpl index 6ade6964b38e..d64642fc0f6a 100644 --- a/mmv1/third_party/terraform/services/gkehub2/go/resource_gke_hub_feature_test.go.tmpl +++ b/mmv1/third_party/terraform/services/gkehub2/go/resource_gke_hub_feature_test.go.tmpl @@ -530,8 +530,9 @@ resource "google_gke_hub_feature" "feature" { fleet_default_member_config { configmanagement { version = "1.16.1" - config_sync { - prevent_drift = true + config_sync { + enabled = true + prevent_drift = true source_format = "unstructured" oci { sync_repo = "us-central1-docker.pkg.dev/corp-gke-build-artifacts/acm/configs:latest" diff --git a/mmv1/third_party/terraform/services/gkehub2/resource_gke_hub_feature_test.go.erb b/mmv1/third_party/terraform/services/gkehub2/resource_gke_hub_feature_test.go.erb index 990e5b3f93af..e75d70ca3813 100644 --- a/mmv1/third_party/terraform/services/gkehub2/resource_gke_hub_feature_test.go.erb +++ b/mmv1/third_party/terraform/services/gkehub2/resource_gke_hub_feature_test.go.erb @@ -531,8 +531,9 @@ resource "google_gke_hub_feature" "feature" { fleet_default_member_config { configmanagement { version = "1.16.1" - config_sync { - prevent_drift = true + config_sync { + enabled = true + prevent_drift = true source_format = "unstructured" oci { sync_repo = "us-central1-docker.pkg.dev/corp-gke-build-artifacts/acm/configs:latest" diff --git a/mmv1/third_party/terraform/services/kms/data_source_google_kms_crypto_keys.go b/mmv1/third_party/terraform/services/kms/data_source_google_kms_crypto_keys.go new file mode 100644 index 000000000000..f27d5e9c53e3 --- /dev/null +++ b/mmv1/third_party/terraform/services/kms/data_source_google_kms_crypto_keys.go @@ -0,0 +1,205 @@ +package kms + +import ( + "fmt" + "log" + "net/http" + "regexp" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" +) + +func DataSourceGoogleKmsCryptoKeys() *schema.Resource { + dsSchema := tpgresource.DatasourceSchemaFromResourceSchema(ResourceKMSCryptoKey().Schema) + tpgresource.AddOptionalFieldsToSchema(dsSchema, "name") + tpgresource.AddOptionalFieldsToSchema(dsSchema, "key_ring") + + // We need to explicitly add the id field to the schema used for individual keys + // Currently the id field in the google_kms_crypto_key resource is implied/added by the SDK + dsSchema["id"] = &schema.Schema{ + Type: schema.TypeString, + Computed: true, + } + + return &schema.Resource{ + Read: dataSourceGoogleKmsCryptoKeysRead, + Schema: map[string]*schema.Schema{ + "key_ring": { + Type: schema.TypeString, + Required: true, + Description: `The key ring that the keys belongs to. Format: 'projects/{{project}}/locations/{{location}}/keyRings/{{keyRing}}'.`, + }, + "filter": { + Type: schema.TypeString, + Optional: true, + Description: ` + The filter argument is used to add a filter query parameter that limits which keys are retrieved by the data source: ?filter={{filter}}. + Example values: + + * "name:my-key-" will retrieve keys that contain "my-key-" anywhere in their name. Note: names take the form projects/{{project}}/locations/{{location}}/keyRings/{{keyRing}}/cryptoKeys/{{cryptoKey}}. + * "name=projects/my-project/locations/global/keyRings/my-key-ring/cryptoKeys/my-key-1" will only retrieve a key with that exact name. + + [See the documentation about using filters](https://cloud.google.com/kms/docs/sorting-and-filtering) + `, + }, + "keys": { + Type: schema.TypeList, + Computed: true, + Description: "A list of all the retrieved keys from the provided key ring", + Elem: &schema.Resource{ + Schema: dsSchema, + }, + }, + }, + } +} + +func dataSourceGoogleKmsCryptoKeysRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + + keyRingId, err := parseKmsKeyRingId(d.Get("key_ring").(string), config) + if err != nil { + return err + } + + id := fmt.Sprintf("%s/cryptoKeys", keyRingId.KeyRingId()) + if filter, ok := d.GetOk("filter"); ok { + id += "/filter=" + filter.(string) + } + d.SetId(id) + + log.Printf("[DEBUG] Searching for keys in key ring %s", keyRingId.KeyRingId()) + keys, err := dataSourceKMSCryptoKeysList(d, meta, keyRingId.KeyRingId()) + if err != nil { + return err + } + + if len(keys) > 0 { + log.Printf("[DEBUG] Found %d keys in key ring %s", len(keys), keyRingId.KeyRingId()) + value, err := flattenKMSKeysList(d, config, keys, keyRingId.KeyRingId()) + if err != nil { + return fmt.Errorf("error flattening keys list: %s", err) + } + if err := d.Set("keys", value); err != nil { + return fmt.Errorf("error setting keys: %s", err) + } + } else { + log.Printf("[DEBUG] Found 0 keys in key ring %s", keyRingId.KeyRingId()) + } + + return nil +} + +// dataSourceKMSCryptoKeysList calls the list endpoint for Crypto Key resources and collects all keys in a slice. +// This function handles pagination by collecting the resources returned by multiple calls to the list endpoint. +// This function also handles server-side filtering by setting the filter query parameter on each API call. +func dataSourceKMSCryptoKeysList(d *schema.ResourceData, meta interface{}, keyRingId string) ([]interface{}, error) { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return nil, err + } + + url, err := tpgresource.ReplaceVars(d, config, "{{KMSBasePath}}{{key_ring}}/cryptoKeys") + if err != nil { + return nil, err + } + + billingProject := "" + + if parts := regexp.MustCompile(`projects\/([^\/]+)\/`).FindStringSubmatch(url); parts != nil { + billingProject = parts[1] + } + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + // Always include the filter param, and optionally include the pageToken parameter for subsequent requests + var params = make(map[string]string, 0) + if filter, ok := d.GetOk("filter"); ok { + log.Printf("[DEBUG] Search for keys in key ring %s is using filter ?filter=%s", keyRingId, filter.(string)) + params["filter"] = filter.(string) + } + + cryptoKeys := make([]interface{}, 0) + for { + // Depending on previous iterations, params might contain a pageToken param + url, err = transport_tpg.AddQueryParams(url, params) + if err != nil { + return nil, err + } + + headers := make(http.Header) + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Headers: headers, + // ErrorRetryPredicates used to allow retrying if rate limits are hit when requesting multiple pages in a row + ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{transport_tpg.Is429RetryableQuotaError}, + }) + if err != nil { + return nil, transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("KMSCryptoKeys %q", d.Id())) + } + + if res == nil { + // Decoding the object has resulted in it being gone. It may be marked deleted + log.Printf("[DEBUG] Removing KMSCryptoKey because it no longer exists.") + d.SetId("") + return nil, nil + } + + // Store info from this page + if v, ok := res["cryptoKeys"].([]interface{}); ok { + cryptoKeys = append(cryptoKeys, v...) + } + + // Handle pagination for next loop, or break loop + v, ok := res["nextPageToken"] + if ok { + params["pageToken"] = v.(string) + } + if !ok { + break + } + } + return cryptoKeys, nil +} + +// flattenKMSKeysList flattens a list of crypto keys from a given crypto key ring +func flattenKMSKeysList(d *schema.ResourceData, config *transport_tpg.Config, keysList []interface{}, keyRingId string) ([]interface{}, error) { + var keys []interface{} + for _, k := range keysList { + key := k.(map[string]interface{}) + parsedId, err := ParseKmsCryptoKeyId(key["name"].(string), config) + if err != nil { + return nil, err + } + + data := map[string]interface{}{} + // The google_kms_crypto_key resource and dataset set + // id as the value of name (projects/{{project}}/locations/{{location}}/keyRings/{{keyRing}}/cryptoKeys/{{name}}) + // and set name is set as just {{name}}. + data["id"] = key["name"] + data["name"] = parsedId.Name + data["key_ring"] = keyRingId + + data["labels"] = flattenKMSCryptoKeyLabels(key["labels"], d, config) + data["primary"] = flattenKMSCryptoKeyPrimary(key["primary"], d, config) + data["purpose"] = flattenKMSCryptoKeyPurpose(key["purpose"], d, config) + data["rotation_period"] = flattenKMSCryptoKeyRotationPeriod(key["rotationPeriod"], d, config) + data["version_template"] = flattenKMSCryptoKeyVersionTemplate(key["versionTemplate"], d, config) + data["destroy_scheduled_duration"] = flattenKMSCryptoKeyDestroyScheduledDuration(key["destroyScheduledDuration"], d, config) + data["import_only"] = flattenKMSCryptoKeyImportOnly(key["importOnly"], d, config) + data["crypto_key_backend"] = flattenKMSCryptoKeyCryptoKeyBackend(key["cryptoKeyBackend"], d, config) + keys = append(keys, data) + } + + return keys, nil +} diff --git a/mmv1/third_party/terraform/services/kms/data_source_google_kms_crypto_keys_test.go b/mmv1/third_party/terraform/services/kms/data_source_google_kms_crypto_keys_test.go new file mode 100644 index 000000000000..bb0fcf78ab9d --- /dev/null +++ b/mmv1/third_party/terraform/services/kms/data_source_google_kms_crypto_keys_test.go @@ -0,0 +1,72 @@ +package kms_test + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-google/google/acctest" +) + +func TestAccDataSourceGoogleKmsCryptoKeys_basic(t *testing.T) { + kms := acctest.BootstrapKMSKey(t) + + id := kms.KeyRing.Name + "/cryptoKeys" + + randomString := acctest.RandString(t, 10) + filterNameFindSharedKeys := "name:tftest-shared-" + filterNameFindsNoKeys := fmt.Sprintf("name:%s", randomString) + + findSharedKeysId := fmt.Sprintf("%s/filter=%s", id, filterNameFindSharedKeys) + findsNoKeysId := fmt.Sprintf("%s/filter=%s", id, filterNameFindsNoKeys) + + context := map[string]interface{}{ + "key_ring": kms.KeyRing.Name, + "filter": "", // Can be overridden using 2nd argument to config funcs + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccDataSourceGoogleKmsCryptoKeys_basic(context, ""), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.google_kms_crypto_keys.all_keys_in_ring", "id", id), + resource.TestCheckResourceAttr("data.google_kms_crypto_keys.all_keys_in_ring", "key_ring", kms.KeyRing.Name), + resource.TestMatchResourceAttr("data.google_kms_crypto_keys.all_keys_in_ring", "keys.#", regexp.MustCompile("[1-9]+[0-9]*")), + ), + }, + { + Config: testAccDataSourceGoogleKmsCryptoKeys_basic(context, fmt.Sprintf("filter = \"%s\"", filterNameFindSharedKeys)), + Check: resource.ComposeTestCheckFunc( + // This filter should retrieve keys in the bootstrapped KMS key ring used by the test + resource.TestCheckResourceAttr("data.google_kms_crypto_keys.all_keys_in_ring", "id", findSharedKeysId), + resource.TestCheckResourceAttr("data.google_kms_crypto_keys.all_keys_in_ring", "key_ring", kms.KeyRing.Name), + resource.TestMatchResourceAttr("data.google_kms_crypto_keys.all_keys_in_ring", "keys.#", regexp.MustCompile("[1-9]+[0-9]*")), + ), + }, + { + Config: testAccDataSourceGoogleKmsCryptoKeys_basic(context, fmt.Sprintf("filter = \"%s\"", filterNameFindsNoKeys)), + Check: resource.ComposeTestCheckFunc( + // This filter should retrieve no keys + resource.TestCheckResourceAttr("data.google_kms_crypto_keys.all_keys_in_ring", "id", findsNoKeysId), + resource.TestCheckResourceAttr("data.google_kms_crypto_keys.all_keys_in_ring", "key_ring", kms.KeyRing.Name), + resource.TestCheckResourceAttr("data.google_kms_crypto_keys.all_keys_in_ring", "keys.#", "0"), + ), + }, + }, + }) +} + +func testAccDataSourceGoogleKmsCryptoKeys_basic(context map[string]interface{}, filter string) string { + context["filter"] = filter + + return acctest.Nprintf(` +data "google_kms_crypto_keys" "all_keys_in_ring" { + key_ring = "%{key_ring}" + %{filter} +} +`, context) +} diff --git a/mmv1/third_party/terraform/services/kms/data_source_google_kms_key_rings.go b/mmv1/third_party/terraform/services/kms/data_source_google_kms_key_rings.go new file mode 100644 index 000000000000..fdc9be311b34 --- /dev/null +++ b/mmv1/third_party/terraform/services/kms/data_source_google_kms_key_rings.go @@ -0,0 +1,173 @@ +package kms + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" +) + +func DataSourceGoogleKmsKeyRings() *schema.Resource { + return &schema.Resource{ + Read: dataSourceGoogleKmsKeyRingsRead, + Schema: map[string]*schema.Schema{ + "project": { + Type: schema.TypeString, + Optional: true, + Description: `Project ID of the project.`, + }, + "location": { + Type: schema.TypeString, + Required: true, + Description: `The canonical id for the location. For example: "us-east1".`, + }, + "filter": { + Type: schema.TypeString, + Optional: true, + Description: ` + The filter argument is used to add a filter query parameter that limits which keys are retrieved by the data source: ?filter={{filter}}. + Example values: + + * "name:my-key-" will retrieve key rings that contain "my-key-" anywhere in their name. Note: names take the form projects/{{project}}/locations/{{location}}/keyRings/{{keyRing}}. + * "name=projects/my-project/locations/global/keyRings/my-key-ring" will only retrieve a key ring with that exact name. + + [See the documentation about using filters](https://cloud.google.com/kms/docs/sorting-and-filtering) + `, + }, + "key_rings": { + Type: schema.TypeList, + Computed: true, + Description: "A list of all the retrieved key rings", + Elem: &schema.Resource{ + // schema isn't used from resource_kms_key_ring due to having project and location fields which are empty when grabbed in a list. + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceGoogleKmsKeyRingsRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + + id, err := tpgresource.ReplaceVars(d, config, "projects/{{project}}/locations/{{location}}/keyRings") + if err != nil { + return err + } + if filter, ok := d.GetOk("filter"); ok { + id += "/filter=" + filter.(string) + } + d.SetId(id) + + log.Printf("[DEBUG] Searching for keyrings") + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for keyRings: %s", err) + } + billingProject = project + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + var keyRings []interface{} + + params := make(map[string]string) + if filter, ok := d.GetOk("filter"); ok { + log.Printf("[DEBUG] Search for key rings using filter ?filter=%s", filter.(string)) + params["filter"] = filter.(string) + if err != nil { + return err + } + } + + url, err := tpgresource.ReplaceVars(d, config, "{{KMSBasePath}}projects/{{project}}/locations/{{location}}/keyRings") + if err != nil { + return err + } + + for { + url, err = transport_tpg.AddQueryParams(url, params) + if err != nil { + return err + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{transport_tpg.Is429RetryableQuotaError}, + }) + if err != nil { + return fmt.Errorf("Error retrieving buckets: %s", err) + } + + if res["keyRings"] == nil { + break + } + pageKeyRings, err := flattenKMSKeyRingsList(config, res["keyRings"]) + if err != nil { + return fmt.Errorf("error flattening key rings list: %s", err) + } + keyRings = append(keyRings, pageKeyRings...) + + pToken, ok := res["nextPageToken"] + if ok && pToken != nil && pToken.(string) != "" { + params["pageToken"] = pToken.(string) + } else { + break + } + } + + log.Printf("[DEBUG] Found %d key rings", len(keyRings)) + if err := d.Set("key_rings", keyRings); err != nil { + return fmt.Errorf("error setting key rings: %s", err) + } + + return nil +} + +// flattenKMSKeyRingsList flattens a list of key rings +func flattenKMSKeyRingsList(config *transport_tpg.Config, keyRingsList interface{}) ([]interface{}, error) { + var keyRings []interface{} + for _, k := range keyRingsList.([]interface{}) { + keyRing := k.(map[string]interface{}) + + parsedId, err := parseKmsKeyRingId(keyRing["name"].(string), config) + if err != nil { + return nil, err + } + + data := map[string]interface{}{} + // The google_kms_key_rings resource and dataset set + // id as the value of name (projects/{{project}}/locations/{{location}}/keyRings/{{name}}) + // and set name is set as just {{name}}. + data["id"] = keyRing["name"] + data["name"] = parsedId.Name + + keyRings = append(keyRings, data) + } + + return keyRings, nil +} diff --git a/mmv1/third_party/terraform/services/kms/data_source_google_kms_key_rings_test.go b/mmv1/third_party/terraform/services/kms/data_source_google_kms_key_rings_test.go new file mode 100644 index 000000000000..779cd0cc076a --- /dev/null +++ b/mmv1/third_party/terraform/services/kms/data_source_google_kms_key_rings_test.go @@ -0,0 +1,70 @@ +package kms_test + +import ( + "fmt" + "regexp" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-google/google/acctest" +) + +func TestAccDataSourceGoogleKmsKeyRings_basic(t *testing.T) { + kms := acctest.BootstrapKMSKey(t) + idPath := strings.Split(kms.KeyRing.Name, "/") + location := idPath[3] + randomString := acctest.RandString(t, 10) + filterNameFindSharedKeys := "name:tftest-shared-" + filterNameFindsNoKeys := fmt.Sprintf("name:%s", randomString) + + keyRingsID := fmt.Sprintf("projects/%s/locations/%s/keyRings", idPath[1], location) + findSharedKeysId := fmt.Sprintf("%s/filter=%s", keyRingsID, filterNameFindSharedKeys) + findsNoKeysId := fmt.Sprintf("%s/filter=%s", keyRingsID, filterNameFindsNoKeys) + + context := map[string]interface{}{ + "filter": "", // Can be overridden using 2nd argument to config funcs + "location": location, + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccDataSourceGoogleKmsKeyRings_basic(context, ""), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.google_kms_key_rings.all_key_rings", "id", keyRingsID), + resource.TestMatchResourceAttr("data.google_kms_key_rings.all_key_rings", "key_rings.#", regexp.MustCompile("[1-9]+[0-9]*")), + ), + }, + { + Config: testAccDataSourceGoogleKmsKeyRings_basic(context, fmt.Sprintf("filter = \"%s\"", filterNameFindSharedKeys)), + Check: resource.ComposeTestCheckFunc( + // This filter should retrieve the bootstrapped KMS key rings used by the test + resource.TestCheckResourceAttr("data.google_kms_key_rings.all_key_rings", "id", findSharedKeysId), + resource.TestMatchResourceAttr("data.google_kms_key_rings.all_key_rings", "key_rings.#", regexp.MustCompile("[1-9]+[0-9]*")), + ), + }, + { + Config: testAccDataSourceGoogleKmsKeyRings_basic(context, fmt.Sprintf("filter = \"%s\"", filterNameFindsNoKeys)), + Check: resource.ComposeTestCheckFunc( + // This filter should retrieve no keys + resource.TestCheckResourceAttr("data.google_kms_key_rings.all_key_rings", "id", findsNoKeysId), + resource.TestCheckResourceAttr("data.google_kms_key_rings.all_key_rings", "key_rings.#", "0"), + ), + }, + }, + }) +} + +func testAccDataSourceGoogleKmsKeyRings_basic(context map[string]interface{}, filter string) string { + context["filter"] = filter + + return acctest.Nprintf(` +data "google_kms_key_rings" "all_key_rings" { + location = "%{location}" + %{filter} +} +`, context) +} diff --git a/mmv1/third_party/terraform/services/logging/data_source_google_logging_sink_test.go b/mmv1/third_party/terraform/services/logging/data_source_google_logging_sink_test.go index 8818dfa7ac75..2cefb0789866 100644 --- a/mmv1/third_party/terraform/services/logging/data_source_google_logging_sink_test.go +++ b/mmv1/third_party/terraform/services/logging/data_source_google_logging_sink_test.go @@ -6,6 +6,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-provider-google/google/acctest" "github.com/hashicorp/terraform-provider-google/google/envvar" + "github.com/hashicorp/terraform-provider-google/google/services/logging" ) func TestAccDataSourceGoogleLoggingSink_basic(t *testing.T) { @@ -38,6 +39,40 @@ func TestAccDataSourceGoogleLoggingSink_basic(t *testing.T) { }) } +func TestUnitLoggingSink_OptionalSurroundingSpacesSuppress(t *testing.T) { + cases := map[string]struct { + Old, New string + ExpectDiffSuppress bool + }{ + "surrounding spaces": { + Old: "value", + New: " value ", + ExpectDiffSuppress: true, + }, + "no surrounding spaces": { + Old: "value", + New: "value", + ExpectDiffSuppress: true, + }, + "one space each": { + Old: " value", + New: "value ", + ExpectDiffSuppress: true, + }, + "different values": { + Old: " different", + New: "values ", + ExpectDiffSuppress: false, + }, + } + + for tn, tc := range cases { + if logging.OptionalSurroundingSpacesSuppress("filter", tc.Old, tc.New, nil) != tc.ExpectDiffSuppress { + t.Fatalf("bad: %s, '%s' => '%s' expect %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress) + } + } +} + func testAccDataSourceGoogleLoggingSink_basic(context map[string]interface{}) string { return acctest.Nprintf(` resource "google_logging_project_sink" "basic" { diff --git a/mmv1/third_party/terraform/services/logging/logging_exclusion_folder.go b/mmv1/third_party/terraform/services/logging/logging_exclusion_folder.go index e891c3b999e8..20286027c9ef 100644 --- a/mmv1/third_party/terraform/services/logging/logging_exclusion_folder.go +++ b/mmv1/third_party/terraform/services/logging/logging_exclusion_folder.go @@ -11,12 +11,18 @@ import ( "google.golang.org/api/logging/v2" ) +func OptionalPrefixSuppress(prefix string) schema.SchemaDiffSuppressFunc { + return func(k, old, new string, d *schema.ResourceData) bool { + return prefix+old == new || prefix+new == old + } +} + var FolderLoggingExclusionSchema = map[string]*schema.Schema{ "folder": { Type: schema.TypeString, Required: true, ForceNew: true, - DiffSuppressFunc: tpgresource.OptionalPrefixSuppress("folders/"), + DiffSuppressFunc: OptionalPrefixSuppress("folders/"), }, } diff --git a/mmv1/third_party/terraform/services/logging/resource_logging_folder_exclusion_test.go b/mmv1/third_party/terraform/services/logging/resource_logging_folder_exclusion_test.go index 1c1498fd94d2..52d770106ba4 100644 --- a/mmv1/third_party/terraform/services/logging/resource_logging_folder_exclusion_test.go +++ b/mmv1/third_party/terraform/services/logging/resource_logging_folder_exclusion_test.go @@ -34,6 +34,45 @@ func TestAccLoggingFolderExclusion(t *testing.T) { } } +func TestUnitLoggingFolder_OptionalPrefixSuppress(t *testing.T) { + cases := map[string]struct { + Old, New string + Prefix string + ExpectDiffSuppress bool + }{ + "with same prefix": { + Old: "my-folder", + New: "folders/my-folder", + Prefix: "folders/", + ExpectDiffSuppress: true, + }, + "with different prefix": { + Old: "folders/my-folder", + New: "organizations/my-folder", + Prefix: "folders/", + ExpectDiffSuppress: false, + }, + "same without prefix": { + Old: "my-folder", + New: "my-folder", + Prefix: "folders/", + ExpectDiffSuppress: false, + }, + "different without prefix": { + Old: "my-folder", + New: "my-new-folder", + Prefix: "folders/", + ExpectDiffSuppress: false, + }, + } + + for tn, tc := range cases { + if logging.OptionalPrefixSuppress(tc.Prefix)("folder", tc.Old, tc.New, nil) != tc.ExpectDiffSuppress { + t.Fatalf("bad: %s, '%s' => '%s' expect %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress) + } + } +} + func testAccLoggingFolderExclusion_basic(t *testing.T) { org := envvar.GetTestOrgFromEnv(t) exclusionName := "tf-test-exclusion-" + acctest.RandString(t, 10) diff --git a/mmv1/third_party/terraform/services/logging/resource_logging_sink.go b/mmv1/third_party/terraform/services/logging/resource_logging_sink.go index 7f1b43538b49..65fcdae6f3a7 100644 --- a/mmv1/third_party/terraform/services/logging/resource_logging_sink.go +++ b/mmv1/third_party/terraform/services/logging/resource_logging_sink.go @@ -5,10 +5,13 @@ import ( "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-provider-google/google/tpgresource" "google.golang.org/api/logging/v2" ) +func OptionalSurroundingSpacesSuppress(k, old, new string, d *schema.ResourceData) bool { + return strings.TrimSpace(old) == strings.TrimSpace(new) +} + func resourceLoggingSinkSchema() map[string]*schema.Schema { return map[string]*schema.Schema{ "name": { @@ -27,7 +30,7 @@ func resourceLoggingSinkSchema() map[string]*schema.Schema { "filter": { Type: schema.TypeString, Optional: true, - DiffSuppressFunc: tpgresource.OptionalSurroundingSpacesSuppress, + DiffSuppressFunc: OptionalSurroundingSpacesSuppress, Description: `The filter to apply when exporting logs. Only log entries that match the filter are exported.`, }, diff --git a/mmv1/third_party/terraform/services/monitoring/resource_monitoring_alert_policy_test.go b/mmv1/third_party/terraform/services/monitoring/resource_monitoring_alert_policy_test.go index c21ebca4a15d..2cf6d3c5194c 100644 --- a/mmv1/third_party/terraform/services/monitoring/resource_monitoring_alert_policy_test.go +++ b/mmv1/third_party/terraform/services/monitoring/resource_monitoring_alert_policy_test.go @@ -329,6 +329,10 @@ resource "google_monitoring_alert_policy" "full" { content = "test content" mime_type = "text/markdown" subject = "test subject" + links { + display_name = "link display name" + url = "http://mydomain.com" + } } } `, alertName, conditionName1, conditionName2) @@ -360,6 +364,14 @@ resource "google_monitoring_alert_policy" "mql" { content = "test content" mime_type = "text/markdown" subject = "test subject" + links { + display_name = "link display name" + url = "http://mydomain.com" + } + links { + display_name = "link display name2" + url = "http://mydomain2.com" + } } } `, alertName, conditionName) @@ -395,7 +407,7 @@ resource "google_monitoring_alert_policy" "log" { documentation { content = "test content" mime_type = "text/markdown" - subject = "test subject" + subject = "test subject" } } `, alertName, conditionName) @@ -457,6 +469,10 @@ resource "google_monitoring_alert_policy" "promql" { content = "test content" mime_type = "text/markdown" subject = "test subject" + links { + display_name = "link display name" + url = "http://mydomain.com" + } } } `, alertName, conditionName) diff --git a/mmv1/third_party/terraform/services/netapp/go/resource_netapp_storage_pool_test.go.tmpl b/mmv1/third_party/terraform/services/netapp/go/resource_netapp_storage_pool_test.go.tmpl new file mode 100644 index 000000000000..6ad76f77b0e3 --- /dev/null +++ b/mmv1/third_party/terraform/services/netapp/go/resource_netapp_storage_pool_test.go.tmpl @@ -0,0 +1,261 @@ +package netapp_test + +import ( + "testing" +{{- if ne $.TargetVersionName "ga" }} + "time" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +{{- end }} + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-google/google/acctest" +) + +func TestAccNetappstoragePool_storagePoolCreateExample_update(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccNetappstoragePool_storagePoolCreateExample_full(context), + }, + { + ResourceName: "google_netapp_storage_pool.test_pool", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels"}, + }, + { + Config: testAccNetappstoragePool_storagePoolCreateExample_update(context), + }, + { + ResourceName: "google_netapp_storage_pool.test_pool", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels"}, + }, + }, + }) +} + +func testAccNetappstoragePool_storagePoolCreateExample_full(context map[string]interface{}) string { + return acctest.Nprintf(` + +resource "google_compute_network" "peering_network" { + name = "tf-test-network%{random_suffix}" +} + +# Create an IP address +resource "google_compute_global_address" "private_ip_alloc" { + name = "tf-test-address%{random_suffix}" + purpose = "VPC_PEERING" + address_type = "INTERNAL" + prefix_length = 16 + network = google_compute_network.peering_network.id +} + +# Create a private connection +resource "google_service_networking_connection" "default" { + network = google_compute_network.peering_network.id + service = "netapp.servicenetworking.goog" + reserved_peering_ranges = [google_compute_global_address.private_ip_alloc.name] +} + +resource "google_netapp_storage_pool" "test_pool" { + name = "tf-test-pool%{random_suffix}" + location = "us-central1" + service_level = "PREMIUM" + capacity_gib = "2048" + network = google_compute_network.peering_network.id + active_directory = "" + description = "this is a test description" + kms_config = "" + labels = { + key= "test" + value= "pool" + } + ldap_enabled = false + +} +`, context) +} + +func testAccNetappstoragePool_storagePoolCreateExample_update(context map[string]interface{}) string { + return acctest.Nprintf(` + +resource "google_compute_network" "peering_network" { + name = "tf-test-network%{random_suffix}" +} + +# Create an IP address +resource "google_compute_global_address" "private_ip_alloc" { + name = "tf-test-address%{random_suffix}" + purpose = "VPC_PEERING" + address_type = "INTERNAL" + prefix_length = 16 + network = google_compute_network.peering_network.id +} + +# Create a private connection +resource "google_service_networking_connection" "default" { + network = google_compute_network.peering_network.id + service = "netapp.servicenetworking.goog" + reserved_peering_ranges = [google_compute_global_address.private_ip_alloc.name] +} + +resource "google_netapp_storage_pool" "test_pool" { + name = "tf-test-pool%{random_suffix}" + location = "us-central1" + service_level = "PREMIUM" + capacity_gib = "4096" + network = google_compute_network.peering_network.id + active_directory = "" + description = "this is test" + kms_config = "" + labels = { + key= "test" + value= "pool" + } + ldap_enabled = false + +} +`, context) +} + +{{ if ne $.TargetVersionName `ga` -}} +func TestAccNetappstoragePool_FlexRegionalStoragePoolCreateExample_update(t *testing.T) { + context := map[string]interface{}{ + "network_name": acctest.BootstrapSharedServiceNetworkingConnection(t, "gcnv-network-config-1", acctest.ServiceNetworkWithParentService("netapp.servicenetworking.goog")), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), + CheckDestroy: testAccCheckNetappstoragePoolDestroyProducer(t), + ExternalProviders: map[string]resource.ExternalProvider{ + "time": {}, + }, + Steps: []resource.TestStep{ + { + Config: testAccNetappstoragePool_FlexRegionalStoragePoolCreateExample_full(context), + }, + { + ResourceName: "google_netapp_storage_pool.test_pool", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels"}, + }, + { + Config: testAccNetappstoragePool_FlexRegionalStoragePoolCreateExample_switchZone(context), + Check: testAccNetappstoragePool_FlexRegionalStoragePoolCreateExample_sleep_5_mins(), + }, + { + ResourceName: "google_netapp_storage_pool.test_pool", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels"}, + }, + { + Config: testAccNetappstoragePool_FlexRegionalStoragePoolCreateExample_switchBackZone(context), + }, + { + ResourceName: "google_netapp_storage_pool.test_pool", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels"}, + }, + }, + }) +} + +func testAccNetappstoragePool_FlexRegionalStoragePoolCreateExample_full(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_netapp_storage_pool" "test_pool" { + provider = google-beta + name = "tf-test-pool%{random_suffix}" + location = "us-east1" + service_level = "FLEX" + capacity_gib = "2048" + network = data.google_compute_network.default.id + zone = "us-east1-c" + replica_zone = "us-east1-b" +} + +resource "time_sleep" "wait_5_minutes" { + depends_on = [google_netapp_storage_pool.test_pool] + destroy_duration = "5m" +} + +data "google_compute_network" "default" { + provider = google-beta + name = "%{network_name}" +} +`, context) +} + +func testAccNetappstoragePool_FlexRegionalStoragePoolCreateExample_switchZone(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_netapp_storage_pool" "test_pool" { + provider = google-beta + name = "tf-test-pool%{random_suffix}" + location = "us-east1" + service_level = "FLEX" + capacity_gib = "2048" + network = data.google_compute_network.default.id + zone = "us-east1-b" + replica_zone = "us-east1-c" +} + +resource "time_sleep" "wait_5_minutes" { + depends_on = [google_netapp_storage_pool.test_pool] + destroy_duration = "5m" +} + +data "google_compute_network" "default" { + provider = google-beta + name = "%{network_name}" +} +`, context) +} + +func testAccNetappstoragePool_FlexRegionalStoragePoolCreateExample_sleep_5_mins() resource.TestCheckFunc { + return func(s *terraform.State) error { + // wait 5 minutes before executing the switchback due to api zone switch issues + time.Sleep(5 * time.Minute) + return nil + } +} + +func testAccNetappstoragePool_FlexRegionalStoragePoolCreateExample_switchBackZone(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_netapp_storage_pool" "test_pool" { + provider = google-beta + name = "tf-test-pool%{random_suffix}" + location = "us-east1" + service_level = "FLEX" + capacity_gib = "2048" + network = data.google_compute_network.default.id + zone = "us-east1-c" + replica_zone = "us-east1-b" +} + +resource "time_sleep" "wait_5_minutes" { + depends_on = [google_netapp_storage_pool.test_pool] + destroy_duration = "5m" +} + +data "google_compute_network" "default" { + provider = google-beta + name = "%{network_name}" +} +`, context) +} + +{{ end }} diff --git a/mmv1/third_party/terraform/services/netapp/resource_netapp_storage_pool_test.go b/mmv1/third_party/terraform/services/netapp/resource_netapp_storage_pool_test.go deleted file mode 100644 index c47b113fd70d..000000000000 --- a/mmv1/third_party/terraform/services/netapp/resource_netapp_storage_pool_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package netapp_test - -import ( - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-provider-google/google/acctest" -) - -func TestAccNetappstoragePool_storagePoolCreateExample_update(t *testing.T) { - t.Parallel() - - context := map[string]interface{}{ - "random_suffix": acctest.RandString(t, 10), - } - - acctest.VcrTest(t, resource.TestCase{ - PreCheck: func() { acctest.AccTestPreCheck(t) }, - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), - Steps: []resource.TestStep{ - { - Config: testAccNetappstoragePool_storagePoolCreateExample_full(context), - }, - { - ResourceName: "google_netapp_storage_pool.test_pool", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels"}, - }, - { - Config: testAccNetappstoragePool_storagePoolCreateExample_update(context), - }, - { - ResourceName: "google_netapp_storage_pool.test_pool", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels"}, - }, - }, - }) -} - -func testAccNetappstoragePool_storagePoolCreateExample_full(context map[string]interface{}) string { - return acctest.Nprintf(` - -resource "google_compute_network" "peering_network" { - name = "tf-test-network%{random_suffix}" -} - -# Create an IP address -resource "google_compute_global_address" "private_ip_alloc" { - name = "tf-test-address%{random_suffix}" - purpose = "VPC_PEERING" - address_type = "INTERNAL" - prefix_length = 16 - network = google_compute_network.peering_network.id -} - -# Create a private connection -resource "google_service_networking_connection" "default" { - network = google_compute_network.peering_network.id - service = "netapp.servicenetworking.goog" - reserved_peering_ranges = [google_compute_global_address.private_ip_alloc.name] -} - -resource "google_netapp_storage_pool" "test_pool" { - name = "tf-test-pool%{random_suffix}" - location = "us-central1" - service_level = "PREMIUM" - capacity_gib = "2048" - network = google_compute_network.peering_network.id - active_directory = "" - description = "this is a test description" - kms_config = "" - labels = { - key= "test" - value= "pool" - } - ldap_enabled = false - -} -`, context) -} - -func testAccNetappstoragePool_storagePoolCreateExample_update(context map[string]interface{}) string { - return acctest.Nprintf(` - -resource "google_compute_network" "peering_network" { - name = "tf-test-network%{random_suffix}" -} - -# Create an IP address -resource "google_compute_global_address" "private_ip_alloc" { - name = "tf-test-address%{random_suffix}" - purpose = "VPC_PEERING" - address_type = "INTERNAL" - prefix_length = 16 - network = google_compute_network.peering_network.id -} - -# Create a private connection -resource "google_service_networking_connection" "default" { - network = google_compute_network.peering_network.id - service = "netapp.servicenetworking.goog" - reserved_peering_ranges = [google_compute_global_address.private_ip_alloc.name] -} - -resource "google_netapp_storage_pool" "test_pool" { - name = "tf-test-pool%{random_suffix}" - location = "us-central1" - service_level = "PREMIUM" - capacity_gib = "4096" - network = google_compute_network.peering_network.id - active_directory = "" - description = "this is test" - kms_config = "" - labels = { - key= "test" - value= "pool" - } - ldap_enabled = false - -} -`, context) -} diff --git a/mmv1/third_party/terraform/services/netapp/resource_netapp_storage_pool_test.go.erb b/mmv1/third_party/terraform/services/netapp/resource_netapp_storage_pool_test.go.erb new file mode 100644 index 000000000000..4a6a5b18218e --- /dev/null +++ b/mmv1/third_party/terraform/services/netapp/resource_netapp_storage_pool_test.go.erb @@ -0,0 +1,262 @@ +<% autogen_exception -%> +package netapp_test + +import ( + "testing" +<% unless version == 'ga' -%> + "time" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +<% end -%> + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-google/google/acctest" +) + +func TestAccNetappstoragePool_storagePoolCreateExample_update(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccNetappstoragePool_storagePoolCreateExample_full(context), + }, + { + ResourceName: "google_netapp_storage_pool.test_pool", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels"}, + }, + { + Config: testAccNetappstoragePool_storagePoolCreateExample_update(context), + }, + { + ResourceName: "google_netapp_storage_pool.test_pool", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels"}, + }, + }, + }) +} + +func testAccNetappstoragePool_storagePoolCreateExample_full(context map[string]interface{}) string { + return acctest.Nprintf(` + +resource "google_compute_network" "peering_network" { + name = "tf-test-network%{random_suffix}" +} + +# Create an IP address +resource "google_compute_global_address" "private_ip_alloc" { + name = "tf-test-address%{random_suffix}" + purpose = "VPC_PEERING" + address_type = "INTERNAL" + prefix_length = 16 + network = google_compute_network.peering_network.id +} + +# Create a private connection +resource "google_service_networking_connection" "default" { + network = google_compute_network.peering_network.id + service = "netapp.servicenetworking.goog" + reserved_peering_ranges = [google_compute_global_address.private_ip_alloc.name] +} + +resource "google_netapp_storage_pool" "test_pool" { + name = "tf-test-pool%{random_suffix}" + location = "us-central1" + service_level = "PREMIUM" + capacity_gib = "2048" + network = google_compute_network.peering_network.id + active_directory = "" + description = "this is a test description" + kms_config = "" + labels = { + key= "test" + value= "pool" + } + ldap_enabled = false + +} +`, context) +} + +func testAccNetappstoragePool_storagePoolCreateExample_update(context map[string]interface{}) string { + return acctest.Nprintf(` + +resource "google_compute_network" "peering_network" { + name = "tf-test-network%{random_suffix}" +} + +# Create an IP address +resource "google_compute_global_address" "private_ip_alloc" { + name = "tf-test-address%{random_suffix}" + purpose = "VPC_PEERING" + address_type = "INTERNAL" + prefix_length = 16 + network = google_compute_network.peering_network.id +} + +# Create a private connection +resource "google_service_networking_connection" "default" { + network = google_compute_network.peering_network.id + service = "netapp.servicenetworking.goog" + reserved_peering_ranges = [google_compute_global_address.private_ip_alloc.name] +} + +resource "google_netapp_storage_pool" "test_pool" { + name = "tf-test-pool%{random_suffix}" + location = "us-central1" + service_level = "PREMIUM" + capacity_gib = "4096" + network = google_compute_network.peering_network.id + active_directory = "" + description = "this is test" + kms_config = "" + labels = { + key= "test" + value= "pool" + } + ldap_enabled = false + +} +`, context) +} + +<% unless version == 'ga' -%> +func TestAccNetappstoragePool_FlexRegionalStoragePoolCreateExample_update(t *testing.T) { + context := map[string]interface{}{ + "network_name": acctest.BootstrapSharedServiceNetworkingConnection(t, "gcnv-network-config-1", acctest.ServiceNetworkWithParentService("netapp.servicenetworking.goog")), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), + CheckDestroy: testAccCheckNetappstoragePoolDestroyProducer(t), + ExternalProviders: map[string]resource.ExternalProvider{ + "time": {}, + }, + Steps: []resource.TestStep{ + { + Config: testAccNetappstoragePool_FlexRegionalStoragePoolCreateExample_full(context), + }, + { + ResourceName: "google_netapp_storage_pool.test_pool", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels"}, + }, + { + Config: testAccNetappstoragePool_FlexRegionalStoragePoolCreateExample_switchZone(context), + Check: testAccNetappstoragePool_FlexRegionalStoragePoolCreateExample_sleep_5_mins(), + }, + { + ResourceName: "google_netapp_storage_pool.test_pool", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels"}, + }, + { + Config: testAccNetappstoragePool_FlexRegionalStoragePoolCreateExample_switchBackZone(context), + }, + { + ResourceName: "google_netapp_storage_pool.test_pool", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels"}, + }, + }, + }) +} + +func testAccNetappstoragePool_FlexRegionalStoragePoolCreateExample_full(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_netapp_storage_pool" "test_pool" { + provider = google-beta + name = "tf-test-pool%{random_suffix}" + location = "us-east1" + service_level = "FLEX" + capacity_gib = "2048" + network = data.google_compute_network.default.id + zone = "us-east1-c" + replica_zone = "us-east1-b" +} + +resource "time_sleep" "wait_5_minutes" { + depends_on = [google_netapp_storage_pool.test_pool] + destroy_duration = "5m" +} + +data "google_compute_network" "default" { + provider = google-beta + name = "%{network_name}" +} +`, context) +} + +func testAccNetappstoragePool_FlexRegionalStoragePoolCreateExample_switchZone(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_netapp_storage_pool" "test_pool" { + provider = google-beta + name = "tf-test-pool%{random_suffix}" + location = "us-east1" + service_level = "FLEX" + capacity_gib = "2048" + network = data.google_compute_network.default.id + zone = "us-east1-b" + replica_zone = "us-east1-c" +} + +resource "time_sleep" "wait_5_minutes" { + depends_on = [google_netapp_storage_pool.test_pool] + destroy_duration = "5m" +} + +data "google_compute_network" "default" { + provider = google-beta + name = "%{network_name}" +} +`, context) +} + +func testAccNetappstoragePool_FlexRegionalStoragePoolCreateExample_sleep_5_mins() resource.TestCheckFunc { + return func(s *terraform.State) error { + // wait 5 minutes before executing the switchback due to api zone switch issues + time.Sleep(5 * time.Minute) + return nil + } +} + +func testAccNetappstoragePool_FlexRegionalStoragePoolCreateExample_switchBackZone(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_netapp_storage_pool" "test_pool" { + provider = google-beta + name = "tf-test-pool%{random_suffix}" + location = "us-east1" + service_level = "FLEX" + capacity_gib = "2048" + network = data.google_compute_network.default.id + zone = "us-east1-c" + replica_zone = "us-east1-b" +} + +resource "time_sleep" "wait_5_minutes" { + depends_on = [google_netapp_storage_pool.test_pool] + destroy_duration = "5m" +} + +data "google_compute_network" "default" { + provider = google-beta + name = "%{network_name}" +} +`, context) +} + +<% end -%> diff --git a/mmv1/third_party/terraform/services/pubsub/resource_pubsub_subscription_test.go b/mmv1/third_party/terraform/services/pubsub/resource_pubsub_subscription_test.go index e368b8d1fa35..c142112b69e0 100644 --- a/mmv1/third_party/terraform/services/pubsub/resource_pubsub_subscription_test.go +++ b/mmv1/third_party/terraform/services/pubsub/resource_pubsub_subscription_test.go @@ -365,6 +365,39 @@ func TestAccPubsubSubscription_pollOnCreate(t *testing.T) { }) } +func TestUnitPubsubSubscription_IgnoreMissingKeyInMap(t *testing.T) { + cases := map[string]struct { + Old, New string + Key string + ExpectDiffSuppress bool + }{ + "missing key in map": { + Old: "", + New: "v1", + Key: "x-goog-version", + ExpectDiffSuppress: true, + }, + "different values": { + Old: "v1", + New: "v2", + Key: "x-goog-version", + ExpectDiffSuppress: false, + }, + "same values": { + Old: "v1", + New: "v1", + Key: "x-goog-version", + ExpectDiffSuppress: false, + }, + } + + for tn, tc := range cases { + if pubsub.IgnoreMissingKeyInMap(tc.Key)("push_config.0.attributes."+tc.Key, tc.Old, tc.New, nil) != tc.ExpectDiffSuppress { + t.Fatalf("bad: %s, '%s' => '%s' expect %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress) + } + } +} + func testAccPubsubSubscription_emptyTTL(topic, subscription string) string { return fmt.Sprintf(` resource "google_pubsub_topic" "foo" { diff --git a/mmv1/third_party/terraform/services/resourcemanager/resource_google_service_account_key.go b/mmv1/third_party/terraform/services/resourcemanager/resource_google_service_account_key.go index 074839783a65..94df73f3b15f 100644 --- a/mmv1/third_party/terraform/services/resourcemanager/resource_google_service_account_key.go +++ b/mmv1/third_party/terraform/services/resourcemanager/resource_google_service_account_key.go @@ -145,6 +145,12 @@ func resourceGoogleServiceAccountKeyCreate(d *schema.ResourceData, meta interfac if err != nil { return err } + + // We can't guarantee complete consistency even after waiting on + // the results, so sleep for some additional time to reduce the + // likelihood of eventual consistency failures. + time.Sleep(10 * time.Second) + return resourceGoogleServiceAccountKeyRead(d, meta) } diff --git a/mmv1/third_party/terraform/services/securitycenterv2/resource_scc_v2_organization_notification_config_test.go b/mmv1/third_party/terraform/services/securitycenterv2/resource_scc_v2_organization_notification_config_test.go new file mode 100644 index 000000000000..aa0211acb79b --- /dev/null +++ b/mmv1/third_party/terraform/services/securitycenterv2/resource_scc_v2_organization_notification_config_test.go @@ -0,0 +1,87 @@ +package securitycenterv2_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/envvar" +) + +func TestAccSecurityCenterV2OrganizationNotificationConfig_basic(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "org_id": envvar.GetTestOrgFromEnv(t), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccSecurityCenterV2OrganizationNotificationConfig_basic(context), + }, + { + ResourceName: "google_scc_v2_organization_notification_config.default", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "config_id", + }, + }, + { + Config: testAccSecurityCenterV2OrganizationNotificationConfig_update(context), + }, + { + ResourceName: "google_scc_v2_organization_notification_config.default", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "config_id", + }, + }, + }, + }) +} + +func testAccSecurityCenterV2OrganizationNotificationConfig_basic(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_pubsub_topic" "scc_v2_organization_notification_config" { + name = "tf-test-topic-%{random_suffix}" +} + +resource "google_scc_v2_organization_notification_config" "default" { + config_id = "tf-test-config-%{random_suffix}" + organization = "%{org_id}" + location = "global" + description = "A test organization notification config" + pubsub_topic = google_pubsub_topic.scc_v2_organization_notification_config.id + + streaming_config { + filter = "severity = \"HIGH\"" + } +} +`, context) +} + +func testAccSecurityCenterV2OrganizationNotificationConfig_update(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_pubsub_topic" "scc_v2_organization_notification_config" { + name = "tf-test-topic-%{random_suffix}" +} + +resource "google_scc_v2_organization_notification_config" "default" { + config_id = "tf-test-config-%{random_suffix}" + organization = "%{org_id}" + location = "global" + description = "An updated test organization notification config" + pubsub_topic = google_pubsub_topic.scc_v2_organization_notification_config.id + + streaming_config { + filter = "severity = \"CRITICAL\"" + } +} +`, context) +} diff --git a/mmv1/third_party/terraform/services/storage/iam_storage_managed_folder.go b/mmv1/third_party/terraform/services/storage/iam_storage_managed_folder.go new file mode 100644 index 000000000000..925a8e2131bb --- /dev/null +++ b/mmv1/third_party/terraform/services/storage/iam_storage_managed_folder.go @@ -0,0 +1,184 @@ +package storage + +import ( + "fmt" + "net/url" + + "github.com/hashicorp/errwrap" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "google.golang.org/api/cloudresourcemanager/v1" + + "github.com/hashicorp/terraform-provider-google/google/tpgiamresource" + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" + "github.com/hashicorp/terraform-provider-google/google/verify" +) + +var StorageManagedFolderIamSchema = map[string]*schema.Schema{ + "bucket": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "managed_folder": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: tpgresource.CompareSelfLinkOrResourceName, + ValidateFunc: verify.ValidateRegexp(`/$`), + }, +} + +type StorageManagedFolderIamUpdater struct { + bucket string + managedFolder string + d tpgresource.TerraformResourceData + Config *transport_tpg.Config +} + +func StorageManagedFolderIamUpdaterProducer(d tpgresource.TerraformResourceData, config *transport_tpg.Config) (tpgiamresource.ResourceIamUpdater, error) { + values := make(map[string]string) + + if v, ok := d.GetOk("bucket"); ok { + values["bucket"] = v.(string) + } + + if v, ok := d.GetOk("managed_folder"); ok { + values["managed_folder"] = v.(string) + } + + u := &StorageManagedFolderIamUpdater{ + bucket: values["bucket"], + managedFolder: values["managed_folder"], + d: d, + Config: config, + } + + if err := d.Set("bucket", u.bucket); err != nil { + return nil, fmt.Errorf("Error setting bucket: %s", err) + } + if err := d.Set("managed_folder", u.managedFolder); err != nil { + return nil, fmt.Errorf("Error setting managed_folder: %s", err) + } + + return u, nil +} + +func StorageManagedFolderIdParseFunc(d *schema.ResourceData, config *transport_tpg.Config) error { + values := make(map[string]string) + + m, err := tpgresource.GetImportIdQualifiers([]string{"(?P[^/]+)/managedFolders/(?P.+)", "(?P[^/]+)/(?P.+)"}, d, config, d.Id()) + if err != nil { + return err + } + + for k, v := range m { + values[k] = v + } + + u := &StorageManagedFolderIamUpdater{ + bucket: values["bucket"], + managedFolder: values["managed_folder"], + d: d, + Config: config, + } + if err := d.Set("bucket", u.bucket); err != nil { + return fmt.Errorf("Error setting bucket: %s", err) + } + if err := d.Set("managed_folder", u.managedFolder); err != nil { + return fmt.Errorf("Error setting managed_folder: %s", err) + } + d.SetId(u.GetResourceId()) + return nil +} + +func (u *StorageManagedFolderIamUpdater) GetResourceIamPolicy() (*cloudresourcemanager.Policy, error) { + url, err := u.qualifyManagedFolderUrl("iam") + if err != nil { + return nil, err + } + + var obj map[string]interface{} + url, err = transport_tpg.AddQueryParams(url, map[string]string{"optionsRequestedPolicyVersion": fmt.Sprintf("%d", tpgiamresource.IamPolicyVersion)}) + if err != nil { + return nil, err + } + + userAgent, err := tpgresource.GenerateUserAgentString(u.d, u.Config.UserAgent) + if err != nil { + return nil, err + } + + policy, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: u.Config, + Method: "GET", + RawURL: url, + UserAgent: userAgent, + Body: obj, + }) + if err != nil { + return nil, errwrap.Wrapf(fmt.Sprintf("Error retrieving IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + out := &cloudresourcemanager.Policy{} + err = tpgresource.Convert(policy, out) + if err != nil { + return nil, errwrap.Wrapf("Cannot convert a policy to a resource manager policy: {{err}}", err) + } + + return out, nil +} + +func (u *StorageManagedFolderIamUpdater) SetResourceIamPolicy(policy *cloudresourcemanager.Policy) error { + json, err := tpgresource.ConvertToMap(policy) + if err != nil { + return err + } + + obj := json + + url, err := u.qualifyManagedFolderUrl("iam") + if err != nil { + return err + } + + userAgent, err := tpgresource.GenerateUserAgentString(u.d, u.Config.UserAgent) + if err != nil { + return err + } + + _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: u.Config, + Method: "PUT", + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: u.d.Timeout(schema.TimeoutCreate), + }) + if err != nil { + return errwrap.Wrapf(fmt.Sprintf("Error setting IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + return nil +} + +func (u *StorageManagedFolderIamUpdater) qualifyManagedFolderUrl(methodIdentifier string) (string, error) { + urlTemplate := fmt.Sprintf("{{StorageBasePath}}b/%s/managedFolders/%s/%s", u.bucket, url.PathEscape(u.managedFolder), methodIdentifier) + url, err := tpgresource.ReplaceVars(u.d, u.Config, urlTemplate) + if err != nil { + return "", err + } + return url, nil +} + +func (u *StorageManagedFolderIamUpdater) GetResourceId() string { + return fmt.Sprintf("b/%s/managedFolders/%s", u.bucket, u.managedFolder) +} + +func (u *StorageManagedFolderIamUpdater) GetMutexKey() string { + return fmt.Sprintf("iam-storage-managedfolder-%s", u.GetResourceId()) +} + +func (u *StorageManagedFolderIamUpdater) DescribeResource() string { + return fmt.Sprintf("storage managedfolder %q", u.GetResourceId()) +} diff --git a/mmv1/third_party/terraform/services/storage/iam_storage_managed_folder_test.go b/mmv1/third_party/terraform/services/storage/iam_storage_managed_folder_test.go new file mode 100644 index 000000000000..d965ef054a2b --- /dev/null +++ b/mmv1/third_party/terraform/services/storage/iam_storage_managed_folder_test.go @@ -0,0 +1,651 @@ +package storage_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/envvar" + "github.com/hashicorp/terraform-provider-google/google/tpgresource" +) + +func TestAccStorageManagedFolderIamBindingGenerated(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + "role": "roles/storage.objectViewer", + "admin_role": "roles/storage.admin", + "condition_title": "expires_after_2019_12_31", + "condition_expr": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + "condition_desc": "Expiring at midnight of 2019-12-31", + "condition_title_no_desc": "expires_after_2019_12_31-no-description", + "condition_expr_no_desc": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccStorageManagedFolderIamBinding_basicGenerated(context), + }, + { + ResourceName: "google_storage_managed_folder_iam_binding.foo", + ImportStateId: fmt.Sprintf("b/%s/managedFolders/managed/folder/name/ roles/storage.objectViewer", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"])), + ImportState: true, + ImportStateVerify: true, + }, + { + // Test Iam Binding update + Config: testAccStorageManagedFolderIamBinding_updateGenerated(context), + }, + { + ResourceName: "google_storage_managed_folder_iam_binding.foo", + ImportStateId: fmt.Sprintf("b/%s/managedFolders/managed/folder/name/ roles/storage.objectViewer", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"])), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccStorageManagedFolderIamMemberGenerated(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + "role": "roles/storage.objectViewer", + "admin_role": "roles/storage.admin", + "condition_title": "expires_after_2019_12_31", + "condition_expr": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + "condition_desc": "Expiring at midnight of 2019-12-31", + "condition_title_no_desc": "expires_after_2019_12_31-no-description", + "condition_expr_no_desc": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + // Test Iam Member creation (no update for member, no need to test) + Config: testAccStorageManagedFolderIamMember_basicGenerated(context), + }, + { + ResourceName: "google_storage_managed_folder_iam_member.foo", + ImportStateId: fmt.Sprintf("b/%s/managedFolders/managed/folder/name/ roles/storage.objectViewer user:admin@hashicorptest.com", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"])), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccStorageManagedFolderIamPolicyGenerated(t *testing.T) { + t.Parallel() + + // This may skip test, so do it first + sa := envvar.GetTestServiceAccountFromEnv(t) + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + "role": "roles/storage.objectViewer", + "admin_role": "roles/storage.admin", + "condition_title": "expires_after_2019_12_31", + "condition_expr": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + "condition_desc": "Expiring at midnight of 2019-12-31", + "condition_title_no_desc": "expires_after_2019_12_31-no-description", + "condition_expr_no_desc": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + } + context["service_account"] = sa + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccStorageManagedFolderIamPolicy_basicGenerated(context), + Check: resource.TestCheckResourceAttrSet("data.google_storage_managed_folder_iam_policy.foo", "policy_data"), + }, + { + ResourceName: "google_storage_managed_folder_iam_policy.foo", + ImportStateId: fmt.Sprintf("b/%s/managedFolders/managed/folder/name/", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"])), + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccStorageManagedFolderIamPolicy_emptyBinding(context), + }, + { + ResourceName: "google_storage_managed_folder_iam_policy.foo", + ImportStateId: fmt.Sprintf("b/%s/managedFolders/managed/folder/name/", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"])), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccStorageManagedFolderIamBindingGenerated_withCondition(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + "role": "roles/storage.objectViewer", + "admin_role": "roles/storage.admin", + "condition_title": "expires_after_2019_12_31", + "condition_expr": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + "condition_desc": "Expiring at midnight of 2019-12-31", + "condition_title_no_desc": "expires_after_2019_12_31-no-description", + "condition_expr_no_desc": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccStorageManagedFolderIamBinding_withConditionGenerated(context), + }, + { + ResourceName: "google_storage_managed_folder_iam_binding.foo", + ImportStateId: fmt.Sprintf("b/%s/managedFolders/managed/folder/name/ roles/storage.objectViewer %s", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"]), context["condition_title"]), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccStorageManagedFolderIamBindingGenerated_withAndWithoutCondition(t *testing.T) { + // Multiple fine-grained resources + acctest.SkipIfVcr(t) + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + "role": "roles/storage.objectViewer", + "admin_role": "roles/storage.admin", + "condition_title": "expires_after_2019_12_31", + "condition_expr": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + "condition_desc": "Expiring at midnight of 2019-12-31", + "condition_title_no_desc": "expires_after_2019_12_31-no-description", + "condition_expr_no_desc": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccStorageManagedFolderIamBinding_withAndWithoutConditionGenerated(context), + }, + { + ResourceName: "google_storage_managed_folder_iam_binding.foo", + ImportStateId: fmt.Sprintf("b/%s/managedFolders/managed/folder/name/ roles/storage.objectViewer", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"])), + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "google_storage_managed_folder_iam_binding.foo2", + ImportStateId: fmt.Sprintf("b/%s/managedFolders/managed/folder/name/ roles/storage.objectViewer %s", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"]), context["condition_title"]), + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "google_storage_managed_folder_iam_binding.foo3", + ImportStateId: fmt.Sprintf("b/%s/managedFolders/managed/folder/name/ roles/storage.objectViewer %s", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"]), context["condition_title_no_desc"]), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccStorageManagedFolderIamMemberGenerated_withCondition(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + "role": "roles/storage.objectViewer", + "admin_role": "roles/storage.admin", + "condition_title": "expires_after_2019_12_31", + "condition_expr": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + "condition_desc": "Expiring at midnight of 2019-12-31", + "condition_title_no_desc": "expires_after_2019_12_31-no-description", + "condition_expr_no_desc": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccStorageManagedFolderIamMember_withConditionGenerated(context), + }, + { + ResourceName: "google_storage_managed_folder_iam_member.foo", + ImportStateId: fmt.Sprintf("b/%s/managedFolders/managed/folder/name/ roles/storage.objectViewer user:admin@hashicorptest.com %s", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"]), context["condition_title"]), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccStorageManagedFolderIamMemberGenerated_withAndWithoutCondition(t *testing.T) { + // Multiple fine-grained resources + acctest.SkipIfVcr(t) + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + "role": "roles/storage.objectViewer", + "admin_role": "roles/storage.admin", + "condition_title": "expires_after_2019_12_31", + "condition_expr": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + "condition_desc": "Expiring at midnight of 2019-12-31", + "condition_title_no_desc": "expires_after_2019_12_31-no-description", + "condition_expr_no_desc": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccStorageManagedFolderIamMember_withAndWithoutConditionGenerated(context), + }, + { + ResourceName: "google_storage_managed_folder_iam_member.foo", + ImportStateId: fmt.Sprintf("b/%s/managedFolders/managed/folder/name/ roles/storage.objectViewer user:admin@hashicorptest.com", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"])), + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "google_storage_managed_folder_iam_member.foo2", + ImportStateId: fmt.Sprintf("b/%s/managedFolders/managed/folder/name/ roles/storage.objectViewer user:admin@hashicorptest.com %s", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"]), context["condition_title"]), + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "google_storage_managed_folder_iam_member.foo3", + ImportStateId: fmt.Sprintf("b/%s/managedFolders/managed/folder/name/ roles/storage.objectViewer user:admin@hashicorptest.com %s", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"]), context["condition_title_no_desc"]), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccStorageManagedFolderIamPolicyGenerated_withCondition(t *testing.T) { + t.Parallel() + + // This may skip test, so do it first + sa := envvar.GetTestServiceAccountFromEnv(t) + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + "role": "roles/storage.objectViewer", + "admin_role": "roles/storage.admin", + "condition_title": "expires_after_2019_12_31", + "condition_expr": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + "condition_desc": "Expiring at midnight of 2019-12-31", + "condition_title_no_desc": "expires_after_2019_12_31-no-description", + "condition_expr_no_desc": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + } + context["service_account"] = sa + + // Test should have 3 bindings: one with a description and one without, and a third for an admin role. Any < chars are converted to a unicode character by the API. + expectedPolicyData := acctest.Nprintf(`{"bindings":[{"members":["serviceAccount:%{service_account}"],"role":"%{admin_role}"},{"condition":{"description":"%{condition_desc}","expression":"%{condition_expr}","title":"%{condition_title}"},"members":["user:admin@hashicorptest.com"],"role":"%{role}"},{"condition":{"expression":"%{condition_expr}","title":"%{condition_title}-no-description"},"members":["user:admin@hashicorptest.com"],"role":"%{role}"}]}`, context) + expectedPolicyData = strings.Replace(expectedPolicyData, "<", "\\u003c", -1) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccStorageManagedFolderIamPolicy_withConditionGenerated(context), + Check: resource.ComposeAggregateTestCheckFunc( + // TODO(SarahFrench) - uncomment once https://github.com/GoogleCloudPlatform/magic-modules/pull/6466 merged + // resource.TestCheckResourceAttr("data.google_iam_policy.foo", "policy_data", expectedPolicyData), + resource.TestCheckResourceAttr("google_storage_managed_folder_iam_policy.foo", "policy_data", expectedPolicyData), + resource.TestCheckResourceAttrWith("data.google_iam_policy.foo", "policy_data", tpgresource.CheckGoogleIamPolicy), + ), + }, + { + ResourceName: "google_storage_managed_folder_iam_policy.foo", + ImportStateId: fmt.Sprintf("b/%s/managedFolders/managed/folder/name/", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"])), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccStorageManagedFolderIamMember_basicGenerated(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_storage_bucket" "bucket" { + name = "tf-test-my-bucket%{random_suffix}" + location = "EU" + uniform_bucket_level_access = true +} + +resource "google_storage_managed_folder" "folder" { + bucket = google_storage_bucket.bucket.name + name = "managed/folder/name/" +} + +resource "google_storage_managed_folder_iam_member" "foo" { + bucket = google_storage_managed_folder.folder.bucket + managed_folder = google_storage_managed_folder.folder.name + role = "%{role}" + member = "user:admin@hashicorptest.com" +} +`, context) +} + +func testAccStorageManagedFolderIamPolicy_basicGenerated(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_storage_bucket" "bucket" { + name = "tf-test-my-bucket%{random_suffix}" + location = "EU" + uniform_bucket_level_access = true +} + +resource "google_storage_managed_folder" "folder" { + bucket = google_storage_bucket.bucket.name + name = "managed/folder/name/" +} + +data "google_iam_policy" "foo" { + binding { + role = "%{role}" + members = ["user:admin@hashicorptest.com"] + } + binding { + role = "%{admin_role}" + members = ["serviceAccount:%{service_account}"] + } +} + +resource "google_storage_managed_folder_iam_policy" "foo" { + bucket = google_storage_managed_folder.folder.bucket + managed_folder = google_storage_managed_folder.folder.name + policy_data = data.google_iam_policy.foo.policy_data +} + +data "google_storage_managed_folder_iam_policy" "foo" { + bucket = google_storage_managed_folder.folder.bucket + managed_folder = google_storage_managed_folder.folder.name + depends_on = [ + google_storage_managed_folder_iam_policy.foo + ] +} +`, context) +} + +func testAccStorageManagedFolderIamPolicy_emptyBinding(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_storage_bucket" "bucket" { + name = "tf-test-my-bucket%{random_suffix}" + location = "EU" + uniform_bucket_level_access = true +} + +resource "google_storage_managed_folder" "folder" { + bucket = google_storage_bucket.bucket.name + name = "managed/folder/name/" +} + +data "google_iam_policy" "foo" { +} + +resource "google_storage_managed_folder_iam_policy" "foo" { + bucket = google_storage_managed_folder.folder.bucket + managed_folder = google_storage_managed_folder.folder.name + policy_data = data.google_iam_policy.foo.policy_data +} +`, context) +} + +func testAccStorageManagedFolderIamBinding_basicGenerated(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_storage_bucket" "bucket" { + name = "tf-test-my-bucket%{random_suffix}" + location = "EU" + uniform_bucket_level_access = true +} + +resource "google_storage_managed_folder" "folder" { + bucket = google_storage_bucket.bucket.name + name = "managed/folder/name/" +} + +resource "google_storage_managed_folder_iam_binding" "foo" { + bucket = google_storage_managed_folder.folder.bucket + managed_folder = google_storage_managed_folder.folder.name + role = "%{role}" + members = ["user:admin@hashicorptest.com"] +} +`, context) +} + +func testAccStorageManagedFolderIamBinding_updateGenerated(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_storage_bucket" "bucket" { + name = "tf-test-my-bucket%{random_suffix}" + location = "EU" + uniform_bucket_level_access = true +} + +resource "google_storage_managed_folder" "folder" { + bucket = google_storage_bucket.bucket.name + name = "managed/folder/name/" +} + +resource "google_storage_managed_folder_iam_binding" "foo" { + bucket = google_storage_managed_folder.folder.bucket + managed_folder = google_storage_managed_folder.folder.name + role = "%{role}" + members = ["user:admin@hashicorptest.com", "user:gterraformtest1@gmail.com"] +} +`, context) +} + +func testAccStorageManagedFolderIamBinding_withConditionGenerated(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_storage_bucket" "bucket" { + name = "tf-test-my-bucket%{random_suffix}" + location = "EU" + uniform_bucket_level_access = true +} + +resource "google_storage_managed_folder" "folder" { + bucket = google_storage_bucket.bucket.name + name = "managed/folder/name/" +} + +resource "google_storage_managed_folder_iam_binding" "foo" { + bucket = google_storage_managed_folder.folder.bucket + managed_folder = google_storage_managed_folder.folder.name + role = "%{role}" + members = ["user:admin@hashicorptest.com"] + condition { + title = "%{condition_title}" + description = "%{condition_desc}" + expression = "%{condition_expr}" + } +} +`, context) +} + +func testAccStorageManagedFolderIamBinding_withAndWithoutConditionGenerated(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_storage_bucket" "bucket" { + name = "tf-test-my-bucket%{random_suffix}" + location = "EU" + uniform_bucket_level_access = true +} + +resource "google_storage_managed_folder" "folder" { + bucket = google_storage_bucket.bucket.name + name = "managed/folder/name/" +} + +resource "google_storage_managed_folder_iam_binding" "foo" { + bucket = google_storage_managed_folder.folder.bucket + managed_folder = google_storage_managed_folder.folder.name + role = "%{role}" + members = ["user:admin@hashicorptest.com"] +} + +resource "google_storage_managed_folder_iam_binding" "foo2" { + bucket = google_storage_managed_folder.folder.bucket + managed_folder = google_storage_managed_folder.folder.name + role = "%{role}" + members = ["user:admin@hashicorptest.com"] + condition { + title = "%{condition_title}" + description = "%{condition_desc}" + expression = "%{condition_expr}" + } +} + +resource "google_storage_managed_folder_iam_binding" "foo3" { + bucket = google_storage_managed_folder.folder.bucket + managed_folder = google_storage_managed_folder.folder.name + role = "%{role}" + members = ["user:admin@hashicorptest.com"] + condition { + # Check that lack of description doesn't cause any issues + # Relates to issue : https://github.com/hashicorp/terraform-provider-google/issues/8701 + title = "%{condition_title_no_desc}" + expression = "%{condition_expr_no_desc}" + } +} +`, context) +} + +func testAccStorageManagedFolderIamMember_withConditionGenerated(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_storage_bucket" "bucket" { + name = "tf-test-my-bucket%{random_suffix}" + location = "EU" + uniform_bucket_level_access = true +} + +resource "google_storage_managed_folder" "folder" { + bucket = google_storage_bucket.bucket.name + name = "managed/folder/name/" +} + +resource "google_storage_managed_folder_iam_member" "foo" { + bucket = google_storage_managed_folder.folder.bucket + managed_folder = google_storage_managed_folder.folder.name + role = "%{role}" + member = "user:admin@hashicorptest.com" + condition { + title = "%{condition_title}" + description = "%{condition_desc}" + expression = "%{condition_expr}" + } +} +`, context) +} + +func testAccStorageManagedFolderIamMember_withAndWithoutConditionGenerated(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_storage_bucket" "bucket" { + name = "tf-test-my-bucket%{random_suffix}" + location = "EU" + uniform_bucket_level_access = true +} + +resource "google_storage_managed_folder" "folder" { + bucket = google_storage_bucket.bucket.name + name = "managed/folder/name/" +} + +resource "google_storage_managed_folder_iam_member" "foo" { + bucket = google_storage_managed_folder.folder.bucket + managed_folder = google_storage_managed_folder.folder.name + role = "%{role}" + member = "user:admin@hashicorptest.com" +} + +resource "google_storage_managed_folder_iam_member" "foo2" { + bucket = google_storage_managed_folder.folder.bucket + managed_folder = google_storage_managed_folder.folder.name + role = "%{role}" + member = "user:admin@hashicorptest.com" + condition { + title = "%{condition_title}" + description = "%{condition_desc}" + expression = "%{condition_expr}" + } +} + +resource "google_storage_managed_folder_iam_member" "foo3" { + bucket = google_storage_managed_folder.folder.bucket + managed_folder = google_storage_managed_folder.folder.name + role = "%{role}" + member = "user:admin@hashicorptest.com" + condition { + # Check that lack of description doesn't cause any issues + # Relates to issue : https://github.com/hashicorp/terraform-provider-google/issues/8701 + title = "%{condition_title_no_desc}" + expression = "%{condition_expr_no_desc}" + } +} +`, context) +} + +func testAccStorageManagedFolderIamPolicy_withConditionGenerated(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_storage_bucket" "bucket" { + name = "tf-test-my-bucket%{random_suffix}" + location = "EU" + uniform_bucket_level_access = true +} + +resource "google_storage_managed_folder" "folder" { + bucket = google_storage_bucket.bucket.name + name = "managed/folder/name/" +} + +data "google_iam_policy" "foo" { + binding { + role = "%{role}" + members = ["user:admin@hashicorptest.com"] + condition { + # Check that lack of description doesn't cause any issues + # Relates to issue : https://github.com/hashicorp/terraform-provider-google/issues/8701 + title = "%{condition_title_no_desc}" + expression = "%{condition_expr_no_desc}" + } + } + binding { + role = "%{role}" + members = ["user:admin@hashicorptest.com"] + condition { + title = "%{condition_title}" + description = "%{condition_desc}" + expression = "%{condition_expr}" + } + } + binding { + role = "%{admin_role}" + members = ["serviceAccount:%{service_account}"] + } +} + +resource "google_storage_managed_folder_iam_policy" "foo" { + bucket = google_storage_managed_folder.folder.bucket + managed_folder = google_storage_managed_folder.folder.name + policy_data = data.google_iam_policy.foo.policy_data +} +`, context) +} diff --git a/mmv1/third_party/terraform/services/vmwareengine/resource_vmwareengine_cluster_sweeper.go b/mmv1/third_party/terraform/services/vmwareengine/resource_vmwareengine_cluster_sweeper.go new file mode 100644 index 000000000000..71afd7f546f9 --- /dev/null +++ b/mmv1/third_party/terraform/services/vmwareengine/resource_vmwareengine_cluster_sweeper.go @@ -0,0 +1,174 @@ +package vmwareengine + +import ( + "context" + "fmt" + "log" + "strings" + "testing" + + "github.com/hashicorp/terraform-provider-google/google/envvar" + "github.com/hashicorp/terraform-provider-google/google/sweeper" + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" +) + +func init() { + sweeper.AddTestSweepers("VmwareengineCluster", testSweepVmwareengineCluster) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepVmwareengineCluster(region string) error { + resourceName := "VmwareengineCluster" + log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s", resourceName) + + config, err := sweeper.SharedConfigForRegion(region) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error getting shared config for region: %s", err) + return err + } + + err = config.LoadAndValidate(context.Background()) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error loading: %s", err) + return err + } + + t := &testing.T{} + billingId := envvar.GetTestBillingAccountFromEnv(t) + + // List of location values includes: + // * zones used for this resource type's acc tests in the past + // * the 'region' passed to the sweeper + locations := []string{region, "us-central1-a", "us-central1-b", "southamerica-west1-a", "southamerica-west1-b", "me-west1-a", "me-west1-b"} + log.Printf("[INFO][SWEEPER_LOG] Sweeping will include these locations: %v.", locations) + for _, location := range locations { + log.Printf("[INFO][SWEEPER_LOG] Beginning the process of sweeping location '%s'.", location) + + // Setup variables to replace in list template + d := &tpgresource.ResourceDataMock{ + FieldsInSchema: map[string]interface{}{ + "project": config.Project, + "region": location, + "location": location, + "zone": location, + "billing_account": billingId, + }, + } + + log.Printf("[INFO][SWEEPER_LOG] looking for parent resources in location '%s'.", location) + privateCloudNames, err := listPrivateCloudsInLocation(d, config) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error finding parental resources in location %s: %s", location, err) + continue + } + for _, parent := range privateCloudNames { + + // `parent` will be string of form projects/my-project/locations/us-central1-a/privateClouds/my-cloud + listUrl := fmt.Sprintf("https://vmwareengine.googleapis.com/v1/projects/%s/clusters", parent) + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: config.Project, + RawURL: listUrl, + UserAgent: config.UserAgent, + }) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err) + continue + } + + resourceList, ok := res["clusters"] + if !ok { + log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.") + continue + } + + rl := resourceList.([]interface{}) + + log.Printf("[INFO][SWEEPER_LOG] Found %d items in %s list response.", len(rl), resourceName) + // Keep count of items that aren't sweepable for logging. + nonPrefixCount := 0 + for _, ri := range rl { + obj := ri.(map[string]interface{}) + if obj["name"] == nil { + log.Printf("[INFO][SWEEPER_LOG] %s resource name was nil", resourceName) + continue + } + + name := tpgresource.GetResourceNameFromSelfLink(obj["name"].(string)) + // Skip resources that shouldn't be sweeped + if !sweeper.IsSweepableTestResource(name) { + nonPrefixCount++ + continue + } + + deleteTemplate := "https://vmwareengine.googleapis.com/v1/{{parent}}/clusters/{{name}}" + deleteUrl, err := tpgresource.ReplaceVars(d, config, deleteTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing delete url: %s", err) + continue + } + deleteUrl = deleteUrl + name + + // Don't wait on operations as we may have a lot to delete + _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "DELETE", + Project: config.Project, + RawURL: deleteUrl, + UserAgent: config.UserAgent, + }) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error deleting for url %s : %s", deleteUrl, err) + } else { + log.Printf("[INFO][SWEEPER_LOG] Sent delete request for %s resource: %s", resourceName, name) + } + } + + if nonPrefixCount > 0 { + log.Printf("[INFO][SWEEPER_LOG] %d items were non-sweepable and skipped.", nonPrefixCount) + } + } + } + return nil +} + +func listPrivateCloudsInLocation(d *tpgresource.ResourceDataMock, config *transport_tpg.Config) ([]string, error) { + listTemplate := strings.Split("https://vmwareengine.googleapis.com/v1/projects/{{project}}/locations/{{location}}/privateClouds", "?")[0] + listUrl, err := tpgresource.ReplaceVars(d, config, listTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing sweeper list url: %s", err) + return nil, err + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: config.Project, + RawURL: listUrl, + UserAgent: config.UserAgent, + }) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err) + return nil, err + } + + resourceList, ok := res["privateClouds"] + if !ok { + log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.") + return nil, fmt.Errorf("nothing found in response") + } + + rl := resourceList.([]interface{}) + privateCloudNames := []string{} + for _, r := range rl { + resource := r.(map[string]interface{}) + if name, ok := resource["name"]; ok { + privateCloudNames = append(privateCloudNames, name.(string)) + } + + } + return privateCloudNames, nil +} diff --git a/mmv1/third_party/terraform/services/vpcaccess/data_source_vpc_access_connector_test.go b/mmv1/third_party/terraform/services/vpcaccess/data_source_vpc_access_connector_test.go index c5defbe76372..b64da1c8fd1f 100644 --- a/mmv1/third_party/terraform/services/vpcaccess/data_source_vpc_access_connector_test.go +++ b/mmv1/third_party/terraform/services/vpcaccess/data_source_vpc_access_connector_test.go @@ -36,7 +36,7 @@ func TestAccVPCAccessConnectorDatasource_basic(t *testing.T) { func testAccVPCAccessConnectorDatasourceConfig(suffix string) string { return fmt.Sprintf(` resource "google_vpc_access_connector" "connector" { - name = "vpc-con-test-%s" + name = "tf-test-%s" ip_cidr_range = "10.8.0.32/28" network = "default" region = "us-central1" diff --git a/mmv1/third_party/terraform/services/workstations/go/resource_workstations_workstation_config_test.go.tmpl b/mmv1/third_party/terraform/services/workstations/go/resource_workstations_workstation_config_test.go.tmpl index 576a21ca1262..2cf6f91f2d2f 100644 --- a/mmv1/third_party/terraform/services/workstations/go/resource_workstations_workstation_config_test.go.tmpl +++ b/mmv1/third_party/terraform/services/workstations/go/resource_workstations_workstation_config_test.go.tmpl @@ -2,8 +2,10 @@ package workstations_test {{- if ne $.TargetVersionName "ga" }} import ( + "fmt" "testing" "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/envvar" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) @@ -707,8 +709,13 @@ func testAccWorkstationsWorkstationConfig_readinessChecks(context map[string]int func TestAccWorkstationsWorkstationConfig_update(t *testing.T) { t.Parallel() + randString := acctest.RandString(t, 10) context := map[string]interface{}{ - "random_suffix": acctest.RandString(t, 10), + "random_suffix": randString, + "project_id": fmt.Sprintf("tf-test-proj-%s", randString), + "key_short_name": fmt.Sprintf("tf-test-key-%s", randString), + "value_short_name": fmt.Sprintf("tf-test-value-%s", randString), + "org_id": envvar.GetTestOrgFromEnv(t), } acctest.VcrTest(t, resource.TestCase{ @@ -749,6 +756,17 @@ func TestAccWorkstationsWorkstationConfig_update(t *testing.T) { func testAccWorkstationsWorkstationConfig_update(context map[string]interface{}) string { return acctest.Nprintf(` +resource "google_tags_tag_key" "tag_key1" { + provider = google-beta + parent = "organizations/%{org_id}" + short_name = "%{key_short_name}" +} + +resource "google_tags_tag_value" "tag_value1" { + provider = google-beta + parent = "tagKeys/${google_tags_tag_key.tag_key1.name}" + short_name = "%{value_short_name}" +} resource "google_compute_network" "default" { provider = google-beta name = "tf-test-workstation-cluster%{random_suffix}" @@ -1292,4 +1310,91 @@ resource "google_workstations_workstation_config" "default" { } `, context) } -{{- end }} + +func TestAccWorkstationsWorkstationConfig_vmTags(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), + CheckDestroy: testAccCheckWorkstationsWorkstationConfigDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccWorkstationsWorkstationConfig_vmTags(context), + }, + { + ResourceName: "google_workstations_workstation_cluster.default", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"etag"}, + }, + }, + }) +} + +func testAccWorkstationsWorkstationConfig_vmTags(context map[string]interface{}) string { +return acctest.Nprintf(` +data "google_project" "project" { + provider = google-beta +} + +resource "google_tags_tag_key" "tag_key1" { + provider = google-beta + parent = "projects/${data.google_project.project.number}" + short_name = "tf_test_tag_key1%{random_suffix}" +} + +resource "google_tags_tag_value" "tag_value1" { + provider = google-beta + parent = "tagKeys/${google_tags_tag_key.tag_key1.name}" + short_name = "tf_test_tag_value1%{random_suffix}" +} + +resource "google_compute_network" "default" { + provider = google-beta + name = "tf-test-workstation-cluster%{random_suffix}" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "default" { + provider = google-beta + name = "tf-test-workstation-cluster%{random_suffix}" + ip_cidr_range = "10.0.0.0/24" + region = "us-central1" + network = google_compute_network.default.name +} + +resource "google_workstations_workstation_cluster" "default" { + provider = google-beta + workstation_cluster_id = "tf-test-workstation-cluster%{random_suffix}" + network = google_compute_network.default.id + subnetwork = google_compute_subnetwork.default.id + location = "us-central1" +} + +resource "google_workstations_workstation_config" "default" { + provider = google-beta + workstation_config_id = "tf-test-workstation-config%{random_suffix}" + workstation_cluster_id = google_workstations_workstation_cluster.default.workstation_cluster_id + location = "us-central1" + + host { + gce_instance { + machine_type = "e2-standard-4" + boot_disk_size_gb = 35 + disable_public_ip_addresses = true + vm_tags = { + "tagKeys/${google_tags_tag_key.tag_key1.name}" = "tagValues/${google_tags_tag_value.tag_value1.name}" + } + } + } + +} +`, context) +} + +{{ end }} diff --git a/mmv1/third_party/terraform/services/workstations/resource_workstations_workstation_config_test.go.erb b/mmv1/third_party/terraform/services/workstations/resource_workstations_workstation_config_test.go.erb index 16fb4c655cdf..08169d2fe496 100644 --- a/mmv1/third_party/terraform/services/workstations/resource_workstations_workstation_config_test.go.erb +++ b/mmv1/third_party/terraform/services/workstations/resource_workstations_workstation_config_test.go.erb @@ -3,8 +3,10 @@ package workstations_test <% unless version == "ga" -%> import ( + "fmt" "testing" "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/envvar" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) @@ -708,8 +710,13 @@ func testAccWorkstationsWorkstationConfig_readinessChecks(context map[string]int func TestAccWorkstationsWorkstationConfig_update(t *testing.T) { t.Parallel() + randString := acctest.RandString(t, 10) context := map[string]interface{}{ - "random_suffix": acctest.RandString(t, 10), + "random_suffix": randString, + "project_id": fmt.Sprintf("tf-test-proj-%s", randString), + "key_short_name": fmt.Sprintf("tf-test-key-%s", randString), + "value_short_name": fmt.Sprintf("tf-test-value-%s", randString), + "org_id": envvar.GetTestOrgFromEnv(t), } acctest.VcrTest(t, resource.TestCase{ @@ -750,6 +757,18 @@ func TestAccWorkstationsWorkstationConfig_update(t *testing.T) { func testAccWorkstationsWorkstationConfig_update(context map[string]interface{}) string { return acctest.Nprintf(` +resource "google_tags_tag_key" "tag_key1" { + provider = google-beta + parent = "organizations/%{org_id}" + short_name = "%{key_short_name}" +} + +resource "google_tags_tag_value" "tag_value1" { + provider = google-beta + parent = "tagKeys/${google_tags_tag_key.tag_key1.name}" + short_name = "%{value_short_name}" +} + resource "google_compute_network" "default" { provider = google-beta name = "tf-test-workstation-cluster%{random_suffix}" @@ -1293,4 +1312,91 @@ resource "google_workstations_workstation_config" "default" { } `, context) } + +func TestAccWorkstationsWorkstationConfig_vmTags(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), + CheckDestroy: testAccCheckWorkstationsWorkstationConfigDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccWorkstationsWorkstationConfig_vmTags(context), + }, + { + ResourceName: "google_workstations_workstation_cluster.default", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"etag"}, + }, + }, + }) +} + +func testAccWorkstationsWorkstationConfig_vmTags(context map[string]interface{}) string { + return acctest.Nprintf(` +data "google_project" "project" { + provider = google-beta +} + +resource "google_tags_tag_key" "tag_key1" { + provider = google-beta + parent = "projects/${data.google_project.project.number}" + short_name = "tf_test_tag_key1%{random_suffix}" +} + +resource "google_tags_tag_value" "tag_value1" { + provider = google-beta + parent = "tagKeys/${google_tags_tag_key.tag_key1.name}" + short_name = "tf_test_tag_value1%{random_suffix}" +} + +resource "google_compute_network" "default" { + provider = google-beta + name = "tf-test-workstation-cluster%{random_suffix}" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "default" { + provider = google-beta + name = "tf-test-workstation-cluster%{random_suffix}" + ip_cidr_range = "10.0.0.0/24" + region = "us-central1" + network = google_compute_network.default.name +} + +resource "google_workstations_workstation_cluster" "default" { + provider = google-beta + workstation_cluster_id = "tf-test-workstation-cluster%{random_suffix}" + network = google_compute_network.default.id + subnetwork = google_compute_subnetwork.default.id + location = "us-central1" +} + +resource "google_workstations_workstation_config" "default" { + provider = google-beta + workstation_config_id = "tf-test-workstation-config%{random_suffix}" + workstation_cluster_id = google_workstations_workstation_cluster.default.workstation_cluster_id + location = "us-central1" + + host { + gce_instance { + machine_type = "e2-standard-4" + boot_disk_size_gb = 35 + disable_public_ip_addresses = true + vm_tags = { + "tagKeys/${google_tags_tag_key.tag_key1.name}" = "tagValues/${google_tags_tag_value.tag_value1.name}" + } + } + } + +} +`, context) +} + <% end -%> diff --git a/mmv1/third_party/terraform/tpgresource/common_diff_suppress.go.erb b/mmv1/third_party/terraform/tpgresource/common_diff_suppress.go.erb index 3d2346a82c6f..4f28d140e426 100644 --- a/mmv1/third_party/terraform/tpgresource/common_diff_suppress.go.erb +++ b/mmv1/third_party/terraform/tpgresource/common_diff_suppress.go.erb @@ -4,52 +4,14 @@ package tpgresource import ( - "crypto/sha256" - "log" - "encoding/hex" - "net" "reflect" "regexp" - "strconv" "strings" "time" - "bytes" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func OptionalPrefixSuppress(prefix string) schema.SchemaDiffSuppressFunc { - return func(k, old, new string, d *schema.ResourceData) bool { - return prefix+old == new || prefix+new == old - } -} - -func IgnoreMissingKeyInMap(key string) schema.SchemaDiffSuppressFunc { - return func(k, old, new string, d *schema.ResourceData) bool { - log.Printf("[DEBUG] - suppressing diff %q with old %q, new %q", k, old, new) - if strings.HasSuffix(k, ".%") { - oldNum, err := strconv.Atoi(old) - if err != nil { - log.Printf("[ERROR] could not parse %q as number, no longer attempting diff suppress", old) - return false - } - newNum, err := strconv.Atoi(new) - if err != nil { - log.Printf("[ERROR] could not parse %q as number, no longer attempting diff suppress", new) - return false - } - return oldNum+1 == newNum - } else if strings.HasSuffix(k, "." + key) { - return old == "" - } - return false - } -} - -func OptionalSurroundingSpacesSuppress(k, old, new string, d *schema.ResourceData) bool { - return strings.TrimSpace(old) == strings.TrimSpace(new) -} - func EmptyOrDefaultStringSuppress(defaultVal string) schema.SchemaDiffSuppressFunc { return func(k, old, new string, d *schema.ResourceData) bool { return (old == "" && new == defaultVal) || (new == "" && old == defaultVal) @@ -61,56 +23,10 @@ func EmptyOrFalseSuppressBoolean(k, old, new string, d *schema.ResourceData) boo return (o == nil && !n.(bool)) } -func IpCidrRangeDiffSuppress(k, old, new string, d *schema.ResourceData) bool { - // The range may be a: - // A) single IP address (e.g. 10.2.3.4) - // B) CIDR format string (e.g. 10.1.2.0/24) - // C) netmask (e.g. /24) - // - // For A) and B), no diff to suppress, they have to match completely. - // For C), The API picks a network IP address and this creates a diff of the form: - // network_interface.0.alias_ip_range.0.ip_cidr_range: "10.128.1.0/24" => "/24" - // We should only compare the mask portion for this case. - if len(new) > 0 && new[0] == '/' { - oldNetmaskStartPos := strings.LastIndex(old, "/") - - if oldNetmaskStartPos != -1 { - oldNetmask := old[strings.LastIndex(old, "/"):] - if oldNetmask == new { - return true - } - } - } - - return false -} - -// Sha256DiffSuppress -// if old is the hex-encoded sha256 sum of new, treat them as equal -func Sha256DiffSuppress(_, old, new string, _ *schema.ResourceData) bool { - return hex.EncodeToString(sha256.New().Sum([]byte(old))) == new -} - func CaseDiffSuppress(_, old, new string, _ *schema.ResourceData) bool { return strings.ToUpper(old) == strings.ToUpper(new) } -// Port range '80' and '80-80' is equivalent. -// `old` is read from the server and always has the full range format (e.g. '80-80', '1024-2048'). -// `new` can be either a single port or a port range. -func PortRangeDiffSuppress(k, old, new string, d *schema.ResourceData) bool { - return old == new+"-"+new -} - -// Single-digit hour is equivalent to hour with leading zero e.g. suppress diff 1:00 => 01:00. -// Assume either value could be in either format. -func Rfc3339TimeDiffSuppress(k, old, new string, d *schema.ResourceData) bool { - if (len(old) == 4 && "0"+old == new) || (len(new) == 4 && "0"+new == old) { - return true - } - return false -} - func EmptyOrUnsetBlockDiffSuppress(k, old, new string, d *schema.ResourceData) bool { o, n := d.GetChange(strings.TrimSuffix(k, ".#")) return EmptyOrUnsetBlockDiffSuppressLogic(k, old, new, o, n) @@ -144,25 +60,6 @@ func EmptyOrUnsetBlockDiffSuppressLogic(k, old, new string, o, n interface{}) bo return true } -// Suppress diffs for values that are equivalent except for their use of the words "location" -// compared to "region" or "zone" -func LocationDiffSuppress(k, old, new string, d *schema.ResourceData) bool { - return LocationDiffSuppressHelper(old, new) || LocationDiffSuppressHelper(new, old) -} - -func LocationDiffSuppressHelper(a, b string) bool { - return strings.Replace(a, "/locations/", "/regions/", 1) == b || - strings.Replace(a, "/locations/", "/zones/", 1) == b -} - -// For managed SSL certs, if new is an absolute FQDN (trailing '.') but old isn't, treat them as equals. -func AbsoluteDomainSuppress(k, old, new string, _ *schema.ResourceData) bool { - if strings.HasPrefix(k, "managed.0.domains.") { - return old == strings.TrimRight(new, ".") || new == strings.TrimRight(old, ".") - } - return false -} - func TimestampDiffSuppress(format string) schema.SchemaDiffSuppressFunc { return func(_, old, new string, _ *schema.ResourceData) bool { oldT, err := time.Parse(format, old) @@ -179,50 +76,6 @@ func TimestampDiffSuppress(format string) schema.SchemaDiffSuppressFunc { } } -// Suppresses diff for IPv4 and IPv6 different formats. -// It also suppresses diffs if an IP is changing to a reference. -func InternalIpDiffSuppress(_, old, new string, _ *schema.ResourceData) bool { - addr_equality := false - netmask_equality := false - - addr_netmask_old := strings.Split(old, "/") - addr_netmask_new := strings.Split(new, "/") - - // Check if old or new are IPs (with or without netmask) - var addr_old net.IP - if net.ParseIP(addr_netmask_old[0]) == nil { - addr_old = net.ParseIP(old) - } else { - addr_old = net.ParseIP(addr_netmask_old[0]) - } - var addr_new net.IP - if net.ParseIP(addr_netmask_new[0]) == nil { - addr_new = net.ParseIP(new) - } else { - addr_new = net.ParseIP(addr_netmask_new[0]) - } - - if addr_old != nil { - if addr_new == nil { - // old is an IP and new is a reference - addr_equality = true - } else { - // old and new are IP addresses - addr_equality = bytes.Equal(addr_old, addr_new) - } - } - - // If old and new both have a netmask compare them, otherwise suppress - // This is not technically correct but prevents the permadiff described in https://github.com/hashicorp/terraform-provider-google/issues/16400 - if (len(addr_netmask_old)) == 2 && (len(addr_netmask_new) == 2) { - netmask_equality = addr_netmask_old[1] == addr_netmask_new[1] - } else { - netmask_equality = true - } - - return addr_equality && netmask_equality -} - // Suppress diffs for duration format. ex "60.0s" and "60s" same // https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#duration func DurationDiffSuppress(k, old, new string, d *schema.ResourceData) bool { @@ -237,50 +90,6 @@ func DurationDiffSuppress(k, old, new string, d *schema.ResourceData) bool { return oDuration == nDuration } -// Use this method when the field accepts either an IP address or a -// self_link referencing a resource (such as google_compute_route's -// next_hop_ilb) -func CompareIpAddressOrSelfLinkOrResourceName(_, old, new string, _ *schema.ResourceData) bool { - // if we can parse `new` as an IP address, then compare as strings - if net.ParseIP(new) != nil { - return new == old - } - - // otherwise compare as self links - return CompareSelfLinkOrResourceName("", old, new, nil) -} - -<% unless version == "ga" -%> -// Suppress all diffs, used for Disk.Interface which is a nonfunctional field -func AlwaysDiffSuppress(_, _, _ string, _ *schema.ResourceData) bool { - return true -} -<% end -%> - -// Use this method when subnet is optioanl and auto_create_subnetworks = true -// API sometimes choose a subnet so the diff needs to be ignored -func CompareOptionalSubnet(_, old, new string, _ *schema.ResourceData) bool { - if IsEmptyValue(reflect.ValueOf(new)) { - return true - } - // otherwise compare as self links - return CompareSelfLinkOrResourceName("", old, new, nil) -} - -// Suppress diffs in below cases -// "https://hello-rehvs75zla-uc.a.run.app/" -> "https://hello-rehvs75zla-uc.a.run.app" -// "https://hello-rehvs75zla-uc.a.run.app" -> "https://hello-rehvs75zla-uc.a.run.app/" -func LastSlashDiffSuppress(_, old, new string, _ *schema.ResourceData) bool { - if last := len(new) - 1; last >= 0 && new[last] == '/' { - new = new[:last] - } - - if last := len(old) - 1; last >= 0 && old[last] == '/' { - old = old[:last] - } - return new == old -} - // Suppress diffs when the value read from api // has the project number instead of the project name func ProjectNumberDiffSuppress(_, old, new string, _ *schema.ResourceData) bool { diff --git a/mmv1/third_party/terraform/tpgresource/common_diff_suppress_test.go b/mmv1/third_party/terraform/tpgresource/common_diff_suppress_test.go index e9ef36952061..3cb2ef2d21c6 100644 --- a/mmv1/third_party/terraform/tpgresource/common_diff_suppress_test.go +++ b/mmv1/third_party/terraform/tpgresource/common_diff_suppress_test.go @@ -4,112 +4,6 @@ package tpgresource import "testing" -func TestOptionalPrefixSuppress(t *testing.T) { - cases := map[string]struct { - Old, New string - Prefix string - ExpectDiffSuppress bool - }{ - "with same prefix": { - Old: "my-folder", - New: "folders/my-folder", - Prefix: "folders/", - ExpectDiffSuppress: true, - }, - "with different prefix": { - Old: "folders/my-folder", - New: "organizations/my-folder", - Prefix: "folders/", - ExpectDiffSuppress: false, - }, - "same without prefix": { - Old: "my-folder", - New: "my-folder", - Prefix: "folders/", - ExpectDiffSuppress: false, - }, - "different without prefix": { - Old: "my-folder", - New: "my-new-folder", - Prefix: "folders/", - ExpectDiffSuppress: false, - }, - } - - for tn, tc := range cases { - if OptionalPrefixSuppress(tc.Prefix)("folder", tc.Old, tc.New, nil) != tc.ExpectDiffSuppress { - t.Fatalf("bad: %s, '%s' => '%s' expect %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress) - } - } -} - -func TestIgnoreMissingKeyInMap(t *testing.T) { - cases := map[string]struct { - Old, New string - Key string - ExpectDiffSuppress bool - }{ - "missing key in map": { - Old: "", - New: "v1", - Key: "x-goog-version", - ExpectDiffSuppress: true, - }, - "different values": { - Old: "v1", - New: "v2", - Key: "x-goog-version", - ExpectDiffSuppress: false, - }, - "same values": { - Old: "v1", - New: "v1", - Key: "x-goog-version", - ExpectDiffSuppress: false, - }, - } - - for tn, tc := range cases { - if IgnoreMissingKeyInMap(tc.Key)("push_config.0.attributes."+tc.Key, tc.Old, tc.New, nil) != tc.ExpectDiffSuppress { - t.Fatalf("bad: %s, '%s' => '%s' expect %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress) - } - } -} - -func TestOptionalSurroundingSpacesSuppress(t *testing.T) { - cases := map[string]struct { - Old, New string - ExpectDiffSuppress bool - }{ - "surrounding spaces": { - Old: "value", - New: " value ", - ExpectDiffSuppress: true, - }, - "no surrounding spaces": { - Old: "value", - New: "value", - ExpectDiffSuppress: true, - }, - "one space each": { - Old: " value", - New: "value ", - ExpectDiffSuppress: true, - }, - "different values": { - Old: " different", - New: "values ", - ExpectDiffSuppress: false, - }, - } - - for tn, tc := range cases { - if OptionalSurroundingSpacesSuppress("filter", tc.Old, tc.New, nil) != tc.ExpectDiffSuppress { - t.Fatalf("bad: %s, '%s' => '%s' expect %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress) - } - } -} - func TestCaseDiffSuppress(t *testing.T) { cases := map[string]struct { Old, New string @@ -139,118 +33,6 @@ func TestCaseDiffSuppress(t *testing.T) { } } -func TestPortRangeDiffSuppress(t *testing.T) { - cases := map[string]struct { - Old, New string - ExpectDiffSuppress bool - }{ - "different single values": { - Old: "80-80", - New: "443", - ExpectDiffSuppress: false, - }, - "different ranges": { - Old: "80-80", - New: "443-444", - ExpectDiffSuppress: false, - }, - "same single values": { - Old: "80-80", - New: "80", - ExpectDiffSuppress: true, - }, - "same ranges": { - Old: "80-80", - New: "80-80", - ExpectDiffSuppress: false, - }, - } - - for tn, tc := range cases { - if PortRangeDiffSuppress("ports", tc.Old, tc.New, nil) != tc.ExpectDiffSuppress { - t.Fatalf("bad: %s, '%s' => '%s' expect %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress) - } - } -} - -func TestLocationDiffSuppress(t *testing.T) { - cases := map[string]struct { - Old, New string - ExpectDiffSuppress bool - }{ - "locations to zones": { - Old: "projects/x/locations/y/resource/z", - New: "projects/x/zones/y/resource/z", - ExpectDiffSuppress: true, - }, - "regions to locations": { - Old: "projects/x/regions/y/resource/z", - New: "projects/x/locations/y/resource/z", - ExpectDiffSuppress: true, - }, - "locations to locations": { - Old: "projects/x/locations/y/resource/z", - New: "projects/x/locations/y/resource/z", - ExpectDiffSuppress: false, - }, - "zones to regions": { - Old: "projects/x/zones/y/resource/z", - New: "projects/x/regions/y/resource/z", - ExpectDiffSuppress: false, - }, - "different locations": { - Old: "projects/x/locations/a/resource/z", - New: "projects/x/locations/b/resource/z", - ExpectDiffSuppress: false, - }, - } - - for tn, tc := range cases { - if LocationDiffSuppress("policy_uri", tc.Old, tc.New, nil) != tc.ExpectDiffSuppress { - t.Fatalf("bad: %s, '%s' => '%s' expect %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress) - } - } -} - -func TestAbsoluteDomainSuppress(t *testing.T) { - cases := map[string]struct { - Old, New string - ExpectDiffSuppress bool - }{ - "new trailing dot": { - Old: "sslcert.tf-test.club", - New: "sslcert.tf-test.club.", - ExpectDiffSuppress: true, - }, - "old trailing dot": { - Old: "sslcert.tf-test.club.", - New: "sslcert.tf-test.club", - ExpectDiffSuppress: true, - }, - "same trailing dot": { - Old: "sslcert.tf-test.club.", - New: "sslcert.tf-test.club.", - ExpectDiffSuppress: false, - }, - "different trailing dot": { - Old: "sslcert.tf-test.club.", - New: "sslcert.tf-test.clubs.", - ExpectDiffSuppress: false, - }, - "different no trailing dot": { - Old: "sslcert.tf-test.club", - New: "sslcert.tf-test.clubs", - ExpectDiffSuppress: false, - }, - } - - for tn, tc := range cases { - if AbsoluteDomainSuppress("managed.0.domains.", tc.Old, tc.New, nil) != tc.ExpectDiffSuppress { - t.Fatalf("bad: %s, '%s' => '%s' expect %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress) - } - } -} - func TestDurationDiffSuppress(t *testing.T) { cases := map[string]struct { Old, New string @@ -285,149 +67,6 @@ func TestDurationDiffSuppress(t *testing.T) { } } -func TestInternalIpDiffSuppress(t *testing.T) { - cases := map[string]struct { - Old, New string - ExpectDiffSuppress bool - }{ - "suppress - same long and short ipv6 IPs without netmask": { - Old: "2600:1900:4020:31cd:8000:0:0:0", - New: "2600:1900:4020:31cd:8000::", - ExpectDiffSuppress: true, - }, - "suppress - long and short ipv6 IPs with netmask": { - Old: "2600:1900:4020:31cd:8000:0:0:0/96", - New: "2600:1900:4020:31cd:8000::/96", - ExpectDiffSuppress: true, - }, - "suppress - long ipv6 IP with netmask and short ipv6 IP without netmask": { - Old: "2600:1900:4020:31cd:8000:0:0:0/96", - New: "2600:1900:4020:31cd:8000::", - ExpectDiffSuppress: true, - }, - "suppress - long ipv6 IP without netmask and short ipv6 IP with netmask": { - Old: "2600:1900:4020:31cd:8000:0:0:0", - New: "2600:1900:4020:31cd:8000::/96", - ExpectDiffSuppress: true, - }, - "suppress - long ipv6 IP with netmask and reference": { - Old: "2600:1900:4020:31cd:8000:0:0:0/96", - New: "projects/project_id/regions/region/addresses/address-name", - ExpectDiffSuppress: true, - }, - "suppress - long ipv6 IP without netmask and reference": { - Old: "2600:1900:4020:31cd:8000:0:0:0", - New: "projects/project_id/regions/region/addresses/address-name", - ExpectDiffSuppress: true, - }, - "do not suppress - ipv6 IPs different netmask": { - Old: "2600:1900:4020:31cd:8000:0:0:0/96", - New: "2600:1900:4020:31cd:8000:0:0:0/95", - ExpectDiffSuppress: false, - }, - "do not suppress - reference and ipv6 IP with netmask": { - Old: "projects/project_id/regions/region/addresses/address-name", - New: "2600:1900:4020:31cd:8000:0:0:0/96", - ExpectDiffSuppress: false, - }, - "do not suppress - ipv6 IPs - 1": { - Old: "2600:1900:4020:31cd:8000:0:0:0", - New: "2600:1900:4020:31cd:8001::", - ExpectDiffSuppress: false, - }, - "do not suppress - ipv6 IPs - 2": { - Old: "2600:1900:4020:31cd:8000:0:0:0", - New: "2600:1900:4020:31cd:8000:0:0:8000", - ExpectDiffSuppress: false, - }, - "suppress - ipv4 IPs": { - Old: "1.2.3.4", - New: "1.2.3.4", - ExpectDiffSuppress: true, - }, - "suppress - ipv4 IP without netmask and ipv4 IP with netmask": { - Old: "1.2.3.4", - New: "1.2.3.4/24", - ExpectDiffSuppress: true, - }, - "suppress - ipv4 IP without netmask and reference": { - Old: "1.2.3.4", - New: "projects/project_id/regions/region/addresses/address-name", - ExpectDiffSuppress: true, - }, - "do not suppress - reference and ipv4 IP without netmask": { - Old: "projects/project_id/regions/region/addresses/address-name", - New: "1.2.3.4", - ExpectDiffSuppress: false, - }, - "do not suppress - different ipv4 IPs": { - Old: "1.2.3.4", - New: "1.2.3.5", - ExpectDiffSuppress: false, - }, - "do not suppress - ipv4 IPs different netmask": { - Old: "1.2.3.4/24", - New: "1.2.3.5/25", - ExpectDiffSuppress: false, - }, - "do not suppress - different references": { - Old: "projects/project_id/regions/region/addresses/address-name", - New: "projects/project_id/regions/region/addresses/address-name-1", - ExpectDiffSuppress: false, - }, - "do not suppress - same references": { - Old: "projects/project_id/regions/region/addresses/address-name", - New: "projects/project_id/regions/region/addresses/address-name", - ExpectDiffSuppress: false, - }, - } - - for tn, tc := range cases { - if InternalIpDiffSuppress("ipv4/v6_compare", tc.Old, tc.New, nil) != tc.ExpectDiffSuppress { - t.Fatalf("bad: %s, '%s' => '%s' expect %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress) - } - } -} - -func TestLastSlashDiffSuppress(t *testing.T) { - cases := map[string]struct { - Old, New string - ExpectDiffSuppress bool - }{ - "slash to no slash": { - Old: "https://hello-rehvs75zla-uc.a.run.app/", - New: "https://hello-rehvs75zla-uc.a.run.app", - ExpectDiffSuppress: true, - }, - "no slash to slash": { - Old: "https://hello-rehvs75zla-uc.a.run.app", - New: "https://hello-rehvs75zla-uc.a.run.app/", - ExpectDiffSuppress: true, - }, - "slash to slash": { - Old: "https://hello-rehvs75zla-uc.a.run.app/", - New: "https://hello-rehvs75zla-uc.a.run.app/", - ExpectDiffSuppress: true, - }, - "no slash to no slash": { - Old: "https://hello-rehvs75zla-uc.a.run.app", - New: "https://hello-rehvs75zla-uc.a.run.app", - ExpectDiffSuppress: true, - }, - "different domains": { - Old: "https://x.a.run.app/", - New: "https://y.a.run.app", - ExpectDiffSuppress: false, - }, - } - - for tn, tc := range cases { - if LastSlashDiffSuppress("uri", tc.Old, tc.New, nil) != tc.ExpectDiffSuppress { - t.Fatalf("bad: %s, '%s' => '%s' expect %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress) - } - } -} - func TestEmptyOrUnsetBlockDiffSuppress(t *testing.T) { cases := map[string]struct { Key, Old, New string diff --git a/mmv1/third_party/terraform/tpgresource/go/common_diff_suppress.go b/mmv1/third_party/terraform/tpgresource/go/common_diff_suppress.go new file mode 100644 index 000000000000..70763fdd58da --- /dev/null +++ b/mmv1/third_party/terraform/tpgresource/go/common_diff_suppress.go @@ -0,0 +1,124 @@ +// Contains common diff suppress functions. + +package tpgresource + +import ( + "reflect" + "regexp" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func EmptyOrDefaultStringSuppress(defaultVal string) schema.SchemaDiffSuppressFunc { + return func(k, old, new string, d *schema.ResourceData) bool { + return (old == "" && new == defaultVal) || (new == "" && old == defaultVal) + } +} + +func EmptyOrFalseSuppressBoolean(k, old, new string, d *schema.ResourceData) bool { + o, n := d.GetChange(k) + return (o == nil && !n.(bool)) +} + +func CaseDiffSuppress(_, old, new string, _ *schema.ResourceData) bool { + return strings.ToUpper(old) == strings.ToUpper(new) +} + +func EmptyOrUnsetBlockDiffSuppress(k, old, new string, d *schema.ResourceData) bool { + o, n := d.GetChange(strings.TrimSuffix(k, ".#")) + return EmptyOrUnsetBlockDiffSuppressLogic(k, old, new, o, n) +} + +// The core logic for EmptyOrUnsetBlockDiffSuppress, in a format that is more conducive +// to unit testing. +func EmptyOrUnsetBlockDiffSuppressLogic(k, old, new string, o, n interface{}) bool { + if !strings.HasSuffix(k, ".#") { + return false + } + var l []interface{} + if old == "0" && new == "1" { + l = n.([]interface{}) + } else if new == "0" && old == "1" { + l = o.([]interface{}) + } else { + // we don't have one set and one unset, so don't suppress the diff + return false + } + + contents, ok := l[0].(map[string]interface{}) + if !ok { + return false + } + for _, v := range contents { + if !IsEmptyValue(reflect.ValueOf(v)) { + return false + } + } + return true +} + +func TimestampDiffSuppress(format string) schema.SchemaDiffSuppressFunc { + return func(_, old, new string, _ *schema.ResourceData) bool { + oldT, err := time.Parse(format, old) + if err != nil { + return false + } + + newT, err := time.Parse(format, new) + if err != nil { + return false + } + + return oldT == newT + } +} + +// Suppress diffs for duration format. ex "60.0s" and "60s" same +// https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#duration +func DurationDiffSuppress(k, old, new string, d *schema.ResourceData) bool { + oDuration, err := time.ParseDuration(old) + if err != nil { + return false + } + nDuration, err := time.ParseDuration(new) + if err != nil { + return false + } + return oDuration == nDuration +} + +// Suppress diffs when the value read from api +// has the project number instead of the project name +func ProjectNumberDiffSuppress(_, old, new string, _ *schema.ResourceData) bool { + var a2, b2 string + reN := regexp.MustCompile("projects/\\d+") + re := regexp.MustCompile("projects/[^/]+") + replacement := []byte("projects/equal") + a2 = string(reN.ReplaceAll([]byte(old), replacement)) + b2 = string(re.ReplaceAll([]byte(new), replacement)) + return a2 == b2 +} + +func IsNewResource(diff TerraformResourceDiff) bool { + name := diff.Get("name") + return name.(string) == "" +} + +func CompareCryptoKeyVersions(_, old, new string, _ *schema.ResourceData) bool { + // The API can return cryptoKeyVersions even though it wasn't specified. + // format: projects//locations//keyRings//cryptoKeys//cryptoKeyVersions/1 + + kmsKeyWithoutVersions := strings.Split(old, "/cryptoKeyVersions")[0] + if kmsKeyWithoutVersions == new { + return true + } + + return false +} + +func CidrOrSizeDiffSuppress(k, old, new string, d *schema.ResourceData) bool { + // If the user specified a size and the API returned a full cidr block, suppress. + return strings.HasPrefix(new, "/") && strings.HasSuffix(old, new) +} \ No newline at end of file diff --git a/mmv1/third_party/terraform/tpgresource/go/common_diff_suppress.go.tmpl b/mmv1/third_party/terraform/tpgresource/go/common_diff_suppress.go.tmpl deleted file mode 100644 index acf087a314f0..000000000000 --- a/mmv1/third_party/terraform/tpgresource/go/common_diff_suppress.go.tmpl +++ /dev/null @@ -1,315 +0,0 @@ -// Contains common diff suppress functions. - -package tpgresource - -import ( - "crypto/sha256" - "log" - "encoding/hex" - "net" - "reflect" - "regexp" - "strconv" - "strings" - "time" - "bytes" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func OptionalPrefixSuppress(prefix string) schema.SchemaDiffSuppressFunc { - return func(k, old, new string, d *schema.ResourceData) bool { - return prefix+old == new || prefix+new == old - } -} - -func IgnoreMissingKeyInMap(key string) schema.SchemaDiffSuppressFunc { - return func(k, old, new string, d *schema.ResourceData) bool { - log.Printf("[DEBUG] - suppressing diff %q with old %q, new %q", k, old, new) - if strings.HasSuffix(k, ".%") { - oldNum, err := strconv.Atoi(old) - if err != nil { - log.Printf("[ERROR] could not parse %q as number, no longer attempting diff suppress", old) - return false - } - newNum, err := strconv.Atoi(new) - if err != nil { - log.Printf("[ERROR] could not parse %q as number, no longer attempting diff suppress", new) - return false - } - return oldNum+1 == newNum - } else if strings.HasSuffix(k, "." + key) { - return old == "" - } - return false - } -} - -func OptionalSurroundingSpacesSuppress(k, old, new string, d *schema.ResourceData) bool { - return strings.TrimSpace(old) == strings.TrimSpace(new) -} - -func EmptyOrDefaultStringSuppress(defaultVal string) schema.SchemaDiffSuppressFunc { - return func(k, old, new string, d *schema.ResourceData) bool { - return (old == "" && new == defaultVal) || (new == "" && old == defaultVal) - } -} - -func EmptyOrFalseSuppressBoolean(k, old, new string, d *schema.ResourceData) bool { - o, n := d.GetChange(k) - return (o == nil && !n.(bool)) -} - -func IpCidrRangeDiffSuppress(k, old, new string, d *schema.ResourceData) bool { - // The range may be a: - // A) single IP address (e.g. 10.2.3.4) - // B) CIDR format string (e.g. 10.1.2.0/24) - // C) netmask (e.g. /24) - // - // For A) and B), no diff to suppress, they have to match completely. - // For C), The API picks a network IP address and this creates a diff of the form: - // network_interface.0.alias_ip_range.0.ip_cidr_range: "10.128.1.0/24" => "/24" - // We should only compare the mask portion for this case. - if len(new) > 0 && new[0] == '/' { - oldNetmaskStartPos := strings.LastIndex(old, "/") - - if oldNetmaskStartPos != -1 { - oldNetmask := old[strings.LastIndex(old, "/"):] - if oldNetmask == new { - return true - } - } - } - - return false -} - -// Sha256DiffSuppress -// if old is the hex-encoded sha256 sum of new, treat them as equal -func Sha256DiffSuppress(_, old, new string, _ *schema.ResourceData) bool { - return hex.EncodeToString(sha256.New().Sum([]byte(old))) == new -} - -func CaseDiffSuppress(_, old, new string, _ *schema.ResourceData) bool { - return strings.ToUpper(old) == strings.ToUpper(new) -} - -// Port range '80' and '80-80' is equivalent. -// `old` is read from the server and always has the full range format (e.g. '80-80', '1024-2048'). -// `new` can be either a single port or a port range. -func PortRangeDiffSuppress(k, old, new string, d *schema.ResourceData) bool { - return old == new+"-"+new -} - -// Single-digit hour is equivalent to hour with leading zero e.g. suppress diff 1:00 => 01:00. -// Assume either value could be in either format. -func Rfc3339TimeDiffSuppress(k, old, new string, d *schema.ResourceData) bool { - if (len(old) == 4 && "0"+old == new) || (len(new) == 4 && "0"+new == old) { - return true - } - return false -} - -func EmptyOrUnsetBlockDiffSuppress(k, old, new string, d *schema.ResourceData) bool { - o, n := d.GetChange(strings.TrimSuffix(k, ".#")) - return EmptyOrUnsetBlockDiffSuppressLogic(k, old, new, o, n) -} - -// The core logic for EmptyOrUnsetBlockDiffSuppress, in a format that is more conducive -// to unit testing. -func EmptyOrUnsetBlockDiffSuppressLogic(k, old, new string, o, n interface{}) bool { - if !strings.HasSuffix(k, ".#") { - return false - } - var l []interface{} - if old == "0" && new == "1" { - l = n.([]interface{}) - } else if new == "0" && old == "1" { - l = o.([]interface{}) - } else { - // we don't have one set and one unset, so don't suppress the diff - return false - } - - contents, ok := l[0].(map[string]interface{}) - if !ok { - return false - } - for _, v := range contents { - if !IsEmptyValue(reflect.ValueOf(v)) { - return false - } - } - return true -} - -// Suppress diffs for values that are equivalent except for their use of the words "location" -// compared to "region" or "zone" -func LocationDiffSuppress(k, old, new string, d *schema.ResourceData) bool { - return LocationDiffSuppressHelper(old, new) || LocationDiffSuppressHelper(new, old) -} - -func LocationDiffSuppressHelper(a, b string) bool { - return strings.Replace(a, "/locations/", "/regions/", 1) == b || - strings.Replace(a, "/locations/", "/zones/", 1) == b -} - -// For managed SSL certs, if new is an absolute FQDN (trailing '.') but old isn't, treat them as equals. -func AbsoluteDomainSuppress(k, old, new string, _ *schema.ResourceData) bool { - if strings.HasPrefix(k, "managed.0.domains.") { - return old == strings.TrimRight(new, ".") || new == strings.TrimRight(old, ".") - } - return false -} - -func TimestampDiffSuppress(format string) schema.SchemaDiffSuppressFunc { - return func(_, old, new string, _ *schema.ResourceData) bool { - oldT, err := time.Parse(format, old) - if err != nil { - return false - } - - newT, err := time.Parse(format, new) - if err != nil { - return false - } - - return oldT == newT - } -} - -// Suppresses diff for IPv4 and IPv6 different formats. -// It also suppresses diffs if an IP is changing to a reference. -func InternalIpDiffSuppress(_, old, new string, _ *schema.ResourceData) bool { - addr_equality := false - netmask_equality := false - - addr_netmask_old := strings.Split(old, "/") - addr_netmask_new := strings.Split(new, "/") - - // Check if old or new are IPs (with or without netmask) - var addr_old net.IP - if net.ParseIP(addr_netmask_old[0]) == nil { - addr_old = net.ParseIP(old) - } else { - addr_old = net.ParseIP(addr_netmask_old[0]) - } - var addr_new net.IP - if net.ParseIP(addr_netmask_new[0]) == nil { - addr_new = net.ParseIP(new) - } else { - addr_new = net.ParseIP(addr_netmask_new[0]) - } - - if addr_old != nil { - if addr_new == nil { - // old is an IP and new is a reference - addr_equality = true - } else { - // old and new are IP addresses - addr_equality = bytes.Equal(addr_old, addr_new) - } - } - - // If old and new both have a netmask compare them, otherwise suppress - // This is not technically correct but prevents the permadiff described in https://github.com/hashicorp/terraform-provider-google/issues/16400 - if (len(addr_netmask_old)) == 2 && (len(addr_netmask_new) == 2) { - netmask_equality = addr_netmask_old[1] == addr_netmask_new[1] - } else { - netmask_equality = true - } - - return addr_equality && netmask_equality -} - -// Suppress diffs for duration format. ex "60.0s" and "60s" same -// https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#duration -func DurationDiffSuppress(k, old, new string, d *schema.ResourceData) bool { - oDuration, err := time.ParseDuration(old) - if err != nil { - return false - } - nDuration, err := time.ParseDuration(new) - if err != nil { - return false - } - return oDuration == nDuration -} - -// Use this method when the field accepts either an IP address or a -// self_link referencing a resource (such as google_compute_route's -// next_hop_ilb) -func CompareIpAddressOrSelfLinkOrResourceName(_, old, new string, _ *schema.ResourceData) bool { - // if we can parse `new` as an IP address, then compare as strings - if net.ParseIP(new) != nil { - return new == old - } - - // otherwise compare as self links - return CompareSelfLinkOrResourceName("", old, new, nil) -} - -{{ if ne $.TargetVersionName `ga` -}} -// Suppress all diffs, used for Disk.Interface which is a nonfunctional field -func AlwaysDiffSuppress(_, _, _ string, _ *schema.ResourceData) bool { - return true -} -{{- end }} - -// Use this method when subnet is optioanl and auto_create_subnetworks = true -// API sometimes choose a subnet so the diff needs to be ignored -func CompareOptionalSubnet(_, old, new string, _ *schema.ResourceData) bool { - if IsEmptyValue(reflect.ValueOf(new)) { - return true - } - // otherwise compare as self links - return CompareSelfLinkOrResourceName("", old, new, nil) -} - -// Suppress diffs in below cases -// "https://hello-rehvs75zla-uc.a.run.app/" -> "https://hello-rehvs75zla-uc.a.run.app" -// "https://hello-rehvs75zla-uc.a.run.app" -> "https://hello-rehvs75zla-uc.a.run.app/" -func LastSlashDiffSuppress(_, old, new string, _ *schema.ResourceData) bool { - if last := len(new) - 1; last >= 0 && new[last] == '/' { - new = new[:last] - } - - if last := len(old) - 1; last >= 0 && old[last] == '/' { - old = old[:last] - } - return new == old -} - -// Suppress diffs when the value read from api -// has the project number instead of the project name -func ProjectNumberDiffSuppress(_, old, new string, _ *schema.ResourceData) bool { - var a2, b2 string - reN := regexp.MustCompile("projects/\\d+") - re := regexp.MustCompile("projects/[^/]+") - replacement := []byte("projects/equal") - a2 = string(reN.ReplaceAll([]byte(old), replacement)) - b2 = string(re.ReplaceAll([]byte(new), replacement)) - return a2 == b2 -} - -func IsNewResource(diff TerraformResourceDiff) bool { - name := diff.Get("name") - return name.(string) == "" -} - -func CompareCryptoKeyVersions(_, old, new string, _ *schema.ResourceData) bool { - // The API can return cryptoKeyVersions even though it wasn't specified. - // format: projects//locations//keyRings//cryptoKeys//cryptoKeyVersions/1 - - kmsKeyWithoutVersions := strings.Split(old, "/cryptoKeyVersions")[0] - if kmsKeyWithoutVersions == new { - return true - } - - return false -} - -func CidrOrSizeDiffSuppress(k, old, new string, d *schema.ResourceData) bool { - // If the user specified a size and the API returned a full cidr block, suppress. - return strings.HasPrefix(new, "/") && strings.HasSuffix(old, new) -} \ No newline at end of file diff --git a/mmv1/third_party/terraform/tpgresource/utils_test.go b/mmv1/third_party/terraform/tpgresource/utils_test.go index 19222a730565..0a657aa9dcaf 100644 --- a/mmv1/third_party/terraform/tpgresource/utils_test.go +++ b/mmv1/third_party/terraform/tpgresource/utils_test.go @@ -225,93 +225,6 @@ func TestConvertStringMap(t *testing.T) { } } -func TestIpCidrRangeDiffSuppress(t *testing.T) { - cases := map[string]struct { - Old, New string - ExpectDiffSuppress bool - }{ - "single ip address": { - Old: "10.2.3.4", - New: "10.2.3.5", - ExpectDiffSuppress: false, - }, - "cidr format string": { - Old: "10.1.2.0/24", - New: "10.1.3.0/24", - ExpectDiffSuppress: false, - }, - "netmask same mask": { - Old: "10.1.2.0/24", - New: "/24", - ExpectDiffSuppress: true, - }, - "netmask different mask": { - Old: "10.1.2.0/24", - New: "/32", - ExpectDiffSuppress: false, - }, - "add netmask": { - Old: "", - New: "/24", - ExpectDiffSuppress: false, - }, - "remove netmask": { - Old: "/24", - New: "", - ExpectDiffSuppress: false, - }, - } - - for tn, tc := range cases { - if tpgresource.IpCidrRangeDiffSuppress("ip_cidr_range", tc.Old, tc.New, nil) != tc.ExpectDiffSuppress { - t.Fatalf("bad: %s, '%s' => '%s' expect %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress) - } - } -} - -func TestRfc3339TimeDiffSuppress(t *testing.T) { - cases := map[string]struct { - Old, New string - ExpectDiffSuppress bool - }{ - "same time, format changed to have leading zero": { - Old: "2:00", - New: "02:00", - ExpectDiffSuppress: true, - }, - "same time, format changed not to have leading zero": { - Old: "02:00", - New: "2:00", - ExpectDiffSuppress: true, - }, - "different time, both without leading zero": { - Old: "2:00", - New: "3:00", - ExpectDiffSuppress: false, - }, - "different time, old with leading zero, new without": { - Old: "02:00", - New: "3:00", - ExpectDiffSuppress: false, - }, - "different time, new with leading zero, oldwithout": { - Old: "2:00", - New: "03:00", - ExpectDiffSuppress: false, - }, - "different time, both with leading zero": { - Old: "02:00", - New: "03:00", - ExpectDiffSuppress: false, - }, - } - for tn, tc := range cases { - if tpgresource.Rfc3339TimeDiffSuppress("time", tc.Old, tc.New, nil) != tc.ExpectDiffSuppress { - t.Errorf("bad: %s, '%s' => '%s' expect DiffSuppress to return %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress) - } - } -} - func TestGetProject(t *testing.T) { cases := map[string]struct { ResourceConfig map[string]interface{} diff --git a/mmv1/third_party/terraform/transport/error_retry_predicates.go b/mmv1/third_party/terraform/transport/error_retry_predicates.go index 3f1c95ca2fe2..ff42b4458c81 100644 --- a/mmv1/third_party/terraform/transport/error_retry_predicates.go +++ b/mmv1/third_party/terraform/transport/error_retry_predicates.go @@ -453,6 +453,31 @@ func Is429QuotaError(err error) (bool, string) { return false, "" } +// Do retry if operation returns a 429 and the reason is RATE_LIMIT_EXCEEDED +func Is429RetryableQuotaError(err error) (bool, string) { + if gerr, ok := err.(*googleapi.Error); ok { + if gerr.Code == 429 { + // Quota error isn't necessarily retryable if it's a resource instance limit; check details + isRateLimitExceeded := false + for _, d := range gerr.Details { + data := d.(map[string]interface{}) + dType, ok := data["@type"] + // Find google.rpc.ErrorInfo in Details + if ok && strings.Contains(dType.(string), "ErrorInfo") { + if v, ok := data["reason"]; ok { + if v.(string) == "RATE_LIMIT_EXCEEDED" { + isRateLimitExceeded = true + break + } + } + } + } + return isRateLimitExceeded, "429s are retryable for this resource, but only if the reason is RATE_LIMIT_EXCEEDED" + } + } + return false, "" +} + // Retry if App Engine operation returns a 409 with a specific message for // concurrent operations, or a 404 indicating p4sa has not yet propagated. func IsAppEngineRetryableError(err error) (bool, string) { diff --git a/mmv1/third_party/terraform/website/docs/d/kms_crypto_keys.html.markdown b/mmv1/third_party/terraform/website/docs/d/kms_crypto_keys.html.markdown new file mode 100644 index 000000000000..b0c27895fde1 --- /dev/null +++ b/mmv1/third_party/terraform/website/docs/d/kms_crypto_keys.html.markdown @@ -0,0 +1,56 @@ +--- +subcategory: "Cloud Key Management Service" +description: |- + Provides access to data about all KMS keys within a key ring with Google Cloud KMS. +--- + +# google_kms_crypto_keys + +Provides access to all Google Cloud Platform KMS CryptoKeys in a given KeyRing. For more information see +[the official documentation](https://cloud.google.com/kms/docs/object-hierarchy#key) +and +[API](https://cloud.google.com/kms/docs/reference/rest/v1/projects.locations.keyRings.cryptoKeys). + +A CryptoKey is an interface to key material which can be used to encrypt and decrypt data. A CryptoKey belongs to a +Google Cloud KMS KeyRing. + +## Example Usage + +```hcl +// Get all keys in the key ring +data "google_kms_crypto_keys" "all_crypto_keys" { + key_ring = data.google_kms_key_ring.my_key_ring.id +} + +// Get keys in the key ring that have "foobar" in their name +data "google_kms_crypto_keys" "all_crypto_keys" { + key_ring = data.google_kms_key_ring.my_key_ring.id + filter = "name:foobar" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `key_ring` - (Required) The key ring that the keys belongs to. Format: 'projects/{{project}}/locations/{{location}}/keyRings/{{keyRing}}'., + +* `filter` - (Optional) The filter argument is used to add a filter query parameter that limits which keys are retrieved by the data source: ?filter={{filter}}. When no value is provided there is no filtering. + +Example filter values if filtering on name. Note: names take the form projects/{{project}}/locations/{{location}}/keyRings/{{keyRing}}/cryptoKeys/{{cryptoKey}}. + +* `"name:my-key-"` will retrieve keys that contain "my-key-" anywhere in their name. +* `"name=projects/my-project/locations/global/keyRings/my-key-ring/cryptoKeys/my-key-1"` will only retrieve a key with that exact name. + +[See the documentation about using filters](https://cloud.google.com/kms/docs/sorting-and-filtering) + + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `keys` - A list of all the retrieved keys from the provided key ring. This list is influenced by the provided filter argument. + +See [google_kms_crypto_key](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/kms_crypto_key) resource for details of the available attributes on each key. + diff --git a/mmv1/third_party/terraform/website/docs/r/compute_instance.html.markdown b/mmv1/third_party/terraform/website/docs/r/compute_instance.html.markdown index 9f7553b529dd..25baeb72ef4c 100644 --- a/mmv1/third_party/terraform/website/docs/r/compute_instance.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/compute_instance.html.markdown @@ -128,8 +128,8 @@ The following arguments are supported: * `metadata` - (Optional) Metadata key/value pairs to make available from within the instance. Ssh keys attached in the Cloud Console will be removed. - Add them to your config in order to keep them attached to your instance. A - list of default metadata values (e.g. ssh-keys) can be found [here](https://cloud.google.com/compute/docs/metadata/default-metadata-values) + Add them to your config in order to keep them attached to your instance. + A list of predefined metadata keys (e.g. ssh-keys) can be found [here](https://cloud.google.com/compute/docs/metadata/predefined-metadata-keys) -> Depending on the OS you choose for your instance, some metadata keys have special functionality. Most linux-based images will run the content of @@ -499,7 +499,7 @@ specified, then this instance will have no external IPv6 Internet access. Struct * `enable_confidential_compute` (Optional) Defines whether the instance should have confidential compute enabled with AMD SEV. If enabled, [`on_host_maintenance`](#on_host_maintenance) can be set to MIGRATE if [`min_cpu_platform`](#min_cpu_platform) is set to `"AMD Milan"`. Otherwise, [`on_host_maintenance`](#on_host_maintenance) has to be set to TERMINATE or this will fail to create the VM. -* `confidential_instance_type` (Optional) [Beta](https://terraform.io/docs/providers/google/guides/provider_versions.html) Defines the confidential computing technology the instance uses. SEV is an AMD feature. One of the following values: `SEV`, `SEV_SNP`. [`on_host_maintenance`](#on_host_maintenance) can be set to MIGRATE if [`confidential_instance_type`](#confidential_instance_type) is set to `SEV` and [`min_cpu_platform`](#min_cpu_platform) is set to `"AMD Milan"`. Otherwise, [`on_host_maintenance`](#on_host_maintenance) has to be set to TERMINATE or this will fail to create the VM. If `SEV_SNP`, currently [`min_cpu_platform`](#min_cpu_platform) has to be set to `"AMD Milan"` or this will fail to create the VM. +* `confidential_instance_type` (Optional) Defines the confidential computing technology the instance uses. SEV is an AMD feature. One of the following values: `SEV`, `SEV_SNP`. [`on_host_maintenance`](#on_host_maintenance) can be set to MIGRATE if [`confidential_instance_type`](#confidential_instance_type) is set to `SEV` and [`min_cpu_platform`](#min_cpu_platform) is set to `"AMD Milan"`. Otherwise, [`on_host_maintenance`](#on_host_maintenance) has to be set to TERMINATE or this will fail to create the VM. If `SEV_SNP`, currently [`min_cpu_platform`](#min_cpu_platform) has to be set to `"AMD Milan"` or this will fail to create the VM. The `advanced_machine_features` block supports: diff --git a/mmv1/third_party/terraform/website/docs/r/compute_instance_template.html.markdown b/mmv1/third_party/terraform/website/docs/r/compute_instance_template.html.markdown index e1030a25b6d9..e984cd86b4ee 100644 --- a/mmv1/third_party/terraform/website/docs/r/compute_instance_template.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/compute_instance_template.html.markdown @@ -662,7 +662,7 @@ The `specific_reservation` block supports: * `enable_confidential_compute` (Optional) Defines whether the instance should have confidential compute enabled with AMD SEV. If enabled, [`on_host_maintenance`](#on_host_maintenance) can be set to MIGRATE if [`min_cpu_platform`](#min_cpu_platform) is set to `"AMD Milan"`. Otherwise, [`on_host_maintenance`](#on_host_maintenance) has to be set to TERMINATE or this will fail to create the VM. -* `confidential_instance_type` (Optional) [Beta](https://terraform.io/docs/providers/google/guides/provider_versions.html) Defines the confidential computing technology the instance uses. SEV is an AMD feature. One of the following values: `SEV`, `SEV_SNP`. [`on_host_maintenance`](#on_host_maintenance) can be set to MIGRATE if [`confidential_instance_type`](#confidential_instance_type) is set to `SEV` and [`min_cpu_platform`](#min_cpu_platform) is set to `"AMD Milan"`. Otherwise, [`on_host_maintenance`](#on_host_maintenance) has to be set to TERMINATE or this will fail to create the VM. If `SEV_SNP`, currently [`min_cpu_platform`](#min_cpu_platform) has to be set to `"AMD Milan"` or this will fail to create the VM. +* `confidential_instance_type` (Optional) Defines the confidential computing technology the instance uses. SEV is an AMD feature. One of the following values: `SEV`, `SEV_SNP`. [`on_host_maintenance`](#on_host_maintenance) can be set to MIGRATE if [`confidential_instance_type`](#confidential_instance_type) is set to `SEV` and [`min_cpu_platform`](#min_cpu_platform) is set to `"AMD Milan"`. Otherwise, [`on_host_maintenance`](#on_host_maintenance) has to be set to TERMINATE or this will fail to create the VM. If `SEV_SNP`, currently [`min_cpu_platform`](#min_cpu_platform) has to be set to `"AMD Milan"` or this will fail to create the VM. The `network_performance_config` block supports: diff --git a/mmv1/third_party/terraform/website/docs/r/compute_region_instance_template.html.markdown b/mmv1/third_party/terraform/website/docs/r/compute_region_instance_template.html.markdown index daf0291d5b01..02d67574847d 100644 --- a/mmv1/third_party/terraform/website/docs/r/compute_region_instance_template.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/compute_region_instance_template.html.markdown @@ -663,7 +663,7 @@ The `specific_reservation` block supports: * `enable_confidential_compute` (Optional) Defines whether the instance should have confidential compute enabled with AMD SEV. If enabled, [`on_host_maintenance`](#on_host_maintenance) can be set to MIGRATE if [`min_cpu_platform`](#min_cpu_platform) is set to `"AMD Milan"`. Otherwise, [`on_host_maintenance`](#on_host_maintenance) has to be set to TERMINATE or this will fail to create the VM. -* `confidential_instance_type` (Optional) [Beta](https://terraform.io/docs/providers/google/guides/provider_versions.html) Defines the confidential computing technology the instance uses. SEV is an AMD feature. One of the following values: `SEV`, `SEV_SNP`. [`on_host_maintenance`](#on_host_maintenance) can be set to MIGRATE if [`confidential_instance_type`](#confidential_instance_type) is set to `SEV` and [`min_cpu_platform`](#min_cpu_platform) is set to `"AMD Milan"`. Otherwise, [`on_host_maintenance`](#on_host_maintenance) has to be set to TERMINATE or this will fail to create the VM. If `SEV_SNP`, currently [`min_cpu_platform`](#min_cpu_platform) has to be set to `"AMD Milan"` or this will fail to create the VM. +* `confidential_instance_type` (Optional) Defines the confidential computing technology the instance uses. SEV is an AMD feature. One of the following values: `SEV`, `SEV_SNP`. [`on_host_maintenance`](#on_host_maintenance) can be set to MIGRATE if [`confidential_instance_type`](#confidential_instance_type) is set to `SEV` and [`min_cpu_platform`](#min_cpu_platform) is set to `"AMD Milan"`. Otherwise, [`on_host_maintenance`](#on_host_maintenance) has to be set to TERMINATE or this will fail to create the VM. If `SEV_SNP`, currently [`min_cpu_platform`](#min_cpu_platform) has to be set to `"AMD Milan"` or this will fail to create the VM. The `network_performance_config` block supports: diff --git a/mmv1/third_party/terraform/website/docs/r/compute_security_policy.html.markdown b/mmv1/third_party/terraform/website/docs/r/compute_security_policy.html.markdown index 76bb154d9a0a..194dcc7989f4 100644 --- a/mmv1/third_party/terraform/website/docs/r/compute_security_policy.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/compute_security_policy.html.markdown @@ -260,6 +260,11 @@ The following arguments are supported: such as `origin.ip`, `source.region_code` and `contents` in the request header. Structure is [documented below](#nested_expr). +* `expr_options` - + (Optional) + The configuration options available when specifying a user defined CEVAL expression (i.e., 'expr'). + Structure is [documented below](#nested_expr_options). + The `config` block supports: * `src_ip_ranges` - (Required) Set of IP addresses or ranges (IPV4 or IPV6) in CIDR notation @@ -271,6 +276,23 @@ The following arguments are supported: * `expression` - (Required) Textual representation of an expression in Common Expression Language syntax. The application context of the containing message determines which well-known feature set of CEL is supported. +The `expr_options` block supports: + +* `recaptcha_options` - + (Required) + reCAPTCHA configuration options to be applied for the rule. If the rule does not evaluate reCAPTCHA tokens, this field has no effect. + Structure is [documented below](#nested_recaptcha_options). + +The `recaptcha_options` block supports: + +* `action_token_site_keys` - + (Optional) + A list of site keys to be used during the validation of reCAPTCHA action-tokens. The provided site keys need to be created from reCAPTCHA API under the same project where the security policy is created. + +* `session_token_site_keys` - + (Optional) + A list of site keys to be used during the validation of reCAPTCHA session-tokens. The provided site keys need to be created from reCAPTCHA API under the same project where the security policy is created. + The `preconfigured_waf_config` block supports: * `exclusion` - (Optional) An exclusion to apply during preconfigured WAF evaluation. Structure is [documented below](#nested_exclusion). @@ -328,6 +350,8 @@ The following arguments are supported: * `HTTP_PATH`: The URL path of the HTTP request. The key value is truncated to the first 128 bytes * `SNI`: Server name indication in the TLS session of the HTTPS request. The key value is truncated to the first 128 bytes. The key type defaults to `ALL` on a HTTP session. * `REGION_CODE`: The country/region from which the request originates. + * `TLS_JA3_FINGERPRINT`: JA3 TLS/SSL fingerprint if the client connects using HTTPS, HTTP/2 or HTTP/3. If not available, the key type defaults to ALL. + * `USER_IP`: The IP address of the originating client, which is resolved based on "user_ip_request_headers" configured with the securitypolicy. If there is no "user_ip_request_headers" configuration or an IP address cannot be resolved from it, the key type defaults to IP. * `enforce_on_key_name` - (Optional) Rate limit key name applicable only for the following key types: @@ -355,6 +379,8 @@ The following arguments are supported: * `HTTP_PATH`: The URL path of the HTTP request. The key value is truncated to the first 128 bytes * `SNI`: Server name indication in the TLS session of the HTTPS request. The key value is truncated to the first 128 bytes. The key type defaults to `ALL` on a HTTP session. * `REGION_CODE`: The country/region from which the request originates. + * `TLS_JA3_FINGERPRINT`: JA3 TLS/SSL fingerprint if the client connects using HTTPS, HTTP/2 or HTTP/3. If not available, the key type defaults to ALL. + * `USER_IP`: The IP address of the originating client, which is resolved based on "user_ip_request_headers" configured with the securitypolicy. If there is no "user_ip_request_headers" configuration or an IP address cannot be resolved from it, the key type defaults to IP. * `exceed_redirect_options` - (Optional) Parameters defining the redirect action that is used as the exceed action. Cannot be specified if the exceed action is not redirect. Structure is [documented below](#nested_exceed_redirect_options). diff --git a/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown b/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown index 70dfa529be9d..048ea25a4b2a 100644 --- a/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown @@ -614,7 +614,7 @@ This block also contains several computed attributes, documented below. The `monitoring_config` block supports: -* `enable_components` - (Optional) The GKE components exposing metrics. Supported values include: `SYSTEM_COMPONENTS`, `APISERVER`, `SCHEDULER`, `CONTROLLER_MANAGER`, `STORAGE`, `HPA`, `POD`, `DAEMONSET`, `DEPLOYMENT`, `STATEFULSET`, `KUBELET` and `CADVISOR`. In beta provider, `WORKLOADS` is supported on top of those 12 values. (`WORKLOADS` is deprecated and removed in GKE 1.24.) `KUBELET` and `CADVISOR` are only supported in GKE 1.29.3-gke.1093000 and above. +* `enable_components` - (Optional) The GKE components exposing metrics. Supported values include: `SYSTEM_COMPONENTS`, `APISERVER`, `SCHEDULER`, `CONTROLLER_MANAGER`, `STORAGE`, `HPA`, `POD`, `DAEMONSET`, `DEPLOYMENT`, `STATEFULSET`, `KUBELET`, `CADVISOR` and `DCGM`. In beta provider, `WORKLOADS` is supported on top of those 12 values. (`WORKLOADS` is deprecated and removed in GKE 1.24.) `KUBELET` and `CADVISOR` are only supported in GKE 1.29.3-gke.1093000 and above. * `managed_prometheus` - (Optional) Configuration for Managed Service for Prometheus. Structure is [documented below](#nested_managed_prometheus). diff --git a/mmv1/third_party/terraform/website/docs/r/logging_billing_account_bucket_config.html.markdown b/mmv1/third_party/terraform/website/docs/r/logging_billing_account_bucket_config.html.markdown index 7f3e127e6298..2f481874d40f 100644 --- a/mmv1/third_party/terraform/website/docs/r/logging_billing_account_bucket_config.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/logging_billing_account_bucket_config.html.markdown @@ -36,9 +36,9 @@ resource "google_logging_billing_account_bucket_config" "example-billing-account retention_days = 30 bucket_id = "_Default" - index_configs = { - file_path = "jsonPayload.request.status" - type = "INDEX_TYPE_STRING" + index_configs { + field_path = "jsonPayload.request.status" + type = "INDEX_TYPE_STRING" } } ``` @@ -62,7 +62,7 @@ The following arguments are supported: The `index_configs` block supports: * `field_path` - The LogEntry field path to index. - Note that some paths are automatically indexed, and other paths are not eligible for indexing. See [indexing documentation]( https://cloud.google.com/logging/docs/view/advanced-queries#indexed-fields) for details. + Note that some paths are automatically indexed, and other paths are not eligible for indexing. See [indexing documentation](https://cloud.google.com/logging/docs/analyze/custom-index) for details. * `type` - The type of data in this index. Allowed types include `INDEX_TYPE_UNSPECIFIED`, `INDEX_TYPE_STRING` and `INDEX_TYPE_INTEGER`. diff --git a/mmv1/third_party/terraform/website/docs/r/logging_folder_bucket_config.html.markdown b/mmv1/third_party/terraform/website/docs/r/logging_folder_bucket_config.html.markdown index e6dd1094a285..e73a8ebd95cf 100644 --- a/mmv1/third_party/terraform/website/docs/r/logging_folder_bucket_config.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/logging_folder_bucket_config.html.markdown @@ -26,9 +26,9 @@ resource "google_logging_folder_bucket_config" "basic" { retention_days = 30 bucket_id = "_Default" - index_configs = { - file_path = "jsonPayload.request.status" - type = "INDEX_TYPE_STRING" + index_configs { + field_path = "jsonPayload.request.status" + type = "INDEX_TYPE_STRING" } } ``` @@ -52,7 +52,7 @@ The following arguments are supported: The `index_configs` block supports: * `field_path` - The LogEntry field path to index. - Note that some paths are automatically indexed, and other paths are not eligible for indexing. See [indexing documentation]( https://cloud.google.com/logging/docs/view/advanced-queries#indexed-fields) for details. + Note that some paths are automatically indexed, and other paths are not eligible for indexing. See [indexing documentation](https://cloud.google.com/logging/docs/analyze/custom-index) for details. * `type` - The type of data in this index. Allowed types include `INDEX_TYPE_UNSPECIFIED`, `INDEX_TYPE_STRING` and `INDEX_TYPE_INTEGER`. diff --git a/mmv1/third_party/terraform/website/docs/r/logging_organization_bucket_config.html.markdown b/mmv1/third_party/terraform/website/docs/r/logging_organization_bucket_config.html.markdown index 6dcefaa42ea9..9255da93ea42 100644 --- a/mmv1/third_party/terraform/website/docs/r/logging_organization_bucket_config.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/logging_organization_bucket_config.html.markdown @@ -25,9 +25,9 @@ resource "google_logging_organization_bucket_config" "basic" { retention_days = 30 bucket_id = "_Default" - index_configs = { - file_path = "jsonPayload.request.status" - type = "INDEX_TYPE_STRING" + index_configs { + field_path = "jsonPayload.request.status" + type = "INDEX_TYPE_STRING" } } ``` @@ -51,7 +51,7 @@ The following arguments are supported: The `index_configs` block supports: * `field_path` - The LogEntry field path to index. - Note that some paths are automatically indexed, and other paths are not eligible for indexing. See [indexing documentation]( https://cloud.google.com/logging/docs/view/advanced-queries#indexed-fields) for details. + Note that some paths are automatically indexed, and other paths are not eligible for indexing. See [indexing documentation](https://cloud.google.com/logging/docs/analyze/custom-index) for details. * `type` - The type of data in this index. Allowed types include `INDEX_TYPE_UNSPECIFIED`, `INDEX_TYPE_STRING` and `INDEX_TYPE_INTEGER`. diff --git a/mmv1/third_party/terraform/website/docs/r/logging_project_bucket_config.html.markdown b/mmv1/third_party/terraform/website/docs/r/logging_project_bucket_config.html.markdown index a829b5c0628f..2e57e1993f01 100644 --- a/mmv1/third_party/terraform/website/docs/r/logging_project_bucket_config.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/logging_project_bucket_config.html.markdown @@ -102,9 +102,9 @@ resource "google_logging_project_bucket_config" "example-project-bucket-index-co retention_days = 30 bucket_id = "custom-bucket" - index_configs = { - file_path = "jsonPayload.request.status" - type = "INDEX_TYPE_STRING" + index_configs { + field_path = "jsonPayload.request.status" + type = "INDEX_TYPE_STRING" } } ``` @@ -156,7 +156,7 @@ See [Enabling CMEK for Logging Buckets](https://cloud.google.com/logging/docs/ro The `index_configs` block supports: * `field_path` - The LogEntry field path to index. -Note that some paths are automatically indexed, and other paths are not eligible for indexing. See [indexing documentation]( https://cloud.google.com/logging/docs/view/advanced-queries#indexed-fields) for details. +Note that some paths are automatically indexed, and other paths are not eligible for indexing. See [indexing documentation](https://cloud.google.com/logging/docs/analyze/custom-index) for details. * `type` - The type of data in this index. Allowed types include `INDEX_TYPE_UNSPECIFIED`, `INDEX_TYPE_STRING` and `INDEX_TYPE_INTEGER`. diff --git a/mmv1/third_party/terraform/website/docs/r/storage_managed_folder_iam.html.markdown b/mmv1/third_party/terraform/website/docs/r/storage_managed_folder_iam.html.markdown new file mode 100644 index 000000000000..92894e372454 --- /dev/null +++ b/mmv1/third_party/terraform/website/docs/r/storage_managed_folder_iam.html.markdown @@ -0,0 +1,203 @@ +--- +subcategory: "Cloud Storage" +description: |- + Collection of resources to manage IAM policy for Cloud Storage ManagedFolder +--- + +# IAM policy for Cloud Storage ManagedFolder +Three different resources help you manage your IAM policy for Cloud Storage ManagedFolder. Each of these resources serves a different use case: + +* `google_storage_managed_folder_iam_policy`: Authoritative. Sets the IAM policy for the managedfolder and replaces any existing policy already attached. +* `google_storage_managed_folder_iam_binding`: Authoritative for a given role. Updates the IAM policy to grant a role to a list of members. Other roles within the IAM policy for the managedfolder are preserved. +* `google_storage_managed_folder_iam_member`: Non-authoritative. Updates the IAM policy to grant a role to a new member. Other members for the role for the managedfolder are preserved. + +A data source can be used to retrieve policy data in advent you do not need creation + +* `google_storage_managed_folder_iam_policy`: Retrieves the IAM policy for the managedfolder + +~> **Note:** `google_storage_managed_folder_iam_policy` **cannot** be used in conjunction with `google_storage_managed_folder_iam_binding` and `google_storage_managed_folder_iam_member` or they will fight over what your policy should be. + +~> **Note:** `google_storage_managed_folder_iam_binding` resources **can be** used in conjunction with `google_storage_managed_folder_iam_member` resources **only if** they do not grant privilege to the same role. + +~> **Note:** This resource supports IAM Conditions but they have some known limitations which can be found [here](https://cloud.google.com/iam/docs/conditions-overview#limitations). Please review this article if you are having issues with IAM Conditions. + + +## google_storage_managed_folder_iam_policy + +```hcl +data "google_iam_policy" "admin" { + binding { + role = "roles/storage.admin" + members = [ + "user:jane@example.com", + ] + } +} + +resource "google_storage_managed_folder_iam_policy" "policy" { + bucket = google_storage_managed_folder.folder.bucket + managed_folder = google_storage_managed_folder.folder.name + policy_data = data.google_iam_policy.admin.policy_data +} +``` + +With IAM Conditions: + +```hcl +data "google_iam_policy" "admin" { + binding { + role = "roles/storage.admin" + members = [ + "user:jane@example.com", + ] + + condition { + title = "expires_after_2019_12_31" + description = "Expiring at midnight of 2019-12-31" + expression = "request.time < timestamp(\"2020-01-01T00:00:00Z\")" + } + } +} + +resource "google_storage_managed_folder_iam_policy" "policy" { + bucket = google_storage_managed_folder.folder.bucket + managed_folder = google_storage_managed_folder.folder.name + policy_data = data.google_iam_policy.admin.policy_data +} +``` +## google_storage_managed_folder_iam_binding + +```hcl +resource "google_storage_managed_folder_iam_binding" "binding" { + bucket = google_storage_managed_folder.folder.bucket + managed_folder = google_storage_managed_folder.folder.name + role = "roles/storage.admin" + members = [ + "user:jane@example.com", + ] +} +``` + +With IAM Conditions: + +```hcl +resource "google_storage_managed_folder_iam_binding" "binding" { + bucket = google_storage_managed_folder.folder.bucket + managed_folder = google_storage_managed_folder.folder.name + role = "roles/storage.admin" + members = [ + "user:jane@example.com", + ] + + condition { + title = "expires_after_2019_12_31" + description = "Expiring at midnight of 2019-12-31" + expression = "request.time < timestamp(\"2020-01-01T00:00:00Z\")" + } +} +``` +## google_storage_managed_folder_iam_member + +```hcl +resource "google_storage_managed_folder_iam_member" "member" { + bucket = google_storage_managed_folder.folder.bucket + managed_folder = google_storage_managed_folder.folder.name + role = "roles/storage.admin" + member = "user:jane@example.com" +} +``` + +With IAM Conditions: + +```hcl +resource "google_storage_managed_folder_iam_member" "member" { + bucket = google_storage_managed_folder.folder.bucket + managed_folder = google_storage_managed_folder.folder.name + role = "roles/storage.admin" + member = "user:jane@example.com" + + condition { + title = "expires_after_2019_12_31" + description = "Expiring at midnight of 2019-12-31" + expression = "request.time < timestamp(\"2020-01-01T00:00:00Z\")" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `bucket` - (Required) The name of the bucket that contains the managed folder. Used to find the parent resource to bind the IAM policy to +* `managed_folder` - (Required) Used to find the parent resource to bind the IAM policy to + +* `member/members` - (Required) Identities that will be granted the privilege in `role`. + Each entry can have one of the following values: + * **allUsers**: A special identifier that represents anyone who is on the internet; with or without a Google account. + * **allAuthenticatedUsers**: A special identifier that represents anyone who is authenticated with a Google account or a service account. + * **user:{emailid}**: An email address that represents a specific Google account. For example, alice@gmail.com or joe@example.com. + * **serviceAccount:{emailid}**: An email address that represents a service account. For example, my-other-app@appspot.gserviceaccount.com. + * **group:{emailid}**: An email address that represents a Google group. For example, admins@example.com. + * **domain:{domain}**: A G Suite domain (primary, instead of alias) name that represents all the users of that domain. For example, google.com or example.com. + * **projectOwner:projectid**: Owners of the given project. For example, "projectOwner:my-example-project" + * **projectEditor:projectid**: Editors of the given project. For example, "projectEditor:my-example-project" + * **projectViewer:projectid**: Viewers of the given project. For example, "projectViewer:my-example-project" + +* `role` - (Required) The role that should be applied. Only one + `google_storage_managed_folder_iam_binding` can be used per role. Note that custom roles must be of the format + `[projects|organizations]/{parent-name}/roles/{role-name}`. + +* `policy_data` - (Required only by `google_storage_managed_folder_iam_policy`) The policy data generated by + a `google_iam_policy` data source. + +* `condition` - (Optional) An [IAM Condition](https://cloud.google.com/iam/docs/conditions-overview) for a given binding. + Structure is documented below. + +--- + +The `condition` block supports: + +* `expression` - (Required) Textual representation of an expression in Common Expression Language syntax. + +* `title` - (Required) A title for the expression, i.e. a short string describing its purpose. + +* `description` - (Optional) An optional description of the expression. This is a longer text which describes the expression, e.g. when hovered over it in a UI. + +~> **Warning:** Terraform considers the `role` and condition contents (`title`+`description`+`expression`) as the + identifier for the binding. This means that if any part of the condition is changed out-of-band, Terraform will + consider it to be an entirely different resource and will treat it as such. +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are +exported: + +* `etag` - (Computed) The etag of the IAM policy. + +## Import + +For all import syntaxes, the "resource in question" can take any of the following forms: + +* b/{{bucket}}/managedFolders/{{managed_folder}} +* {{bucket}}/{{managed_folder}} + +Any variables not passed in the import command will be taken from the provider configuration. + +Cloud Storage managedfolder IAM resources can be imported using the resource identifiers, role, and member. + +IAM member imports use space-delimited identifiers: the resource in question, the role, and the member identity, e.g. +``` +$ terraform import google_storage_managed_folder_iam_member.editor "b/{{bucket}}/managedFolders/{{managed_folder}} roles/storage.objectViewer user:jane@example.com" +``` + +IAM binding imports use space-delimited identifiers: the resource in question and the role, e.g. +``` +$ terraform import google_storage_managed_folder_iam_binding.editor "b/{{bucket}}/managedFolders/{{managed_folder}} roles/storage.objectViewer" +``` + +IAM policy imports use the identifier of the resource in question, e.g. +``` +$ terraform import google_storage_managed_folder_iam_policy.editor b/{{bucket}}/managedFolders/{{managed_folder}} +``` + +-> **Custom Roles**: If you're importing a IAM resource with a custom role, make sure to use the + full name of the custom role, e.g. `[projects/my-project|organizations/my-org]/roles/my-custom-role`. diff --git a/mmv1/third_party/tgc/appengine_standard_version.go b/mmv1/third_party/tgc/appengine_standard_version.go new file mode 100644 index 000000000000..a86bfb70e97d --- /dev/null +++ b/mmv1/third_party/tgc/appengine_standard_version.go @@ -0,0 +1,1014 @@ +package google + +import ( + "reflect" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/GoogleCloudPlatform/terraform-google-conversion/v5/tfplan2cai/converters/google/resources/cai" + "github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport" +) + +const AppEngineVersionAssetType string = "appengine.googleapis.com/Version" + +func resourceAppEngineStandardAppVersion() cai.ResourceConverter { + return cai.ResourceConverter{ + AssetType: AppEngineVersionAssetType, + Convert: GetAppEngineVersionCaiObject, + } +} + +func GetAppEngineVersionCaiObject(d tpgresource.TerraformResourceData, config *transport_tpg.Config) ([]cai.Asset, error) { + name, err := cai.AssetName(d, config, "//appengine.googleapis.com/apps/{{project}}/services/default/versions/v1") + if err != nil { + return []cai.Asset{}, err + } + if obj, err := GetAppEngineVersionApiObject(d, config); err == nil { + return []cai.Asset{{ + Name: name, + Type: AppEngineVersionAssetType, + Resource: &cai.AssetResource{ + Version: "v1", + DiscoveryDocumentURI: "https://www.googleapis.com/discovery/v1/apis/appengine/v1/rest", + DiscoveryName: "Version", + Data: obj, + }, + }}, nil + } else { + return []cai.Asset{}, err + } +} + +func GetAppEngineVersionApiObject(d tpgresource.TerraformResourceData, config *transport_tpg.Config) (map[string]interface{}, error) { + obj := make(map[string]interface{}) + + nameProp, err := expandAppEngineVersionName(d.Get("name"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("name"); !tpgresource.IsEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) { + obj["name"] = nameProp + } + + idProp, err := expandAppEngineVersionId(d.Get("version_id"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("version_id"); !tpgresource.IsEmptyValue(reflect.ValueOf(idProp)) && (ok || !reflect.DeepEqual(v, idProp)) { + obj["id"] = idProp + } + + automaticScalingProp, err := expandAppEngineVersionAutomaticScaling(d.Get("automatic_scaling"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("automatic_scaling"); !tpgresource.IsEmptyValue(reflect.ValueOf(automaticScalingProp)) && (ok || !reflect.DeepEqual(v, automaticScalingProp)) { + obj["automaticScaling"] = automaticScalingProp + } + + basicScalingProp, err := expandAppEngineVersionBasicScaling(d.Get("basic_scaling"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("basic_scaling"); !tpgresource.IsEmptyValue(reflect.ValueOf(basicScalingProp)) && (ok || !reflect.DeepEqual(v, basicScalingProp)) { + obj["basicScaling"] = basicScalingProp + } + + manualScalingProp, err := expandAppEngineVersionManualScaling(d.Get("manual_scaling"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("manual_scaling"); !tpgresource.IsEmptyValue(reflect.ValueOf(manualScalingProp)) && (ok || !reflect.DeepEqual(v, manualScalingProp)) { + obj["manualScaling"] = manualScalingProp + } + + instanceClassProp, err := expandAppEngineVersionInstanceClass(d.Get("instance_class"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("instance_class"); !tpgresource.IsEmptyValue(reflect.ValueOf(instanceClassProp)) && (ok || !reflect.DeepEqual(v, instanceClassProp)) { + obj["instanceClass"] = instanceClassProp + } + + zonesProp, err := expandAppEngineVersionZones(d.Get("zones"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("zones"); !tpgresource.IsEmptyValue(reflect.ValueOf(zonesProp)) && (ok || !reflect.DeepEqual(v, zonesProp)) { + obj["zones"] = zonesProp + } + + runtimeProp, err := expandAppEngineVersionRuntime(d.Get("runtime"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("runtime"); !tpgresource.IsEmptyValue(reflect.ValueOf(runtimeProp)) && (ok || !reflect.DeepEqual(v, runtimeProp)) { + obj["runtime"] = runtimeProp + } + + threadsafeProp, err := expandAppEngineVersionThreadsafe(d.Get("threadsafe"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("threadsafe"); !tpgresource.IsEmptyValue(reflect.ValueOf(threadsafeProp)) && (ok || !reflect.DeepEqual(v, threadsafeProp)) { + obj["threadsafe"] = threadsafeProp + } + + vmProp, err := expandAppEngineVersionVm(d.Get("vm"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("vm"); !tpgresource.IsEmptyValue(reflect.ValueOf(vmProp)) && (ok || !reflect.DeepEqual(v, vmProp)) { + obj["vm"] = vmProp + } + + appEngineApisProp, err := expandAppEngineVersionAppEngineApis(d.Get("app_engine_apis"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("app_engine_apis"); !tpgresource.IsEmptyValue(reflect.ValueOf(appEngineApisProp)) && (ok || !reflect.DeepEqual(v, appEngineApisProp)) { + obj["appEngineApis"] = appEngineApisProp + } + + envProp, err := expandAppEngineVersionEnv(d.Get("env"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("env"); !tpgresource.IsEmptyValue(reflect.ValueOf(envProp)) && (ok || !reflect.DeepEqual(v, envProp)) { + obj["env"] = envProp + } + + createTimeProp, err := expandAppEngineVersionCreateTime(d.Get("create"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("create"); !tpgresource.IsEmptyValue(reflect.ValueOf(createTimeProp)) && (ok || !reflect.DeepEqual(v, createTimeProp)) { + obj["createTime"] = createTimeProp + } + + runtimeApiVersionProp, err := expandAppEngineVersionRuntimeApiVersion(d.Get("runtime_api_version"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("runtime_api_version"); !tpgresource.IsEmptyValue(reflect.ValueOf(runtimeApiVersionProp)) && (ok || !reflect.DeepEqual(v, runtimeApiVersionProp)) { + obj["runtimeApiVersion"] = runtimeApiVersionProp + } + + serviceAccountProp, err := expandAppEngineVersionServiceAccount(d.Get("service_account"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("service_account"); !tpgresource.IsEmptyValue(reflect.ValueOf(serviceAccountProp)) && (ok || !reflect.DeepEqual(v, serviceAccountProp)) { + obj["serviceAccount"] = serviceAccountProp + } + + handlersProp, err := expandAppEngineVersionHandlers(d.Get("handlers"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("handlers"); !tpgresource.IsEmptyValue(reflect.ValueOf(handlersProp)) && (ok || !reflect.DeepEqual(v, handlersProp)) { + obj["handlers"] = handlersProp + } + + librariesProp, err := expandAppEngineVersionLibraries(d.Get("libraries"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("libraries"); !tpgresource.IsEmptyValue(reflect.ValueOf(librariesProp)) && (ok || !reflect.DeepEqual(v, librariesProp)) { + obj["libraries"] = librariesProp + } + + deploymentProp, err := expandAppEngineVersionDeployment(d.Get("deployment"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("deployment"); !tpgresource.IsEmptyValue(reflect.ValueOf(deploymentProp)) && (ok || !reflect.DeepEqual(v, deploymentProp)) { + obj["deployment"] = deploymentProp + } + + entrypointProp, err := expandAppEngineVersionEntrypoint(d.Get("entrypoint"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("entrypoint"); !tpgresource.IsEmptyValue(reflect.ValueOf(entrypointProp)) && (ok || !reflect.DeepEqual(v, entrypointProp)) { + obj["entrypoint"] = entrypointProp + } + + envVariablesProp, err := expandAppEngineVersionEnvVariables(d.Get("env_variables"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("envVariables"); !tpgresource.IsEmptyValue(reflect.ValueOf(envVariablesProp)) && (ok || !reflect.DeepEqual(v, envVariablesProp)) { + obj["envVariables"] = envVariablesProp + } + + vpcAccessConnectorProp, err := expandAppEngineVersionVpcAccessConnector(d.Get("vpc_access_connector"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("vpc_access_connector"); !tpgresource.IsEmptyValue(reflect.ValueOf(vpcAccessConnectorProp)) && (ok || !reflect.DeepEqual(v, vpcAccessConnectorProp)) { + obj["vpcAccessConnector"] = vpcAccessConnectorProp + } + + inboundServicesProp, err := expandAppEngineVersionInboundServices(d.Get("inbound_services"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("inbound_services"); !tpgresource.IsEmptyValue(reflect.ValueOf(inboundServicesProp)) && (ok || !reflect.DeepEqual(v, inboundServicesProp)) { + obj["inboundServices"] = inboundServicesProp + } + + projectProp, err := expandAppEngineVersionProject(d.Get("project"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("project"); !tpgresource.IsEmptyValue(reflect.ValueOf(projectProp)) && (ok || !reflect.DeepEqual(v, projectProp)) { + obj["project"] = projectProp + } + + noopOnDestroyProp, err := expandAppEngineVersionNoopOnDestroy(d.Get("noop_on_destroy"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("noop_on_destroy"); !tpgresource.IsEmptyValue(reflect.ValueOf(noopOnDestroyProp)) && (ok || !reflect.DeepEqual(v, noopOnDestroyProp)) { + obj["noopOnDestroy"] = noopOnDestroyProp + } + + deleteServiceOnDestroyProp, err := expandAppEngineVersionDeleteServiceOnDestroy(d.Get("delete_service_on_destroy"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("delete_service_on_destroy"); !tpgresource.IsEmptyValue(reflect.ValueOf(deleteServiceOnDestroyProp)) && (ok || !reflect.DeepEqual(v, deleteServiceOnDestroyProp)) { + obj["deleteServiceOnDestroy"] = deleteServiceOnDestroyProp + } + + serviceProp, err := expandAppEngineVersionService(d.Get("service"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("service"); !tpgresource.IsEmptyValue(reflect.ValueOf(serviceProp)) && (ok || !reflect.DeepEqual(v, serviceProp)) { + obj["service"] = serviceProp + } + + return obj, nil +} + +func expandAppEngineVersionService(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionDeleteServiceOnDestroy(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionNoopOnDestroy(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionProject(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionInboundServices(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + v = v.(*schema.Set).List() + return v, nil +} + +func expandAppEngineVersionVpcAccessConnector(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedName, err := expandAppEngineVersionName(original["name"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedName); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["name"] = transformedName + } + + transformedEgressSetting, err := expandAppEngineVersionEgressSetting(original["egress_setting"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedEgressSetting); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["egressSetting"] = transformedEgressSetting + } + + return transformed, nil +} + +func expandAppEngineVersionEgressSetting(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionEnvVariables(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionId(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionName(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionAutomaticScaling(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedMaxConcurrentRequests, err := expandAppEngineVersionMaxConcurrentRequests(original["max_concurrent_requests"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedMaxConcurrentRequests); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["maxConcurrentRequests"] = transformedMaxConcurrentRequests + } + + transformedMaxIdleInstances, err := expandAppEngineVersionMaxIdleInstances(original["max_idle_instances"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedMaxIdleInstances); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["maxIdleInstances"] = transformedMaxIdleInstances + } + + transformedMaxPendingLatency, err := expandAppEngineVersionMaxPendingLatency(original["max_pending_latency"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedMaxPendingLatency); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["maxPendingLatency"] = transformedMaxPendingLatency + } + + transformedMinIdleInstances, err := expandAppEngineVersionMinIdleInstances(original["min_idle_instances"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedMinIdleInstances); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["minIdleInstances"] = transformedMinIdleInstances + } + + transformedMinPendingLatency, err := expandAppEngineVersionMinPendingLatency(original["min_pending_latency"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedMinPendingLatency); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["minPendingLatency"] = transformedMinPendingLatency + } + + transformedStandardSchedulerSettings, err := expandAppEngineVersionStandardSchedulerSettings(original["standard_scheduler_settings"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedStandardSchedulerSettings); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["standardSchedulerSettings"] = transformedStandardSchedulerSettings + } + + return transformed, nil +} + +func expandAppEngineVersionStandardSchedulerSettings(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedTargetCpuUtilization, err := expandAppEngineVersionTargetCpuUtilization(original["target_cpu_utilization"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedTargetCpuUtilization); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["targetCpuUtilization"] = transformedTargetCpuUtilization + } + + transformedTargetThroughputUtilization, err := expandAppEngineVersionTargetThroughputUtilization(original["target_throughput_utilization"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedTargetThroughputUtilization); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["targetThroughputUtilization"] = transformedTargetThroughputUtilization + } + + transformedMinInstances, err := expandAppEngineVersionMinInstances(original["min_instances"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedMinInstances); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["minInstances"] = transformedMinInstances + } + + transformedMaxInstances, err := expandAppEngineVersionMaxInstances(original["max_instances"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedMaxInstances); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["maxInstances"] = transformedMaxInstances + } + + return transformed, nil +} + +func expandAppEngineVersionForwardedPorts(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionInstanceTag(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionSubnetworkName(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionSessionAffinity(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionCoolDownPeriod(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionAggregationWindowLength(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionTargetUtilization(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionMaxConcurrentRequests(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionMaxIdleInstances(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionMaxTotalInstances(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionMaxPendingLatency(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionMinIdleInstances(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionMinTotalInstances(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionMinPendingLatency(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionTargetRequestCountPerSecond(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionTargetConcurrentRequests(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionTargetWriteOpsPerSecond(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionTargetReadBytesPerSecond(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionTargetWriteBytesPerSecond(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionTargetReadOpsPerSecond(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionTargetSentBytesPerSecond(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionTargetSentPacketsPerSecond(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionTargetReceivedBytesPerSecond(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionTargetReceivedPacketsPerSecond(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionTargetCpuUtilization(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionTargetThroughputUtilization(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionMinInstances(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionMaxInstances(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionBasicScaling(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedIdleTimeout, err := expandAppEngineVersionIdleTimeout(original["idle_timeout"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedIdleTimeout); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["idleTimeout"] = transformedIdleTimeout + } + + transformedMaxInstances, err := expandAppEngineVersionMaxInstances(original["max_instances"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedMaxInstances); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["maxInstances"] = transformedMaxInstances + } + + return transformed, nil +} + +func expandAppEngineVersionIdleTimeout(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionManualScaling(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedInstances, err := expandAppEngineVersionInstances(original["instances"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedInstances); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["instances"] = transformedInstances + } + + return transformed, nil +} + +func expandAppEngineVersionInstances(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionInstanceClass(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionZones(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionCpu(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionDiskGb(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionMemoryGb(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionKmsKeyReference(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionVolumeType(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionSizeGb(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionRuntime(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionRuntimeChannel(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionThreadsafe(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionVm(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionOperatingSystem(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionrRuntimeVersion(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionAppEngineApis(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionEnv(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionCreateTime(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionDiskUsageBytes(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionRuntimeApiVersion(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionRuntimeMainExecutablePath(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionServiceAccount(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionCreatedBy(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionUrlRegex(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionSecurityLevel(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionLogin(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionAuthFailAction(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionRedirectHttpResponseCode(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionStaticFilesHandler(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedPath, err := expandAppEngineVersionPath(original["path"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPath); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["path"] = transformedPath + } + + transformedUploadPathRegex, err := expandAppEngineVersionUploadPathRegex(original["upload_path_regex"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedUploadPathRegex); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["uploadPathRegex"] = transformedUploadPathRegex + } + + transformedHttpHeaders, err := expandAppEngineVersionHttpHeaders(original["http_headers"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedHttpHeaders); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["httpHeaders"] = transformedHttpHeaders + } + + transformedMimeType, err := expandAppEngineVersionMimeType(original["mime_type"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedMimeType); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["mimeType"] = transformedMimeType + } + + transformedExpiration, err := expandAppEngineVersionExpiration(original["expirtation"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedExpiration); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["expiration"] = transformedExpiration + } + + transformedRequireMatchingFile, err := expandAppEngineVersionRequireMatchingFile(original["require_matching_file"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedRequireMatchingFile); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["requireMatchingFile"] = transformedRequireMatchingFile + } + + transformedApplicationReadable, err := expandAppEngineVersionApplicationReadable(original["application_readable"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedApplicationReadable); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["applicationReadable"] = transformedApplicationReadable + } + + return transformed, nil +} + +func expandAppEngineVersionPath(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionUploadPathRegex(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionHttpHeaders(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionMimeType(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionExpiration(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionRequireMatchingFile(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionApplicationReadable(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionScriptHandler(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedScriptPath, err := expandAppEngineVersionScriptPath(original["script_path"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedScriptPath); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["scriptPath"] = transformedScriptPath + } + + return transformed, nil +} + +func expandAppEngineVersionHandlers(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + continue + } + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedUrlRegex, err := expandAppEngineVersionUrlRegex(original["url_regex"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedUrlRegex); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["urlRegex"] = transformedUrlRegex + } + + transformedSecurityLevel, err := expandAppEngineVersionSecurityLevel(original["security_level"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSecurityLevel); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["securityLevel"] = transformedSecurityLevel + } + + transformedLogin, err := expandAppEngineVersionLogin(original["login"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedLogin); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["login"] = transformedLogin + } + + transformedAuthFailAction, err := expandAppEngineVersionAuthFailAction(original["auth_fail_action"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedAuthFailAction); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["authFailAction"] = transformedAuthFailAction + } + + transformedRedirectHttpResponseCode, err := expandAppEngineVersionRedirectHttpResponseCode(original["redirect_http_response_code"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedRedirectHttpResponseCode); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["redirectHttpResponseCode"] = transformedRedirectHttpResponseCode + } + + transformedScript, err := expandAppEngineVersionScriptHandler(original["script"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedScript); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["script"] = transformedScript + } + + transformedStaticFiles, err := expandAppEngineVersionStaticFilesHandler(original["static_files"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedStaticFiles); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["staticFiles"] = transformedStaticFiles + } + + req = append(req, transformed) + } + + return req, nil +} + +func expandAppEngineVersionScriptPath(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionStaticFile(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionMimeTypeFile(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionLibraries(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + continue + } + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedName, err := expandAppEngineVersionName(original["name"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedName); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["name"] = transformedName + } + + transformedVersion, err := expandAppEngineVersionVersion(original["version"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedVersion); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["version"] = transformedVersion + } + req = append(req, transformed) + } + + return req, nil +} + +func expandAppEngineVersionVersion(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineUrl(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionEntrypoint(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedShell, err := expandAppEngineVersionShell(original["shell"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedShell); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["shell"] = transformedShell + } + + return transformed, nil +} + +func expandAppEngineVersionShell(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionDeployment(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedFiles, err := expandAppEngineVersionFiles(original["files"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedFiles); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["files"] = transformedFiles + } + + transformedZip, err := expandAppEngineVersionZip(original["zip"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedZip); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["zip"] = transformedZip + } + + return transformed, nil +} + +func expandAppEngineVersionZip(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedSourceUrlProp, err := expandAppEngineVersionSourceUrl(original["source_url"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSourceUrlProp); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["sourceUrl"] = transformedSourceUrlProp + } + + filesCountProp, err := expandAppEngineVersionFilesCount(original["files_count"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(filesCountProp); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["filesCount"] = filesCountProp + } + + return transformed, nil +} + +func expandAppEngineVersionSourceUrl(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionFilesCount(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineVersionFiles(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + v = v.(*schema.Set).List() + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + continue + } + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedSourceUrlProp, err := expandAppEngineSourceUrl(original["source_url"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSourceUrlProp); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["sourceUrl"] = transformedSourceUrlProp + } + + transformedSha1SumProp, err := expandAppEngineSha1Sum(original["sha1_sum"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSha1SumProp); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["sha1Sum"] = transformedSha1SumProp + } + + transformedNameProp, err := expandAppEngineName(original["name"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedNameProp); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["name"] = transformedNameProp + } + + req = append(req, transformed) + } + + return req, nil +} + +func expandAppEngineSourceUrl(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineSha1Sum(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandAppEngineName(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} diff --git a/mmv1/third_party/tgc/tests/data/example_google_app_engine_standard_app_version.json b/mmv1/third_party/tgc/tests/data/example_google_app_engine_standard_app_version.json new file mode 100644 index 000000000000..9bfe1a105c46 --- /dev/null +++ b/mmv1/third_party/tgc/tests/data/example_google_app_engine_standard_app_version.json @@ -0,0 +1,28 @@ +[ + { + "name": "//appengine.googleapis.com/apps/{{.Provider.project}}/services/default/versions/v1", + "asset_type": "appengine.googleapis.com/Version", + "ancestry_path": "{{.Ancestry}}/project/{{.Provider.project}}", + "resource": { + "version": "v1", + "discovery_document_uri": "https://www.googleapis.com/discovery/v1/apis/appengine/v1/rest", + "discovery_name": "Version", + "parent": "//cloudresourcemanager.googleapis.com/projects/{{.Provider.project}}", + "data": { + "deployment": { + "zip": { + "sourceUrl": "https://storage.googleapis.com/bucket-app-engine/world.zip" + } + }, + "entrypoint": { + "shell": "python3 world.py" + }, + "id": "v1", + "project": "{{.Provider.project}}", + "runtime": "python39", + "service": "default" + } + }, + "ancestors": ["organizations/{{.OrgID}}"] + } +] \ No newline at end of file diff --git a/mmv1/third_party/tgc/tests/data/example_google_app_engine_standard_app_version.tf b/mmv1/third_party/tgc/tests/data/example_google_app_engine_standard_app_version.tf new file mode 100644 index 000000000000..2f214bf7267d --- /dev/null +++ b/mmv1/third_party/tgc/tests/data/example_google_app_engine_standard_app_version.tf @@ -0,0 +1,28 @@ +terraform { + required_providers { + google = { + source = "hashicorp/google-beta" + version = "~> {{.Provider.version}}" + } + } +} + +provider "google" { + {{if .Provider.credentials }}credentials = "{{.Provider.credentials}}"{{end}} +} + +resource "google_app_engine_standard_app_version" "my_app_v1" { + version_id = "v1" + service = "default" + runtime = "python39" + + entrypoint { + shell = "python3 world.py" + } + + deployment { + zip { + source_url = "https://storage.googleapis.com/bucket-app-engine/world.zip" + } + } +} \ No newline at end of file diff --git a/tpgtools/ignored_handwritten/common_diff_suppress.go b/tpgtools/ignored_handwritten/common_diff_suppress.go index c82d7c1fe5e4..0c4af16934ee 100644 --- a/tpgtools/ignored_handwritten/common_diff_suppress.go +++ b/tpgtools/ignored_handwritten/common_diff_suppress.go @@ -3,8 +3,6 @@ package google import ( - "crypto/sha256" - "encoding/hex" "net" "strings" "time" @@ -12,80 +10,16 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func OptionalPrefixSuppress(prefix string) schema.SchemaDiffSuppressFunc { - return func(k, old, new string, d *schema.ResourceData) bool { - return prefix+old == new || prefix+new == old - } -} - -func OptionalSurroundingSpacesSuppress(k, old, new string, d *schema.ResourceData) bool { - return strings.TrimSpace(old) == strings.TrimSpace(new) -} - func EmptyOrDefaultStringSuppress(defaultVal string) schema.SchemaDiffSuppressFunc { return func(k, old, new string, d *schema.ResourceData) bool { return (old == "" && new == defaultVal) || (new == "" && old == defaultVal) } } -func IpCidrRangeDiffSuppress(k, old, new string, d *schema.ResourceData) bool { - // The range may be a: - // A) single IP address (e.g. 10.2.3.4) - // B) CIDR format string (e.g. 10.1.2.0/24) - // C) netmask (e.g. /24) - // - // For A) and B), no diff to suppress, they have to match completely. - // For C), The API picks a network IP address and this creates a diff of the form: - // network_interface.0.alias_ip_range.0.ip_cidr_range: "10.128.1.0/24" => "/24" - // We should only compare the mask portion for this case. - if len(new) > 0 && new[0] == '/' { - oldNetmaskStartPos := strings.LastIndex(old, "/") - - if oldNetmaskStartPos != -1 { - oldNetmask := old[strings.LastIndex(old, "/"):] - if oldNetmask == new { - return true - } - } - } - - return false -} - -// Sha256DiffSuppress -// if old is the hex-encoded sha256 sum of new, treat them as equal -func Sha256DiffSuppress(_, old, new string, _ *schema.ResourceData) bool { - return hex.EncodeToString(sha256.New().Sum([]byte(old))) == new -} - func CaseDiffSuppress(_, old, new string, _ *schema.ResourceData) bool { return strings.ToUpper(old) == strings.ToUpper(new) } -// Port range '80' and '80-80' is equivalent. -// `old` is read from the server and always has the full range format (e.g. '80-80', '1024-2048'). -// `new` can be either a single port or a port range. -func PortRangeDiffSuppress(k, old, new string, d *schema.ResourceData) bool { - return old == new+"-"+new -} - -// Single-digit hour is equivalent to hour with leading zero e.g. suppress diff 1:00 => 01:00. -// Assume either value could be in either format. -func Rfc3339TimeDiffSuppress(k, old, new string, d *schema.ResourceData) bool { - if (len(old) == 4 && "0"+old == new) || (len(new) == 4 && "0"+new == old) { - return true - } - return false -} - -// For managed SSL certs, if new is an absolute FQDN (trailing '.') but old isn't, treat them as equals. -func AbsoluteDomainSuppress(k, old, new string, _ *schema.ResourceData) bool { - if k == "managed.0.domains.0" { - return old == strings.TrimRight(new, ".") - } - return old == new -} - func TimestampDiffSuppress(format string) schema.SchemaDiffSuppressFunc { return func(_, old, new string, _ *schema.ResourceData) bool { oldT, err := time.Parse(format, old) diff --git a/tpgtools/overrides/bigqueryreservation/assignment.yaml b/tpgtools/overrides/bigqueryreservation/assignment.yaml deleted file mode 100644 index af851d79a0c9..000000000000 --- a/tpgtools/overrides/bigqueryreservation/assignment.yaml +++ /dev/null @@ -1,4 +0,0 @@ -- type: CUSTOMIZE_DIFF - details: - functions: - - tpgresource.DefaultProviderProject diff --git a/tpgtools/overrides/bigqueryreservation/beta/assignment.yaml b/tpgtools/overrides/bigqueryreservation/beta/assignment.yaml deleted file mode 100644 index af851d79a0c9..000000000000 --- a/tpgtools/overrides/bigqueryreservation/beta/assignment.yaml +++ /dev/null @@ -1,4 +0,0 @@ -- type: CUSTOMIZE_DIFF - details: - functions: - - tpgresource.DefaultProviderProject diff --git a/tpgtools/overrides/bigqueryreservation/beta/tpgtools_product.yaml b/tpgtools/overrides/bigqueryreservation/beta/tpgtools_product.yaml deleted file mode 100644 index 94e8bdc9919a..000000000000 --- a/tpgtools/overrides/bigqueryreservation/beta/tpgtools_product.yaml +++ /dev/null @@ -1,9 +0,0 @@ -## product level overrides - -## Skip base path generation... already generated by magic modules -- type: PRODUCT_BASE_PATH - details: - skip: true -- type: PRODUCT_DOCS_SECTION - details: - docssection: BigQuery Reservation \ No newline at end of file diff --git a/tpgtools/overrides/bigqueryreservation/samples/assignment/basic.tf.tmpl b/tpgtools/overrides/bigqueryreservation/samples/assignment/basic.tf.tmpl deleted file mode 100644 index 3a59b5ccb669..000000000000 --- a/tpgtools/overrides/bigqueryreservation/samples/assignment/basic.tf.tmpl +++ /dev/null @@ -1,13 +0,0 @@ -resource "google_bigquery_reservation" "basic" { - name = "tf-test-my-reservation%{random_suffix}" - project = "{{project}}" - location = "us-central1" - slot_capacity = 0 - ignore_idle_slots = false -} - -resource "google_bigquery_reservation_assignment" "primary" { - assignee = "projects/{{project}}" - job_type = "PIPELINE" - reservation = google_bigquery_reservation.basic.id -} \ No newline at end of file diff --git a/tpgtools/overrides/bigqueryreservation/samples/assignment/basic.yaml b/tpgtools/overrides/bigqueryreservation/samples/assignment/basic.yaml deleted file mode 100644 index 0281b96065c4..000000000000 --- a/tpgtools/overrides/bigqueryreservation/samples/assignment/basic.yaml +++ /dev/null @@ -1,7 +0,0 @@ -updates: -variables: - - name: "project" - type: "project" - - name: "reservation" - type: "resource_name" - diff --git a/tpgtools/overrides/bigqueryreservation/samples/assignment/meta.yaml b/tpgtools/overrides/bigqueryreservation/samples/assignment/meta.yaml deleted file mode 100644 index 33eeb9b6691a..000000000000 --- a/tpgtools/overrides/bigqueryreservation/samples/assignment/meta.yaml +++ /dev/null @@ -1,3 +0,0 @@ - -ignore_read: - - "reservation" \ No newline at end of file diff --git a/tpgtools/overrides/bigqueryreservation/tpgtools_product.yaml b/tpgtools/overrides/bigqueryreservation/tpgtools_product.yaml deleted file mode 100644 index d909522fa961..000000000000 --- a/tpgtools/overrides/bigqueryreservation/tpgtools_product.yaml +++ /dev/null @@ -1,9 +0,0 @@ -## product level overrides - -## Skip base path generation... already generated by magic modules -- type: PRODUCT_BASE_PATH - details: - skip: true -- type: PRODUCT_DOCS_SECTION - details: - docssection: BigQuery Reservation \ No newline at end of file