From 3366b0ddd5059452bef573c619b7e0a1eea3cf3f Mon Sep 17 00:00:00 2001 From: amanpruthi Date: Wed, 15 May 2024 14:38:34 +0530 Subject: [PATCH 1/3] added nginx contoller support --- main.tf | 13 +- modules/nginx/main.tf | 5 + modules/nginx/nginx/.helmignore | 23 ++++ modules/nginx/nginx/Chart.yaml | 5 + .../nginx/nginx/templates/clusterrole.yaml | 88 +++++++++++++ .../nginx/templates/clusterrolebinding.yaml | 25 ++++ modules/nginx/nginx/templates/configmap.yaml | 7 ++ modules/nginx/nginx/templates/deployment.yaml | 118 ++++++++++++++++++ .../nginx/nginx/templates/ingressclass.yaml | 33 +++++ modules/nginx/nginx/templates/job.yaml | 91 ++++++++++++++ modules/nginx/nginx/templates/namespace.yaml | 4 + modules/nginx/nginx/templates/role.yaml | 97 ++++++++++++++ .../nginx/nginx/templates/rolebinding.yaml | 27 ++++ modules/nginx/nginx/templates/service.yaml | 43 +++++++ .../nginx/nginx/templates/serviceaccount.yaml | 12 ++ variables.tf | 5 + 16 files changed, 595 insertions(+), 1 deletion(-) create mode 100644 modules/nginx/main.tf create mode 100644 modules/nginx/nginx/.helmignore create mode 100644 modules/nginx/nginx/Chart.yaml create mode 100644 modules/nginx/nginx/templates/clusterrole.yaml create mode 100644 modules/nginx/nginx/templates/clusterrolebinding.yaml create mode 100644 modules/nginx/nginx/templates/configmap.yaml create mode 100644 modules/nginx/nginx/templates/deployment.yaml create mode 100644 modules/nginx/nginx/templates/ingressclass.yaml create mode 100644 modules/nginx/nginx/templates/job.yaml create mode 100644 modules/nginx/nginx/templates/namespace.yaml create mode 100644 modules/nginx/nginx/templates/role.yaml create mode 100644 modules/nginx/nginx/templates/rolebinding.yaml create mode 100644 modules/nginx/nginx/templates/service.yaml create mode 100644 modules/nginx/nginx/templates/serviceaccount.yaml diff --git a/main.tf b/main.tf index 584df74..69a0e3e 100644 --- a/main.tf +++ b/main.tf @@ -152,6 +152,13 @@ module "cert_manager" { depends_on = [module.app_aks] } + +module "nginx" { + count = var.nginx_controller ? 1 : 0 + source = "./modules/nginx" + depends_on = [module.app_aks] +} + module "wandb" { source = "wandb/wandb/helm" version = "1.2.0" @@ -216,7 +223,11 @@ module "wandb" { // the operator after testing. Trying to reduce the diff. issuer = { create = false } - annotations = { + annotations = var.nginx_controller ? { + "kubernetes.io/ingress.class" = "nginx" + "cert-manager.io/cluster-issuer" = "cert-issuer" + "cert-manager.io/acme-challenge-type" = "http01" + } : { "kubernetes.io/ingress.class" = "azure/application-gateway" "cert-manager.io/cluster-issuer" = "cert-issuer" "cert-manager.io/acme-challenge-type" = "http01" diff --git a/modules/nginx/main.tf b/modules/nginx/main.tf new file mode 100644 index 0000000..10fd196 --- /dev/null +++ b/modules/nginx/main.tf @@ -0,0 +1,5 @@ +resource "helm_release" "nginx" { + name = "nginx" + chart = "nginx" + repository = path.module +} diff --git a/modules/nginx/nginx/.helmignore b/modules/nginx/nginx/.helmignore new file mode 100644 index 0000000..691fa13 --- /dev/null +++ b/modules/nginx/nginx/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ \ No newline at end of file diff --git a/modules/nginx/nginx/Chart.yaml b/modules/nginx/nginx/Chart.yaml new file mode 100644 index 0000000..2a71a6c --- /dev/null +++ b/modules/nginx/nginx/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v2 +description: A Helm chart to install a nginx controller. +name: nginx +type: application +version: 0.1.0 \ No newline at end of file diff --git a/modules/nginx/nginx/templates/clusterrole.yaml b/modules/nginx/nginx/templates/clusterrole.yaml new file mode 100644 index 0000000..082a7b1 --- /dev/null +++ b/modules/nginx/nginx/templates/clusterrole.yaml @@ -0,0 +1,88 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: ingress-nginx +rules: +- apiGroups: + - "" + resources: + - configmaps + - endpoints + - nodes + - pods + - secrets + - namespaces + verbs: + - list + - watch +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - list + - watch +- apiGroups: + - "" + resources: + - nodes + verbs: + - get +- apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update +- apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list + - watch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: ingress-nginx-admission +rules: +- apiGroups: + - admissionregistration.k8s.io + resources: + - validatingwebhookconfigurations + verbs: + - get + - update \ No newline at end of file diff --git a/modules/nginx/nginx/templates/clusterrolebinding.yaml b/modules/nginx/nginx/templates/clusterrolebinding.yaml new file mode 100644 index 0000000..bd89330 --- /dev/null +++ b/modules/nginx/nginx/templates/clusterrolebinding.yaml @@ -0,0 +1,25 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: ingress-nginx +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: ingress-nginx +subjects: +- kind: ServiceAccount + name: ingress-nginx + namespace: ingress-nginx +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: ingress-nginx-admission +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: ingress-nginx-admission +subjects: +- kind: ServiceAccount + name: ingress-nginx-admission + namespace: ingress-nginx \ No newline at end of file diff --git a/modules/nginx/nginx/templates/configmap.yaml b/modules/nginx/nginx/templates/configmap.yaml new file mode 100644 index 0000000..ae22951 --- /dev/null +++ b/modules/nginx/nginx/templates/configmap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: ingress-nginx-controller + namespace: ingress-nginx +data: + allow-snippet-annotations: "false" \ No newline at end of file diff --git a/modules/nginx/nginx/templates/deployment.yaml b/modules/nginx/nginx/templates/deployment.yaml new file mode 100644 index 0000000..8437058 --- /dev/null +++ b/modules/nginx/nginx/templates/deployment.yaml @@ -0,0 +1,118 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + name: ingress-nginx-controller + namespace: ingress-nginx +spec: + minReadySeconds: 0 + revisionHistoryLimit: 10 + selector: + matchLabels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + strategy: + rollingUpdate: + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + spec: + containers: + - args: + - /nginx-ingress-controller + - --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller + - --election-id=ingress-nginx-leader + - --controller-class=k8s.io/ingress-nginx + - --ingress-class=nginx + - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller + - --validating-webhook=:8443 + - --validating-webhook-certificate=/usr/local/certificates/cert + - --validating-webhook-key=/usr/local/certificates/key + - --enable-metrics=false + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: LD_PRELOAD + value: /usr/local/lib/libmimalloc.so + image: registry.k8s.io/ingress-nginx/controller:v1.10.1@sha256:e24f39d3eed6bcc239a56f20098878845f62baa34b9f2be2fd2c38ce9fb0f29e + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - /wait-shutdown + livenessProbe: + failureThreshold: 5 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + name: controller + ports: + - containerPort: 80 + name: http + protocol: TCP + - containerPort: 443 + name: https + protocol: TCP + - containerPort: 8443 + name: webhook + protocol: TCP + readinessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + resources: + requests: + cpu: 100m + memory: 90Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + add: + - NET_BIND_SERVICE + drop: + - ALL + readOnlyRootFilesystem: false + runAsNonRoot: true + runAsUser: 101 + seccompProfile: + type: RuntimeDefault + volumeMounts: + - mountPath: /usr/local/certificates/ + name: webhook-cert + readOnly: true + dnsPolicy: ClusterFirst + nodeSelector: + kubernetes.io/os: linux + serviceAccountName: ingress-nginx + terminationGracePeriodSeconds: 300 + volumes: + - name: webhook-cert + secret: + secretName: ingress-nginx-admission \ No newline at end of file diff --git a/modules/nginx/nginx/templates/ingressclass.yaml b/modules/nginx/nginx/templates/ingressclass.yaml new file mode 100644 index 0000000..93c8643 --- /dev/null +++ b/modules/nginx/nginx/templates/ingressclass.yaml @@ -0,0 +1,33 @@ +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: nginx +spec: + controller: k8s.io/ingress-nginx +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: ingress-nginx-admission +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: ingress-nginx-controller-admission + namespace: ingress-nginx + path: /networking/v1/ingresses + failurePolicy: Fail + matchPolicy: Equivalent + name: validate.nginx.ingress.kubernetes.io + rules: + - apiGroups: + - networking.k8s.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - ingresses + sideEffects: None \ No newline at end of file diff --git a/modules/nginx/nginx/templates/job.yaml b/modules/nginx/nginx/templates/job.yaml new file mode 100644 index 0000000..4bab23b --- /dev/null +++ b/modules/nginx/nginx/templates/job.yaml @@ -0,0 +1,91 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: ingress-nginx-admission-create + namespace: ingress-nginx +spec: + template: + metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + name: ingress-nginx-admission-create + spec: + containers: + - args: + - create + - --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc + - --namespace=$(POD_NAMESPACE) + - --secret-name=ingress-nginx-admission + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.4.1@sha256:36d05b4077fb8e3d13663702fa337f124675ba8667cbd949c03a8e8ea6fa4366 + imagePullPolicy: IfNotPresent + name: create + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 65532 + seccompProfile: + type: RuntimeDefault + nodeSelector: + kubernetes.io/os: linux + restartPolicy: OnFailure + serviceAccountName: ingress-nginx-admission +--- +apiVersion: batch/v1 +kind: Job +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + name: ingress-nginx-admission-patch + namespace: ingress-nginx +spec: + template: + metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + name: ingress-nginx-admission-patch + spec: + containers: + - args: + - patch + - --webhook-name=ingress-nginx-admission + - --namespace=$(POD_NAMESPACE) + - --patch-mutating=false + - --secret-name=ingress-nginx-admission + - --patch-failure-policy=Fail + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.4.1@sha256:36d05b4077fb8e3d13663702fa337f124675ba8667cbd949c03a8e8ea6fa4366 + imagePullPolicy: IfNotPresent + name: patch + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 65532 + seccompProfile: + type: RuntimeDefault + nodeSelector: + kubernetes.io/os: linux + restartPolicy: OnFailure + serviceAccountName: ingress-nginx-admission \ No newline at end of file diff --git a/modules/nginx/nginx/templates/namespace.yaml b/modules/nginx/nginx/templates/namespace.yaml new file mode 100644 index 0000000..bc9ce85 --- /dev/null +++ b/modules/nginx/nginx/templates/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: ingress-nginx \ No newline at end of file diff --git a/modules/nginx/nginx/templates/role.yaml b/modules/nginx/nginx/templates/role.yaml new file mode 100644 index 0000000..11503a5 --- /dev/null +++ b/modules/nginx/nginx/templates/role.yaml @@ -0,0 +1,97 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: ingress-nginx + namespace: ingress-nginx +rules: +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get +- apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - endpoints + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update +- apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list + - watch +- apiGroups: + - coordination.k8s.io + resourceNames: + - ingress-nginx-leader + resources: + - leases + verbs: + - get + - update +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: ingress-nginx-admission + namespace: ingress-nginx +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - create \ No newline at end of file diff --git a/modules/nginx/nginx/templates/rolebinding.yaml b/modules/nginx/nginx/templates/rolebinding.yaml new file mode 100644 index 0000000..5ee8114 --- /dev/null +++ b/modules/nginx/nginx/templates/rolebinding.yaml @@ -0,0 +1,27 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: ingress-nginx + namespace: ingress-nginx +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: ingress-nginx +subjects: +- kind: ServiceAccount + name: ingress-nginx + namespace: ingress-nginx +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: ingress-nginx-admission + namespace: ingress-nginx +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: ingress-nginx-admission +subjects: +- kind: ServiceAccount + name: ingress-nginx-admission + namespace: ingress-nginx \ No newline at end of file diff --git a/modules/nginx/nginx/templates/service.yaml b/modules/nginx/nginx/templates/service.yaml new file mode 100644 index 0000000..a83806c --- /dev/null +++ b/modules/nginx/nginx/templates/service.yaml @@ -0,0 +1,43 @@ +apiVersion: v1 +kind: Service +metadata: + name: ingress-nginx-controller + namespace: ingress-nginx +spec: + externalTrafficPolicy: Local + ipFamilies: + - IPv4 + ipFamilyPolicy: SingleStack + ports: + - appProtocol: http + name: http + port: 80 + protocol: TCP + targetPort: http + - appProtocol: https + name: https + port: 443 + protocol: TCP + targetPort: https + selector: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + type: LoadBalancer +--- +apiVersion: v1 +kind: Service +metadata: + name: ingress-nginx-controller-admission + namespace: ingress-nginx +spec: + ports: + - appProtocol: https + name: https-webhook + port: 443 + targetPort: webhook + selector: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + type: ClusterIP \ No newline at end of file diff --git a/modules/nginx/nginx/templates/serviceaccount.yaml b/modules/nginx/nginx/templates/serviceaccount.yaml new file mode 100644 index 0000000..36d25cd --- /dev/null +++ b/modules/nginx/nginx/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +automountServiceAccountToken: true +kind: ServiceAccount +metadata: + name: ingress-nginx + namespace: ingress-nginx +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: ingress-nginx-admission + namespace: ingress-nginx \ No newline at end of file diff --git a/variables.tf b/variables.tf index 62d8927..cd1990a 100644 --- a/variables.tf +++ b/variables.tf @@ -198,3 +198,8 @@ variable "parquet_wandb_env" { description = "Extra environment variables for W&B" default = {} } + +variable "nginx_controller" { + type = bool + default = false +} \ No newline at end of file From 84f73ba12f4d0f9446d36572a6e7aee27d854ce5 Mon Sep 17 00:00:00 2001 From: amanpruthi Date: Wed, 15 May 2024 14:50:12 +0530 Subject: [PATCH 2/3] Added nginx support --- main.tf | 4 ++-- variables.tf | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/main.tf b/main.tf index 69a0e3e..a16f2ae 100644 --- a/main.tf +++ b/main.tf @@ -153,7 +153,7 @@ module "cert_manager" { } -module "nginx" { +module "nginx_controller" { count = var.nginx_controller ? 1 : 0 source = "./modules/nginx" depends_on = [module.app_aks] @@ -223,7 +223,7 @@ module "wandb" { // the operator after testing. Trying to reduce the diff. issuer = { create = false } - annotations = var.nginx_controller ? { + annotations = var.use_nginx && var.nginx_controller ? { "kubernetes.io/ingress.class" = "nginx" "cert-manager.io/cluster-issuer" = "cert-issuer" "cert-manager.io/acme-challenge-type" = "http01" diff --git a/variables.tf b/variables.tf index cd1990a..938a5d8 100644 --- a/variables.tf +++ b/variables.tf @@ -202,4 +202,9 @@ variable "parquet_wandb_env" { variable "nginx_controller" { type = bool default = false +} + +variable "use_nginx" { + type = bool + default = false } \ No newline at end of file From 5bd9c79e89461a78fa586524b35bdeee89f86b87 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 15 May 2024 09:21:11 +0000 Subject: [PATCH 3/3] terraform-docs: automated action --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index a3b5dc2..ac2cfdc 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ resources that lack official modules. | [database](#module\_database) | ./modules/database | n/a | | [identity](#module\_identity) | ./modules/identity | n/a | | [networking](#module\_networking) | ./modules/networking | n/a | +| [nginx\_controller](#module\_nginx\_controller) | ./modules/nginx | n/a | | [redis](#module\_redis) | ./modules/redis | n/a | | [storage](#module\_storage) | ./modules/storage | n/a | | [vault](#module\_vault) | ./modules/vault | n/a | @@ -82,6 +83,7 @@ resources that lack official modules. | [license](#input\_license) | Your wandb/local license | `string` | n/a | yes | | [location](#input\_location) | n/a | `string` | n/a | yes | | [namespace](#input\_namespace) | String used for prefix resources. | `string` | n/a | yes | +| [nginx\_controller](#input\_nginx\_controller) | n/a | `bool` | `false` | no | | [oidc\_auth\_method](#input\_oidc\_auth\_method) | OIDC auth method | `string` | `"implicit"` | no | | [oidc\_client\_id](#input\_oidc\_client\_id) | The Client ID of application in your identity provider | `string` | `""` | no | | [oidc\_issuer](#input\_oidc\_issuer) | A url to your Open ID Connect identity provider, i.e. https://cognito-idp.us-east-1.amazonaws.com/us-east-1_uiIFNdacd | `string` | `""` | no | @@ -94,6 +96,7 @@ resources that lack official modules. | [subdomain](#input\_subdomain) | Subdomain for accessing the Weights & Biases UI. Default creates record at Route53 Route. | `string` | `null` | no | | [tags](#input\_tags) | Map of tags for resource | `map(string)` | `{}` | no | | [use\_internal\_queue](#input\_use\_internal\_queue) | Uses an internal redis queue instead of using azure queue. | `bool` | `false` | no | +| [use\_nginx](#input\_use\_nginx) | n/a | `bool` | `false` | no | | [wandb\_image](#input\_wandb\_image) | Docker repository of to pull the wandb image from. | `string` | `"wandb/local"` | no | | [wandb\_version](#input\_wandb\_version) | The version of Weights & Biases local to deploy. | `string` | `"latest"` | no | | [weave\_wandb\_env](#input\_weave\_wandb\_env) | Extra environment variables for W&B | `map(string)` | `{}` | no |