diff --git a/mmv1/third_party/terraform/go.mod.erb b/mmv1/third_party/terraform/go.mod.erb index b5d4fd065d93..fa30a092113d 100644 --- a/mmv1/third_party/terraform/go.mod.erb +++ b/mmv1/third_party/terraform/go.mod.erb @@ -5,7 +5,7 @@ go 1.20 require ( cloud.google.com/go/bigtable v1.19.0 - github.com/GoogleCloudPlatform/declarative-resource-client-library v1.62.0 + github.com/GoogleCloudPlatform/declarative-resource-client-library v1.63.0 github.com/apparentlymart/go-cidr v1.1.0 github.com/davecgh/go-spew v1.1.1 github.com/dnaeon/go-vcr v1.0.1 diff --git a/mmv1/third_party/terraform/go.sum b/mmv1/third_party/terraform/go.sum index 6837513a22df..63d8188cf48c 100644 --- a/mmv1/third_party/terraform/go.sum +++ b/mmv1/third_party/terraform/go.sum @@ -413,3 +413,5 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= +github.com/GoogleCloudPlatform/declarative-resource-client-library v1.63.0 h1:eSOBYPZVnU2fZul9sAJFGLVCgv6stNVKkmsogKF7UeY= +github.com/GoogleCloudPlatform/declarative-resource-client-library v1.63.0/go.mod h1:pL2Qt5HT+x6xrTd806oMiM3awW6kNIXB/iiuClz6m6k= diff --git a/mmv1/third_party/terraform/services/gkehub/resource_gke_hub_feature_membership_test.go.erb b/mmv1/third_party/terraform/services/gkehub/resource_gke_hub_feature_membership_test.go.erb index d231ea19e62c..a7ab46c640a6 100644 --- a/mmv1/third_party/terraform/services/gkehub/resource_gke_hub_feature_membership_test.go.erb +++ b/mmv1/third_party/terraform/services/gkehub/resource_gke_hub_feature_membership_test.go.erb @@ -1008,6 +1008,17 @@ func TestAccGKEHubFeatureMembership_gkehubFeaturePolicyController(t *testing.T) ImportState: true, ImportStateVerify: true, }, + { + Config: testAccGKEHubFeatureMembership_policycontrollerUpdateMaps(context), + Check: resource.ComposeTestCheckFunc( + testAccCheckGkeHubFeatureMembershipPresent(t, fmt.Sprintf("tf-test-gkehub%s", context["random_suffix"]), "global", "policycontroller", fmt.Sprintf("tf-test1%s", context["random_suffix"])), + ), + }, + { + ResourceName: "google_gke_hub_feature_membership.feature_member", + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -1064,9 +1075,92 @@ resource "google_gke_hub_feature_membership" "feature_member" { "PROMETHEUS" ] } + deployment_configs { + component_name = "admission" + replica_count = 3 + pod_affinity = "ANTI_AFFINITY" + container_resources { + limits { + memory = "1Gi" + cpu = "1.5" + } + requests { + memory = "500Mi" + cpu = "150m" + } + } + pod_tolerations { + key = "key1" + operator = "Equal" + value = "value1" + effect = "NoSchedule" + } + } + deployment_configs { + component_name = "mutation" + replica_count = 3 + pod_affinity = "ANTI_AFFINITY" + } policy_content { template_library { - installation = "NOT_INSTALLED" + installation = "ALL" + } + bundles { + bundle_name = "pci-dss-v3.2.1" + exempted_namespaces = ["sample-namespace"] + } + bundles { + bundle_name = "nist-sp-800-190" + } + } + } + version = "1.17.0" + } +} +`, context) +} + +func testAccGKEHubFeatureMembership_policycontrollerUpdateMaps(context map[string]interface{}) string { + return gkeHubFeatureProjectSetup(context) + gkeHubClusterMembershipSetup(context) + acctest.Nprintf(` +resource "google_gke_hub_feature" "feature" { + project = google_project.project.project_id + name = "policycontroller" + location = "global" + depends_on = [google_project_service.container, google_project_service.gkehub, google_project_service.poco] +} + +resource "google_gke_hub_feature_membership" "feature_member" { + project = google_project.project.project_id + location = "global" + feature = google_gke_hub_feature.feature.name + membership = google_gke_hub_membership.membership.membership_id + policycontroller { + policy_controller_hub_config { + install_spec = "INSTALL_SPEC_SUSPENDED" + constraint_violation_limit = 50 + referential_rules_enabled = true + log_denies_enabled = true + mutation_enabled = true + monitoring { + backends = [ + "PROMETHEUS" + ] + } + deployment_configs { + component_name = "admission" + pod_affinity = "NO_AFFINITY" + } + deployment_configs { + component_name = "audit" + container_resources { + limits { + memory = "1Gi" + cpu = "1.5" + } + requests { + memory = "500Mi" + cpu = "150m" + } } } } diff --git a/mmv1/third_party/terraform/website/docs/r/gke_hub_feature_membership.html.markdown b/mmv1/third_party/terraform/website/docs/r/gke_hub_feature_membership.html.markdown index ed571b6d7bdd..bd79aaa35da8 100644 --- a/mmv1/third_party/terraform/website/docs/r/gke_hub_feature_membership.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/gke_hub_feature_membership.html.markdown @@ -504,6 +504,10 @@ The following arguments are supported: (Optional) The maximum number of audit violations to be stored in a constraint. If not set, the default of 20 will be used. + * `deployment_configs` - + (Optional) + Map of deployment configs to deployments ("admission", "audit", "mutation"). + * `policy_content` - (Optional) Specifies the desired policy content on the cluster. Structure is [documented below](#nested_policy_content). @@ -514,12 +518,97 @@ The following arguments are supported: (Optional) Specifies the list of backends Policy Controller will export to. Must be one of `CLOUD_MONITORING` or `PROMETHEUS`. Defaults to [`CLOUD_MONITORING`, `PROMETHEUS`]. Specifying an empty value `[]` disables metrics export. +The `deployment_configs` block supports: + +* `component_name` - + (Required) + The name of the component. One of `admission` `audit` or `mutation` + +* `container_resources` - + (Optional) + Container resource requirements. + +* `pod_affinity` - + (Optional) + Pod affinity configuration. Possible values: AFFINITY_UNSPECIFIED, NO_AFFINITY, ANTI_AFFINITY + +* `pod_tolerations` - + (Optional) + Pod tolerations of node taints. + +* `replica_count` - + (Optional) + Pod replica count. + +The `container_resources` block supports: + +* `limits` - + (Optional) + Limits describes the maximum amount of compute resources allowed for use by the running container. + +* `requests` - + (Optional) + Requests describes the amount of compute resources reserved for the container by the kube-scheduler. + +The `limits` block supports: + +* `cpu` - + (Optional) + CPU requirement expressed in Kubernetes resource units. + +* `memory` - + (Optional) + Memory requirement expressed in Kubernetes resource units. + +The `requests` block supports: + +* `cpu` - + (Optional) + CPU requirement expressed in Kubernetes resource units. + +* `memory` - + (Optional) + Memory requirement expressed in Kubernetes resource units. + +The `pod_tolerations` block supports: + +* `effect` - + (Optional) + Matches a taint effect. + +* `key` - + (Optional) + Matches a taint key (not necessarily unique). + +* `operator` - + (Optional) + Matches a taint operator. + +* `value` - + (Optional) + Matches a taint value. + The `policy_content` block supports: +* `bundles` - + (Optional) + map of bundle name to BundleInstallSpec. The bundle name maps to the `bundleName` key in the `policycontroller.gke.io/constraintData` annotation on a constraint. + * `template_library` (Optional) Configures the installation of the Template Library. Structure is [documented below](#nested_template_library). +The `template_library` block supports: +The `bundles` block supports: + +* `bundle_name` - + (Required) + The name of the bundle. + +* `exempted_namespaces` - + (Optional) + The set of namespaces to be exempted from the bundle. + The `template_library` block supports: * `installation` diff --git a/tpgtools/go.mod b/tpgtools/go.mod index 14c13f11e191..90f55b270e1e 100644 --- a/tpgtools/go.mod +++ b/tpgtools/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( bitbucket.org/creachadair/stringset v0.0.11 - github.com/GoogleCloudPlatform/declarative-resource-client-library v1.62.0 + github.com/GoogleCloudPlatform/declarative-resource-client-library v1.63.0 github.com/golang/glog v1.1.2 github.com/hashicorp/hcl v1.0.0 github.com/kylelemons/godebug v1.1.0 diff --git a/tpgtools/go.sum b/tpgtools/go.sum index 86a5ac602ab9..30d683a7cf40 100644 --- a/tpgtools/go.sum +++ b/tpgtools/go.sum @@ -6,12 +6,8 @@ cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdi cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/GoogleCloudPlatform/declarative-resource-client-library v1.60.0 h1:RFZs9I3tXewC7cJf8RKbUMpQZO6jWZ9SHSnNd+auxsQ= -github.com/GoogleCloudPlatform/declarative-resource-client-library v1.60.0/go.mod h1:pL2Qt5HT+x6xrTd806oMiM3awW6kNIXB/iiuClz6m6k= -github.com/GoogleCloudPlatform/declarative-resource-client-library v1.61.0 h1:IAr9UlYbxURIYABRMagXXo8pDlkFNFFXWz5J2+srrnc= -github.com/GoogleCloudPlatform/declarative-resource-client-library v1.61.0/go.mod h1:pL2Qt5HT+x6xrTd806oMiM3awW6kNIXB/iiuClz6m6k= -github.com/GoogleCloudPlatform/declarative-resource-client-library v1.62.0 h1:s4Y6r6RrYLBnqosGXLwR0h1Gqr0VT3wgd6rqvHsD9OE= -github.com/GoogleCloudPlatform/declarative-resource-client-library v1.62.0/go.mod h1:pL2Qt5HT+x6xrTd806oMiM3awW6kNIXB/iiuClz6m6k= +github.com/GoogleCloudPlatform/declarative-resource-client-library v1.63.0 h1:eSOBYPZVnU2fZul9sAJFGLVCgv6stNVKkmsogKF7UeY= +github.com/GoogleCloudPlatform/declarative-resource-client-library v1.63.0/go.mod h1:pL2Qt5HT+x6xrTd806oMiM3awW6kNIXB/iiuClz6m6k= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= diff --git a/tpgtools/override.go b/tpgtools/override.go index a190f8026e7e..5ce55962733c 100644 --- a/tpgtools/override.go +++ b/tpgtools/override.go @@ -79,6 +79,7 @@ const ( CustomListSize = "CUSTOM_LIST_SIZE_CONSTRAINT" CustomDefault = "CUSTOM_DEFAULT" CustomSchemaValues = "CUSTOM_SCHEMA_VALUES" + ComplexMapKey = "COMPLEX_MAP_KEY_NAME" ) // Overrides represents the type a resource's override file can be marshalled diff --git a/tpgtools/override_details.go b/tpgtools/override_details.go index 12ee16832a59..963c23656e92 100644 --- a/tpgtools/override_details.go +++ b/tpgtools/override_details.go @@ -230,3 +230,8 @@ type StateUpgradeDetails struct { // The current schema version SchemaVersion int } + +type ComplexMapKeyDetails struct { + // The name of the key as exposed by Terraform + KeyName string +} diff --git a/tpgtools/overrides/gkehub/beta/feature_membership.yaml b/tpgtools/overrides/gkehub/beta/feature_membership.yaml index 3eac7364e119..8acee09e053a 100644 --- a/tpgtools/overrides/gkehub/beta/feature_membership.yaml +++ b/tpgtools/overrides/gkehub/beta/feature_membership.yaml @@ -29,3 +29,11 @@ details: functions: - tpgresource.DefaultProviderProject +- type: COMPLEX_MAP_KEY_NAME + field: policycontroller.policy_controller_hub_config.policy_content.bundles + details: + keyname: bundle_name +- type: COMPLEX_MAP_KEY_NAME + field: policycontroller.policy_controller_hub_config.deployment_configs + details: + keyname: component_name \ No newline at end of file diff --git a/tpgtools/overrides/gkehub/feature_membership.yaml b/tpgtools/overrides/gkehub/feature_membership.yaml index 2e8b8f32503c..cd6cf1876fcc 100644 --- a/tpgtools/overrides/gkehub/feature_membership.yaml +++ b/tpgtools/overrides/gkehub/feature_membership.yaml @@ -24,4 +24,12 @@ field: mesh.control_plane details: message: >- - Deprecated in favor of the `management` field \ No newline at end of file + Deprecated in favor of the `management` field +- type: COMPLEX_MAP_KEY_NAME + field: policycontroller.policy_controller_hub_config.policy_content.bundles + details: + keyname: bundle_name +- type: COMPLEX_MAP_KEY_NAME + field: policycontroller.policy_controller_hub_config.deployment_configs + details: + keyname: component_name \ No newline at end of file diff --git a/tpgtools/property.go b/tpgtools/property.go index f7939be2a3d9..62e5aa4fa51c 100644 --- a/tpgtools/property.go +++ b/tpgtools/property.go @@ -107,6 +107,10 @@ type Property struct { // Sub-properties of nested objects or arrays with nested objects Properties []Property + // If this is a complex map type, this string represents the name of the + // field that the key to the map can be set with + ComplexMapKeyName string + // Reference to the parent resource. // note: "Properties" will not be available. resource *Resource @@ -198,13 +202,24 @@ func (p Property) ObjectType() string { } func (p Property) IsArray() bool { - return (p.Type.String() == SchemaTypeList || p.Type.String() == SchemaTypeSet) && !p.Type.IsObject() + return (p.Type.String() == SchemaTypeList || p.Type.String() == SchemaTypeSet) && !p.Type.IsObject() && !p.IsComplexMap() } func (t Type) IsSet() bool { return t.String() == SchemaTypeSet } +// Complex map is for maps of string --> object that are supported in DCL but +// not in Terraform. We handle this by adding a field in the Terraform schema +// for the key in the map. This must be added via a COMPLEX_MAP_KEY_NAME +// override +func (t Type) IsComplexMap() bool { + if t.typ.AdditionalProperties != nil { + return t.typ.AdditionalProperties.Type != "string" + } + return false +} + // ShouldGenerateNestedSchema returns true if an object's nested schema function should be generated. func (p Property) ShouldGenerateNestedSchema() bool { return len(p.Properties) > 0 && !p.Collapsed @@ -278,6 +293,9 @@ func buildGetter(p Property, rawGetter string) string { if p.Type.IsEnumArray() { return fmt.Sprintf("expand%s%sArray(%s)", p.resource.PathType(), p.PackagePath(), rawGetter) } + if p.Type.IsComplexMap() { + return fmt.Sprintf("expand%s%sMap(%s)", p.resource.PathType(), p.PackagePath(), rawGetter) + } if p.Type.typ.Items != nil && p.Type.typ.Items.Type == "string" { return fmt.Sprintf("tpgdclresource.ExpandStringArray(%s)", rawGetter) } @@ -317,6 +335,9 @@ func (p Property) DefaultStateSetter() string { return fmt.Sprintf("d.Set(%q, res.%s)", p.Name(), p.PackageName) case SchemaTypeList, SchemaTypeSet: + if p.IsComplexMap() { + return fmt.Sprintf("d.Set(%q, flatten%s%sMap(res.%s))", p.Name(), p.resource.PathType(), p.PackagePath(), p.PackageName) + } if p.typ.Items != nil && ((p.typ.Items.Type == "string" && len(p.typ.Items.Enum) == 0) || p.typ.Items.Type == "integer") { return fmt.Sprintf("d.Set(%q, res.%s)", p.Name(), p.PackageName) } @@ -365,6 +386,9 @@ func (p Property) flattenGetterWithParent(parent string) string { if p.Type.IsEnumArray() { return fmt.Sprintf("flatten%s%sArray(obj.%s)", p.resource.PathType(), p.PackagePath(), p.PackageName) } + if p.Type.IsComplexMap() { + return fmt.Sprintf("flatten%s%sMap(%s.%s)", p.resource.PathType(), p.PackagePath(), parent, p.PackageName) + } if p.Type.typ.Items != nil && p.Type.typ.Items.Type == "integer" { return fmt.Sprintf("%s.%s", parent, p.PackageName) } @@ -376,7 +400,6 @@ func (p Property) flattenGetterWithParent(parent string) string { return fmt.Sprintf("flatten%s%sArray(%s.%s)", p.resource.PathType(), p.PackagePath(), parent, p.PackageName) } } - if p.typ.Type == "object" { return fmt.Sprintf("flatten%s%s(%s.%s)", p.resource.PathType(), p.PackagePath(), parent, p.PackageName) } @@ -651,6 +674,38 @@ func createPropertiesFromSchema(schema *openapi.Schema, typeFetcher *TypeFetcher p.ElemIsBasicType = true } } + // Complex maps are represented as TypeSet but don't have v.Items set. + // Use AdditionalProperties instead, and add an additional `name` field + // that represents the key in the map + if p.Type.IsComplexMap() { + props, err := createPropertiesFromSchema(p.Type.typ.AdditionalProperties, typeFetcher, overrides, resource, &p, location) + if err != nil { + return nil, err + } + cm := ComplexMapKeyDetails{} + cmOk, err := overrides.PropertyOverrideWithDetails(ComplexMapKey, p, &cm, location) + if err != nil { + return nil, fmt.Errorf("failed to decode complex map key name details") + } + if !cmOk { + return nil, fmt.Errorf("failed to find complex map key name for map named: %s", p.Name()) + } + keyProp := Property{ + title: cm.KeyName, + Type: Type{&openapi.Schema{Type: "string"}}, + resource: resource, + parent: &p, + Required: true, + Description: "The name for the key in the map for which this object is mapped to in the API", + } + props = append([]Property{keyProp}, props...) + + p.Properties = props + e := fmt.Sprintf("%s%sSchema()", resource.PathType(), p.PackagePath()) + p.Elem = &e + p.ElemIsBasicType = false + p.ComplexMapKeyName = cm.KeyName + } if !p.Computed { if stringInSlice(v.Title, schema.Required) { @@ -779,7 +834,7 @@ func createPropertiesFromSchema(schema *openapi.Schema, typeFetcher *TypeFetcher p.ValidateFunc = &vf.Function } - if p.Type.String() == SchemaTypeSet { + if p.Type.IsSet() { shf := SetHashFuncDetails{} shfOk, err := overrides.PropertyOverrideWithDetails(SetHashFunc, p, &shf, location) if err != nil { diff --git a/tpgtools/templates/resource.go.tmpl b/tpgtools/templates/resource.go.tmpl index 8de8fa398c60..b65085ba771f 100644 --- a/tpgtools/templates/resource.go.tmpl +++ b/tpgtools/templates/resource.go.tmpl @@ -661,6 +661,39 @@ func expand{{$.PathType}}{{$v.PackagePath}}Array(o interface{}) []{{$.Package}}. items = append(items, *i) } + return items +} + {{- end }} + + {{ if $v.IsComplexMap -}} +func expand{{$.PathType}}{{$v.PackagePath}}Map(o interface{}) map[string]{{$.Package}}.{{$v.ObjectType}} { + if o == nil { + {{- if $v.Computed }} + return nil + {{- else }} + return make(map[string]{{$.Package}}.{{$v.ObjectType}}) + {{- end }} + } + + o = o.(*schema.Set).List() + + objs := o.([]interface{}) + if len(objs) == 0 || objs[0] == nil { + {{- if $v.Computed }} + return nil + {{- else }} + return make(map[string]{{$.Package}}.{{$v.ObjectType}}) + {{- end }} + } + + items := make(map[string]{{$.Package}}.{{$v.ObjectType}}) + for _, item := range objs { + i := expand{{$.PathType}}{{$v.PackagePath}}(item) + if item != nil { + items[item.(map[string]interface{})["{{$v.ComplexMapKeyName}}"].(string)] = *i + } + } + return items } {{- end }} @@ -688,7 +721,7 @@ func expand{{$.PathType}}{{$v.PackagePath}}(o interface{}) *{{$.Package}}.{{$v.O {{- end }} return &{{$.Package}}.{{$v.ObjectType}}{ {{- range $p := $v.Properties }} - {{- if and ($p.Settable) ($p.ExpandGetter) }} + {{- if and ($p.Settable) ($p.ExpandGetter) (or (not $v.IsComplexMap) (ne $p.Name $v.ComplexMapKeyName)) }} {{$p.PackageName}}: {{$p.ExpandGetter}}, {{- end -}} {{ end }} @@ -713,17 +746,36 @@ func flatten{{$.PathType}}{{$v.PackagePath}}Array(objs []{{$.Package}}.{{$v.Obje } {{- end }} -func flatten{{$.PathType}}{{$v.PackagePath}}(obj *{{$.Package}}.{{$v.ObjectType}}) interface{} { - if obj == nil || obj.Empty(){ + {{ if $v.IsComplexMap -}} +func flatten{{$.PathType}}{{$v.PackagePath}}Map(objs map[string]{{$.Package}}.{{$v.ObjectType}}) []interface{} { + if objs == nil { + return nil + } + + items := []interface{}{} + for name, item := range objs { + i := flatten{{$.PathType}}{{$v.PackagePath}}(&item, name) + items = append(items, i) + } + + return items +} + {{- end }} + +func flatten{{$.PathType}}{{$v.PackagePath}}(obj *{{$.Package}}.{{$v.ObjectType}}{{- if $v.IsComplexMap -}}, name string{{- end -}}) interface{} { + if obj == nil {{- if not $v.IsComplexMap -}}|| obj.Empty(){{- end -}}{ return nil } transformed := map[string]interface{}{ {{- range $p := $v.Properties }} - {{- if ($p.FlattenGetter) }} + {{- if or (not $v.IsComplexMap) (ne $p.Name $v.ComplexMapKeyName) }} "{{$p.Name}}": {{$p.FlattenGetter}}, {{- end -}} {{ end }} } +{{ if $v.IsComplexMap }} + transformed["{{$v.ComplexMapKeyName}}"] = name +{{ end }} {{ if $v.IsObject }} return []interface{}{transformed} {{ else }} diff --git a/tpgtools/type.go b/tpgtools/type.go index 47adedaa30c9..cb31f0a7c562 100644 --- a/tpgtools/type.go +++ b/tpgtools/type.go @@ -67,14 +67,13 @@ func (t Type) String() string { } return "unknown number type" case "object": - // assume if this is set, it's a string -> string map for now. - // https://swagger.io/docs/specification/data-models/dictionaries/ - // describes the behaviour of AdditionalProperties for type: object if t.typ.AdditionalProperties != nil { if v := t.typ.AdditionalProperties.Type; v == "string" { return SchemaTypeMap } else { - return fmt.Sprintf("unknown AdditionalProperties: %q", v) + // Complex maps are handled as sets with an extra value for the + // name of the object + return SchemaTypeSet } } return SchemaTypeList