How do I provide the following <YAML key-value pai...
# dotnet
r
How do I provide the following YAML key-value pair array (representing env variables) in the Values input map of a Helm chart deployment? Yaml equivalent:
Copy code
env: []
     - name: DD_APM_ENABLED
       value: true
     - name: DD_APM_NON_LOCAL_TRAFFIC
       value: true
Pulumi definition:
Copy code
var datadogChart = new Chart("datadog-chart",
    new ChartArgs
    {
        Chart = ChartName,
        Version = args.ChartVersion,
        Namespace = args.Namespace,
        Values = new Dictionary<string, object>
        {
            // Chart values schema can be found here: <https://github.com/DataDog/helm-charts/blob/main/charts/datadog/values.yaml>
            ["datadog"] = new Dictionary<string, object>
            {
                ["apiKey"] = args.ApiKey,
                ["site"] = "<http://datadoghq.eu|datadoghq.eu>",
                ["apm"] = new Dictionary<string, object>
                {
                    ["portEnabled"] = true,
                },
                // Below is incorrect - how do I provide this??
                ["env"] = new List<Dictionary<string, object>>
                {
                    new Dictionary<string, object>
                    {
                        ["name"] = "DD_APM_ENABLED",
                        ["value"] = true
                    },
                    new Dictionary<string, object>
                    {
                        ["name"] = "DD_APM_NON_LOCAL_TRAFFIC",
                        ["value"] = true
                    }
                }
            }
        });
c
Here is what we ended up doing managing complex config items.
Missed the model
w
I use anonymous types as much as possible and dictionaries when the keys cannot be c# names:
Copy code
var kubePrometheusStackValues = GrafanaPassword.Apply(grafanaPassword =>
    new Dictionary<string, object>
    {
        ["nameOverride"] = "kps",
        ["fullnameOverride"] = "kps",
        ["kubeVersionOverride"] = K8sConfig.Version,
        ["kubeTargetVersionOverride"] = K8sConfig.Version,
        ["alertmanager"] = new
        {
            alertmanagerSpec = new
            {
                storage = new
                {
                    volumeClaimTemplate = new
                    {
                        metadata = new { name = "alertmanager-db" },
                        spec = new
                        {
                            storageClassName = "efs-sc",
                            accessModes = new[] { "ReadWriteOnce" },
                            resources = new { requests = new { storage = "10Gi" } } // mandatory but ignored by efs
                        }
                    }
                },
                nodeSelector = new { role = "monitoring" },
                tolerations = new[] { new { key = "role", @operator = "Exists" } }
            },
            ingress = new
            {
                enabled = true,
                hosts = new[] { $"alertmanager.{AwsConfig.Route53.Internal.Domain}" },
                pathType = "Prefix",
                annotations = new Dictionary<string, string>
                {
                    ["<http://alb.ingress.kubernetes.io/load-balancer-name|alb.ingress.kubernetes.io/load-balancer-name>"] = $"{EnvName}-{AwsConfig.Alb.Internal.Default.Group}",
                    ["<http://alb.ingress.kubernetes.io/scheme|alb.ingress.kubernetes.io/scheme>"] = AwsConfig.Alb.Internal.Default.Scheme,
                    ["<http://alb.ingress.kubernetes.io/group.name|alb.ingress.kubernetes.io/group.name>"] = AwsConfig.Alb.Internal.Default.Group,
                    ["<http://alb.ingress.kubernetes.io/target-type|alb.ingress.kubernetes.io/target-type>"] = AwsConfig.Alb.Internal.Default.TargetType,
                    ["<http://alb.ingress.kubernetes.io/healthcheck-path|alb.ingress.kubernetes.io/healthcheck-path>"] = "/-/healthy",
                    ["<http://external-dns.alpha.kubernetes.io/hostname|external-dns.alpha.kubernetes.io/hostname>"] = $"alertmanager.{AwsConfig.Route53.Internal.Domain}",
                    ["<http://kubernetes.io/ingress.class|kubernetes.io/ingress.class>"] = AwsConfig.Alb.Internal.Default.Class
                }
            }
        },
        ["grafana"] = new
        {
            nameOverride = "kps-grafana",
            fullnameOverride = "kps-grafana",
            adminPassword = grafanaPassword,
            nodeSelector = new { role = "monitoring" },
            tolerations = new[] { new { key = "role", @operator = "Exists" } },
            ingress = new
            {
                enabled = true,
                hosts = new[] { $"grafana.{AwsConfig.Route53.Internal.Domain}" },
                pathType = "Prefix",
                annotations = new Dictionary<string, string>
                {
                    ["<http://alb.ingress.kubernetes.io/load-balancer-name|alb.ingress.kubernetes.io/load-balancer-name>"] = $"{EnvName}-{AwsConfig.Alb.Internal.Default.Group}",
                    ["<http://alb.ingress.kubernetes.io/scheme|alb.ingress.kubernetes.io/scheme>"] = AwsConfig.Alb.Internal.Default.Scheme,
                    ["<http://alb.ingress.kubernetes.io/group.name|alb.ingress.kubernetes.io/group.name>"] = AwsConfig.Alb.Internal.Default.Group,
                    ["<http://alb.ingress.kubernetes.io/target-type|alb.ingress.kubernetes.io/target-type>"] = AwsConfig.Alb.Internal.Default.TargetType,
                    ["<http://alb.ingress.kubernetes.io/healthcheck-path|alb.ingress.kubernetes.io/healthcheck-path>"] = "/api/health",
                    ["<http://external-dns.alpha.kubernetes.io/hostname|external-dns.alpha.kubernetes.io/hostname>"] = $"grafana.{AwsConfig.Route53.Internal.Domain}",
                    ["<http://kubernetes.io/ingress.class|kubernetes.io/ingress.class>"] = AwsConfig.Alb.Internal.Default.Class
                }
            }
        },
        ["kubeControllerManager"] = new { enabled = false },
        ["kubeEtcd"] = new { enabled = false },
        //["kubelet"] = new
        //{
        //    serviceMonitor = new
        //    {
        //        metricRelabelings = new[]
        //        {
        //            new
        //            {
        //                sourceLabels = new[] { "node" },
        //                targetLabel = "instance"
        //            }
        //        }
        //    }
        //},
        ["kubeScheduler"] = new { enabled = false },
        ["kube-state-metrics"] = new
        {
            nameOverride = "kps-kube-state-metrics",
            fullnameOverride = "kps-kube-state-metrics",
            nodeSelector = new { role = "monitoring" },
            tolerations = new[] { new { key = "role", @operator = "Exists" } }
        },
        ["prometheus"] = new
        {
            prometheusSpec = new
            {
                podMonitorSelectorNilUsesHelmValues = false,
                probeSelectorNilUsesHelmValues = false,
                ruleSelectorNilUsesHelmValues = false,
                serviceMonitorSelectorNilUsesHelmValues = false,
                retention = "30d",
                storageSpec = new
                {
                    volumeClaimTemplate = new
                    {
                        metadata = new { name = "prometheus-db" },
                        spec = new
                        {
                            storageClassName = "efs-sc",
                            accessModes = new[] { "ReadWriteOnce" },
                            resources = new { requests = new { storage = "150Gi" } } // mandatory but ignored by efs
                        }
                    }
                },
                nodeSelector = new { role = "monitoring" },
                tolerations = new[] { new { key = "role", @operator = "Exists" } }
            },
            ingress = new
            {
                enabled = true,
                hosts = new[] { $"prometheus.{AwsConfig.Route53.Internal.Domain}" },
                pathType = "Prefix",
                annotations = new Dictionary<string, string>
                {
                    ["<http://alb.ingress.kubernetes.io/load-balancer-name|alb.ingress.kubernetes.io/load-balancer-name>"] = $"{EnvName}-{AwsConfig.Alb.Internal.Default.Group}",
                    ["<http://alb.ingress.kubernetes.io/scheme|alb.ingress.kubernetes.io/scheme>"] = AwsConfig.Alb.Internal.Default.Scheme,
                    ["<http://alb.ingress.kubernetes.io/group.name|alb.ingress.kubernetes.io/group.name>"] = AwsConfig.Alb.Internal.Default.Group,
                    ["<http://alb.ingress.kubernetes.io/target-type|alb.ingress.kubernetes.io/target-type>"] = AwsConfig.Alb.Internal.Default.TargetType,
                    ["<http://alb.ingress.kubernetes.io/healthcheck-path|alb.ingress.kubernetes.io/healthcheck-path>"] = "/-/healthy",
                    ["<http://external-dns.alpha.kubernetes.io/hostname|external-dns.alpha.kubernetes.io/hostname>"] = $"prometheus.{AwsConfig.Route53.Internal.Domain}",
                    ["<http://kubernetes.io/ingress.class|kubernetes.io/ingress.class>"] = AwsConfig.Alb.Internal.Default.Class
                }
            }
        },
        //["nodeExporter"] = new
        //{
        //    serviceMonitor = new
        //    {
        //        relabelings = new[]
        //        {
        //            new
        //            {
        //                sourceLabels = new[] { "__meta_kubernetes_pod_node_name" },
        //                targetLabel = "kubernetes_node"
        //            }
        //        }
        //    }
        //},
        ["prometheus-node-exporter"] = new
        {
            nameOverride = "kps-prometheus-node-exporter",
            fullnameOverride = "kps-prometheus-node-exporter",
            tolerations = new[] { new { key = "role", @operator = "Exists" } }
        },
        ["prometheusOperator"] = new
        {
            admissionWebhooks = new
            {
                certManager = new { enabled = true },
                patch = new
                {
                    nodeSelector = new { role = "monitoring" },
                    tolerations = new[] { new { key = "role", @operator = "Exists" } }
                }
            },
            nodeSelector = new { role = "monitoring" },
            tolerations = new[] { new { key = "role", @operator = "Exists" } }
        }
    }.ToDictionary()); // workaround <https://github.com/pulumi/pulumi/issues/8013>
Generally, only the top-level key-value needs to use dictionary indexing.
My helpers for the above workaround:
Copy code
public static class ObjectExtensions
{
    public static Dictionary<string, object> ToDictionary(this object obj) => obj.ToJson().DeserializeJson();

    public static string ToJson(this object obj, bool writeIndented = true) =>
        JsonSerializer.Serialize(obj, new JsonSerializerOptions { WriteIndented = writeIndented }).Replace("\r\n", "\n");
}

public static class StringExtensions
{
    public static Dictionary<string, object> DeserializeJson(this string json) => JsonSerializer.Deserialize<Dictionary<string, object>>(json)!;
}
r
Thanks - very helpful