gruntwork-io / boilerplate

A tool for generating files and folders ("boilerplate") from a set of templates
https://www.gruntwork.io
Mozilla Public License 2.0
158 stars 12 forks source link

Add support for rendering jsonnet templates #72

Closed yorinasub17 closed 3 years ago

yorinasub17 commented 3 years ago

This PR adds support for alternative templating engines besides Go Templating, starting with jsonnet. I chose jsonnet here instead of others like dhall for a few reasons:

Note that I omitted some features for hackday:

Use case

The one big limitation of jsonnet is that it can only render json data. This can be converted to other formats, but it must use json as an intermediary, which means lots of useful things can be lost (e.g., comments). This makes it hard to use with something like our architecture catalog.

However, this is useful for templating out configurations that are not meant to be user parsed or user touched. The primary use case that motivated me to implement this is preview environments. I look at preview environments as machine generated infrastructure configuration that are not meant to be touched by the user. In these cases, it works quite well to use something like jsonnet to implement the templates, seeing as both terraform and terragrunt support json configuration. Furthermore, json configuration with terragrunt supports comments like terraform (using the special "//" key).

For example, at the bottom of the PR description is what the template for the aperture preview environment might look like. Some things to highlight:

I also can see us extending this further. For example, we can export a libsonnet file from terragrunt that is autogenerated from the config to type check the json using assert, which can allow us to give the user an error during rendering of the template if the resulting terragrunt.hcl.json is malformed.

Side note: Hopefully in the future we can even have static type checking from the editor while we write this (e.g., see https://github.com/google/jsonnet/issues/605). Or Hashicorp keeps their promise of building a json->hcl converter so we can start using this for the architecture catalog!

local moduleVersion = "v0.6.1",

function(boilerplateVars) {
  "//": "This file was autogenerated by boilerplate to manage a preview environment. DO NOT HAND EDIT!",
  terraform: {
    "source": "git::ssh://git@github.com/gruntwork-io/dogfood-infrastructure-modules.git//services/k8s-service?ref=" + moduleVersion,
  },
  include: {
    "path": "${find_in_parent_folders()}",
  },
  dependencies: {
    paths: [
      "../../../vpc",
      "../../eks-cluster",
      "../../../../_global/kms-master-key",
      "../../k8s-applications-namespace",
      "../../../data-stores/postgres",
    ],
  },
  inputs: {
    # Derive the application name from the pull request number.
    application_name: "aperture-preview-environment-pr-" + boilerplateVars.PullRequestNumber,
    image: "706132791050.dkr.ecr.us-east-1.amazonaws.com/aperture",
    # Set the image version to the built version from the PR.
    image_version: boilerplateVars.DockerImageVersion,

    desired_number_of_pods: 1,
    namespace: "applications",

    service_port: 443,

    # Configurations to ensure the preview environment hooks up to the common ALB.
    ingress_group_name: "preview-envs",
    ingress_access_logs_s3_bucket_name: "alb-preview-envs-access-logs",
    create_access_logs_s3_bucket: false,
    ingress_path: "/*",
    ingress_backend_protocol: "HTTP",
    ingress_health_check_path: "/",
    # We create a public preview to make it easily accessible.
    expose_type: "external",

    container_port: 3000,

    enable_liveness_probe: true,
    liveness_probe_port: 3000,
    liveness_probe_path: "/",
    liveness_probe_protocol: "HTTP",

    enable_readiness_probe: true,
    readiness_probe_port: 3000,
    readiness_probe_path: "/",
    readiness_probe_protocol: "HTTP",

    extra_env_vars: {
      CDN_URL: "https://cdn.gruntwork.io/aperture/",
      GRUNTWORK_NEW_SITE_BIAS: "1",
      CHARGEBEE_SITE: "gruntwork-test",
    },

    env_var_secrets: [
      {
        name: "gruntwork-website-github-access",
        keys: [
          { key: "github_config", env_var_name: "GITHUB_CONFIG" },
        ]
      }
    ],

    create_route53_entry: true,
    domain_name: "aperture-pr-{pull_request_number}.dogfood-dev.com",

    db_remote_state_path: "data-stores/postgres/terraform.tfstate",

    # Preview environments are automatically destroyed on PR close, so make sure force destroy is supported.
    force_destroy_ingress_access_logs: true,
  }
}
bwhaley commented 3 years ago

Really cool POC, Yori. Very forward thinking. I love the flexibility that you've brought and some of the capabilities that this unleashes! I don't have any comments on your implementation. Looks quite good to me overall.

Or Hashicorp keeps their promise of building a json->hcl converter so we can start using this for the architecture catalog!

This is the only thing that has my head spinning given that we are just now completing the architecture catalog using the Go templating engine. Is there enough benefits in jsonnet to merit refactoring all the templates again, when we just spent months perfecting it? But the JSON->HCL converter doesn't exist for now, so perhaps it's a moot point.

yorinasub17 commented 3 years ago

This is the only thing that has my head spinning given that we are just now completing the architecture catalog using the Go templating engine. Is there enough benefits in jsonnet to merit refactoring all the templates again, when we just spent months perfecting it? But the JSON->HCL converter doesn't exist for now, so perhaps it's a moot point.

Good question! I was thinking this would be an incremental shift. Given that this hasn't gone through the rounds of testing and production usage, I envision us being very careful when and where we introduce this if at all. I see us using this in places that require more complex templating logic (perhaps where we need to DRY things up, like with stringing through a service port across the stack from network firewalls to the container port), but rely on good old go templating for most things.

Perhaps at some point we have enough things in jsonnet, and we've tested it out enough to be confident in the technology that we start migrating implementations and thus eventually architecture catalog is solely in jsonnet, but this will take time.

yorinasub17 commented 3 years ago

But the home page says, "Generate JSON, YAML, INI, and other formats" and shows some helpers like std.manifestYamlDoc... So maybe we can do more than just output JSON?

These work because you can convert from JSON to these formats relatively easily. However, because each of these are supersets of JSON, there is data loss in the conversion (e.g., you can't render yaml comments).

The support for syntax highlighting is huge... But it seems like it doesn't really solve the problem we have. That is, if we're generating, say, pieces of HCL code, the JSON around it will be syntax highlighted, but the HCL itself—which is just as important to highlight—will be seen as an opaque string, right?

As mentioned in the drawbacks above, we can't really generate anything other than JSON. All the other formats depend on transpilers that take the rendered JSON and convert it to the respective formats. So generating HCL is actually about generating the JSON configuration version, and transpiling that output to HCL. In that regard, you don't really need HCL syntax highlighting.

But note that we can't really use this in the architecture catalog yet because there is no JSON to HCL transpiler. Hashicorp promised that this was coming in the tf12 announcement post, but it doesn't exist yet. With that said, I think it is worth experimenting jsonnet to see if there is any advantage to using it to render terragrunt configuration over go templating with our use case of preview environments.

As mentioned above in the PR description, I think json based terragrunt config is actually fine for preview environments because they are by nature not meant to be user manipulated. So I was thinking that we can take advantage of jsonnet there, and experiment to see if it improves the template writing experience.


As a side note, this is actually step 1 in a multiseries hackday project to convert the dogfood preview environments to ECS deploy runner. Here are the next steps:

yorinasub17 commented 3 years ago

Thanks for the reviews! I'm going to merge this in now so we have it available for further experimentation in the wild in the preview env use case.