I'm porting some code from typescript to c# and fi...
# dotnet
w
I'm porting some code from typescript to c# and fighting the static typing models...
Given the following typescript:
Copy code
clusterName.apply(name => {
    const transform = function(obj: any): void {
        if (obj.kind === "Deployment") {
            obj.spec.template.spec.containers[0].args.push(
                `--aws-region=${awsRegion}`
            );
            obj.spec.template.spec.containers[0].args.push(
                `--cluster-name=${name}`
            );
        }
    };

    new k8s.yaml.ConfigFile(
        "ingress-controller.yml",
        { transformations: [transform] },
        {
            provider: provider
        }
    );
});
I've got the following c#:
Copy code
clusterName.Apply(name =>
{
    new ConfigFile($"{name}-ingress",
        new ConfigFileArgs
        {
            File = "ingress-controller.yml",
            Transformations =
            {
                Transform
            }
        },
        new ComponentResourceOptions { Provider = kubeProvider });

    ImmutableDictionary<string, object> Transform(ImmutableDictionary<string, object> obj, CustomResourceOptions opts)
    {
        if ((string)obj["kind"] == "Deployment")
        {
            var spec = (ImmutableDictionary<string, object>)obj["spec"];
            var template = (ImmutableDictionary<string, object>)spec["template"];
            spec = (ImmutableDictionary<string, object>)template["spec"];
            var containers = (ImmutableArray<object>)spec["containers"];
            var container = (ImmutableDictionary<string, object>)containers[0];
            var args = (ImmutableArray<object>)container["args"];
            //...
        }
        return obj;
    }

    return name;
});
There has got to be a better way to apply transforms; the immutable types and the use of
object
are a PITA.
... and I'm not showing the
SetItem
calls needed to mutate the nested
args
value.
First thought is that the transform delegate would be more friendly if it used mutable collections, which would make more sense and save every user needing to jump through the same collection mutation hoops.
As for
object
, I appreciate there's no easy way to handle it, unless we can somehow leverage
dynamic
or somehow have access to strong types for each k8s resource.
Do any of the other language providers use immutable types so heavily, or is this just in the pulumi dotnet sdk?
t
I guess we could think of some helper API to make this nicer to use. Immutable containers are what’s used internally for serialization, so we kind of lazily exposed those without searching for a better model.
w
It definitely needs some helpers as the above is embarrassingly complex, ceremony-wise, compared to the dynamically typed languages.
@tall-librarian-49374 FWIW, I had a crack at some helpers using
dynamic
so that I can specify the transform as follows:
Copy code
ImmutableDictionary<string, object> Transform(ImmutableDictionary<string, object> obj, CustomResourceOptions opts) =>
    obj.Mutate("Deployment", deployment =>
    {
        var args = (List<object>)deployment.spec.template.spec.containers[0].args;
        args.AddRange(new[] { $"--aws-region={awsRegion}", $"--cluster-name={name}" });
    });
Using the following extensions:
Copy code
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Dynamic;
using System.Linq;

public static class ImmutableExtensions
{
    public static ImmutableDictionary<string, object> Mutate(this ImmutableDictionary<string, object> immutable, Action<dynamic> mutate)
    {
        var mutable = immutable.ToMutable();
        mutate(mutable);
        return mutable.ToImmutable();
    }

    public static ImmutableDictionary<string, object> Mutate(this ImmutableDictionary<string, object> immutable, string kind, Action<dynamic> mutate)
        => Mutate(immutable, resource => (string)resource["kind"] == kind, mutate);

    public static ImmutableDictionary<string, object> Mutate(this ImmutableDictionary<string, object> immutable, Func<ImmutableDictionary<string, object>, bool> predicate, Action<dynamic> mutate)
        => predicate(immutable) ? Mutate(immutable, mutate) : immutable;

    public static ImmutableDictionary<string, object> ToImmutable(this ExpandoObject mutable)
    {
        static object Value(object value) => value switch
        {
            ExpandoObject expando => expando.ToImmutable(),
            List<object> list => list.Select(Value).ToImmutableArray(),
            _ => value
        };

        return mutable.ToImmutableDictionary(entry => entry.Key, entry => Value(entry.Value));
    }

    public static ExpandoObject ToMutable(this ImmutableDictionary<string, object> immutable)
    {
        static object Value(object value) => value switch
        {
            ImmutableDictionary<string, object> dictionary => dictionary.ToMutable(),
            ImmutableArray<object> array => array.Select(Value).ToList(),
            _ => value
        };

        var expando = new ExpandoObject();
        var expandoDict = (IDictionary<string, object>)expando;
        foreach (var (key, value) in immutable)
        {
            expandoDict[key] = Value(value);
        }
        return expando;
    }
}