winglang / wing

A programming language for the cloud ☁️ A unified programming model, combining infrastructure and runtime code into one language ⚡
https://winglang.io
Other
4.76k stars 187 forks source link

Resource queries for escape hatches #6561

Open eladb opened 1 month ago

eladb commented 1 month ago

Use Case

I'd like to be able to patch up the underlying Terraform/CloudFormation resource from Wing code.

As a random example, say I'd like to set the SnapStart.ApplyOn setting for the GET / AWS Lambda handler used in a cloud.Api handler to PublishedVersions.

This is my app:


let api = new cloud.Api();

api.get("/", inflight () => {
  log("hello, get!");
});

api.post("/", inflight () => {
  log("hello, post!");
});

CDKs have a concept called "escape hatches" (AWS CDK, CDKTF), but in order to be able to use these tools, we need to get a handle to the low level (L1) construct first, which is platform-dependent.

Proposed Solution

We propose some kind of a platform-specific query API which can be used to search for a particular L1 based various attributes (such as it's CloudFormation/Terraform resource type).

bring awscdk;

if let lambda = awscdk.resources(api, type: "AWS::Lambda::Function", pathContains: "post") {
  lambda.addPropertyOverride("SnapStart.ApplyOn", "PublishedVersions");
}

Implementation Notes

Prototype for Terraform:

bring cloud;
bring expect;
bring "cdktf" as cdktf;
bring util;

let api = new cloud.Api();

api.get("/", inflight () => {
  log("hello, world");
});

api.post("/", inflight () => {
  log("hello post!");
});

struct ResourceQuery {
  type: str?;
  pathContains: str?;
}

class tf {
  /// Returns all the child Terraform L1 resources under the given root node.
  ///
  /// Returns `nil` if the / current compilation target is not a Terraform 
  /// target (e.g. doesn't start with `tf-`).
  pub static resources(root: std.IResource, query: ResourceQuery?): Array<cdktf.TerraformResource>? {
    if !util.env("WING_TARGET").startsWith("tf-") {
      return nil; // not a terraform target
    }

    let resources = MutArray<cdktf.TerraformResource>[];
    for c in nodeof(root).findAll() {
      if cdktf.TerraformResource.isTerraformResource(c) {
        let r: cdktf.TerraformResource = unsafeCast(c);
        let var found = true;

        if let type = query?.type {
          if type != r.terraformResourceType {
            found = false;
          }
        }

        if let pathContains = query?.pathContains {
          if !r.node.path.contains(pathContains) {
            found = false;
          }
        }

        if found {
          resources.push(r);
        }
      }
    }

    return resources.copy();
  }
}

// returns all the terraform resources under `api`
if let resources = tf.resources(api) {
  expect.equal(resources.length, 17);
}

// returns all the terraform resources under `api` that are of type `aws_lambda_function`
if let resources = tf.resources(api, type: "aws_lambda_function") {
  expect.equal(resources.length, 2);
}

// returns all the terraform resources under `api` that are of type `aws_lambda_function` and have a path that contains `post`
if let resources = tf.resources(api, type: "aws_lambda_function", pathContains: "post") {
  expect.equal(resources.length, 1);
}

// always returns `nil` if we are not a TF target
if !util.env("WING_TARGET").startsWith("tf-") {
  expect.equal(tf.resources(api), nil); 
}

Component

No response

Community Notes