pulumi/changelog/pending
Will Jones cb8277d6a4
Try and avoid serializing `runtime/resource.js` (#17247)
Several Pulumi NodeJS SDKs support passing NodeJS closures directly to
resources in lieu of e.g. source files that must be managed separately
on the file system. The example below, for instance, shows how a NodeJS
closure can be used to declare an AWS Lambda that responds to objects
being created in an S3 bucket:

```typescript
const bucket = new aws.s3.Bucket("my-bucket")

bucket.onObjectCreated("listener", () => {
  console.log("An object was created in the S3 bucket!")
})
```

Under the hood, Pulumi serializes the passed closure to a piece of code
and takes care of packaging that code appropriately for use as a Lambda
function. This serialization is non-trivial: for packaged code to work,
Pulumi must be careful to capture and serialize the transitive
dependency graph of the closure passed -- the functions and values that
the closure depends on, as well as the dependencies of those
dependencies, and so on.

This change fixes a regression whereby code that transitively depends on
the Pulumi SDK no longer serializes to working code. Let's modify the
above example to use `@pulumi/pulumi` types and values:

```typescript
const bucket = new aws.s3.Bucket("my-bucket")

const task = new awsx.ecs.FargateTaskDefinition("task", {
  container: {
    name: "container",
    image: "node",
  },
})

bucket.onObjectCreated("listener", () => {
  console.log(task.taskDefinition.get())
})
```

When this code is serialized, it captures parts of the Pulumi SDK, since
`task.taskDefinition.get()` involves SDK classes and their members. The
Pulumi SDK is marked as a `deploymentOnlyModule`, meaning that it is not
intended to be captured in its entirety, since it is not expected to
work in its entirety in such a context. However, there may be select
parts of the SDK (or other `deploymentOnlyModule`s) that _do_ work and
so the serialization code tries to grab just the referenced values.

One such referenced value is the `Resource.getProvider` method, which
was recently refactored to use a helper method, `pkgFromType`, defined
in the `runtime/resource` module. This causes the serializer to generate
a `require("@pulumi/pulumi/runtime/resource")` call, which fails since
the remainder of the SDK has not been captured.

It might be possible to fix this "properly" with a better or more
considered approach to serialization in the presence of
`deploymentOnlyModule`, but in the interests of fixing the regression
this commit takes the lazier option. Since `sdk/nodejs/resource` and
`sdk/nodejs/runtime/resource` _already_ depend on each other cyclically
(this is permitted by NodeJS), we just move the `pkgFromType` helper
function (which is used by `resource` and `runtime/resource`) to
`resource` instead. Thus, when we serialize `resource`, we don't
generate the import to `runtime/resource`.

In theory, moving the function like this could create the same issue in
the opposite direction (whereby the use of `runtime/resource` generates
a bad `require` of `resource`). However, it feels even less likely that
`pulumi.runtime.*` functions should be supported inside serializable
closures, and the only indirect caller of the majority of
`runtime/resource`'s exports _is_ `resource` (the others being
`Callable`s, `settings`, and `rpc`, which again feel like they have no
business being in a serialized closure). As a result, this is hopefully
safe. If it turns out not to be, duplicating the function would be the
next step to take.

Fixes #14671
2024-09-12 16:27:53 +00:00
..
20240912--sdk-nodejs--fix-closure-serialization-when-using-the-pulumi-sdk-inside-anonymous-functions.yaml Try and avoid serializing `runtime/resource.js` (#17247) 2024-09-12 16:27:53 +00:00