jsonnet

Conditionally creating a massive object, generated by several for loops?


I have some condition, if it is satisfied I've to create a massive object and merge it into the other one (at least that's how I understand jsonnet works in general):

local something_something = true;
local tst = {
  n1: {
    a: "10.172.52.0/22",
    b: "10.172.56.0/22",
  },
  n2: {
    c: "10.172.60.0/22",
  },
};

{
  aws_route: {
    public: {
      route_table_id: '${aws_route_table.public.id}',
      destination_cidr_block: '0.0.0.0/0',
      gateway_id: '${aws_internet_gateway.tst.id}',
    }
  } + (
    if something_something then {
    ['nat_%s' % zone]: {
      route_table_id: '${aws_route_table.nat_%s.id}' % zone,
      destination_cidr_block: '0.0.0.0/0',
      nat_gateway_id: '${aws_nat_gateway.ngw_%s.id}' % zone,
    }
    for zone in std.objectFields(tst.n1)
  } else {})
  + (
    if something_something then {
    ['nat_%s' % zone]: {
      route_table_id: '${aws_route_table.nat_%s.id}' % zone,
      destination_cidr_block: '0.0.0.0/0',
      nat_gateway_id: '${aws_nat_gateway.ngw_%s.id}' % zone,
    }
    for zone in std.objectFields(tst.n2)
  } else {})
}

This yields the desired result:

$ ./jsonnet tst.jsonnet
{
   "aws_route": {
      "nat_a": {
         "destination_cidr_block": "0.0.0.0/0",
         "nat_gateway_id": "${aws_nat_gateway.ngw_a.id}",
         "route_table_id": "${aws_route_table.nat_a.id}"
      },
      "nat_b": {
         "destination_cidr_block": "0.0.0.0/0",
         "nat_gateway_id": "${aws_nat_gateway.ngw_b.id}",
         "route_table_id": "${aws_route_table.nat_b.id}"
      },
      "nat_c": {
         "destination_cidr_block": "0.0.0.0/0",
         "nat_gateway_id": "${aws_nat_gateway.ngw_c.id}",
         "route_table_id": "${aws_route_table.nat_c.id}"
      },
      "public": {
         "destination_cidr_block": "0.0.0.0/0",
         "gateway_id": "${aws_internet_gateway.tst.id}",
         "route_table_id": "${aws_route_table.public.id}"
      }
   }
}

This works, but there's a lot of boilerplate, namely I have to use exactly the same if-then-else over and over again... What I'm dreaming of is to have one if-then-else and be able to set my massive object in one go, something along the lines:

{
  aws_route: {
    public: {
      route_table_id: '${aws_route_table.public.id}',
      destination_cidr_block: '0.0.0.0/0',
      gateway_id: '${aws_internet_gateway.tst.id}',
    }
  } + (
    if something_something then {
    ['nat_%s' % zone]: {
      route_table_id: '${aws_route_table.nat_%s.id}' % zone,
      destination_cidr_block: '0.0.0.0/0',
      nat_gateway_id: '${aws_nat_gateway.ngw_%s.id}' % zone,
    }
    for zone in std.objectFields(tst.n1),
    ['nat_%s' % zone]: {
      route_table_id: '${aws_route_table.nat_%s.id}' % zone,
      destination_cidr_block: '0.0.0.0/0',
      nat_gateway_id: '${aws_nat_gateway.ngw_%s.id}' % zone,
    }
    for zone in std.objectFields(tst.n2)
  } else {})
}

Sadly this is not supported:

STATIC ERROR: tst.jsonnet:26:41: expected for, if or "}" after for clause, got: ","

Does my take make sense in jsonnet in general, are there better ways to achieve the same?


Solution

  • 1) fixed if-then-else clause

    You can aggregate both comprehensions inside the if-then-else clause, the below code implements it:

    local something_something = true;
    local tst = {
      n1: {
        a: '10.172.52.0/22',
        b: '10.172.56.0/22',
      },
      n2: {
        c: '10.172.60.0/22',
      },
    };
    {
      aws_route: {
        public: {
          route_table_id: '${aws_route_table.public.id}',
          destination_cidr_block: '0.0.0.0/0',
          gateway_id: '${aws_internet_gateway.tst.id}',
        },
      } + (
        // Aggregate (merge actually) both comprehensions, i.e. this construct:
        //   if cond then {obj} + {obj} else {}
        // where obj are the below comprehensions
        if something_something then {
          ['nat_%s' % zone]: {
            route_table_id: '${aws_route_table.nat_%s.id}' % zone,
            destination_cidr_block: '0.0.0.0/0',
            nat_gateway_id: '${aws_nat_gateway.ngw_%s.id}' % zone,
          }
          for zone in std.objectFields(tst.n1)
        } + {
          ['nat_%s' % zone]: {
            route_table_id: '${aws_route_table.nat_%s.id}' % zone,
            destination_cidr_block: '0.0.0.0/0',
            nat_gateway_id: '${aws_nat_gateway.ngw_%s.id}' % zone,
          }
          for zone in std.objectFields(tst.n2)
        }
        else {}
      ),
    }
    

    2) using function and looping in nested zones object

    After the above fix, generalize to loop inside nested zones object

    local something_something = true;
    local tst = {
      n1: {
        a: '10.172.52.0/22',
        b: '10.172.56.0/22',
      },
      n2: {
        c: '10.172.60.0/22',
      },
    };
    // Loop over nested zones object to manifest nat blocks
    local aws_nat(cond, zones) = (
      if cond then {
        ['nat_%s' % zone]: {
          route_table_id: '${aws_route_table.nat_%s.id}' % zone,
          destination_cidr_block: '0.0.0.0/0',
          nat_gateway_id: '${aws_nat_gateway.ngw_%s.id}' % zone,
        }
        for entry in std.objectFields(zones)
        for zone in std.objectFields(zones[entry])
      } else {}
    );
    {
      aws_route: {
        public: {
          route_table_id: '${aws_route_table.public.id}',
          destination_cidr_block: '0.0.0.0/0',
          gateway_id: '${aws_internet_gateway.tst.id}',
        },
      } + aws_nat(something_something, tst),
    }