From 885fb3af6e0b057cb893c82467d2f1c3db603bea Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Thu, 21 Mar 2024 18:49:06 +0200 Subject: [PATCH] feat(sdk): introducing `sim.Container` (#6022) The `sim.Container` resource allows running containers in the Wing Simulator. ```js let c = new sim.Container( name: "http-echo", image: "hashicorp/http-echo", containerPort: 5678, args: ["-text=bang"], ); test "send request" { http.get("http://localhost:{c.hostPort}"); } ``` There is also support for building containers from a local `Dockerfile`: ```js new sim.Container( name: "my-service", image: "./my-service", containerPort: 8080, ); ``` Migrated the `redis` and `dynamodb` to use this new resource instead of starting/stopping their containers by hand. ### Motivation Standardize how we manage containers in the Wing Simulator so we can improve the DX across the board and offer a nice UI in the Wing Console for this. ### Misc Added a couple of utilities: * `util.md5(dir)` - calculates the md5 hash on all the files in a directory (recursively). * `util.glob(pattern)` - returns the files matching a glob pattern. ## Checklist - [x] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted) - [x] Description explains motivation and solution - [x] Tests added (always) - [x] Docs updated (only required for features) - [x] Added `pr/e2e-full` label if this feature requires end-to-end testing *By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*. --- .../04-standard-library/fs/api-reference.md | 185 ++++++++++++++ .../04-standard-library/sim/api-reference.md | 235 +++++++++++++++++- .../sdk_tests/container/container.test.w | 34 +++ .../container/my-docker-image/Dockerfile | 4 + .../container/my-docker-image/index.js | 15 ++ libs/wingsdk/.projen/deps.json | 8 + libs/wingsdk/.projenrc.ts | 2 + libs/wingsdk/package.json | 3 + libs/wingsdk/src/ex/redis.ts | 2 +- libs/wingsdk/src/fs/fs.ts | 90 +++++++ libs/wingsdk/src/shared/misc.ts | 58 +---- libs/wingsdk/src/simulator/simulator.ts | 6 +- libs/wingsdk/src/target-sim/app.ts | 5 + .../src/target-sim/container.inflight.ts | 147 +++++++++++ libs/wingsdk/src/target-sim/container.md | 60 +++++ libs/wingsdk/src/target-sim/container.ts | 146 +++++++++++ .../src/target-sim/dynamodb-table.inflight.ts | 28 +-- libs/wingsdk/src/target-sim/dynamodb-table.ts | 18 ++ libs/wingsdk/src/target-sim/index.ts | 2 +- libs/wingsdk/src/target-sim/redis.inflight.ts | 40 +-- libs/wingsdk/src/target-sim/redis.ts | 24 +- .../src/target-sim/schema-resources.ts | 31 ++- libs/wingsdk/src/target-sim/state.ts | 4 + .../__snapshots__/connections.test.ts.snap | 4 + libs/wingsdk/test/shared/misc.test.ts | 23 -- .../target-sim/__snapshots__/api.test.ts.snap | 64 +++++ .../__snapshots__/bucket.test.ts.snap | 16 ++ .../__snapshots__/counter.test.ts.snap | 28 +++ .../__snapshots__/dynamodb-table.test.ts.snap | 133 ++++++++++ .../__snapshots__/file-counter.test.ts.snap | 4 + .../__snapshots__/function.test.ts.snap | 24 ++ .../immutable-capture.test.ts.snap | 56 +++++ .../__snapshots__/on-deploy.test.ts.snap | 4 + .../__snapshots__/queue.test.ts.snap | 20 ++ .../__snapshots__/redis.test.ts.snap | 31 ++- .../__snapshots__/schedule.test.ts.snap | 16 ++ .../__snapshots__/secret.test.ts.snap | 4 + .../__snapshots__/service.test.ts.snap | 4 + .../__snapshots__/table.test.ts.snap | 28 +++ .../__snapshots__/test.test.ts.snap | 4 + .../__snapshots__/topic.test.ts.snap | 4 + .../wingsdk/test/target-sim/container.test.ts | 139 +++++++++++ .../test/target-sim/dynamodb-table.test.ts | 4 + .../target-sim/my-docker-image/Dockerfile | 4 + .../test/target-sim/my-docker-image/index.js | 15 ++ libs/wingsdk/test/target-sim/redis.test.ts | 4 +- libs/wingsdk/test/target-sim/util.ts | 6 +- pnpm-lock.yaml | 6 + tools/generate-workspace/src/cli.ts | 2 + .../container.test.w_compile_tf-aws.md | 21 ++ .../container/container.test.w_test_sim.md | 16 ++ wing.code-workspace | 8 + 52 files changed, 1704 insertions(+), 135 deletions(-) create mode 100644 examples/tests/sdk_tests/container/container.test.w create mode 100644 examples/tests/sdk_tests/container/my-docker-image/Dockerfile create mode 100755 examples/tests/sdk_tests/container/my-docker-image/index.js create mode 100644 libs/wingsdk/src/target-sim/container.inflight.ts create mode 100644 libs/wingsdk/src/target-sim/container.md create mode 100644 libs/wingsdk/src/target-sim/container.ts delete mode 100644 libs/wingsdk/test/shared/misc.test.ts create mode 100644 libs/wingsdk/test/target-sim/container.test.ts create mode 100644 libs/wingsdk/test/target-sim/my-docker-image/Dockerfile create mode 100755 libs/wingsdk/test/target-sim/my-docker-image/index.js create mode 100644 tools/hangar/__snapshots__/test_corpus/sdk_tests/container/container.test.w_compile_tf-aws.md create mode 100644 tools/hangar/__snapshots__/test_corpus/sdk_tests/container/container.test.w_test_sim.md diff --git a/docs/docs/04-standard-library/fs/api-reference.md b/docs/docs/04-standard-library/fs/api-reference.md index c544713878f..d1f9e903155 100644 --- a/docs/docs/04-standard-library/fs/api-reference.md +++ b/docs/docs/04-standard-library/fs/api-reference.md @@ -44,8 +44,10 @@ new fs.Util(); | dirname | Retrieve the name of the directory from a given file path. | | exists | Check if the path exists. | | extension | Extracts the extension (without the leading dot) from the path, if possible. | +| glob | Match files using the patterns the shell uses. | | isDir | Checks if the given path is a directory and exists. | | join | Join all arguments together and normalize the resulting path. | +| md5 | Calculate an MD5 content hash of all the files that match a glob pattern. | | metadata | Gets the stats of the given path. | | mkdir | Create a directory. | | mkdtemp | Create a temporary directory. | @@ -202,6 +204,34 @@ The path to get extension for. --- +##### `glob` + +```wing +bring fs; + +fs.glob(pattern: str, options?: GlobOptions); +``` + +Match files using the patterns the shell uses. + +Built with the great `glob` package, based on https://www.npmjs.com/package/glob + +###### `pattern`Required + +- *Type:* str + +The pattern to match. + +--- + +###### `options`Optional + +- *Type:* GlobOptions + +Glob options. + +--- + ##### `isDir` ```wing @@ -238,6 +268,32 @@ The array of path need to join. --- +##### `md5` + +```wing +bring fs; + +fs.md5(dir: str, globPattern?: str); +``` + +Calculate an MD5 content hash of all the files that match a glob pattern. + +###### `dir`Required + +- *Type:* str + +The root directory. + +--- + +###### `globPattern`Optional + +- *Type:* str + +The glob pattern to match (defaults to all files and subdirectories). + +--- + ##### `metadata` ```wing @@ -693,6 +749,135 @@ The YANL objects to be dumped. ## Structs +### GlobOptions + +Options for `glob`, based on https://www.npmjs.com/package/glob. + +#### Initializer + +```wing +bring fs; + +let GlobOptions = fs.GlobOptions{ ... }; +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| absolute | bool | Set to `true` to always receive absolute paths for matched files. | +| cwd | str | The current working directory in which to search. | +| dot | bool | Include `.dot` files in normal matches and globstar matches. Note that an explicit dot in a portion of the pattern will always match dot files. | +| follow | bool | Follow symlinked directories when expanding `**` patterns. | +| ignore | MutArray<str> | An array of glob patterns to exclude from matches. | +| maxDepth | num | Specify a number to limit the depth of the directory traversal to this many levels below the cwd. | +| nodir | bool | Do not match directories, only files. | + +--- + +##### `absolute`Optional + +```wing +absolute: bool; +``` + +- *Type:* bool +- *Default:* false + +Set to `true` to always receive absolute paths for matched files. + +Set to `false` to always +receive relative paths for matched files. + +--- + +##### `cwd`Optional + +```wing +cwd: str; +``` + +- *Type:* str +- *Default:* process.cwd() + +The current working directory in which to search. + +--- + +##### `dot`Optional + +```wing +dot: bool; +``` + +- *Type:* bool +- *Default:* false + +Include `.dot` files in normal matches and globstar matches. Note that an explicit dot in a portion of the pattern will always match dot files. + +--- + +##### `follow`Optional + +```wing +follow: bool; +``` + +- *Type:* bool +- *Default:* false + +Follow symlinked directories when expanding `**` patterns. + +This can result in a lot of +duplicate references in the presence of cyclic links, and make performance quite bad. + +--- + +##### `ignore`Optional + +```wing +ignore: MutArray; +``` + +- *Type:* MutArray<str> +- *Default:* [] + +An array of glob patterns to exclude from matches. + +To ignore all children within a directory, +as well as the entry itself, append '/**' to the ignore pattern. + +--- + +##### `maxDepth`Optional + +```wing +maxDepth: num; +``` + +- *Type:* num +- *Default:* no limit + +Specify a number to limit the depth of the directory traversal to this many levels below the cwd. + +--- + +##### `nodir`Optional + +```wing +nodir: bool; +``` + +- *Type:* bool +- *Default:* false + +Do not match directories, only files. + +(Note: to match only directories, put a `/` at the end of +the pattern.) + +--- + ### Metadata Metadata of a file system object. diff --git a/docs/docs/04-standard-library/sim/api-reference.md b/docs/docs/04-standard-library/sim/api-reference.md index a2f834e3726..0ecb35cda5c 100644 --- a/docs/docs/04-standard-library/sim/api-reference.md +++ b/docs/docs/04-standard-library/sim/api-reference.md @@ -12,6 +12,120 @@ sidebar_position: 100 ## Resources +### Container + +- *Implements:* ISimulatorResource + +Represents a container running in the Wing Simulator. + +#### Initializers + +```wing +bring sim; + +new sim.Container(props: ContainerProps); +``` + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| props | ContainerProps | *No description.* | + +--- + +##### `props`Required + +- *Type:* ContainerProps + +--- + +#### Methods + +##### Preflight Methods + +| **Name** | **Description** | +| --- | --- | +| toSimulator | Convert this resource to a resource schema for the simulator. | + +--- + +##### `toSimulator` + +```wing +toSimulator(): BaseResourceSchema +``` + +Convert this resource to a resource schema for the simulator. + +#### Static Functions + +| **Name** | **Description** | +| --- | --- | +| onLiftType | A hook called by the Wing compiler once for each inflight host that needs to use this type inflight. | + +--- + +##### `onLiftType` + +```wing +bring sim; + +sim.Container.onLiftType(host: IInflightHost, ops: MutArray); +``` + +A hook called by the Wing compiler once for each inflight host that needs to use this type inflight. + +The list of requested inflight methods +needed by the inflight host are given by `ops`. + +This method is commonly used for adding permissions, environment variables, or +other capabilities to the inflight host. + +###### `host`Required + +- *Type:* IInflightHost + +--- + +###### `ops`Required + +- *Type:* MutArray<str> + +--- + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| node | constructs.Node | The tree node. | +| hostPort | str | A token that resolves to the host port of this container. | + +--- + +##### `node`Required + +```wing +node: Node; +``` + +- *Type:* constructs.Node + +The tree node. + +--- + +##### `hostPort`Optional + +```wing +hostPort: str; +``` + +- *Type:* str + +A token that resolves to the host port of this container. + +--- + + ### State - *Implements:* ISimulatorResource @@ -202,6 +316,125 @@ The tree node. +## Structs + +### ContainerProps + +Initialization properties for `sim.Container`. + +#### Initializer + +```wing +bring sim; + +let ContainerProps = sim.ContainerProps{ ... }; +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| image | str | A name of a public Docker image to pull and run or a path to a local directory with a `Dockerfile`. | +| name | str | A name for the container. | +| args | MutArray<str> | Container arguments. | +| containerPort | num | Internal container port to expose. | +| env | MutMap<str> | Environment variables to set in the container. | +| sourceHash | str | An explicit source hash that represents the container source. | +| sourcePattern | str | A glob of local files to consider as input sources for the container, relative to the build context directory. | + +--- + +##### `image`Required + +```wing +image: str; +``` + +- *Type:* str + +A name of a public Docker image to pull and run or a path to a local directory with a `Dockerfile`. + +--- + +##### `name`Required + +```wing +name: str; +``` + +- *Type:* str + +A name for the container. + +--- + +##### `args`Optional + +```wing +args: MutArray; +``` + +- *Type:* MutArray<str> +- *Default:* [] + +Container arguments. + +--- + +##### `containerPort`Optional + +```wing +containerPort: num; +``` + +- *Type:* num +- *Default:* no port exposed + +Internal container port to expose. + +--- + +##### `env`Optional + +```wing +env: MutMap; +``` + +- *Type:* MutMap<str> +- *Default:* {} + +Environment variables to set in the container. + +--- + +##### `sourceHash`Optional + +```wing +sourceHash: str; +``` + +- *Type:* str +- *Default:* calculated based on the source files + +An explicit source hash that represents the container source. + +if not set, and `sourcePattern` +is set, the hash will be calculated based on the content of the source files. + +--- + +##### `sourcePattern`Optional + +```wing +sourcePattern: str; +``` + +- *Type:* str +- *Default:* all files + +A glob of local files to consider as input sources for the container, relative to the build context directory. + +--- ## Protocols @@ -209,7 +442,7 @@ The tree node. - *Extends:* IResource -- *Implemented By:* State, ISimulatorResource +- *Implemented By:* Container, State, ISimulatorResource Interfaces shared by all polycon implementations (preflight classes) targeting the simulator. diff --git a/examples/tests/sdk_tests/container/container.test.w b/examples/tests/sdk_tests/container/container.test.w new file mode 100644 index 00000000000..ac2d1f87d24 --- /dev/null +++ b/examples/tests/sdk_tests/container/container.test.w @@ -0,0 +1,34 @@ +bring sim; +bring http; +bring util; +bring expect; + +// only relevant in simulator +if util.env("WING_TARGET") == "sim" { + + let echo = new sim.Container( + name: "http-echo", + image: "hashicorp/http-echo", + containerPort: 5678, + args: ["-text=bang"], + ) as "http-echo"; + + let app = new sim.Container( + name: "my-app", + image: "./my-docker-image", + containerPort: 3000, + ) as "my-app"; + + test "get echo" { + let response = http.get("http://localhost:{echo.hostPort!}"); + log(response.body); + expect.equal("bang\n", response.body); + } + + test "get app" { + let response = http.get("http://localhost:{app.hostPort!}"); + log(response.body); + expect.equal("Hello, Wingnuts!", response.body); + } + +} diff --git a/examples/tests/sdk_tests/container/my-docker-image/Dockerfile b/examples/tests/sdk_tests/container/my-docker-image/Dockerfile new file mode 100644 index 00000000000..686c128323e --- /dev/null +++ b/examples/tests/sdk_tests/container/my-docker-image/Dockerfile @@ -0,0 +1,4 @@ +FROM node:20.8.0-alpine +EXPOSE 3000 +ADD index.js /app/index.js +ENTRYPOINT [ "/app/index.js" ] \ No newline at end of file diff --git a/examples/tests/sdk_tests/container/my-docker-image/index.js b/examples/tests/sdk_tests/container/my-docker-image/index.js new file mode 100755 index 00000000000..55e56a40950 --- /dev/null +++ b/examples/tests/sdk_tests/container/my-docker-image/index.js @@ -0,0 +1,15 @@ +#!/usr/bin/env node +const http = require('http'); + +process.on('SIGINT', () => { + console.info("Interrupted") + process.exit(0) +}); + +const server = http.createServer((req, res) => { + console.log(`request received: ${req.method} ${req.url}`); + res.end('Hello, Wingnuts!'); +}); + +console.log('listening on port 3000'); +server.listen(3000); \ No newline at end of file diff --git a/libs/wingsdk/.projen/deps.json b/libs/wingsdk/.projen/deps.json index 8776ce3074e..a3d550c41d8 100644 --- a/libs/wingsdk/.projen/deps.json +++ b/libs/wingsdk/.projen/deps.json @@ -17,6 +17,10 @@ "name": "@types/fs-extra", "type": "build" }, + { + "name": "@types/glob", + "type": "build" + }, { "name": "@types/mime-types", "type": "build" @@ -275,6 +279,10 @@ "name": "express", "type": "bundled" }, + { + "name": "glob", + "type": "bundled" + }, { "name": "google-auth-library", "type": "bundled" diff --git a/libs/wingsdk/.projenrc.ts b/libs/wingsdk/.projenrc.ts index c88de57f158..f2abc59a017 100644 --- a/libs/wingsdk/.projenrc.ts +++ b/libs/wingsdk/.projenrc.ts @@ -90,6 +90,7 @@ const project = new cdk.JsiiProject({ "ulid", // tunnels "@winglang/wingtunnels@workspace:^", + "glob", ], devDeps: [ `@cdktf/provider-aws@^19`, // only for testing Wing plugins @@ -100,6 +101,7 @@ const project = new cdk.JsiiProject({ "@types/mime-types", "mock-gcs@^1.2.0", "@types/express", + "@types/glob", "aws-sdk-client-mock@3.0.0", "aws-sdk-client-mock-jest@3.0.0", `cdktf-cli@${CDKTF_VERSION}`, diff --git a/libs/wingsdk/package.json b/libs/wingsdk/package.json index 8cdc839af21..9e6cc45117e 100644 --- a/libs/wingsdk/package.json +++ b/libs/wingsdk/package.json @@ -39,6 +39,7 @@ "@types/aws-lambda": "^8.10.109", "@types/express": "^4.17.17", "@types/fs-extra": "^11.0.1", + "@types/glob": "^7.2.0", "@types/mime-types": "^2.1.2", "@types/node": "^20.11.0", "@types/uuid": "^9.0.3", @@ -102,6 +103,7 @@ "constructs": "^10.3", "cron-parser": "^4.9.0", "express": "^4.18.2", + "glob": "^8.1.0", "google-auth-library": "^8.9.0", "ioredis": "^5.3.2", "jsonschema": "^1.4.1", @@ -142,6 +144,7 @@ "cdktf", "cron-parser", "express", + "glob", "google-auth-library", "ioredis", "jsonschema", diff --git a/libs/wingsdk/src/ex/redis.ts b/libs/wingsdk/src/ex/redis.ts index 2cdf78c3444..950ac086259 100644 --- a/libs/wingsdk/src/ex/redis.ts +++ b/libs/wingsdk/src/ex/redis.ts @@ -4,7 +4,7 @@ import { INFLIGHT_SYMBOL } from "../core/types"; import { Node, Resource } from "../std"; /** - * Global identifier for `Bucket`. + * Global identifier for `Redis`. */ export const REDIS_FQN = fqnForType("ex.Redis"); diff --git a/libs/wingsdk/src/fs/fs.ts b/libs/wingsdk/src/fs/fs.ts index 004e613e280..be0f46c4edc 100644 --- a/libs/wingsdk/src/fs/fs.ts +++ b/libs/wingsdk/src/fs/fs.ts @@ -1,6 +1,8 @@ +import * as crypto from "crypto"; import * as fs from "fs"; import * as os from "os"; import * as nodePath from "path"; +import * as glob from "glob"; import * as yaml from "yaml"; import { InflightClient } from "../core"; import { normalPath } from "../shared/misc"; @@ -124,6 +126,63 @@ export interface Metadata { readonly created: Datetime; } +/** + * Options for `glob`, based on https://www.npmjs.com/package/glob + */ +export interface GlobOptions { + /** + * The current working directory in which to search. + * + * @default process.cwd() + */ + readonly cwd?: string; + + /** + * Include `.dot` files in normal matches and globstar matches. Note that an explicit dot in a + * portion of the pattern will always match dot files. + * + * @default false + */ + readonly dot?: boolean; + + /** + * Do not match directories, only files. (Note: to match only directories, put a `/` at the end of + * the pattern.) + * + * @default false + */ + readonly nodir?: boolean; + + /** + * An array of glob patterns to exclude from matches. To ignore all children within a directory, + * as well as the entry itself, append '/**' to the ignore pattern. + * @default [] + */ + readonly ignore?: string[]; + + /** + * Follow symlinked directories when expanding `**` patterns. This can result in a lot of + * duplicate references in the presence of cyclic links, and make performance quite bad. + * @default false + */ + readonly follow?: boolean; + + /** + * Set to `true` to always receive absolute paths for matched files. Set to `false` to always + * receive relative paths for matched files. + * @default false + */ + readonly absolute?: boolean; + + /** + * Specify a number to limit the depth of the directory traversal to this many levels below the + * cwd. + * + * @default - no limit + */ + readonly maxDepth?: number; +} + /** * The fs class is used for interacting with the file system. * All file paths must be POSIX file paths (/ instead of \), @@ -461,6 +520,37 @@ export class Util { fs.symlinkSync(target, path, type); } + /** + * Match files using the patterns the shell uses. + * + * Built with the great `glob` package, based on https://www.npmjs.com/package/glob + + * @param pattern The pattern to match. + * @param options Glob options. + * @returns List of matching files. + */ + public static glob(pattern: string, options: GlobOptions = {}): string[] { + return glob.sync(pattern, options); + } + + /** + * Calculate an MD5 content hash of all the files that match a glob pattern. + * + * @param dir The root directory. + * @param globPattern The glob pattern to match (defaults to all files and subdirectories). + * @returns An md5 hash of the file contents. + */ + public static md5(dir: string, globPattern: string = "**/*") { + const hash = crypto.createHash("md5"); + const files = this.glob(globPattern, { nodir: true, cwd: dir }); + for (const f of files) { + const data = fs.readFileSync(this.join(dir, f)); + hash.update(data); + } + + return hash.digest("hex"); + } + /** * @internal */ diff --git a/libs/wingsdk/src/shared/misc.ts b/libs/wingsdk/src/shared/misc.ts index b02145d2e36..17dff4650e0 100644 --- a/libs/wingsdk/src/shared/misc.ts +++ b/libs/wingsdk/src/shared/misc.ts @@ -1,7 +1,6 @@ -import { execFile } from "child_process"; +import { ExecFileOptions, execFile } from "child_process"; import { readFileSync } from "fs"; import { promisify } from "util"; -import { v4 as uuidv4 } from "uuid"; const execFilePromise = promisify(execFile); @@ -27,53 +26,16 @@ export function normalPath(path: string) { /** * Just a helpful wrapper around `execFile` that returns a promise. */ -export async function runCommand(cmd: string, args: string[]): Promise { - const { stdout } = await execFilePromise(cmd, args); +export async function runCommand( + cmd: string, + args: string[], + options?: ExecFileOptions +): Promise { + const { stdout } = await execFilePromise(cmd, args, options); return stdout; } -export function generateDockerContainerName(prefix: string): string { - // Docker checks against [a-zA-Z0-9][a-zA-Z0-9_.-] - const name = prefix.replace(/[^a-zA-Z0-9_.-]/g, "-"); - return `${name}-${uuidv4()}`; -} - -export interface runDockerImageProps { - imageName: string; - containerName: string; - containerPort: string; -} - -/** - * Runs a given docker image and returns the host port for the new container. - */ -export async function runDockerImage({ - imageName, - containerName, - containerPort, -}: runDockerImageProps): Promise<{ hostPort: string }> { - // Pull docker image - try { - await runCommand("docker", ["inspect", imageName]); - } catch { - await runCommand("docker", ["pull", imageName]); - } - // Run the container and allow docker to assign a host port dynamically - await runCommand("docker", [ - "run", - "--detach", - "--name", - containerName, - "-p", - containerPort, - imageName, - ]); - - // Inspect the container to get the host port - const out = await runCommand("docker", ["inspect", containerName]); - const hostPort = - JSON.parse(out)[0].NetworkSettings.Ports[`${containerPort}/tcp`][0] - .HostPort; - - return { hostPort }; +export function isPath(s: string) { + s = normalPath(s); + return s.startsWith("./") || s.startsWith("/"); } diff --git a/libs/wingsdk/src/simulator/simulator.ts b/libs/wingsdk/src/simulator/simulator.ts index 8a43e74d65a..3f536ae8ad0 100644 --- a/libs/wingsdk/src/simulator/simulator.ts +++ b/libs/wingsdk/src/simulator/simulator.ts @@ -522,7 +522,11 @@ export class Simulator { } private typeInfo(fqn: string): TypeSchema { - return this._model.schema.types[fqn]; + const schema = this._model.schema.types[fqn]; + if (!schema) { + throw new Error(`Unknown simulator type ${fqn}`); + } + return schema; } /** diff --git a/libs/wingsdk/src/target-sim/app.ts b/libs/wingsdk/src/target-sim/app.ts index 5d7b5de4ac4..b70fe19413f 100644 --- a/libs/wingsdk/src/target-sim/app.ts +++ b/libs/wingsdk/src/target-sim/app.ts @@ -2,6 +2,7 @@ import * as fs from "fs"; import * as path from "path"; import { Api } from "./api"; import { Bucket } from "./bucket"; +import { SIM_CONTAINER_FQN } from "./container"; import { Counter } from "./counter"; import { Domain } from "./domain"; import { DynamodbTable } from "./dynamodb-table"; @@ -67,6 +68,7 @@ const SIMULATOR_CLASS_DATA = { [SECRET_FQN]: "Secret", [SERVICE_FQN]: "Service", [STATE_FQN]: "State", + [SIM_CONTAINER_FQN]: "Container", [TABLE_FQN]: "Table", [TEST_RUNNER_FQN]: "TestRunner", [TOPIC_FQN]: "Topic", @@ -154,6 +156,9 @@ export class App extends core.App { case WEBSITE_FQN: return require.resolve("./website.inflight"); + + case SIM_CONTAINER_FQN: + return require.resolve("./container.inflight"); } return undefined; diff --git a/libs/wingsdk/src/target-sim/container.inflight.ts b/libs/wingsdk/src/target-sim/container.inflight.ts new file mode 100644 index 00000000000..1fe1afffca5 --- /dev/null +++ b/libs/wingsdk/src/target-sim/container.inflight.ts @@ -0,0 +1,147 @@ +import { IContainerClient, HOST_PORT_ATTR } from "./container"; +import { ContainerSchema, ContainerAttributes } from "./schema-resources"; +import { isPath, runCommand } from "../shared/misc"; +import { + ISimulatorContext, + ISimulatorResourceInstance, +} from "../simulator/simulator"; +import { Duration, TraceType } from "../std"; +import { Util } from "../util"; + +export class Container implements IContainerClient, ISimulatorResourceInstance { + private readonly imageTag: string; + private readonly containerName: string; + + public constructor( + private readonly props: ContainerSchema["props"], + private readonly context: ISimulatorContext + ) { + this.imageTag = props.imageTag; + + this.containerName = `wing-container-${Util.ulid()}`; + } + + private log(message: string) { + this.context.addTrace({ + data: { message }, + sourcePath: this.context.resourcePath, + sourceType: "container", + timestamp: new Date().toISOString(), + type: TraceType.LOG, + }); + } + + public async init(): Promise { + // if this a reference to a local directory, build the image from a docker file + if (isPath(this.props.image)) { + // check if the image is already built + try { + await runCommand("docker", ["inspect", this.imageTag]); + this.log(`image ${this.imageTag} already exists`); + } catch { + this.log( + `building locally from ${this.props.image} and tagging ${this.imageTag}...` + ); + await runCommand( + "docker", + ["build", "-t", this.imageTag, this.props.image], + { cwd: this.props.cwd } + ); + } + } else { + try { + await runCommand("docker", ["inspect", this.imageTag]); + this.log(`image ${this.imageTag} already exists`); + } catch { + this.log(`pulling ${this.imageTag}`); + await runCommand("docker", ["pull", this.imageTag]); + } + } + + // start the new container + const dockerRun: string[] = []; + dockerRun.push("run"); + dockerRun.push("--detach"); + dockerRun.push("--rm"); + + dockerRun.push("--name", this.containerName); + + if (this.props.containerPort) { + dockerRun.push("-p"); + dockerRun.push(this.props.containerPort.toString()); + } + + if (this.props.env && Object.keys(this.props.env).length > 0) { + dockerRun.push("-e"); + for (const k of Object.keys(this.props.env)) { + dockerRun.push(`${k}=${this.props.env[k]}`); + } + } + + dockerRun.push(this.imageTag); + + for (const a of this.props.args ?? []) { + dockerRun.push(a); + } + + this.log(`starting container from image ${this.imageTag}`); + this.log(`docker ${dockerRun.join(" ")}`); + + await runCommand("docker", dockerRun); + + this.log(`containerName=${this.containerName}`); + + // wait until the container is running + await waitUntil(async () => { + const container = JSON.parse( + await runCommand("docker", ["inspect", this.containerName]) + ); + return container?.[0]?.State?.Running; + }); + + if (!this.props.containerPort) { + return {}; + } + + const container = JSON.parse( + await runCommand("docker", ["inspect", this.containerName]) + ); + + const hostPort = + container?.[0]?.NetworkSettings?.Ports?.[ + `${this.props.containerPort}/tcp` + ]?.[0]?.HostPort; + + if (!hostPort) { + throw new Error( + `Container does not listen to port ${this.props.containerPort}` + ); + } + + return { + [HOST_PORT_ATTR]: hostPort, + }; + } + + public async cleanup(): Promise { + this.log(`Stopping container ${this.containerName}`); + await runCommand("docker", ["rm", "-f", this.containerName]); + } + + public async save(): Promise {} +} + +async function waitUntil(predicate: () => Promise) { + const timeout = Duration.fromMinutes(1); + const interval = Duration.fromSeconds(0.1); + let elapsed = 0; + while (elapsed < timeout.seconds) { + if (await predicate()) { + return true; + } + elapsed += interval.seconds; + await Util.sleep(interval); + } + + throw new Error("Timeout elapsed"); +} diff --git a/libs/wingsdk/src/target-sim/container.md b/libs/wingsdk/src/target-sim/container.md new file mode 100644 index 00000000000..6a150639ce5 --- /dev/null +++ b/libs/wingsdk/src/target-sim/container.md @@ -0,0 +1,60 @@ +--- +title: Container +id: container +description: Runs a container in the Wing Simulator +keywords: + [ + Wing reference, + Wing language, + language, + Wing standard library, + Wing programming language, + docker, + container, + docker compose, + simulator + ] +--- + +The `sim.Container` resource allows running containers in the Wing Simulator: + +```js +bring sim; +bring http; + +let c = new sim.Container( + name: "http-echo", + image: "hashicorp/http-echo", + containerPort: 5678, + args: ["-text=bang"], +); + +test "send request" { + http.get("http://localhost:{c.hostPort}"); +} +``` + +There is also support for building containers from a local directory with a `Dockerfile`: + +```js +new sim.Container( + name: "my-service", + image: "./my-service", + containerPort: 8080, +); +``` + +## API + +* `name` - a name for the container. +* `image` - a name of a public Docker image to pull and run or a path to a local directory with a + `Dockerfile`. +* `containerPort` - a TCP port to expose from the container (optional). +* `env` - environment variables to set in the container. +* `args` - container entrypoint arguments +* `sourcePattern` - a glob pattern to use to match the files for calculating the source hash when + determining if a rebuild is needed. By default this is all the files in the docker build context + directory (and below). +* `sourceHash` - An explicit source hash that represents the container source. if not set, and + `sourcePattern` is set, the hash will be calculated based on the content of the source files. + diff --git a/libs/wingsdk/src/target-sim/container.ts b/libs/wingsdk/src/target-sim/container.ts new file mode 100644 index 00000000000..947553686de --- /dev/null +++ b/libs/wingsdk/src/target-sim/container.ts @@ -0,0 +1,146 @@ +import { Construct } from "constructs"; +import { ISimulatorResource } from "./resource"; +import { ContainerSchema } from "./schema-resources"; +import { simulatorAttrToken } from "./tokens"; +import { bindSimulatorResource, makeSimulatorJsClient } from "./util"; +import { fqnForType } from "../constants"; +import { App } from "../core"; +import { INFLIGHT_SYMBOL } from "../core/types"; +import { Util as fs } from "../fs"; +import { isPath } from "../shared/misc"; +import { BaseResourceSchema } from "../simulator/simulator"; +import { IInflightHost, Resource } from "../std"; + +export const SIM_CONTAINER_FQN = fqnForType("sim.Container"); +export const HOST_PORT_ATTR = "host_port"; + +/** + * Initialization properties for `sim.Container`. + */ +export interface ContainerProps { + /** + * A name for the container. + */ + readonly name: string; + + /** + * A name of a public Docker image to pull and run or a path to a local directory with a `Dockerfile`. + */ + readonly image: string; + + /** + * Internal container port to expose. + * @default - no port exposed + */ + readonly containerPort?: number; + + /** + * Environment variables to set in the container. + * @default {} + */ + readonly env?: Record; + + /** + * Container arguments + * @default [] + */ + readonly args?: string[]; + + /** + * A glob of local files to consider as input sources for the container, relative to the build + * context directory. + * + * @default - all files + */ + readonly sourcePattern?: string; + + /** + * An explicit source hash that represents the container source. if not set, and `sourcePattern` + * is set, the hash will be calculated based on the content of the source files. + * @default - calculated based on the source files + */ + readonly sourceHash?: string; +} + +/** + * Represents a container running in the Wing Simulator. + * + * @inflight `@winglang/sdk.sim.IContainerClient` + */ +export class Container extends Resource implements ISimulatorResource { + /** @internal */ + public [INFLIGHT_SYMBOL]?: IContainerClient; + + private readonly imageTag: string; + + /** + * A token that resolves to the host port of this container. + */ + public readonly hostPort?: string; + + constructor( + scope: Construct, + id: string, + private readonly props: ContainerProps + ) { + super(scope, id); + + // determine image tag - if the image is a path, we use a source hash (either explicitly + // provided or calculated based on the source files). if the image is a name, we use the name + if (isPath(props.image)) { + const hash = props.sourceHash ?? fs.md5(props.image, props.sourcePattern); + this.imageTag = `${props.name}:${hash}`; + } else { + this.imageTag = props.image; + } + + if (props.containerPort) { + this.hostPort = simulatorAttrToken(this, HOST_PORT_ATTR); + } + } + + public toSimulator(): BaseResourceSchema { + const schema: ContainerSchema = { + type: SIM_CONTAINER_FQN, + path: this.node.path, + addr: this.node.addr, + props: { + image: this.props.image, + imageTag: this.imageTag, + containerPort: this.props.containerPort, + env: this.props.env, + args: this.props.args, + cwd: App.of(this).entrypointDir, + }, + attrs: {} as any, + }; + + return schema; + } + + public onLift(host: IInflightHost, ops: string[]): void { + bindSimulatorResource(__filename, this, host); + super.onLift(host, ops); + } + + /** @internal */ + public _supportedOps(): string[] { + return []; + } + + /** @internal */ + public _toInflight(): string { + return makeSimulatorJsClient(__filename, this); + } +} + +/** + * List of inflight operations available for `sim.Container`. + * @internal + */ +export enum ContainerInflightMethods {} + +/** + * Inflight interface for `Redis`. + */ +export interface IContainerClient {} diff --git a/libs/wingsdk/src/target-sim/dynamodb-table.inflight.ts b/libs/wingsdk/src/target-sim/dynamodb-table.inflight.ts index c28e40653a3..a07ec5fa8f7 100644 --- a/libs/wingsdk/src/target-sim/dynamodb-table.inflight.ts +++ b/libs/wingsdk/src/target-sim/dynamodb-table.inflight.ts @@ -9,11 +9,6 @@ import { DynamodbTableSchema, } from "./schema-resources"; import { DynamodbTableClientBase, GlobalSecondaryIndex } from "../ex"; -import { - generateDockerContainerName, - runDockerImage, - runCommand, -} from "../shared/misc"; import { ISimulatorContext, ISimulatorResourceInstance, @@ -25,34 +20,19 @@ export class DynamodbTable extends DynamodbTableClientBase implements ISimulatorResourceInstance { - private containerName: string; - private readonly WING_DYNAMODB_IMAGE = - process.env.WING_DYNAMODB_IMAGE ?? "amazon/dynamodb-local:2.0.0"; - private readonly context: ISimulatorContext; private client?: DynamoDBClient; private _endpoint?: string; public constructor( - private props: DynamodbTableSchema["props"], - context: ISimulatorContext + private readonly props: DynamodbTableSchema["props"], + _context: ISimulatorContext ) { super(props.name); - - this.context = context; - this.containerName = generateDockerContainerName( - `wing-sim-dynamodb-${this.context.resourcePath}` - ); } public async init(): Promise { try { - const { hostPort } = await runDockerImage({ - imageName: this.WING_DYNAMODB_IMAGE, - containerName: this.containerName, - containerPort: "8000", - }); - - this._endpoint = `http://0.0.0.0:${hostPort}`; + this._endpoint = `http://0.0.0.0:${this.props.hostPort}`; // dynamodb url based on host port this.client = new DynamoDBClient({ @@ -76,8 +56,6 @@ export class DynamodbTable public async cleanup(): Promise { // disconnect from the dynamodb server this.client?.destroy(); - // stop the dynamodb container - await runCommand("docker", ["rm", "-f", this.containerName]); this._endpoint = undefined; } diff --git a/libs/wingsdk/src/target-sim/dynamodb-table.ts b/libs/wingsdk/src/target-sim/dynamodb-table.ts index 04617c6678f..1d0e6065789 100644 --- a/libs/wingsdk/src/target-sim/dynamodb-table.ts +++ b/libs/wingsdk/src/target-sim/dynamodb-table.ts @@ -1,4 +1,5 @@ import { Construct } from "constructs"; +import { Container } from "./container"; import { ISimulatorResource } from "./resource"; import { DynamodbTableSchema } from "./schema-resources"; import { bindSimulatorResource, makeSimulatorJsClient } from "./util"; @@ -15,11 +16,27 @@ export class DynamodbTable extends ex.DynamodbTable implements ISimulatorResource { + private readonly WING_DYNAMODB_IMAGE = + process.env.WING_DYNAMODB_IMAGE ?? "amazon/dynamodb-local:2.0.0"; + private readonly props: ex.DynamodbTableProps; + private readonly hostPort: string; constructor(scope: Construct, id: string, props: ex.DynamodbTableProps) { super(scope, id, props); + const c = new Container(this, "Container", { + name: "dynamodb", + image: this.WING_DYNAMODB_IMAGE, + containerPort: 8000, + }); + + if (!c.hostPort) { + throw new Error("Failed to get host port for the dynamodb container"); + } + + this.hostPort = c.hostPort; + this.props = props; } @@ -29,6 +46,7 @@ export class DynamodbTable path: this.node.path, addr: this.node.addr, props: { + hostPort: this.hostPort, name: this.name, attributeDefinitions: this.props.attributeDefinitions, hashKey: this.props.hashKey, diff --git a/libs/wingsdk/src/target-sim/index.ts b/libs/wingsdk/src/target-sim/index.ts index 15e8b993d26..dd8a337a355 100644 --- a/libs/wingsdk/src/target-sim/index.ts +++ b/libs/wingsdk/src/target-sim/index.ts @@ -1,4 +1,4 @@ // only include here types that we want to expose in userland - +export * from "./container"; export * from "./resource"; export * from "./state"; diff --git a/libs/wingsdk/src/target-sim/redis.inflight.ts b/libs/wingsdk/src/target-sim/redis.inflight.ts index fe06cc1b349..e703407d575 100644 --- a/libs/wingsdk/src/target-sim/redis.inflight.ts +++ b/libs/wingsdk/src/target-sim/redis.inflight.ts @@ -1,11 +1,6 @@ import IoRedis from "ioredis"; import { RedisAttributes, RedisSchema } from "./schema-resources"; import { RedisClientBase } from "../ex"; -import { - generateDockerContainerName, - runCommand, - runDockerImage, -} from "../shared/misc"; import { ISimulatorContext, ISimulatorResourceInstance, @@ -15,23 +10,15 @@ export class Redis extends RedisClientBase implements ISimulatorResourceInstance { - private containerName: string; - private readonly WING_REDIS_IMAGE = - process.env.WING_REDIS_IMAGE ?? - // Redis version 7.0.9 - "redis@sha256:e50c7e23f79ae81351beacb20e004720d4bed657415e68c2b1a2b5557c075ce0"; - private readonly context: ISimulatorContext; - - private connection_url?: string = undefined; + private connectionUrl?: string = undefined; private connection?: IoRedis; private isCleanedUp = false; - public constructor(_props: RedisSchema["props"], context: ISimulatorContext) { + public constructor( + private readonly props: RedisSchema["props"], + _context: ISimulatorContext + ) { super(); - this.context = context; - this.containerName = generateDockerContainerName( - `wing-sim-redis-${this.context.resourcePath}` - ); } public async init(): Promise { @@ -40,13 +27,8 @@ export class Redis return {}; } - const { hostPort } = await runDockerImage({ - imageName: this.WING_REDIS_IMAGE, - containerName: this.containerName, - containerPort: "6379", - }); // redis url based on host port - this.connection_url = `redis://0.0.0.0:${hostPort}`; + this.connectionUrl = `redis://0.0.0.0:${this.props.port}`; return {}; } catch (e) { @@ -60,8 +42,6 @@ export class Redis // disconnect from the redis server await this.connection?.quit(); this.connection?.disconnect(); - // stop the redis container - await runCommand("docker", ["rm", "-f", this.containerName]); } public async save(): Promise {} @@ -71,8 +51,8 @@ export class Redis return this.connection; } - if (this.connection_url) { - this.connection = new IoRedis(this.connection_url); + if (this.connectionUrl) { + this.connection = new IoRedis(this.connectionUrl); return this.connection; } @@ -80,8 +60,8 @@ export class Redis } public async url(): Promise { - if (this.connection_url != undefined) { - return this.connection_url; + if (this.connectionUrl != undefined) { + return this.connectionUrl; } else { throw new Error("Redis server not initialized"); } diff --git a/libs/wingsdk/src/target-sim/redis.ts b/libs/wingsdk/src/target-sim/redis.ts index 4ff633e25b3..908ee7f9ccb 100644 --- a/libs/wingsdk/src/target-sim/redis.ts +++ b/libs/wingsdk/src/target-sim/redis.ts @@ -1,4 +1,5 @@ import { Construct } from "constructs"; +import { Container } from "./container"; import { ISimulatorResource } from "./resource"; import { RedisSchema } from "./schema-resources"; import { bindSimulatorResource, makeSimulatorJsClient } from "./util"; @@ -12,8 +13,27 @@ import { IInflightHost } from "../std"; * @inflight `@winglang/sdk.redis.IRedisClient` */ export class Redis extends ex.Redis implements ISimulatorResource { + private readonly WING_REDIS_IMAGE = + process.env.WING_REDIS_IMAGE ?? + // Redis version 7.0.9 + "redis@sha256:e50c7e23f79ae81351beacb20e004720d4bed657415e68c2b1a2b5557c075ce0"; + + private readonly hostPort: string; + constructor(scope: Construct, id: string) { super(scope, id); + + const c = new Container(this, "Container", { + name: "redis", + image: this.WING_REDIS_IMAGE, + containerPort: 6379, + }); + + if (!c.hostPort) { + throw new Error("Failed to get host port for the redis container"); + } + + this.hostPort = c.hostPort; } public toSimulator(): BaseResourceSchema { @@ -21,7 +41,9 @@ export class Redis extends ex.Redis implements ISimulatorResource { type: ex.REDIS_FQN, path: this.node.path, addr: this.node.addr, - props: {}, + props: { + port: this.hostPort, + }, attrs: {} as any, }; return schema; diff --git a/libs/wingsdk/src/target-sim/schema-resources.ts b/libs/wingsdk/src/target-sim/schema-resources.ts index f2901973e78..0fef6abcb52 100644 --- a/libs/wingsdk/src/target-sim/schema-resources.ts +++ b/libs/wingsdk/src/target-sim/schema-resources.ts @@ -1,3 +1,4 @@ +import { SIM_CONTAINER_FQN } from "./container"; import { EVENT_MAPPING_FQN } from "./event-mapping"; import { STATE_FQN } from "./state"; import { @@ -229,8 +230,11 @@ export interface TestRunnerAttributes {} /** Schema for redis.Redis */ export interface RedisSchema extends BaseResourceSchema { readonly type: typeof REDIS_FQN; - readonly props: {}; + readonly props: { + readonly port: string; + }; } + /** * Custom routes created in preflight. * Each contains the data to send to the user and a contentType header. @@ -305,6 +309,14 @@ export interface DynamodbTableAttributes {} export interface DynamodbTableSchema extends BaseResourceSchema { readonly type: typeof DYNAMODB_TABLE_FQN; readonly props: { + /** + * The port of the DynamoDB container. + */ + readonly hostPort: string; + + /** + * The table name. + */ readonly name: string; /** * Table attribute definitions. e.g. { "myKey": "S", "myOtherKey": "S" }. @@ -364,3 +376,20 @@ export interface EndpointSchema extends BaseResourceSchema { }; readonly attrs: EndpointAttributes & BaseResourceAttributes; } + +/** Schema for sim.Container */ +export interface ContainerSchema extends BaseResourceSchema { + readonly type: typeof SIM_CONTAINER_FQN; + readonly props: { + imageTag: string; + image: string; + containerPort?: number; + env?: Record; + args?: string[]; + cwd: string; + }; + readonly attrs: ContainerAttributes & BaseResourceAttributes; +} + +/** Runtime attributes for sim.Container */ +export interface ContainerAttributes {} diff --git a/libs/wingsdk/src/target-sim/state.ts b/libs/wingsdk/src/target-sim/state.ts index 337fa194d08..7f3ed33cc6e 100644 --- a/libs/wingsdk/src/target-sim/state.ts +++ b/libs/wingsdk/src/target-sim/state.ts @@ -2,6 +2,7 @@ import { ISimulatorResource } from "./resource"; import { simulatorAttrToken } from "./tokens"; import { bindSimulatorResource, makeSimulatorJsClient } from "./util"; import { fqnForType } from "../constants"; +import { INFLIGHT_SYMBOL } from "../core/types"; import { BaseResourceSchema } from "../simulator/simulator"; import { IInflightHost, Json, Resource } from "../std"; @@ -25,6 +26,9 @@ export const STATE_FQN = fqnForType("sim.State"); * @inflight `@winglang/sdk.sim.IStateClient` */ export class State extends Resource implements ISimulatorResource { + /** @internal */ + public [INFLIGHT_SYMBOL]?: IStateClient; + /** * Returns a token that can be used to retrieve the value of the state after the simulation has * run. diff --git a/libs/wingsdk/test/core/__snapshots__/connections.test.ts.snap b/libs/wingsdk/test/core/__snapshots__/connections.test.ts.snap index 416f15e9cb7..4c4b3dd39ff 100644 --- a/libs/wingsdk/test/core/__snapshots__/connections.test.ts.snap +++ b/libs/wingsdk/test/core/__snapshots__/connections.test.ts.snap @@ -126,6 +126,10 @@ return class Handler { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", diff --git a/libs/wingsdk/test/shared/misc.test.ts b/libs/wingsdk/test/shared/misc.test.ts deleted file mode 100644 index 06027195c6d..00000000000 --- a/libs/wingsdk/test/shared/misc.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { describe, expect, test } from "vitest"; -import { generateDockerContainerName } from "../../src/shared/misc"; - -describe("generateDockerContainerName", () => { - test("includes the provided prefix", () => { - const prefix = "my-example-name"; - const actual = generateDockerContainerName(prefix); - expect(actual).toContain(prefix); - }); - - test("removes disallowed characters", () => { - const actual = generateDockerContainerName( - "wing-container-type-App.Name With Spaces/And/Stuff" - ); - expect(actual).not.contains(/[ \/]/); - }); - - test("includes a uuid v4 suffix", () => { - const uuidRegexp = /[a-f\d]{8}(-[a-f\d]{4}){3}-[a-f\d]{12}$/i; - const actual = generateDockerContainerName("wing-container-type-name"); - expect(actual).toMatch(uuidRegexp); - }); -}); diff --git a/libs/wingsdk/test/target-sim/__snapshots__/api.test.ts.snap b/libs/wingsdk/test/target-sim/__snapshots__/api.test.ts.snap index 99448498f90..6d2c8a0786d 100644 --- a/libs/wingsdk/test/target-sim/__snapshots__/api.test.ts.snap +++ b/libs/wingsdk/test/target-sim/__snapshots__/api.test.ts.snap @@ -192,6 +192,10 @@ return class Handler { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -464,6 +468,10 @@ return class Handler { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -736,6 +744,10 @@ return class Handler { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -1008,6 +1020,10 @@ return class Handler { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -1280,6 +1296,10 @@ return class Handler { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -1552,6 +1572,10 @@ return class Handler { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -1956,6 +1980,10 @@ return class Handler { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -2245,6 +2273,10 @@ return class Handler { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -2526,6 +2558,10 @@ return class Handler { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -2796,6 +2832,10 @@ return class Handler { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -3142,6 +3182,10 @@ return class Handler { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -3515,6 +3559,10 @@ return class Handler { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -3812,6 +3860,10 @@ return class Handler { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -4093,6 +4145,10 @@ return class Handler { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -4365,6 +4421,10 @@ return class Handler { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -4548,6 +4608,10 @@ exports[`create an api 1`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", diff --git a/libs/wingsdk/test/target-sim/__snapshots__/bucket.test.ts.snap b/libs/wingsdk/test/target-sim/__snapshots__/bucket.test.ts.snap index 5250ece5155..40b92f775b5 100644 --- a/libs/wingsdk/test/target-sim/__snapshots__/bucket.test.ts.snap +++ b/libs/wingsdk/test/target-sim/__snapshots__/bucket.test.ts.snap @@ -151,6 +151,10 @@ exports[`can add file in preflight 2`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -296,6 +300,10 @@ exports[`can add object in preflight 2`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -428,6 +436,10 @@ exports[`create a bucket 1`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -568,6 +580,10 @@ exports[`get invalid object throws an error 2`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", diff --git a/libs/wingsdk/test/target-sim/__snapshots__/counter.test.ts.snap b/libs/wingsdk/test/target-sim/__snapshots__/counter.test.ts.snap index 0f0a012662f..d6b1be3b2b4 100644 --- a/libs/wingsdk/test/target-sim/__snapshots__/counter.test.ts.snap +++ b/libs/wingsdk/test/target-sim/__snapshots__/counter.test.ts.snap @@ -88,6 +88,10 @@ exports[`create a counter 1`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -229,6 +233,10 @@ exports[`dec 2`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -370,6 +378,10 @@ exports[`inc 2`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -511,6 +523,10 @@ exports[`key dec 2`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -652,6 +668,10 @@ exports[`key inc 2`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -791,6 +811,10 @@ exports[`key set to new value 2`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -930,6 +954,10 @@ exports[`set to new value 2`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", diff --git a/libs/wingsdk/test/target-sim/__snapshots__/dynamodb-table.test.ts.snap b/libs/wingsdk/test/target-sim/__snapshots__/dynamodb-table.test.ts.snap index 8206342b869..ca7b5676b2c 100644 --- a/libs/wingsdk/test/target-sim/__snapshots__/dynamodb-table.test.ts.snap +++ b/libs/wingsdk/test/target-sim/__snapshots__/dynamodb-table.test.ts.snap @@ -17,10 +17,23 @@ exports[`create a table 1`] = ` "id": "S", }, "hashKey": "id", + "hostPort": "\${wsim#root/create_table/Container#attrs.host_port}", "name": "new_table", }, "type": "@winglang/sdk.ex.DynamodbTable", }, + { + "addr": "c82d9c21ab6a376c9bcdf5239702cd3d3a582b5e50", + "attrs": {}, + "path": "root/create_table/Container", + "props": { + "containerPort": 8000, + "cwd": "/home/runner/work/wing/wing/libs/wingsdk/test", + "image": "amazon/dynamodb-local:2.0.0", + "imageTag": "amazon/dynamodb-local:2.0.0", + }, + "type": "@winglang/sdk.sim.Container", + }, ], "sdkVersion": "0.0.0", "types": { @@ -92,6 +105,10 @@ exports[`create a table 1`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -110,6 +127,17 @@ exports[`create a table 1`] = ` "tree": { "children": { "create_table": { + "children": { + "Container": { + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0", + }, + "display": {}, + "id": "Container", + "path": "root/create_table/Container", + }, + }, "constructInfo": { "fqn": "constructs.Construct", "version": "10.3.0", @@ -136,8 +164,15 @@ exports[`create a table 1`] = ` exports[`get item 1`] = ` [ + "image amazon/dynamodb-local:.0.0 already exists", + "starting container from image amazon/dynamodb-local:.0.0", + "docker run --detach --rm --name wing-container- -p 8000 amazon/dynamodb-local:.0.0", + "containerName=wing-container-", + "root/get_table/Container started", "root/get_table started", "root/get_table stopped", + "Stopping container wing-container-", + "root/get_table/Container stopped", ] `; @@ -158,10 +193,23 @@ exports[`get item 2`] = ` "id": "S", }, "hashKey": "id", + "hostPort": "\${wsim#root/get_table/Container#attrs.host_port}", "name": "my_get_table", }, "type": "@winglang/sdk.ex.DynamodbTable", }, + { + "addr": "c8b10674f74fa0a89c85159f18ee72997a19272e1a", + "attrs": {}, + "path": "root/get_table/Container", + "props": { + "containerPort": 8000, + "cwd": "/home/runner/work/wing/wing/libs/wingsdk/test", + "image": "amazon/dynamodb-local:2.0.0", + "imageTag": "amazon/dynamodb-local:2.0.0", + }, + "type": "@winglang/sdk.sim.Container", + }, ], "sdkVersion": "0.0.0", "types": { @@ -233,6 +281,10 @@ exports[`get item 2`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -251,6 +303,17 @@ exports[`get item 2`] = ` "tree": { "children": { "get_table": { + "children": { + "Container": { + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0", + }, + "display": {}, + "id": "Container", + "path": "root/get_table/Container", + }, + }, "constructInfo": { "fqn": "constructs.Construct", "version": "10.3.0", @@ -277,8 +340,15 @@ exports[`get item 2`] = ` exports[`put item 1`] = ` [ + "image amazon/dynamodb-local:.0.0 already exists", + "starting container from image amazon/dynamodb-local:.0.0", + "docker run --detach --rm --name wing-container- -p 8000 amazon/dynamodb-local:.0.0", + "containerName=wing-container-", + "root/put_table/Container started", "root/put_table started", "root/put_table stopped", + "Stopping container wing-container-", + "root/put_table/Container stopped", ] `; @@ -299,10 +369,23 @@ exports[`put item 2`] = ` "id": "S", }, "hashKey": "id", + "hostPort": "\${wsim#root/put_table/Container#attrs.host_port}", "name": "my_insert_table", }, "type": "@winglang/sdk.ex.DynamodbTable", }, + { + "addr": "c8934f0d9367e400f390b6380b526e9c5ceaa3833e", + "attrs": {}, + "path": "root/put_table/Container", + "props": { + "containerPort": 8000, + "cwd": "/home/runner/work/wing/wing/libs/wingsdk/test", + "image": "amazon/dynamodb-local:2.0.0", + "imageTag": "amazon/dynamodb-local:2.0.0", + }, + "type": "@winglang/sdk.sim.Container", + }, ], "sdkVersion": "0.0.0", "types": { @@ -374,6 +457,10 @@ exports[`put item 2`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -392,6 +479,17 @@ exports[`put item 2`] = ` "tree": { "children": { "put_table": { + "children": { + "Container": { + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0", + }, + "display": {}, + "id": "Container", + "path": "root/put_table/Container", + }, + }, "constructInfo": { "fqn": "constructs.Construct", "version": "10.3.0", @@ -418,8 +516,15 @@ exports[`put item 2`] = ` exports[`update item 1`] = ` [ + "image amazon/dynamodb-local:.0.0 already exists", + "starting container from image amazon/dynamodb-local:.0.0", + "docker run --detach --rm --name wing-container- -p 8000 amazon/dynamodb-local:.0.0", + "containerName=wing-container-", + "root/update_table/Container started", "root/update_table started", "root/update_table stopped", + "Stopping container wing-container-", + "root/update_table/Container stopped", ] `; @@ -440,10 +545,23 @@ exports[`update item 2`] = ` "id": "S", }, "hashKey": "id", + "hostPort": "\${wsim#root/update_table/Container#attrs.host_port}", "name": "my_update_table", }, "type": "@winglang/sdk.ex.DynamodbTable", }, + { + "addr": "c87d51114bd971aa78d45b09d1581b2d2490f8d31a", + "attrs": {}, + "path": "root/update_table/Container", + "props": { + "containerPort": 8000, + "cwd": "/home/runner/work/wing/wing/libs/wingsdk/test", + "image": "amazon/dynamodb-local:2.0.0", + "imageTag": "amazon/dynamodb-local:2.0.0", + }, + "type": "@winglang/sdk.sim.Container", + }, ], "sdkVersion": "0.0.0", "types": { @@ -515,6 +633,10 @@ exports[`update item 2`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -533,6 +655,17 @@ exports[`update item 2`] = ` "tree": { "children": { "update_table": { + "children": { + "Container": { + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0", + }, + "display": {}, + "id": "Container", + "path": "root/update_table/Container", + }, + }, "constructInfo": { "fqn": "constructs.Construct", "version": "10.3.0", diff --git a/libs/wingsdk/test/target-sim/__snapshots__/file-counter.test.ts.snap b/libs/wingsdk/test/target-sim/__snapshots__/file-counter.test.ts.snap index 5c661456c95..bcd55a21b89 100644 --- a/libs/wingsdk/test/target-sim/__snapshots__/file-counter.test.ts.snap +++ b/libs/wingsdk/test/target-sim/__snapshots__/file-counter.test.ts.snap @@ -204,6 +204,10 @@ bucket: (function() { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", diff --git a/libs/wingsdk/test/target-sim/__snapshots__/function.test.ts.snap b/libs/wingsdk/test/target-sim/__snapshots__/function.test.ts.snap index 66fa0e3c6e2..615e5c4b303 100644 --- a/libs/wingsdk/test/target-sim/__snapshots__/function.test.ts.snap +++ b/libs/wingsdk/test/target-sim/__snapshots__/function.test.ts.snap @@ -135,6 +135,10 @@ async handle(event) { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -307,6 +311,10 @@ async handle(event) { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -479,6 +487,10 @@ async handle(event) { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -653,6 +665,10 @@ async handle(event) { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -815,6 +831,10 @@ async handle() { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -1076,6 +1096,10 @@ return class Handler { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", diff --git a/libs/wingsdk/test/target-sim/__snapshots__/immutable-capture.test.ts.snap b/libs/wingsdk/test/target-sim/__snapshots__/immutable-capture.test.ts.snap index b1194ddf286..c8926828727 100644 --- a/libs/wingsdk/test/target-sim/__snapshots__/immutable-capture.test.ts.snap +++ b/libs/wingsdk/test/target-sim/__snapshots__/immutable-capture.test.ts.snap @@ -114,6 +114,10 @@ my_capture: ["hello","dude"] "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -270,6 +274,10 @@ my_array: [(new (require("[REDACTED]/wingsdk/src/std/duration.js").Duration)(600 "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -425,6 +433,10 @@ my_array: [new Map([["foo",1],["bar",2]]),new Map([["foo",3],["bar",4]])] "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -579,6 +591,10 @@ my_capture: false "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -735,6 +751,10 @@ my_capture: (new (require("[REDACTED]/wingsdk/src/std/duration.js").Duration)(72 "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -893,6 +913,10 @@ my_capture: new Map([["foo",123],["bar",456]]) "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -1053,6 +1077,10 @@ my_map: new Map([["foo",[1,2]],["bar",[3,4]]]) "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -1210,6 +1238,10 @@ my_map: new Map([["foo",[(new (require("[REDACTED]/wingsdk/src/std/duration.js") "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -1364,6 +1396,10 @@ my_capture: 123 "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -1521,6 +1557,10 @@ my_capture: new Set(["boom","bam","bang"]) "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -1677,6 +1717,10 @@ my_set: new Set([(new (require("[REDACTED]/wingsdk/src/std/duration.js").Duratio "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -1832,6 +1876,10 @@ my_capture: "bam bam bam" "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -1989,6 +2037,10 @@ my_capture: {"hello": "dude","world": "cup","foo": "bar",} "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -2146,6 +2198,10 @@ my_struct: {"foo": new Map([["foo",1],["bar",2]]),"bar": new Map([["foo",3],["ba "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", diff --git a/libs/wingsdk/test/target-sim/__snapshots__/on-deploy.test.ts.snap b/libs/wingsdk/test/target-sim/__snapshots__/on-deploy.test.ts.snap index 4398e5d3abf..c049247eb8b 100644 --- a/libs/wingsdk/test/target-sim/__snapshots__/on-deploy.test.ts.snap +++ b/libs/wingsdk/test/target-sim/__snapshots__/on-deploy.test.ts.snap @@ -121,6 +121,10 @@ return class Handler { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", diff --git a/libs/wingsdk/test/target-sim/__snapshots__/queue.test.ts.snap b/libs/wingsdk/test/target-sim/__snapshots__/queue.test.ts.snap index ceae7ab1536..ed167ed13f0 100644 --- a/libs/wingsdk/test/target-sim/__snapshots__/queue.test.ts.snap +++ b/libs/wingsdk/test/target-sim/__snapshots__/queue.test.ts.snap @@ -89,6 +89,10 @@ exports[`create a queue 1`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -679,6 +683,10 @@ exports[`push rejects empty message 2`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -822,6 +830,10 @@ exports[`queue batch size of 2, purge the queue 2`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -1080,6 +1092,10 @@ async handle(message) { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -1322,6 +1338,10 @@ async handle(message) { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", diff --git a/libs/wingsdk/test/target-sim/__snapshots__/redis.test.ts.snap b/libs/wingsdk/test/target-sim/__snapshots__/redis.test.ts.snap index 5fb691e2370..0a4a764ce2e 100644 --- a/libs/wingsdk/test/target-sim/__snapshots__/redis.test.ts.snap +++ b/libs/wingsdk/test/target-sim/__snapshots__/redis.test.ts.snap @@ -12,9 +12,23 @@ exports[`create a Redis resource 1`] = ` "addr": "c84bd402f6d507613026d171fe74fc8ce07ced29ae", "attrs": {}, "path": "root/my_redis", - "props": {}, + "props": { + "port": "\${wsim#root/my_redis/Container#attrs.host_port}", + }, "type": "@winglang/sdk.ex.Redis", }, + { + "addr": "c80acc96b1d952059eb0beee72912b94d23302fa03", + "attrs": {}, + "path": "root/my_redis/Container", + "props": { + "containerPort": 6379, + "cwd": "/home/runner/work/wing/wing/libs/wingsdk/test", + "image": "redis@sha256:e50c7e23f79ae81351beacb20e004720d4bed657415e68c2b1a2b5557c075ce0", + "imageTag": "redis@sha256:e50c7e23f79ae81351beacb20e004720d4bed657415e68c2b1a2b5557c075ce0", + }, + "type": "@winglang/sdk.sim.Container", + }, ], "sdkVersion": "0.0.0", "types": { @@ -86,6 +100,10 @@ exports[`create a Redis resource 1`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -104,6 +122,17 @@ exports[`create a Redis resource 1`] = ` "tree": { "children": { "my_redis": { + "children": { + "Container": { + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0", + }, + "display": {}, + "id": "Container", + "path": "root/my_redis/Container", + }, + }, "constructInfo": { "fqn": "constructs.Construct", "version": "10.3.0", diff --git a/libs/wingsdk/test/target-sim/__snapshots__/schedule.test.ts.snap b/libs/wingsdk/test/target-sim/__snapshots__/schedule.test.ts.snap index c255a568385..fad2928638e 100644 --- a/libs/wingsdk/test/target-sim/__snapshots__/schedule.test.ts.snap +++ b/libs/wingsdk/test/target-sim/__snapshots__/schedule.test.ts.snap @@ -88,6 +88,10 @@ exports[`create a schedule 1`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -272,6 +276,10 @@ console.log("Hello from schedule!"); "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -482,6 +490,10 @@ console.log("Hello from schedule!"); "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -692,6 +704,10 @@ console.log("Hello from schedule!"); "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", diff --git a/libs/wingsdk/test/target-sim/__snapshots__/secret.test.ts.snap b/libs/wingsdk/test/target-sim/__snapshots__/secret.test.ts.snap index 4e81291e198..6105d947c71 100644 --- a/libs/wingsdk/test/target-sim/__snapshots__/secret.test.ts.snap +++ b/libs/wingsdk/test/target-sim/__snapshots__/secret.test.ts.snap @@ -88,6 +88,10 @@ exports[`create a secret 1`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", diff --git a/libs/wingsdk/test/target-sim/__snapshots__/service.test.ts.snap b/libs/wingsdk/test/target-sim/__snapshots__/service.test.ts.snap index db3ee48c48c..65a2b62fcf2 100644 --- a/libs/wingsdk/test/target-sim/__snapshots__/service.test.ts.snap +++ b/libs/wingsdk/test/target-sim/__snapshots__/service.test.ts.snap @@ -112,6 +112,10 @@ exports.handle = async function() { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", diff --git a/libs/wingsdk/test/target-sim/__snapshots__/table.test.ts.snap b/libs/wingsdk/test/target-sim/__snapshots__/table.test.ts.snap index bd139bec879..5d30f5cb7b2 100644 --- a/libs/wingsdk/test/target-sim/__snapshots__/table.test.ts.snap +++ b/libs/wingsdk/test/target-sim/__snapshots__/table.test.ts.snap @@ -109,6 +109,10 @@ exports[`can add row in preflight 2`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -245,6 +249,10 @@ exports[`create a table 1`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -391,6 +399,10 @@ exports[`get row 2`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -535,6 +547,10 @@ exports[`insert row 2`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -681,6 +697,10 @@ exports[`list table 2`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -826,6 +846,10 @@ exports[`tryGet row 2`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", @@ -974,6 +998,10 @@ exports[`update row 2`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", diff --git a/libs/wingsdk/test/target-sim/__snapshots__/test.test.ts.snap b/libs/wingsdk/test/target-sim/__snapshots__/test.test.ts.snap index 5c40e12dd55..b99dc7414b6 100644 --- a/libs/wingsdk/test/target-sim/__snapshots__/test.test.ts.snap +++ b/libs/wingsdk/test/target-sim/__snapshots__/test.test.ts.snap @@ -126,6 +126,10 @@ async handle(event) { "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", diff --git a/libs/wingsdk/test/target-sim/__snapshots__/topic.test.ts.snap b/libs/wingsdk/test/target-sim/__snapshots__/topic.test.ts.snap index 581ab483523..0ead56b475e 100644 --- a/libs/wingsdk/test/target-sim/__snapshots__/topic.test.ts.snap +++ b/libs/wingsdk/test/target-sim/__snapshots__/topic.test.ts.snap @@ -86,6 +86,10 @@ exports[`create a topic 1`] = ` "className": "Table", "sourcePath": "/table.inflight.js", }, + "@winglang/sdk.sim.Container": { + "className": "Container", + "sourcePath": "/container.inflight.js", + }, "@winglang/sdk.sim.EventMapping": { "className": "EventMapping", "sourcePath": "/event-mapping.inflight.js", diff --git a/libs/wingsdk/test/target-sim/container.test.ts b/libs/wingsdk/test/target-sim/container.test.ts new file mode 100644 index 00000000000..cd84c229e03 --- /dev/null +++ b/libs/wingsdk/test/target-sim/container.test.ts @@ -0,0 +1,139 @@ +import { copyFileSync, cpSync, writeFileSync } from "fs"; +import { join } from "path"; +import { test, expect } from "vitest"; +import { Function, IFunctionClient } from "../../src/cloud"; +import { Testing } from "../../src/simulator"; +import { Container } from "../../src/target-sim/container"; +import { SimApp } from "../sim-app"; +import { mkdtemp } from "../util"; + +test("simple container from registry", async () => { + const app = new SimApp(); + + const c = new Container(app, "Container", { + name: "http-echo", + image: "hashicorp/http-echo", + containerPort: 5678, + args: ["-text=bang"], + }); + + new Function( + app, + "Function", + Testing.makeHandler( + ` + async handle() { + const url = "http://localhost:" + this.hostPort; + const res = await fetch(url); + return res.text(); + } + `, + { hostPort: { obj: c.hostPort, ops: [] } } + ) + ); + + const sim = await app.startSimulator(); + sim.onTrace({ callback: (trace) => console.log(">", trace.data.message) }); + + const fn = sim.getResource("root/Function") as IFunctionClient; + const response = await fn.invoke(); + expect(response).toStrictEqual("bang\n"); + + await sim.stop(); +}); + +test("simple container from a dockerfile", async () => { + const app = new SimApp(); + + const c = new Container(app, "Container", { + name: "my-app", + image: join(__dirname, "my-docker-image"), + containerPort: 3000, + }); + + new Function( + app, + "Function", + Testing.makeHandler( + ` + async handle() { + const url = "http://localhost:" + this.hostPort; + const res = await fetch(url); + return res.text(); + } + `, + { hostPort: { obj: c.hostPort, ops: [] } } + ) + ); + + const sim = await app.startSimulator(); + sim.onTrace({ callback: (trace) => console.log(">", trace.data.message) }); + + const fn = sim.getResource("root/Function") as IFunctionClient; + const response = await fn.invoke(); + expect(response).toStrictEqual("Hello, Wingnuts!"); + + await sim.stop(); +}); + +test("no port, no urls", async () => { + const app = new SimApp(); + const c = new Container(app, "Container", { + name: "my-app", + image: "bla", + }); + expect(c.hostPort).toBeUndefined(); +}); + +test("no public url", async () => { + const app = new SimApp(); + const c = new Container(app, "Container", { + name: "my-app", + image: "bla", + }); + expect(c.hostPort).toBeUndefined(); +}); + +test("rebuild only if content had changes", async () => { + const workdir = mkdtemp(); + cpSync(join(__dirname, "my-docker-image"), workdir, { recursive: true }); + + class MyApp extends SimApp { + constructor() { + super(); + + const c = new Container(this, "Container", { + name: "my-app", + image: workdir, + containerPort: 3000, + }); + } + + public async cycle() { + const sim = await this.startSimulator(); + await sim.stop(); + return sim.listTraces().map((t) => t.data.message); + } + } + + const app1 = new MyApp(); + const r1 = await app1.cycle(); + + const app2 = new MyApp(); + const r2 = await app2.cycle(); + + expect(r2[0]).toBe( + "image my-app:a9ae83b54b1ec21faa1a3255f05c095c already exists" + ); + + // add a file to the workdir and see that we are rebuilding + writeFileSync( + join(workdir, "new-file"), + `${new Date().toISOString()}-${Math.random() * 9999}` + ); + + const app3 = new MyApp(); + const r3 = await app3.cycle(); + + expect(r3[0].startsWith(`building locally from ${workdir}`)).toBeTruthy(); +}); diff --git a/libs/wingsdk/test/target-sim/dynamodb-table.test.ts b/libs/wingsdk/test/target-sim/dynamodb-table.test.ts index 6060462b52d..bb411dedabd 100644 --- a/libs/wingsdk/test/target-sim/dynamodb-table.test.ts +++ b/libs/wingsdk/test/target-sim/dynamodb-table.test.ts @@ -29,6 +29,7 @@ test("create a table", async () => { id: "S", }, hashKey: "id", + hostPort: expect.any(String), }, type: ex.DYNAMODB_TABLE_FQN, }); @@ -65,6 +66,7 @@ test("put item", async () => { id: "S", }, hashKey: "id", + hostPort: expect.any(String), }, type: ex.DYNAMODB_TABLE_FQN, }); @@ -102,6 +104,7 @@ test("get item", async () => { id: "S", }, hashKey: "id", + hostPort: expect.any(String), }, type: ex.DYNAMODB_TABLE_FQN, }); @@ -146,6 +149,7 @@ test("update item", async () => { id: "S", }, hashKey: "id", + hostPort: expect.any(String), }, type: ex.DYNAMODB_TABLE_FQN, }); diff --git a/libs/wingsdk/test/target-sim/my-docker-image/Dockerfile b/libs/wingsdk/test/target-sim/my-docker-image/Dockerfile new file mode 100644 index 00000000000..686c128323e --- /dev/null +++ b/libs/wingsdk/test/target-sim/my-docker-image/Dockerfile @@ -0,0 +1,4 @@ +FROM node:20.8.0-alpine +EXPOSE 3000 +ADD index.js /app/index.js +ENTRYPOINT [ "/app/index.js" ] \ No newline at end of file diff --git a/libs/wingsdk/test/target-sim/my-docker-image/index.js b/libs/wingsdk/test/target-sim/my-docker-image/index.js new file mode 100755 index 00000000000..55e56a40950 --- /dev/null +++ b/libs/wingsdk/test/target-sim/my-docker-image/index.js @@ -0,0 +1,15 @@ +#!/usr/bin/env node +const http = require('http'); + +process.on('SIGINT', () => { + console.info("Interrupted") + process.exit(0) +}); + +const server = http.createServer((req, res) => { + console.log(`request received: ${req.method} ${req.url}`); + res.end('Hello, Wingnuts!'); +}); + +console.log('listening on port 3000'); +server.listen(3000); \ No newline at end of file diff --git a/libs/wingsdk/test/target-sim/redis.test.ts b/libs/wingsdk/test/target-sim/redis.test.ts index 4f3a82f3863..0881a560b99 100644 --- a/libs/wingsdk/test/target-sim/redis.test.ts +++ b/libs/wingsdk/test/target-sim/redis.test.ts @@ -19,7 +19,9 @@ test("create a Redis resource", async () => { }, path: "root/my_redis", addr: expect.any(String), - props: {}, + props: { + port: expect.any(String), + }, type: REDIS_FQN, }); expect(app.snapshot()).toMatchSnapshot(); diff --git a/libs/wingsdk/test/target-sim/util.ts b/libs/wingsdk/test/target-sim/util.ts index 938cd0f5a62..de8c41ed6fb 100644 --- a/libs/wingsdk/test/target-sim/util.ts +++ b/libs/wingsdk/test/target-sim/util.ts @@ -32,7 +32,11 @@ export interface IScopeCallback { export function listMessages(s: Simulator) { const message = s.listTraces().map((trace) => trace.data.message); // Redact any messages containing port numbers - return message.map((m) => m.replace(/:\d+/, ":")); + return message.map((m) => + m + .replace(/wing-container-\w+/g, "wing-container-") + .replace(/:\d+/, ":") + ); } export async function sleep(ms: number) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 512c78ec08a..565760cad05 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1323,6 +1323,9 @@ importers: express: specifier: ^4.18.2 version: 4.18.2 + glob: + specifier: ^8.1.0 + version: 8.1.0 google-auth-library: specifier: ^8.9.0 version: 8.9.0 @@ -1376,6 +1379,9 @@ importers: '@types/fs-extra': specifier: ^11.0.1 version: 11.0.2 + '@types/glob': + specifier: ^7.2.0 + version: 7.2.0 '@types/mime-types': specifier: ^2.1.2 version: 2.1.2 diff --git a/tools/generate-workspace/src/cli.ts b/tools/generate-workspace/src/cli.ts index bcd17d70496..12083693adb 100644 --- a/tools/generate-workspace/src/cli.ts +++ b/tools/generate-workspace/src/cli.ts @@ -44,6 +44,8 @@ const nameMapping: Record = { "vscode-wing": "VSCode", "wing-api-checker": "API Checker", winglang: "CLI", + "wingcli-v2": "CLIv2", + "@winglang/wingtunnels": "Tunnel Server", }; async function getWorkspaceDir() { diff --git a/tools/hangar/__snapshots__/test_corpus/sdk_tests/container/container.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/sdk_tests/container/container.test.w_compile_tf-aws.md new file mode 100644 index 00000000000..a2fe48a7876 --- /dev/null +++ b/tools/hangar/__snapshots__/test_corpus/sdk_tests/container/container.test.w_compile_tf-aws.md @@ -0,0 +1,21 @@ +# [container.test.w](../../../../../../examples/tests/sdk_tests/container/container.test.w) | compile | tf-aws + +## main.tf.json +```json +{ + "//": { + "metadata": { + "backend": "local", + "stackName": "root", + "version": "0.20.3" + }, + "outputs": {} + }, + "provider": { + "aws": [ + {} + ] + } +} +``` + diff --git a/tools/hangar/__snapshots__/test_corpus/sdk_tests/container/container.test.w_test_sim.md b/tools/hangar/__snapshots__/test_corpus/sdk_tests/container/container.test.w_test_sim.md new file mode 100644 index 00000000000..7b32bca71ae --- /dev/null +++ b/tools/hangar/__snapshots__/test_corpus/sdk_tests/container/container.test.w_test_sim.md @@ -0,0 +1,16 @@ +# [container.test.w](../../../../../../examples/tests/sdk_tests/container/container.test.w) | test | sim + +## stdout.log +```log +pass ┌ container.test.wsim » root/env0/test:get echo + │ bang + └ +pass ┌ container.test.wsim » root/env1/test:get app + └ Hello, Wingnuts! + + +Tests 2 passed (2) +Test Files 1 passed (1) +Duration +``` + diff --git a/wing.code-workspace b/wing.code-workspace index be41fa934e2..d32f86a8be6 100644 --- a/wing.code-workspace +++ b/wing.code-workspace @@ -59,6 +59,14 @@ { "name": "CLI", "path": "./apps/wing" + }, + { + "name": "CLIv2", + "path": "./apps/wingcli-v2" + }, + { + "name": "Tunnel Server", + "path": "./libs/wingtunnels" } ], "settings": {}