From c0d1315200714cf636cc7dc02138201dac018678 Mon Sep 17 00:00:00 2001 From: Hasan Date: Mon, 8 Apr 2024 12:19:57 -0400 Subject: [PATCH 01/29] feat(cli): wing secrets --- apps/wing/src/cli.ts | 16 +++++ apps/wing/src/commands/secrets.ts | 34 +++++++++++ libs/wingsdk/src/cloud/secret.ts | 9 ++- libs/wingsdk/src/platform/platform-manager.ts | 61 ++++++++++++++----- libs/wingsdk/src/platform/platform.ts | 5 ++ libs/wingsdk/src/target-sim/platform.ts | 29 +++++++++ libs/wingsdk/src/target-sim/secret.ts | 5 +- libs/wingsdk/src/target-tf-aws/secret.ts | 3 +- 8 files changed, 142 insertions(+), 20 deletions(-) create mode 100644 apps/wing/src/commands/secrets.ts diff --git a/apps/wing/src/cli.ts b/apps/wing/src/cli.ts index 0807e3aa7ae..f30defacff5 100644 --- a/apps/wing/src/cli.ts +++ b/apps/wing/src/cli.ts @@ -181,6 +181,22 @@ async function main() { .hook("preAction", collectAnalyticsHook) .action(runSubCommand("compile")); + program + .command("secrets") + .description("Manage secrets") + .argument("[entrypoint]", "program .w entrypoint") + .option( + "-t, --platform --platform ", + "Target platform provider (builtin: sim, tf-aws, tf-azure, tf-gcp, awscdk)", + collectPlatformVariadic, + DEFAULT_PLATFORM + ) + .option("-v, --value ", "Platform-specific value in the form KEY=VALUE", addValue, []) + .option("--values ", "File with platform-specific values (TOML|YAML|JSON)") + .hook("preAction", progressHook) + .hook("preAction", collectAnalyticsHook) + .action(runSubCommand("secrets")); + program .command("test") .description( diff --git a/apps/wing/src/commands/secrets.ts b/apps/wing/src/commands/secrets.ts new file mode 100644 index 00000000000..56f09cbf038 --- /dev/null +++ b/apps/wing/src/commands/secrets.ts @@ -0,0 +1,34 @@ +import { PlatformManager } from "@winglang/sdk/lib/platform"; +import { CompileOptions, compile } from "./compile"; +import fs from "fs"; +import path from "path"; +import { cwd } from "process"; +import inquirer from "inquirer"; + +export interface SecretsOptions extends CompileOptions {} + +export async function secrets(entrypoint?: string, options?: SecretsOptions): Promise { + // Compile the program to generate secrets file + const outdir = await compile(entrypoint, options); + + let secrets = JSON.parse(fs.readFileSync(path.join(outdir, "secrets.json"), "utf-8")); + + process.env.WING_SOURCE_DIR = cwd(); + let secretValues: any = {}; + console.log(`${secrets.length} secret(s) found in ${entrypoint}\n`); + for (const secret of secrets) { + const response = await inquirer.prompt([ + { + type: "password", + name: "value", + message: `Enter the secret value for ${secret}:`, + }, + ]); + secretValues[secret] = response.value; + } + + const plaformManager = new PlatformManager({platformPaths: options?.platform}) + const response = await plaformManager.createSecrets(secretValues); + + console.log(`\n${response}`); +} \ No newline at end of file diff --git a/libs/wingsdk/src/cloud/secret.ts b/libs/wingsdk/src/cloud/secret.ts index 75efc9ec633..f7b8abc6a21 100644 --- a/libs/wingsdk/src/cloud/secret.ts +++ b/libs/wingsdk/src/cloud/secret.ts @@ -34,6 +34,9 @@ export class Secret extends Resource { /** @internal */ public [INFLIGHT_SYMBOL]?: ISecretClient; + /** @internal */ + protected _name?: string; + constructor(scope: Construct, id: string, props: SecretProps = {}) { if (new.target === Secret) { return Resource._newFromFactory(SECRET_FQN, scope, id, props); @@ -44,7 +47,11 @@ export class Secret extends Resource { Node.of(this).title = "Secret"; Node.of(this).description = "A cloud secret"; - props; + this._name = props.name; + } + + public get name(): string | undefined { + return this._name; } } diff --git a/libs/wingsdk/src/platform/platform-manager.ts b/libs/wingsdk/src/platform/platform-manager.ts index 57f370de2a8..b2fc7b6556e 100644 --- a/libs/wingsdk/src/platform/platform-manager.ts +++ b/libs/wingsdk/src/platform/platform-manager.ts @@ -1,10 +1,11 @@ -import { readFileSync } from "fs"; +import { readFileSync, writeFileSync } from "fs"; import { basename, dirname, join } from "path"; import { cwd } from "process"; import * as vm from "vm"; import { IPlatform } from "./platform"; import { scanDirForPlatformFile } from "./util"; import { App, AppProps, SynthHooks } from "../core"; +import { Secret } from "../cloud"; interface PlatformManagerOptions { /** @@ -19,10 +20,16 @@ const BUILTIN_PLATFORMS = ["tf-aws", "tf-azure", "tf-gcp", "sim"]; export class PlatformManager { private readonly platformPaths: string[]; private readonly platformInstances: IPlatform[] = []; + private synthHooks: SynthHooks = {}; + private newInstanceOverridesHooks: any[] = []; + private parameterSchemas: any[] = []; + private createSecretsHook: any; constructor(options: PlatformManagerOptions) { this.platformPaths = options.platformPaths ?? []; this.retrieveImplicitPlatforms(); + this.createPlatformInstances(); + this.collectHooks(); } /** @@ -97,19 +104,14 @@ export class PlatformManager { }); } - // This method is called from preflight.cjs in order to return an App instance - // that can be synthesized - public createApp(appProps: AppProps): App { - this.createPlatformInstances(); - - let appCall = this.platformInstances[0].newApp; - - if (!appCall) { - throw new Error( - `No newApp method found on platform: ${this.platformPaths[0]} (Hint: The first platform provided must have a newApp method)` - ); + public async createSecrets(secretNames: string[]): Promise { + if (!this.createSecretsHook) { + throw new Error("No createSecrets method found on platform"); } + return await this.createSecretsHook(secretNames); + } + private collectHooks() { let synthHooks: SynthHooks = { preSynthesize: [], postSynthesize: [], @@ -140,17 +142,46 @@ export class PlatformManager { if (instance.newInstance) { newInstanceOverrides.push(instance.newInstance.bind(instance)); } + + if (instance.createSecrets) { + this.createSecretsHook = instance.createSecrets.bind(instance); + } }); + this.synthHooks = synthHooks; + this.newInstanceOverridesHooks = newInstanceOverrides; + this.parameterSchemas = parameterSchemas; + } + + // This method is called from preflight.cjs in order to return an App instance + // that can be synthesized + public createApp(appProps: AppProps): App { + let appCall = this.platformInstances[0].newApp; + + if (!appCall) { + throw new Error( + `No newApp method found on platform: ${this.platformPaths[0]} (Hint: The first platform provided must have a newApp method)` + ); + } + const app = appCall!({ ...appProps, - synthHooks, - newInstanceOverrides, + synthHooks: this.synthHooks, + newInstanceOverrides: this.newInstanceOverridesHooks, }) as App; + let secretsIds = []; + for (const c of app.node.findAll()) { + if (c instanceof Secret) { + const secret = c as Secret; + secretsIds.push(secret.name); + } + } + writeFileSync(join(app.outdir, "secrets.json"), JSON.stringify(secretsIds)); + let registrar = app.parameters; - parameterSchemas.forEach((schema) => { + this.parameterSchemas.forEach((schema) => { registrar.addSchema(schema); }); diff --git a/libs/wingsdk/src/platform/platform.ts b/libs/wingsdk/src/platform/platform.ts index b5466b84ce9..6f85f0fc67e 100644 --- a/libs/wingsdk/src/platform/platform.ts +++ b/libs/wingsdk/src/platform/platform.ts @@ -54,4 +54,9 @@ export interface IPlatform { * @param config generated config */ validate?(config: any): any; + + /** + * Hook for creating secrets + */ + createSecrets?(secrets: {[name: string]: string}): Promise; } diff --git a/libs/wingsdk/src/target-sim/platform.ts b/libs/wingsdk/src/target-sim/platform.ts index 076d917f24e..99c5dcca839 100644 --- a/libs/wingsdk/src/target-sim/platform.ts +++ b/libs/wingsdk/src/target-sim/platform.ts @@ -1,5 +1,6 @@ import { App } from "./app"; import { IPlatform } from "../platform"; +import fs from "fs"; /** * Sim Platform @@ -11,4 +12,32 @@ export class Platform implements IPlatform { public newApp(appProps: any): any { return new App(appProps); } + + public async createSecrets(secrets: { [key: string]: string }): Promise { + let existingSecretsContent = ""; + try { + existingSecretsContent = fs.readFileSync('./.env', 'utf8'); + } catch (error) {} + + const existingSecrets = existingSecretsContent.split('\n') + .filter(line => line.trim() !== '') + .reduce((acc, line) => { + const [key, value] = line.split('=', 2); + acc[key] = value; + return acc; + }, {} as { [key: string]: string }); + + for (const key in secrets) { + existingSecrets[key] = secrets[key]; + } + + const updatedContent = Object.entries(existingSecrets) + .map(([key, value]) => `${key}=${value}`) + .join('\n'); + + fs.writeFileSync('./.env', updatedContent); + + // Step 5: Return success message + return "Secrets created/updated for sim platform"; + } } diff --git a/libs/wingsdk/src/target-sim/secret.ts b/libs/wingsdk/src/target-sim/secret.ts index 1e315c7b773..63b3fe92414 100644 --- a/libs/wingsdk/src/target-sim/secret.ts +++ b/libs/wingsdk/src/target-sim/secret.ts @@ -13,11 +13,10 @@ import { IInflightHost } from "../std"; * @inflight `@winglang/sdk.cloud.ISecretClient` */ export class Secret extends cloud.Secret implements ISimulatorResource { - private readonly name: string; constructor(scope: Construct, id: string, props: cloud.SecretProps = {}) { super(scope, id, props); - this.name = + this._name = props.name ?? ResourceNames.generateName(this, { disallowedRegex: /[^\w]/g }); } @@ -42,7 +41,7 @@ export class Secret extends cloud.Secret implements ISimulatorResource { public toSimulator(): ToSimulatorOutput { const props: SecretSchema = { - name: this.name, + name: this.name!, }; return { type: cloud.SECRET_FQN, diff --git a/libs/wingsdk/src/target-tf-aws/secret.ts b/libs/wingsdk/src/target-tf-aws/secret.ts index b8535b1e99d..8a950321e2e 100644 --- a/libs/wingsdk/src/target-tf-aws/secret.ts +++ b/libs/wingsdk/src/target-tf-aws/secret.ts @@ -32,8 +32,9 @@ export class Secret extends cloud.Secret { name: props.name, }); } else { + this._name = ResourceNames.generateName(this, NAME_OPTS), this.secret = new SecretsmanagerSecret(this, "Default", { - name: ResourceNames.generateName(this, NAME_OPTS), + name: this._name }); new TerraformOutput(this, "SecretArn", { From 40bfd06cf3e9499f4644a606dc7222164447a9b2 Mon Sep 17 00:00:00 2001 From: Hasan Date: Sun, 14 Apr 2024 11:31:52 -0400 Subject: [PATCH 02/29] tidy --- apps/wing/src/commands/secrets.ts | 2 +- libs/compatibility-spy/secrets.json | 1 + libs/wingsdk/src/platform/platform-manager.ts | 12 ++++++------ libs/wingsdk/src/platform/platform.ts | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) create mode 100644 libs/compatibility-spy/secrets.json diff --git a/apps/wing/src/commands/secrets.ts b/apps/wing/src/commands/secrets.ts index 56f09cbf038..2cb47fc3fe1 100644 --- a/apps/wing/src/commands/secrets.ts +++ b/apps/wing/src/commands/secrets.ts @@ -28,7 +28,7 @@ export async function secrets(entrypoint?: string, options?: SecretsOptions): Pr } const plaformManager = new PlatformManager({platformPaths: options?.platform}) - const response = await plaformManager.createSecrets(secretValues); + const response = await plaformManager.storeSecrets(secretValues); console.log(`\n${response}`); } \ No newline at end of file diff --git a/libs/compatibility-spy/secrets.json b/libs/compatibility-spy/secrets.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/libs/compatibility-spy/secrets.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/libs/wingsdk/src/platform/platform-manager.ts b/libs/wingsdk/src/platform/platform-manager.ts index b2fc7b6556e..a86558bf8f8 100644 --- a/libs/wingsdk/src/platform/platform-manager.ts +++ b/libs/wingsdk/src/platform/platform-manager.ts @@ -23,7 +23,7 @@ export class PlatformManager { private synthHooks: SynthHooks = {}; private newInstanceOverridesHooks: any[] = []; private parameterSchemas: any[] = []; - private createSecretsHook: any; + private storeSecretsHook: any; constructor(options: PlatformManagerOptions) { this.platformPaths = options.platformPaths ?? []; @@ -104,11 +104,11 @@ export class PlatformManager { }); } - public async createSecrets(secretNames: string[]): Promise { - if (!this.createSecretsHook) { + public async storeSecrets(secretNames: string[]): Promise { + if (!this.storeSecretsHook) { throw new Error("No createSecrets method found on platform"); } - return await this.createSecretsHook(secretNames); + return await this.storeSecretsHook(secretNames); } private collectHooks() { @@ -143,8 +143,8 @@ export class PlatformManager { newInstanceOverrides.push(instance.newInstance.bind(instance)); } - if (instance.createSecrets) { - this.createSecretsHook = instance.createSecrets.bind(instance); + if (instance.storeSecrets) { + this.storeSecretsHook = instance.storeSecrets.bind(instance); } }); diff --git a/libs/wingsdk/src/platform/platform.ts b/libs/wingsdk/src/platform/platform.ts index 6f85f0fc67e..39816fae90f 100644 --- a/libs/wingsdk/src/platform/platform.ts +++ b/libs/wingsdk/src/platform/platform.ts @@ -56,7 +56,7 @@ export interface IPlatform { validate?(config: any): any; /** - * Hook for creating secrets + * Hook for creating and storing secrets */ - createSecrets?(secrets: {[name: string]: string}): Promise; + storeSecrets?(secrets: {[name: string]: string}): Promise; } From ed37da48ff0d2a97907ca96714d20116e9955cf4 Mon Sep 17 00:00:00 2001 From: Hasan Date: Sun, 14 Apr 2024 11:33:51 -0400 Subject: [PATCH 03/29] updates --- libs/wingsdk/src/target-sim/platform.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/libs/wingsdk/src/target-sim/platform.ts b/libs/wingsdk/src/target-sim/platform.ts index 99c5dcca839..29d3d788395 100644 --- a/libs/wingsdk/src/target-sim/platform.ts +++ b/libs/wingsdk/src/target-sim/platform.ts @@ -13,7 +13,7 @@ export class Platform implements IPlatform { return new App(appProps); } - public async createSecrets(secrets: { [key: string]: string }): Promise { + public async storeSecrets(secrets: { [key: string]: string }): Promise { let existingSecretsContent = ""; try { existingSecretsContent = fs.readFileSync('./.env', 'utf8'); @@ -21,10 +21,10 @@ export class Platform implements IPlatform { const existingSecrets = existingSecretsContent.split('\n') .filter(line => line.trim() !== '') - .reduce((acc, line) => { + .reduce((s, line) => { const [key, value] = line.split('=', 2); - acc[key] = value; - return acc; + s[key] = value; + return s; }, {} as { [key: string]: string }); for (const key in secrets) { @@ -36,8 +36,7 @@ export class Platform implements IPlatform { .join('\n'); fs.writeFileSync('./.env', updatedContent); - - // Step 5: Return success message + return "Secrets created/updated for sim platform"; } } From df069166783485eee2680f13231bb25fe3a68bd1 Mon Sep 17 00:00:00 2001 From: Hasan Date: Mon, 15 Apr 2024 20:14:02 -0400 Subject: [PATCH 04/29] testing --- apps/wing/src/commands/compile.test.ts | 3 ++ apps/wing/src/commands/secrets.test.ts | 41 +++++++++++++++++++++++++ apps/wing/src/commands/secrets.ts | 5 ++- libs/wingsdk/src/platform/platform.ts | 2 +- libs/wingsdk/src/target-sim/platform.ts | 4 +-- 5 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 apps/wing/src/commands/secrets.test.ts diff --git a/apps/wing/src/commands/compile.test.ts b/apps/wing/src/commands/compile.test.ts index 5a6c460835b..94f66c6d635 100644 --- a/apps/wing/src/commands/compile.test.ts +++ b/apps/wing/src/commands/compile.test.ts @@ -31,6 +31,7 @@ describe( [ ".wing", "connections.json", + "secrets.json", "simulator.json", "tree.json", ] @@ -65,6 +66,7 @@ describe( [ ".wing", "connections.json", + "secrets.json", "simulator.json", "tree.json", ] @@ -154,6 +156,7 @@ describe( [ ".wing", "connections.json", + "secrets.json", "simulator.json", "tree.json", ] diff --git a/apps/wing/src/commands/secrets.test.ts b/apps/wing/src/commands/secrets.test.ts new file mode 100644 index 00000000000..9de3356ba0a --- /dev/null +++ b/apps/wing/src/commands/secrets.test.ts @@ -0,0 +1,41 @@ +import { writeFile } from "fs/promises"; +import { join } from "path"; +// import inquirer from "inquirer"; +import { BuiltinPlatform } from "@winglang/compiler"; +import { describe, expect, test, vitest, beforeEach, afterEach, vi } from "vitest"; +import { generateTmpDir } from "../util"; +import { secrets } from "../commands/secrets"; + +vitest.mock("inquirer"); + +describe("secrets", () => { + let log: any; + + beforeEach(() => { + log = console.log; + console.log = vi.fn(); + }); + + afterEach(() => { + console.log = log; + }); + + test("no secrets found", async () => { + const workdir = await generateTmpDir(); + process.chdir(workdir); + + const wingCode = ` + bring cloud; + `; + + await writeFile(join(workdir, "main.w"), wingCode); + + await secrets("main.w", { + platform: [BuiltinPlatform.SIM], + targetDir: workdir, + }); + + expect(console.log).toHaveBeenCalledWith("0 secrets(s) found in main.w\n"); + }) + +}); \ No newline at end of file diff --git a/apps/wing/src/commands/secrets.ts b/apps/wing/src/commands/secrets.ts index 2cb47fc3fe1..7830f0dd06c 100644 --- a/apps/wing/src/commands/secrets.ts +++ b/apps/wing/src/commands/secrets.ts @@ -16,6 +16,7 @@ export async function secrets(entrypoint?: string, options?: SecretsOptions): Pr process.env.WING_SOURCE_DIR = cwd(); let secretValues: any = {}; console.log(`${secrets.length} secret(s) found in ${entrypoint}\n`); + for (const secret of secrets) { const response = await inquirer.prompt([ { @@ -28,7 +29,5 @@ export async function secrets(entrypoint?: string, options?: SecretsOptions): Pr } const plaformManager = new PlatformManager({platformPaths: options?.platform}) - const response = await plaformManager.storeSecrets(secretValues); - - console.log(`\n${response}`); + await plaformManager.storeSecrets(secretValues); } \ No newline at end of file diff --git a/libs/wingsdk/src/platform/platform.ts b/libs/wingsdk/src/platform/platform.ts index 39816fae90f..88460072cd1 100644 --- a/libs/wingsdk/src/platform/platform.ts +++ b/libs/wingsdk/src/platform/platform.ts @@ -58,5 +58,5 @@ export interface IPlatform { /** * Hook for creating and storing secrets */ - storeSecrets?(secrets: {[name: string]: string}): Promise; + storeSecrets?(secrets: {[name: string]: string}): Promise; } diff --git a/libs/wingsdk/src/target-sim/platform.ts b/libs/wingsdk/src/target-sim/platform.ts index 29d3d788395..8f626a2b903 100644 --- a/libs/wingsdk/src/target-sim/platform.ts +++ b/libs/wingsdk/src/target-sim/platform.ts @@ -13,7 +13,7 @@ export class Platform implements IPlatform { return new App(appProps); } - public async storeSecrets(secrets: { [key: string]: string }): Promise { + public async storeSecrets(secrets: { [key: string]: string }): Promise { let existingSecretsContent = ""; try { existingSecretsContent = fs.readFileSync('./.env', 'utf8'); @@ -37,6 +37,6 @@ export class Platform implements IPlatform { fs.writeFileSync('./.env', updatedContent); - return "Secrets created/updated for sim platform"; + console.log("Secrets created/updated for sim platform"); } } From f8a83b26b4c23b9edc03f07f6f74f599a80ac112 Mon Sep 17 00:00:00 2001 From: Hasan Date: Wed, 17 Apr 2024 11:56:39 -0400 Subject: [PATCH 05/29] wip --- apps/wing/src/commands/compile.test.ts | 2 -- apps/wing/src/commands/secrets.test.ts | 25 +++++++++++++++++-- apps/wing/src/commands/secrets.ts | 9 +++++-- .../test/__snapshots__/test.test.ts.snap | 6 +++++ libs/awscdk/src/function.ts | 8 +++++- libs/wingsdk/src/platform/platform-manager.ts | 4 ++- 6 files changed, 46 insertions(+), 8 deletions(-) diff --git a/apps/wing/src/commands/compile.test.ts b/apps/wing/src/commands/compile.test.ts index 94f66c6d635..2acd97b9e46 100644 --- a/apps/wing/src/commands/compile.test.ts +++ b/apps/wing/src/commands/compile.test.ts @@ -66,7 +66,6 @@ describe( [ ".wing", "connections.json", - "secrets.json", "simulator.json", "tree.json", ] @@ -156,7 +155,6 @@ describe( [ ".wing", "connections.json", - "secrets.json", "simulator.json", "tree.json", ] diff --git a/apps/wing/src/commands/secrets.test.ts b/apps/wing/src/commands/secrets.test.ts index 9de3356ba0a..da4024ccd74 100644 --- a/apps/wing/src/commands/secrets.test.ts +++ b/apps/wing/src/commands/secrets.test.ts @@ -35,7 +35,28 @@ describe("secrets", () => { targetDir: workdir, }); - expect(console.log).toHaveBeenCalledWith("0 secrets(s) found in main.w\n"); - }) + expect(console.log).toHaveBeenCalledWith("0 secret(s) found in main.w\n"); + }); + + test("secrets found", async () => { + const workdir = await generateTmpDir(); + process.chdir(workdir); + + const wingCode = ` + bring cloud; + + let s1 = new cloud.Secret(name: "my-secret") as "s1"; + let s2 = new cloud.Secret(name: "other-secret") as "s2"; + `; + + await writeFile(join(workdir, "main.w"), wingCode); + + await secrets("main.w", { + platform: [BuiltinPlatform.SIM], + targetDir: workdir, + }); + + expect(console.log).toHaveBeenCalledWith("2 secret(s) found in main.w\n"); + }); }); \ No newline at end of file diff --git a/apps/wing/src/commands/secrets.ts b/apps/wing/src/commands/secrets.ts index 7830f0dd06c..1baf8c7e4d1 100644 --- a/apps/wing/src/commands/secrets.ts +++ b/apps/wing/src/commands/secrets.ts @@ -10,13 +10,18 @@ export interface SecretsOptions extends CompileOptions {} export async function secrets(entrypoint?: string, options?: SecretsOptions): Promise { // Compile the program to generate secrets file const outdir = await compile(entrypoint, options); - - let secrets = JSON.parse(fs.readFileSync(path.join(outdir, "secrets.json"), "utf-8")); + const secretsFile = path.join(outdir, "secrets.json"); + + let secrets = fs.existsSync(secretsFile) ? JSON.parse(fs.readFileSync(path.join(outdir, "secrets.json"), "utf-8")) : []; process.env.WING_SOURCE_DIR = cwd(); let secretValues: any = {}; console.log(`${secrets.length} secret(s) found in ${entrypoint}\n`); + if (secrets.length === 0) { + return; + } + for (const secret of secrets) { const response = await inquirer.prompt([ { diff --git a/apps/wing/src/commands/test/__snapshots__/test.test.ts.snap b/apps/wing/src/commands/test/__snapshots__/test.test.ts.snap index ed39356d511..e0e0bd6261e 100644 --- a/apps/wing/src/commands/test/__snapshots__/test.test.ts.snap +++ b/apps/wing/src/commands/test/__snapshots__/test.test.ts.snap @@ -2,7 +2,13 @@ exports[`printing test reports > resource traces are not shown if debug mode is disabled 1`] = ` "fail ┌ hello.w » root/env0/test:test + │ [trace] Push (message=cool). │ sleeping for 500 ms + │ [trace] Sending messages (messages=[\\"cool\\"], subscriber=sim-4). + │ [trace] Invoke (payload=\\"{\\\\\\"messages\\\\\\":[\\\\\\"cool\\\\\\"]}\\"). + │ [trace] Subscriber error - returning 1 messages to queue: Missing environment variable: QUEUE_HANDLE_7164aec4 + │ [trace] Get (key=file.txt). + │ [trace] Invoke (payload=\\"\\"). └ Error: Object does not exist (key=file.txt)" `; diff --git a/libs/awscdk/src/function.ts b/libs/awscdk/src/function.ts index 5ac2a3311d8..627adf9c5ab 100644 --- a/libs/awscdk/src/function.ts +++ b/libs/awscdk/src/function.ts @@ -1,4 +1,4 @@ -import { Duration } from "aws-cdk-lib"; +import { Duration, Lazy } from "aws-cdk-lib"; import { PolicyStatement as CdkPolicyStatement } from "aws-cdk-lib/aws-iam"; import { Architecture, @@ -16,6 +16,7 @@ import { IAwsFunction, PolicyStatement } from "@winglang/sdk/lib/shared-aws"; import { resolve } from "path"; import { renameSync, rmSync, writeFileSync } from "fs"; import { App } from "./app"; +import * as ec2 from "aws-cdk-lib/aws-ec2"; /** * Implementation of `awscdk.Function` are expected to implement this @@ -174,6 +175,11 @@ export class Function memorySize: props.memory ?? 1024, architecture: Architecture.ARM_64, logGroup: logs, + vpcSubnets: { + subnets: Lazy.list({ + produce: () => [], + }) as any, + } }); } diff --git a/libs/wingsdk/src/platform/platform-manager.ts b/libs/wingsdk/src/platform/platform-manager.ts index a86558bf8f8..6b5bacf17b3 100644 --- a/libs/wingsdk/src/platform/platform-manager.ts +++ b/libs/wingsdk/src/platform/platform-manager.ts @@ -177,7 +177,9 @@ export class PlatformManager { secretsIds.push(secret.name); } } - writeFileSync(join(app.outdir, "secrets.json"), JSON.stringify(secretsIds)); + if (secretsIds.length > 0) { + writeFileSync(join(app.outdir, "secrets.json"), JSON.stringify(secretsIds)); + } let registrar = app.parameters; From 744ff53c34b0a7f5073c164a4a2be1dc9a085c1c Mon Sep 17 00:00:00 2001 From: Hasan Date: Wed, 17 Apr 2024 13:46:48 -0400 Subject: [PATCH 06/29] tf-aws working --- apps/wing/src/commands/compile.test.ts | 1 - libs/awscdk/src/function.ts | 1 - libs/wingsdk/src/target-sim/platform.ts | 2 +- .../wingsdk/src/target-sim/secret.inflight.ts | 20 +++++++++-- libs/wingsdk/src/target-tf-aws/platform.ts | 34 +++++++++++++++++++ 5 files changed, 52 insertions(+), 6 deletions(-) diff --git a/apps/wing/src/commands/compile.test.ts b/apps/wing/src/commands/compile.test.ts index 2acd97b9e46..5a6c460835b 100644 --- a/apps/wing/src/commands/compile.test.ts +++ b/apps/wing/src/commands/compile.test.ts @@ -31,7 +31,6 @@ describe( [ ".wing", "connections.json", - "secrets.json", "simulator.json", "tree.json", ] diff --git a/libs/awscdk/src/function.ts b/libs/awscdk/src/function.ts index 627adf9c5ab..d0a284aff3c 100644 --- a/libs/awscdk/src/function.ts +++ b/libs/awscdk/src/function.ts @@ -16,7 +16,6 @@ import { IAwsFunction, PolicyStatement } from "@winglang/sdk/lib/shared-aws"; import { resolve } from "path"; import { renameSync, rmSync, writeFileSync } from "fs"; import { App } from "./app"; -import * as ec2 from "aws-cdk-lib/aws-ec2"; /** * Implementation of `awscdk.Function` are expected to implement this diff --git a/libs/wingsdk/src/target-sim/platform.ts b/libs/wingsdk/src/target-sim/platform.ts index 8f626a2b903..a9b3659a827 100644 --- a/libs/wingsdk/src/target-sim/platform.ts +++ b/libs/wingsdk/src/target-sim/platform.ts @@ -37,6 +37,6 @@ export class Platform implements IPlatform { fs.writeFileSync('./.env', updatedContent); - console.log("Secrets created/updated for sim platform"); + console.log(`${Object.keys(secrets).length} secret(s) stored in .env`); } } diff --git a/libs/wingsdk/src/target-sim/secret.inflight.ts b/libs/wingsdk/src/target-sim/secret.inflight.ts index 73c989c5cad..8b97e7faeeb 100644 --- a/libs/wingsdk/src/target-sim/secret.inflight.ts +++ b/libs/wingsdk/src/target-sim/secret.inflight.ts @@ -1,5 +1,4 @@ import * as fs from "fs"; -import * as os from "os"; import * as path from "path"; import { SecretAttributes, SecretSchema } from "./schema-resources"; import { ISecretClient, SECRET_FQN } from "../cloud"; @@ -16,7 +15,8 @@ export class Secret implements ISecretClient, ISimulatorResourceInstance { private readonly name: string; constructor(props: SecretSchema) { - this.secretsFile = path.join(os.homedir(), ".wing", "secrets.json"); + this.secretsFile = path.join(process.cwd(), ".env"); + if (!fs.existsSync(this.secretsFile)) { throw new Error( `No secrets file found at ${this.secretsFile} while looking for secret ${props.name}` @@ -57,7 +57,8 @@ export class Secret implements ISecretClient, ISimulatorResourceInstance { timestamp: new Date().toISOString(), }); - const secrets = JSON.parse(fs.readFileSync(this.secretsFile, "utf-8")); + const secretsContent = fs.readFileSync(this.secretsFile, "utf-8"); + const secrets = this.parseEnvFile(secretsContent); if (!secrets[this.name]) { throw new Error(`No secret value for secret ${this.name}`); @@ -69,4 +70,17 @@ export class Secret implements ISecretClient, ISimulatorResourceInstance { public async valueJson(): Promise { return JSON.parse(await this.value()); } + + private parseEnvFile(contents: string): { [key: string]: string } { + return contents.split('\n') + .map(line => line.trim()) + .filter(line => line && !line.startsWith('#')) // Ignore empty lines and comments + .reduce((acc, line) => { + const [key, value] = line.split('=', 2); + if (key) { + acc[key.trim()] = value.trim(); + } + return acc; + }, {} as { [key: string]: string }); + } } diff --git a/libs/wingsdk/src/target-tf-aws/platform.ts b/libs/wingsdk/src/target-tf-aws/platform.ts index 24a2bf8e545..019dbb1bb44 100644 --- a/libs/wingsdk/src/target-tf-aws/platform.ts +++ b/libs/wingsdk/src/target-tf-aws/platform.ts @@ -1,5 +1,6 @@ import { App } from "./app"; import { IPlatform } from "../platform"; +import { SecretsManagerClient, GetSecretValueCommand, CreateSecretCommand, UpdateSecretCommand } from "@aws-sdk/client-secrets-manager"; /** * AWS Terraform Platform @@ -67,4 +68,37 @@ export class Platform implements IPlatform { public newApp?(appProps: any): any { return new App(appProps); } + + public async storeSecrets(secrets: { [name: string]: string; }): Promise { + console.log("Storing secrets in AWS Secrets Manager"); + const client = new SecretsManagerClient({}); + + for (const [name, value] of Object.entries(secrets)) { + try { + // Attempt to retrieve the secret to check if it exists + await client.send(new GetSecretValueCommand({ SecretId: name })); + console.log(`Secret ${name} exists, updating it.`); + // Update the secret if it exists + await client.send(new UpdateSecretCommand({ + SecretId: name, + SecretString: JSON.stringify({ [name]: value }), + })); + } catch (error: any) { + if (error.name === 'ResourceNotFoundException') { + // If the secret does not exist, create it + console.log(`Secret ${name} does not exist, creating it.`); + await client.send(new CreateSecretCommand({ + Name: name, + SecretString: JSON.stringify({ [name]: value }), + })); + } else { + // Log other errors + console.error(`Failed to store secret ${name}:`, error); + throw error; // Re-throw the error if it is not related to the secret not existing + } + } + } + + console.log(`${Object.keys(secrets).length} secret(s) stored AWS Secrets Manager`); + } } From 80fc610b6e378a1bc86901f2ab54f832e552f4cb Mon Sep 17 00:00:00 2001 From: Hasan Date: Wed, 17 Apr 2024 14:04:58 -0400 Subject: [PATCH 07/29] fix list command --- apps/wing/src/cli.ts | 1 + apps/wing/src/commands/secrets.ts | 12 ++++++++++-- libs/wingsdk/src/cloud/secret.ts | 1 + 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/wing/src/cli.ts b/apps/wing/src/cli.ts index f30defacff5..7083289cda8 100644 --- a/apps/wing/src/cli.ts +++ b/apps/wing/src/cli.ts @@ -193,6 +193,7 @@ async function main() { ) .option("-v, --value ", "Platform-specific value in the form KEY=VALUE", addValue, []) .option("--values ", "File with platform-specific values (TOML|YAML|JSON)") + .addOption(new Option("--list", "List required application secrets")) .hook("preAction", progressHook) .hook("preAction", collectAnalyticsHook) .action(runSubCommand("secrets")); diff --git a/apps/wing/src/commands/secrets.ts b/apps/wing/src/commands/secrets.ts index 1baf8c7e4d1..ac89d7e11ea 100644 --- a/apps/wing/src/commands/secrets.ts +++ b/apps/wing/src/commands/secrets.ts @@ -5,7 +5,9 @@ import path from "path"; import { cwd } from "process"; import inquirer from "inquirer"; -export interface SecretsOptions extends CompileOptions {} +export interface SecretsOptions extends CompileOptions { + readonly list?: boolean; +} export async function secrets(entrypoint?: string, options?: SecretsOptions): Promise { // Compile the program to generate secrets file @@ -15,8 +17,14 @@ export async function secrets(entrypoint?: string, options?: SecretsOptions): Pr let secrets = fs.existsSync(secretsFile) ? JSON.parse(fs.readFileSync(path.join(outdir, "secrets.json"), "utf-8")) : []; process.env.WING_SOURCE_DIR = cwd(); + let secretValues: any = {}; - console.log(`${secrets.length} secret(s) found in ${entrypoint}\n`); + console.log(`${secrets.length} secret(s) found\n`); + + if (options?.list) { + console.log("- "+secrets.join("\n- ")); + return; + } if (secrets.length === 0) { return; diff --git a/libs/wingsdk/src/cloud/secret.ts b/libs/wingsdk/src/cloud/secret.ts index f7b8abc6a21..936e758bc79 100644 --- a/libs/wingsdk/src/cloud/secret.ts +++ b/libs/wingsdk/src/cloud/secret.ts @@ -50,6 +50,7 @@ export class Secret extends Resource { this._name = props.name; } + /** get secret name */ public get name(): string | undefined { return this._name; } From b15787539da3543ca19e809adb4d6b901473b4d4 Mon Sep 17 00:00:00 2001 From: Hasan Date: Wed, 17 Apr 2024 14:21:41 -0400 Subject: [PATCH 08/29] test --- libs/compatibility-spy/test/spy.test.ts | 26 +++++++++---------- libs/wingsdk/src/platform/platform-manager.ts | 8 +++--- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/libs/compatibility-spy/test/spy.test.ts b/libs/compatibility-spy/test/spy.test.ts index 3ae80b32010..9b4eb358794 100644 --- a/libs/compatibility-spy/test/spy.test.ts +++ b/libs/compatibility-spy/test/spy.test.ts @@ -15,13 +15,13 @@ describe("compatibility spy", async () => { platformPaths: ["sim", join(__dirname, "../lib")], }); - vi.spyOn(manager, "loadPlatformPath").mockImplementation( - (platformPath: string) => { - manager.platformInstances.push( - platformPath === "sim" ? new SimPlatform() : spyPlatform - ); - } - ); + // vi.spyOn(manager, "loadPlatformPath").mockImplementation( + // (platformPath: string) => { + // manager.platformInstances.push( + // platformPath === "sim" ? new SimPlatform() : spyPlatform + // ); + // } + // ); const app = manager.createApp({ entrypointDir: __dirname, @@ -41,10 +41,10 @@ describe("compatibility spy", async () => { bucket.addObject("a", "b"); bucket.public; - test("each new instance is wrapped in a proxy", () => { - expect(spyPlatform.newInstance).toBeCalledTimes(1); - expect(spyPlatform._usageContext.get("Bucket")).toEqual( - new Set(["addObject"]) - ); - }); + // test("each new instance is wrapped in a proxy", () => { + // expect(spyPlatform.newInstance).toBeCalledTimes(1); + // expect(spyPlatform._usageContext.get("Bucket")).toEqual( + // new Set(["addObject"]) + // ); + // }); }); diff --git a/libs/wingsdk/src/platform/platform-manager.ts b/libs/wingsdk/src/platform/platform-manager.ts index 6b5bacf17b3..caba9652803 100644 --- a/libs/wingsdk/src/platform/platform-manager.ts +++ b/libs/wingsdk/src/platform/platform-manager.ts @@ -170,15 +170,15 @@ export class PlatformManager { newInstanceOverrides: this.newInstanceOverridesHooks, }) as App; - let secretsIds = []; + let secretsNames = []; for (const c of app.node.findAll()) { if (c instanceof Secret) { const secret = c as Secret; - secretsIds.push(secret.name); + secretsNames.push(secret.name); } } - if (secretsIds.length > 0) { - writeFileSync(join(app.outdir, "secrets.json"), JSON.stringify(secretsIds)); + if (secretsNames.length > 0) { + writeFileSync(join(app.outdir, "secrets.json"), JSON.stringify(secretsNames)); } let registrar = app.parameters; From 40a34045e89438e0fc72bfc171f108aef0df4970 Mon Sep 17 00:00:00 2001 From: Hasan Date: Wed, 17 Apr 2024 14:33:47 -0400 Subject: [PATCH 09/29] clean up --- libs/awscdk/src/function.ts | 7 +------ libs/compatibility-spy/secrets.json | 1 - 2 files changed, 1 insertion(+), 7 deletions(-) delete mode 100644 libs/compatibility-spy/secrets.json diff --git a/libs/awscdk/src/function.ts b/libs/awscdk/src/function.ts index d0a284aff3c..5ac2a3311d8 100644 --- a/libs/awscdk/src/function.ts +++ b/libs/awscdk/src/function.ts @@ -1,4 +1,4 @@ -import { Duration, Lazy } from "aws-cdk-lib"; +import { Duration } from "aws-cdk-lib"; import { PolicyStatement as CdkPolicyStatement } from "aws-cdk-lib/aws-iam"; import { Architecture, @@ -174,11 +174,6 @@ export class Function memorySize: props.memory ?? 1024, architecture: Architecture.ARM_64, logGroup: logs, - vpcSubnets: { - subnets: Lazy.list({ - produce: () => [], - }) as any, - } }); } diff --git a/libs/compatibility-spy/secrets.json b/libs/compatibility-spy/secrets.json deleted file mode 100644 index 0637a088a01..00000000000 --- a/libs/compatibility-spy/secrets.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file From d779b781d95d8984706dd256fbddf3456f0bcade Mon Sep 17 00:00:00 2001 From: Hasan Date: Wed, 17 Apr 2024 14:54:32 -0400 Subject: [PATCH 10/29] wip --- libs/compatibility-spy/test/spy.test.ts | 26 +++++++++---------- libs/wingsdk/src/platform/platform-manager.ts | 2 ++ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/libs/compatibility-spy/test/spy.test.ts b/libs/compatibility-spy/test/spy.test.ts index 9b4eb358794..3ae80b32010 100644 --- a/libs/compatibility-spy/test/spy.test.ts +++ b/libs/compatibility-spy/test/spy.test.ts @@ -15,13 +15,13 @@ describe("compatibility spy", async () => { platformPaths: ["sim", join(__dirname, "../lib")], }); - // vi.spyOn(manager, "loadPlatformPath").mockImplementation( - // (platformPath: string) => { - // manager.platformInstances.push( - // platformPath === "sim" ? new SimPlatform() : spyPlatform - // ); - // } - // ); + vi.spyOn(manager, "loadPlatformPath").mockImplementation( + (platformPath: string) => { + manager.platformInstances.push( + platformPath === "sim" ? new SimPlatform() : spyPlatform + ); + } + ); const app = manager.createApp({ entrypointDir: __dirname, @@ -41,10 +41,10 @@ describe("compatibility spy", async () => { bucket.addObject("a", "b"); bucket.public; - // test("each new instance is wrapped in a proxy", () => { - // expect(spyPlatform.newInstance).toBeCalledTimes(1); - // expect(spyPlatform._usageContext.get("Bucket")).toEqual( - // new Set(["addObject"]) - // ); - // }); + test("each new instance is wrapped in a proxy", () => { + expect(spyPlatform.newInstance).toBeCalledTimes(1); + expect(spyPlatform._usageContext.get("Bucket")).toEqual( + new Set(["addObject"]) + ); + }); }); diff --git a/libs/wingsdk/src/platform/platform-manager.ts b/libs/wingsdk/src/platform/platform-manager.ts index caba9652803..d28ac65f2f6 100644 --- a/libs/wingsdk/src/platform/platform-manager.ts +++ b/libs/wingsdk/src/platform/platform-manager.ts @@ -156,6 +156,8 @@ export class PlatformManager { // This method is called from preflight.cjs in order to return an App instance // that can be synthesized public createApp(appProps: AppProps): App { + this.createPlatformInstances(); + let appCall = this.platformInstances[0].newApp; if (!appCall) { From 7c8b4a2224ec6d8e11f949efbb35658c68f4e4da Mon Sep 17 00:00:00 2001 From: Hasan Date: Wed, 17 Apr 2024 15:04:13 -0400 Subject: [PATCH 11/29] somthings cooking --- libs/wingsdk/src/platform/platform-manager.ts | 114 ++++++++++-------- 1 file changed, 64 insertions(+), 50 deletions(-) diff --git a/libs/wingsdk/src/platform/platform-manager.ts b/libs/wingsdk/src/platform/platform-manager.ts index d28ac65f2f6..87572c7a303 100644 --- a/libs/wingsdk/src/platform/platform-manager.ts +++ b/libs/wingsdk/src/platform/platform-manager.ts @@ -1,11 +1,10 @@ -import { readFileSync, writeFileSync } from "fs"; +import { readFileSync } from "fs"; import { basename, dirname, join } from "path"; import { cwd } from "process"; import * as vm from "vm"; import { IPlatform } from "./platform"; import { scanDirForPlatformFile } from "./util"; import { App, AppProps, SynthHooks } from "../core"; -import { Secret } from "../cloud"; interface PlatformManagerOptions { /** @@ -20,16 +19,10 @@ const BUILTIN_PLATFORMS = ["tf-aws", "tf-azure", "tf-gcp", "sim"]; export class PlatformManager { private readonly platformPaths: string[]; private readonly platformInstances: IPlatform[] = []; - private synthHooks: SynthHooks = {}; - private newInstanceOverridesHooks: any[] = []; - private parameterSchemas: any[] = []; - private storeSecretsHook: any; constructor(options: PlatformManagerOptions) { this.platformPaths = options.platformPaths ?? []; this.retrieveImplicitPlatforms(); - this.createPlatformInstances(); - this.collectHooks(); } /** @@ -104,14 +97,19 @@ export class PlatformManager { }); } - public async storeSecrets(secretNames: string[]): Promise { - if (!this.storeSecretsHook) { - throw new Error("No createSecrets method found on platform"); + // This method is called from preflight.cjs in order to return an App instance + // that can be synthesized + public createApp(appProps: AppProps): App { + this.createPlatformInstances(); + + let appCall = this.platformInstances[0].newApp; + + if (!appCall) { + throw new Error( + `No newApp method found on platform: ${this.platformPaths[0]} (Hint: The first platform provided must have a newApp method)` + ); } - return await this.storeSecretsHook(secretNames); - } - private collectHooks() { let synthHooks: SynthHooks = { preSynthesize: [], postSynthesize: [], @@ -142,50 +140,17 @@ export class PlatformManager { if (instance.newInstance) { newInstanceOverrides.push(instance.newInstance.bind(instance)); } - - if (instance.storeSecrets) { - this.storeSecretsHook = instance.storeSecrets.bind(instance); - } }); - this.synthHooks = synthHooks; - this.newInstanceOverridesHooks = newInstanceOverrides; - this.parameterSchemas = parameterSchemas; - } - - // This method is called from preflight.cjs in order to return an App instance - // that can be synthesized - public createApp(appProps: AppProps): App { - this.createPlatformInstances(); - - let appCall = this.platformInstances[0].newApp; - - if (!appCall) { - throw new Error( - `No newApp method found on platform: ${this.platformPaths[0]} (Hint: The first platform provided must have a newApp method)` - ); - } - const app = appCall!({ ...appProps, - synthHooks: this.synthHooks, - newInstanceOverrides: this.newInstanceOverridesHooks, + synthHooks, + newInstanceOverrides, }) as App; - let secretsNames = []; - for (const c of app.node.findAll()) { - if (c instanceof Secret) { - const secret = c as Secret; - secretsNames.push(secret.name); - } - } - if (secretsNames.length > 0) { - writeFileSync(join(app.outdir, "secrets.json"), JSON.stringify(secretsNames)); - } - let registrar = app.parameters; - this.parameterSchemas.forEach((schema) => { + parameterSchemas.forEach((schema) => { registrar.addSchema(schema); }); @@ -193,6 +158,7 @@ export class PlatformManager { } } + /** * Custom platforms need to be loaded into a custom context in order to * resolve their dependencies correctly. @@ -273,3 +239,51 @@ export function _loadCustomPlatform(customPlatformPath: string): any { ); } } + +export interface CollectHooksResult { + synthHooks: SynthHooks; + newInstanceOverrides: any[]; + parameterSchemas: any[]; + storeSecretsHook?: any; +} + +function collectHooks(platformInstances: IPlatform[]) { + let result: CollectHooksResult = { + synthHooks: { + preSynthesize: [], + postSynthesize: [], + validate: [], + }, + newInstanceOverrides: [], + parameterSchemas: [], + storeSecretsHook: undefined + }; + + platformInstances.forEach((instance) => { + if (instance.parameters) { + result.parameterSchemas.push(instance.parameters); + } + + if (instance.preSynth) { + result.synthHooks.preSynthesize!.push(instance.preSynth.bind(instance)); + } + + if (instance.postSynth) { + result.synthHooks.postSynthesize!.push(instance.postSynth.bind(instance)); + } + + if (instance.validate) { + result.synthHooks.validate!.push(instance.validate.bind(instance)); + } + + if (instance.newInstance) { + result.newInstanceOverrides.push(instance.newInstance.bind(instance)); + } + + if (instance.storeSecrets) { + result.storeSecretsHook = instance.storeSecrets.bind(instance); + } + }); + + return result; +} \ No newline at end of file From 7bcb0cab725469cba20f676b0b6a8b465bff36c5 Mon Sep 17 00:00:00 2001 From: Hasan Date: Wed, 17 Apr 2024 15:13:31 -0400 Subject: [PATCH 12/29] i think its gonna fix it --- libs/wingsdk/src/platform/platform-manager.ts | 50 ++++++------------- 1 file changed, 15 insertions(+), 35 deletions(-) diff --git a/libs/wingsdk/src/platform/platform-manager.ts b/libs/wingsdk/src/platform/platform-manager.ts index 87572c7a303..803b3997984 100644 --- a/libs/wingsdk/src/platform/platform-manager.ts +++ b/libs/wingsdk/src/platform/platform-manager.ts @@ -110,52 +110,32 @@ export class PlatformManager { ); } - let synthHooks: SynthHooks = { - preSynthesize: [], - postSynthesize: [], - validate: [], - }; - - let newInstanceOverrides: any[] = []; - - let parameterSchemas: any[] = []; - - this.platformInstances.forEach((instance) => { - if (instance.parameters) { - parameterSchemas.push(instance.parameters); - } - - if (instance.preSynth) { - synthHooks.preSynthesize!.push(instance.preSynth.bind(instance)); - } - - if (instance.postSynth) { - synthHooks.postSynthesize!.push(instance.postSynth.bind(instance)); - } - - if (instance.validate) { - synthHooks.validate!.push(instance.validate.bind(instance)); - } - - if (instance.newInstance) { - newInstanceOverrides.push(instance.newInstance.bind(instance)); - } - }); + let hooks = collectHooks(this.platformInstances); const app = appCall!({ ...appProps, - synthHooks, - newInstanceOverrides, + synthHooks: hooks.synthHooks, + newInstanceOverrides: hooks.newInstanceOverrides, }) as App; let registrar = app.parameters; - parameterSchemas.forEach((schema) => { + hooks.parameterSchemas.forEach((schema) => { registrar.addSchema(schema); }); return app; } + + public async storeSecrets(secretNames: string[]): Promise { + const hooks = collectHooks(this.platformInstances); + if (!hooks.storeSecretsHook) { + throw new Error( + `No storeSecrets method found on any platform` + ); + } + return await hooks.storeSecretsHook(secretNames); + } } @@ -240,7 +220,7 @@ export function _loadCustomPlatform(customPlatformPath: string): any { } } -export interface CollectHooksResult { +interface CollectHooksResult { synthHooks: SynthHooks; newInstanceOverrides: any[]; parameterSchemas: any[]; From a31bd2eca8d04740ec79f621419f44f1b0616ae0 Mon Sep 17 00:00:00 2001 From: Hasan Date: Wed, 17 Apr 2024 15:19:49 -0400 Subject: [PATCH 13/29] tidy --- libs/wingsdk/src/platform/platform-manager.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/wingsdk/src/platform/platform-manager.ts b/libs/wingsdk/src/platform/platform-manager.ts index 803b3997984..2df2a4e53e6 100644 --- a/libs/wingsdk/src/platform/platform-manager.ts +++ b/libs/wingsdk/src/platform/platform-manager.ts @@ -134,6 +134,7 @@ export class PlatformManager { `No storeSecrets method found on any platform` ); } + // @typescript-eslint/return-await return await hooks.storeSecretsHook(secretNames); } } From 5e0a3567ccf79b785a29595e818b3d52557c7daf Mon Sep 17 00:00:00 2001 From: Hasan Date: Wed, 17 Apr 2024 15:29:09 -0400 Subject: [PATCH 14/29] oops --- libs/wingsdk/src/platform/platform-manager.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libs/wingsdk/src/platform/platform-manager.ts b/libs/wingsdk/src/platform/platform-manager.ts index 2df2a4e53e6..b6194564842 100644 --- a/libs/wingsdk/src/platform/platform-manager.ts +++ b/libs/wingsdk/src/platform/platform-manager.ts @@ -127,15 +127,14 @@ export class PlatformManager { return app; } - public async storeSecrets(secretNames: string[]): Promise { + public async storeSecrets(secretNames: string[]): Promise { const hooks = collectHooks(this.platformInstances); if (!hooks.storeSecretsHook) { throw new Error( `No storeSecrets method found on any platform` ); } - // @typescript-eslint/return-await - return await hooks.storeSecretsHook(secretNames); + await hooks.storeSecretsHook(secretNames); } } From b444b6291e90334f2c2b2ac3f4c2e08bb80676b6 Mon Sep 17 00:00:00 2001 From: Hasan Date: Wed, 17 Apr 2024 15:53:07 -0400 Subject: [PATCH 15/29] fixed tests --- docs/docs/04-standard-library/cloud/secret.md | 13 +++ libs/wingsdk/src/cloud/secret.ts | 2 +- libs/wingsdk/src/platform/platform-manager.ts | 9 +- libs/wingsdk/src/platform/platform.ts | 2 +- libs/wingsdk/src/target-sim/platform.ts | 23 ++--- .../wingsdk/src/target-sim/secret.inflight.ts | 11 ++- libs/wingsdk/src/target-tf-aws/platform.ts | 37 +++++--- libs/wingsdk/src/target-tf-aws/secret.ts | 8 +- .../__snapshots__/secret.test.ts.snap | 2 +- libs/wingsdk/test/target-sim/secret.test.ts | 93 +++++++++---------- 10 files changed, 112 insertions(+), 88 deletions(-) diff --git a/docs/docs/04-standard-library/cloud/secret.md b/docs/docs/04-standard-library/cloud/secret.md index 64cf3f573ac..59d02cb808c 100644 --- a/docs/docs/04-standard-library/cloud/secret.md +++ b/docs/docs/04-standard-library/cloud/secret.md @@ -189,6 +189,7 @@ other capabilities to the inflight host. | **Name** | **Type** | **Description** | | --- | --- | --- | | node | constructs.Node | The tree node. | +| name | str | Get secret name. | --- @@ -204,6 +205,18 @@ The tree node. --- +##### `name`Optional + +```wing +name: str; +``` + +- *Type:* str + +Get secret name. + +--- + ## Structs diff --git a/libs/wingsdk/src/cloud/secret.ts b/libs/wingsdk/src/cloud/secret.ts index 936e758bc79..b7695bd2130 100644 --- a/libs/wingsdk/src/cloud/secret.ts +++ b/libs/wingsdk/src/cloud/secret.ts @@ -50,7 +50,7 @@ export class Secret extends Resource { this._name = props.name; } - /** get secret name */ + /** Get secret name */ public get name(): string | undefined { return this._name; } diff --git a/libs/wingsdk/src/platform/platform-manager.ts b/libs/wingsdk/src/platform/platform-manager.ts index b6194564842..2522f89a2de 100644 --- a/libs/wingsdk/src/platform/platform-manager.ts +++ b/libs/wingsdk/src/platform/platform-manager.ts @@ -130,15 +130,12 @@ export class PlatformManager { public async storeSecrets(secretNames: string[]): Promise { const hooks = collectHooks(this.platformInstances); if (!hooks.storeSecretsHook) { - throw new Error( - `No storeSecrets method found on any platform` - ); + throw new Error(`No storeSecrets method found on any platform`); } await hooks.storeSecretsHook(secretNames); } } - /** * Custom platforms need to be loaded into a custom context in order to * resolve their dependencies correctly. @@ -236,7 +233,7 @@ function collectHooks(platformInstances: IPlatform[]) { }, newInstanceOverrides: [], parameterSchemas: [], - storeSecretsHook: undefined + storeSecretsHook: undefined, }; platformInstances.forEach((instance) => { @@ -266,4 +263,4 @@ function collectHooks(platformInstances: IPlatform[]) { }); return result; -} \ No newline at end of file +} diff --git a/libs/wingsdk/src/platform/platform.ts b/libs/wingsdk/src/platform/platform.ts index 88460072cd1..3d08bf1388e 100644 --- a/libs/wingsdk/src/platform/platform.ts +++ b/libs/wingsdk/src/platform/platform.ts @@ -58,5 +58,5 @@ export interface IPlatform { /** * Hook for creating and storing secrets */ - storeSecrets?(secrets: {[name: string]: string}): Promise; + storeSecrets?(secrets: { [name: string]: string }): Promise; } diff --git a/libs/wingsdk/src/target-sim/platform.ts b/libs/wingsdk/src/target-sim/platform.ts index a9b3659a827..8e89f45dc00 100644 --- a/libs/wingsdk/src/target-sim/platform.ts +++ b/libs/wingsdk/src/target-sim/platform.ts @@ -1,6 +1,6 @@ +import fs from "fs"; import { App } from "./app"; import { IPlatform } from "../platform"; -import fs from "fs"; /** * Sim Platform @@ -16,27 +16,28 @@ export class Platform implements IPlatform { public async storeSecrets(secrets: { [key: string]: string }): Promise { let existingSecretsContent = ""; try { - existingSecretsContent = fs.readFileSync('./.env', 'utf8'); + existingSecretsContent = fs.readFileSync("./.env", "utf8"); } catch (error) {} - - const existingSecrets = existingSecretsContent.split('\n') - .filter(line => line.trim() !== '') + + const existingSecrets = existingSecretsContent + .split("\n") + .filter((line) => line.trim() !== "") .reduce((s, line) => { - const [key, value] = line.split('=', 2); + const [key, value] = line.split("=", 2); s[key] = value; return s; }, {} as { [key: string]: string }); - + for (const key in secrets) { existingSecrets[key] = secrets[key]; } - + const updatedContent = Object.entries(existingSecrets) .map(([key, value]) => `${key}=${value}`) - .join('\n'); + .join("\n"); + + fs.writeFileSync("./.env", updatedContent); - fs.writeFileSync('./.env', updatedContent); - console.log(`${Object.keys(secrets).length} secret(s) stored in .env`); } } diff --git a/libs/wingsdk/src/target-sim/secret.inflight.ts b/libs/wingsdk/src/target-sim/secret.inflight.ts index 8b97e7faeeb..f3520657a4b 100644 --- a/libs/wingsdk/src/target-sim/secret.inflight.ts +++ b/libs/wingsdk/src/target-sim/secret.inflight.ts @@ -16,7 +16,7 @@ export class Secret implements ISecretClient, ISimulatorResourceInstance { constructor(props: SecretSchema) { this.secretsFile = path.join(process.cwd(), ".env"); - + if (!fs.existsSync(this.secretsFile)) { throw new Error( `No secrets file found at ${this.secretsFile} while looking for secret ${props.name}` @@ -72,11 +72,12 @@ export class Secret implements ISecretClient, ISimulatorResourceInstance { } private parseEnvFile(contents: string): { [key: string]: string } { - return contents.split('\n') - .map(line => line.trim()) - .filter(line => line && !line.startsWith('#')) // Ignore empty lines and comments + return contents + .split("\n") + .map((line) => line.trim()) + .filter((line) => line && !line.startsWith("#")) // Ignore empty lines and comments .reduce((acc, line) => { - const [key, value] = line.split('=', 2); + const [key, value] = line.split("=", 2); if (key) { acc[key.trim()] = value.trim(); } diff --git a/libs/wingsdk/src/target-tf-aws/platform.ts b/libs/wingsdk/src/target-tf-aws/platform.ts index 019dbb1bb44..f679fcddf03 100644 --- a/libs/wingsdk/src/target-tf-aws/platform.ts +++ b/libs/wingsdk/src/target-tf-aws/platform.ts @@ -1,6 +1,11 @@ +import { + SecretsManagerClient, + GetSecretValueCommand, + CreateSecretCommand, + UpdateSecretCommand, +} from "@aws-sdk/client-secrets-manager"; import { App } from "./app"; import { IPlatform } from "../platform"; -import { SecretsManagerClient, GetSecretValueCommand, CreateSecretCommand, UpdateSecretCommand } from "@aws-sdk/client-secrets-manager"; /** * AWS Terraform Platform @@ -69,7 +74,9 @@ export class Platform implements IPlatform { return new App(appProps); } - public async storeSecrets(secrets: { [name: string]: string; }): Promise { + public async storeSecrets(secrets: { + [name: string]: string; + }): Promise { console.log("Storing secrets in AWS Secrets Manager"); const client = new SecretsManagerClient({}); @@ -79,18 +86,22 @@ export class Platform implements IPlatform { await client.send(new GetSecretValueCommand({ SecretId: name })); console.log(`Secret ${name} exists, updating it.`); // Update the secret if it exists - await client.send(new UpdateSecretCommand({ - SecretId: name, - SecretString: JSON.stringify({ [name]: value }), - })); + await client.send( + new UpdateSecretCommand({ + SecretId: name, + SecretString: JSON.stringify({ [name]: value }), + }) + ); } catch (error: any) { - if (error.name === 'ResourceNotFoundException') { + if (error.name === "ResourceNotFoundException") { // If the secret does not exist, create it console.log(`Secret ${name} does not exist, creating it.`); - await client.send(new CreateSecretCommand({ - Name: name, - SecretString: JSON.stringify({ [name]: value }), - })); + await client.send( + new CreateSecretCommand({ + Name: name, + SecretString: JSON.stringify({ [name]: value }), + }) + ); } else { // Log other errors console.error(`Failed to store secret ${name}:`, error); @@ -99,6 +110,8 @@ export class Platform implements IPlatform { } } - console.log(`${Object.keys(secrets).length} secret(s) stored AWS Secrets Manager`); + console.log( + `${Object.keys(secrets).length} secret(s) stored AWS Secrets Manager` + ); } } diff --git a/libs/wingsdk/src/target-tf-aws/secret.ts b/libs/wingsdk/src/target-tf-aws/secret.ts index 8a950321e2e..97c3bc1f02c 100644 --- a/libs/wingsdk/src/target-tf-aws/secret.ts +++ b/libs/wingsdk/src/target-tf-aws/secret.ts @@ -32,10 +32,10 @@ export class Secret extends cloud.Secret { name: props.name, }); } else { - this._name = ResourceNames.generateName(this, NAME_OPTS), - this.secret = new SecretsmanagerSecret(this, "Default", { - name: this._name - }); + (this._name = ResourceNames.generateName(this, NAME_OPTS)), + (this.secret = new SecretsmanagerSecret(this, "Default", { + name: this._name, + })); new TerraformOutput(this, "SecretArn", { value: this.secret.arn, 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 6fc96fd4e1f..163b6ef22f0 100644 --- a/libs/wingsdk/test/target-sim/__snapshots__/secret.test.ts.snap +++ b/libs/wingsdk/test/target-sim/__snapshots__/secret.test.ts.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`create a secret 1`] = ` +exports[`secrets > create a secret 1`] = ` { "connections.json": { "connections": [], diff --git a/libs/wingsdk/test/target-sim/secret.test.ts b/libs/wingsdk/test/target-sim/secret.test.ts index c17b68d41d9..19899edabf6 100644 --- a/libs/wingsdk/test/target-sim/secret.test.ts +++ b/libs/wingsdk/test/target-sim/secret.test.ts @@ -1,65 +1,64 @@ -import * as os from "os"; import * as path from "path"; import * as fs from "fs-extra"; -import { test, expect } from "vitest"; +import { test, expect, beforeEach, afterEach, describe } from "vitest"; import * as cloud from "../../src/cloud"; import { SimApp } from "../sim-app"; -const SECRETS_FILE = path.join(os.homedir(), ".wing", "secrets.json"); +const SECRETS_FILE = path.join(process.cwd(), ".env"); +describe("secrets", () => { + beforeEach(() => { + fs.createFileSync(SECRETS_FILE); + }); -test("create a secret", async () => { - // GIVEN - const app = new SimApp(); - new cloud.Secret(app, "my_secret"); + afterEach(() => { + fs.removeSync(SECRETS_FILE); + }); - await fs.ensureFile(SECRETS_FILE); + test("create a secret", async () => { + // GIVEN + const app = new SimApp(); + new cloud.Secret(app, "my_secret"); - const s = await app.startSimulator(); + const s = await app.startSimulator(); - // THEN - expect(s.getResourceConfig("/my_secret")).toEqual({ - attrs: { - handle: expect.any(String), - }, - path: "root/my_secret", - addr: expect.any(String), - policy: [], - props: { - name: "my_secret-c84793b7", - }, - type: cloud.SECRET_FQN, - }); - await s.stop(); + // THEN + expect(s.getResourceConfig("/my_secret")).toEqual({ + attrs: { + handle: expect.any(String), + }, + path: "root/my_secret", + addr: expect.any(String), + policy: [], + props: { + name: "my_secret-c84793b7", + }, + type: cloud.SECRET_FQN, + }); + await s.stop(); - expect(app.snapshot()).toMatchSnapshot(); -}); - -test("can get the secret value", async () => { - // GIVEN - const app = new SimApp(); - new cloud.Secret(app, "my_secret", { - name: "wing-sim-test-my-secret", + expect(app.snapshot()).toMatchSnapshot(); }); - await fs.ensureFile(SECRETS_FILE); - const secretsContent = await fs.readFile(SECRETS_FILE, "utf-8"); + test("can get the secret value", async () => { + // GIVEN + const app = new SimApp(); + new cloud.Secret(app, "my_secret", { + name: "wing-sim-test-my-secret", + }); + + const secretsContent = await fs.readFile(SECRETS_FILE, "utf-8"); - const secrets = tryParse(secretsContent) ?? {}; - await fs.writeFile( - SECRETS_FILE, - JSON.stringify({ - ...secrets, - "wing-sim-test-my-secret": "secret-value", - }) - ); + const secrets = tryParse(secretsContent) ?? {}; + await fs.writeFile(SECRETS_FILE, "wing-sim-test-my-secret=secret-value"); - const s = await app.startSimulator(); - const secretClient = s.getResource("/my_secret") as cloud.ISecretClient; + const s = await app.startSimulator(); + const secretClient = s.getResource("/my_secret") as cloud.ISecretClient; - // THEN - const secretValue = await secretClient.value(); - expect(secretValue).toBe("secret-value"); - await s.stop(); + // THEN + const secretValue = await secretClient.value(); + expect(secretValue).toBe("secret-value"); + await s.stop(); + }); }); function tryParse(content: string): string | undefined { From 82c36222f33aa4282aa7f7bd9b741396b70e8f13 Mon Sep 17 00:00:00 2001 From: Hasan Date: Wed, 17 Apr 2024 15:59:29 -0400 Subject: [PATCH 16/29] shadowing issue --- apps/wing/src/commands/secrets.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/wing/src/commands/secrets.ts b/apps/wing/src/commands/secrets.ts index ac89d7e11ea..d8c9fc7f893 100644 --- a/apps/wing/src/commands/secrets.ts +++ b/apps/wing/src/commands/secrets.ts @@ -14,23 +14,23 @@ export async function secrets(entrypoint?: string, options?: SecretsOptions): Pr const outdir = await compile(entrypoint, options); const secretsFile = path.join(outdir, "secrets.json"); - let secrets = fs.existsSync(secretsFile) ? JSON.parse(fs.readFileSync(path.join(outdir, "secrets.json"), "utf-8")) : []; + let secretNames = fs.existsSync(secretsFile) ? JSON.parse(fs.readFileSync(path.join(outdir, "secrets.json"), "utf-8")) : []; process.env.WING_SOURCE_DIR = cwd(); let secretValues: any = {}; - console.log(`${secrets.length} secret(s) found\n`); + console.log(`${secretNames.length} secret(s) found\n`); if (options?.list) { - console.log("- "+secrets.join("\n- ")); + console.log("- "+secretNames.join("\n- ")); return; } - if (secrets.length === 0) { + if (secretNames.length === 0) { return; } - for (const secret of secrets) { + for (const secret of secretNames) { const response = await inquirer.prompt([ { type: "password", From 95837879c2553645a40ec556bfc88844b0e1ad68 Mon Sep 17 00:00:00 2001 From: Hasan Date: Wed, 17 Apr 2024 16:21:01 -0400 Subject: [PATCH 17/29] fix --- apps/wing/src/commands/secrets.test.ts | 4 ++-- .../commands/test/__snapshots__/test.test.ts.snap | 6 ------ libs/wingsdk/src/platform/platform-manager.ts | 15 ++++++++++++++- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/apps/wing/src/commands/secrets.test.ts b/apps/wing/src/commands/secrets.test.ts index da4024ccd74..05dff969fbb 100644 --- a/apps/wing/src/commands/secrets.test.ts +++ b/apps/wing/src/commands/secrets.test.ts @@ -35,7 +35,7 @@ describe("secrets", () => { targetDir: workdir, }); - expect(console.log).toHaveBeenCalledWith("0 secret(s) found in main.w\n"); + expect(console.log).toHaveBeenCalledWith("0 secret(s) found\n"); }); test("secrets found", async () => { @@ -56,7 +56,7 @@ describe("secrets", () => { targetDir: workdir, }); - expect(console.log).toHaveBeenCalledWith("2 secret(s) found in main.w\n"); + expect(console.log).toHaveBeenCalledWith("2 secret(s) found\n"); }); }); \ No newline at end of file diff --git a/apps/wing/src/commands/test/__snapshots__/test.test.ts.snap b/apps/wing/src/commands/test/__snapshots__/test.test.ts.snap index e0e0bd6261e..ed39356d511 100644 --- a/apps/wing/src/commands/test/__snapshots__/test.test.ts.snap +++ b/apps/wing/src/commands/test/__snapshots__/test.test.ts.snap @@ -2,13 +2,7 @@ exports[`printing test reports > resource traces are not shown if debug mode is disabled 1`] = ` "fail ┌ hello.w » root/env0/test:test - │ [trace] Push (message=cool). │ sleeping for 500 ms - │ [trace] Sending messages (messages=[\\"cool\\"], subscriber=sim-4). - │ [trace] Invoke (payload=\\"{\\\\\\"messages\\\\\\":[\\\\\\"cool\\\\\\"]}\\"). - │ [trace] Subscriber error - returning 1 messages to queue: Missing environment variable: QUEUE_HANDLE_7164aec4 - │ [trace] Get (key=file.txt). - │ [trace] Invoke (payload=\\"\\"). └ Error: Object does not exist (key=file.txt)" `; diff --git a/libs/wingsdk/src/platform/platform-manager.ts b/libs/wingsdk/src/platform/platform-manager.ts index 2522f89a2de..ec7f1c0c0a7 100644 --- a/libs/wingsdk/src/platform/platform-manager.ts +++ b/libs/wingsdk/src/platform/platform-manager.ts @@ -1,10 +1,11 @@ -import { readFileSync } from "fs"; +import { readFileSync, writeFileSync } from "fs"; import { basename, dirname, join } from "path"; import { cwd } from "process"; import * as vm from "vm"; import { IPlatform } from "./platform"; import { scanDirForPlatformFile } from "./util"; import { App, AppProps, SynthHooks } from "../core"; +import { Secret } from "../cloud"; interface PlatformManagerOptions { /** @@ -118,6 +119,18 @@ export class PlatformManager { newInstanceOverrides: hooks.newInstanceOverrides, }) as App; + let secretNames = []; + for (const c of app.node.findAll()) { + if (c instanceof Secret) { + const secret = c as Secret; + secretNames.push(secret.name); + } + } + + if (secretNames.length > 0) { + writeFileSync(join(app.outdir, "secrets.json"), JSON.stringify(secretNames)); + } + let registrar = app.parameters; hooks.parameterSchemas.forEach((schema) => { From 6bbed5a4e5d4aaf34c28d40dc233b3f2df31deb4 Mon Sep 17 00:00:00 2001 From: Hasan Date: Wed, 17 Apr 2024 16:50:23 -0400 Subject: [PATCH 18/29] fuck circular dependencies --- apps/wing/src/commands/secrets.test.ts | 1 + libs/wingsdk/src/cloud/secret.ts | 4 +++- libs/wingsdk/src/core/types.ts | 6 ++++++ libs/wingsdk/src/platform/platform-manager.ts | 8 +++++--- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/apps/wing/src/commands/secrets.test.ts b/apps/wing/src/commands/secrets.test.ts index 05dff969fbb..89a538614fb 100644 --- a/apps/wing/src/commands/secrets.test.ts +++ b/apps/wing/src/commands/secrets.test.ts @@ -54,6 +54,7 @@ describe("secrets", () => { await secrets("main.w", { platform: [BuiltinPlatform.SIM], targetDir: workdir, + list: true, }); expect(console.log).toHaveBeenCalledWith("2 secret(s) found\n"); diff --git a/libs/wingsdk/src/cloud/secret.ts b/libs/wingsdk/src/cloud/secret.ts index b7695bd2130..51da9bedd43 100644 --- a/libs/wingsdk/src/cloud/secret.ts +++ b/libs/wingsdk/src/cloud/secret.ts @@ -1,6 +1,6 @@ import { Construct } from "constructs"; import { fqnForType } from "../constants"; -import { INFLIGHT_SYMBOL } from "../core/types"; +import { INFLIGHT_SYMBOL, SECRET_SYMBOL } from "../core/types"; import { Json, Node, Resource } from "../std"; /** @@ -33,6 +33,8 @@ export interface SecretProps { export class Secret extends Resource { /** @internal */ public [INFLIGHT_SYMBOL]?: ISecretClient; + /** @internal */ + public [SECRET_SYMBOL] = true; /** @internal */ protected _name?: string; diff --git a/libs/wingsdk/src/core/types.ts b/libs/wingsdk/src/core/types.ts index bdf0a22c7cd..e75c579485f 100644 --- a/libs/wingsdk/src/core/types.ts +++ b/libs/wingsdk/src/core/types.ts @@ -8,6 +8,12 @@ export { Construct } from "constructs"; /** Flag to signify the `inflight` side of a `preflight` object */ export const INFLIGHT_SYMBOL: unique symbol = Symbol("@winglang/sdk.inflight"); +/** This symbol is not defined in cloud/secrets.ts due to circular dependencies + * between cloud/secrets.ts and platform/platform-manager.ts, which need to be revisited + * in the meantime this dependency inversion is used to avoid the circular dependency +*/ +export const SECRET_SYMBOL = Symbol("@winglang/sdk.cloud.Secret"); + /** `preflight` representation of an `inflight` */ export type Inflight = IInflight & { /** Note: This is not actually callable, diff --git a/libs/wingsdk/src/platform/platform-manager.ts b/libs/wingsdk/src/platform/platform-manager.ts index ec7f1c0c0a7..f6f159e97bb 100644 --- a/libs/wingsdk/src/platform/platform-manager.ts +++ b/libs/wingsdk/src/platform/platform-manager.ts @@ -5,7 +5,7 @@ import * as vm from "vm"; import { IPlatform } from "./platform"; import { scanDirForPlatformFile } from "./util"; import { App, AppProps, SynthHooks } from "../core"; -import { Secret } from "../cloud"; +import { SECRET_SYMBOL } from "../core/types"; interface PlatformManagerOptions { /** @@ -121,8 +121,10 @@ export class PlatformManager { let secretNames = []; for (const c of app.node.findAll()) { - if (c instanceof Secret) { - const secret = c as Secret; + // This duplicates the symbol from ../cloud/secret.ts, we cant import it here + // due to circular dependencies, which need to be revisited + if ((c as any)[SECRET_SYMBOL]) { + const secret = c as any; secretNames.push(secret.name); } } From 88c09ca23ee6fe5af79678ba291ef5bff6d102ed Mon Sep 17 00:00:00 2001 From: "monada-bot[bot]" Date: Wed, 17 Apr 2024 21:01:20 +0000 Subject: [PATCH 19/29] chore: self mutation (build.diff) Signed-off-by: monada-bot[bot] --- apps/wing/src/commands/secrets.test.ts | 5 ++--- apps/wing/src/commands/secrets.ts | 16 +++++++++------- .../incomplete_inflight_namespace.snap | 2 +- .../completions/namespace_middle_dot.snap | 2 +- .../completions/new_expression_nested.snap | 2 +- .../partial_type_reference_annotation.snap | 2 +- .../variable_type_annotation_namespace.snap | 2 +- libs/wingsdk/src/core/types.ts | 4 ++-- libs/wingsdk/src/platform/platform-manager.ts | 5 ++++- 9 files changed, 22 insertions(+), 18 deletions(-) diff --git a/apps/wing/src/commands/secrets.test.ts b/apps/wing/src/commands/secrets.test.ts index 89a538614fb..dbc5d079ac8 100644 --- a/apps/wing/src/commands/secrets.test.ts +++ b/apps/wing/src/commands/secrets.test.ts @@ -3,8 +3,8 @@ import { join } from "path"; // import inquirer from "inquirer"; import { BuiltinPlatform } from "@winglang/compiler"; import { describe, expect, test, vitest, beforeEach, afterEach, vi } from "vitest"; -import { generateTmpDir } from "../util"; import { secrets } from "../commands/secrets"; +import { generateTmpDir } from "../util"; vitest.mock("inquirer"); @@ -59,5 +59,4 @@ describe("secrets", () => { expect(console.log).toHaveBeenCalledWith("2 secret(s) found\n"); }); - -}); \ No newline at end of file +}); diff --git a/apps/wing/src/commands/secrets.ts b/apps/wing/src/commands/secrets.ts index d8c9fc7f893..b15aa405d5f 100644 --- a/apps/wing/src/commands/secrets.ts +++ b/apps/wing/src/commands/secrets.ts @@ -1,9 +1,9 @@ -import { PlatformManager } from "@winglang/sdk/lib/platform"; -import { CompileOptions, compile } from "./compile"; import fs from "fs"; import path from "path"; import { cwd } from "process"; +import { PlatformManager } from "@winglang/sdk/lib/platform"; import inquirer from "inquirer"; +import { CompileOptions, compile } from "./compile"; export interface SecretsOptions extends CompileOptions { readonly list?: boolean; @@ -14,15 +14,17 @@ export async function secrets(entrypoint?: string, options?: SecretsOptions): Pr const outdir = await compile(entrypoint, options); const secretsFile = path.join(outdir, "secrets.json"); - let secretNames = fs.existsSync(secretsFile) ? JSON.parse(fs.readFileSync(path.join(outdir, "secrets.json"), "utf-8")) : []; + let secretNames = fs.existsSync(secretsFile) + ? JSON.parse(fs.readFileSync(path.join(outdir, "secrets.json"), "utf-8")) + : []; process.env.WING_SOURCE_DIR = cwd(); - + let secretValues: any = {}; console.log(`${secretNames.length} secret(s) found\n`); if (options?.list) { - console.log("- "+secretNames.join("\n- ")); + console.log("- " + secretNames.join("\n- ")); return; } @@ -41,6 +43,6 @@ export async function secrets(entrypoint?: string, options?: SecretsOptions): Pr secretValues[secret] = response.value; } - const plaformManager = new PlatformManager({platformPaths: options?.platform}) + const plaformManager = new PlatformManager({ platformPaths: options?.platform }); await plaformManager.storeSecrets(secretValues); -} \ No newline at end of file +} diff --git a/libs/wingc/src/lsp/snapshots/completions/incomplete_inflight_namespace.snap b/libs/wingc/src/lsp/snapshots/completions/incomplete_inflight_namespace.snap index ec5f074cc87..3e2a90098aa 100644 --- a/libs/wingc/src/lsp/snapshots/completions/incomplete_inflight_namespace.snap +++ b/libs/wingc/src/lsp/snapshots/completions/incomplete_inflight_namespace.snap @@ -59,7 +59,7 @@ source: libs/wingc/src/lsp/completions.rs kind: 7 documentation: kind: markdown - value: "```wing\nclass Secret\n```\n---\nA cloud secret.\n\n### Initializer\n- `...props` — `SecretProps?`\n \n - `name?` — `str?` — The secret's name.\n### Fields\n- `node` — `Node` — The tree node.\n### Methods\n- `isConstruct` — `preflight (x: any): bool` — Checks if `x` is a construct.\n- `onLift` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this resource inflight.\n- `onLiftType` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this type inflight.\n- `toString` — `preflight (): str` — Returns a string representation of this construct.\n- `value` — `inflight (options: GetSecretValueOptions?): str` — Retrieve the value of the secret.\n- `valueJson` — `inflight (options: GetSecretValueOptions?): Json` — Retrieve the Json value of the secret." + value: "```wing\nclass Secret\n```\n---\nA cloud secret.\n\n### Initializer\n- `...props` — `SecretProps?`\n \n - `name?` — `str?` — The secret's name.\n### Fields\n- `node` — `Node` — The tree node.\n- `name?` — `str?` — Get secret name.\n### Methods\n- `isConstruct` — `preflight (x: any): bool` — Checks if `x` is a construct.\n- `onLift` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this resource inflight.\n- `onLiftType` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this type inflight.\n- `toString` — `preflight (): str` — Returns a string representation of this construct.\n- `value` — `inflight (options: GetSecretValueOptions?): str` — Retrieve the value of the secret.\n- `valueJson` — `inflight (options: GetSecretValueOptions?): Json` — Retrieve the Json value of the secret." sortText: gg|Secret - label: Service kind: 7 diff --git a/libs/wingc/src/lsp/snapshots/completions/namespace_middle_dot.snap b/libs/wingc/src/lsp/snapshots/completions/namespace_middle_dot.snap index ec5f074cc87..3e2a90098aa 100644 --- a/libs/wingc/src/lsp/snapshots/completions/namespace_middle_dot.snap +++ b/libs/wingc/src/lsp/snapshots/completions/namespace_middle_dot.snap @@ -59,7 +59,7 @@ source: libs/wingc/src/lsp/completions.rs kind: 7 documentation: kind: markdown - value: "```wing\nclass Secret\n```\n---\nA cloud secret.\n\n### Initializer\n- `...props` — `SecretProps?`\n \n - `name?` — `str?` — The secret's name.\n### Fields\n- `node` — `Node` — The tree node.\n### Methods\n- `isConstruct` — `preflight (x: any): bool` — Checks if `x` is a construct.\n- `onLift` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this resource inflight.\n- `onLiftType` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this type inflight.\n- `toString` — `preflight (): str` — Returns a string representation of this construct.\n- `value` — `inflight (options: GetSecretValueOptions?): str` — Retrieve the value of the secret.\n- `valueJson` — `inflight (options: GetSecretValueOptions?): Json` — Retrieve the Json value of the secret." + value: "```wing\nclass Secret\n```\n---\nA cloud secret.\n\n### Initializer\n- `...props` — `SecretProps?`\n \n - `name?` — `str?` — The secret's name.\n### Fields\n- `node` — `Node` — The tree node.\n- `name?` — `str?` — Get secret name.\n### Methods\n- `isConstruct` — `preflight (x: any): bool` — Checks if `x` is a construct.\n- `onLift` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this resource inflight.\n- `onLiftType` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this type inflight.\n- `toString` — `preflight (): str` — Returns a string representation of this construct.\n- `value` — `inflight (options: GetSecretValueOptions?): str` — Retrieve the value of the secret.\n- `valueJson` — `inflight (options: GetSecretValueOptions?): Json` — Retrieve the Json value of the secret." sortText: gg|Secret - label: Service kind: 7 diff --git a/libs/wingc/src/lsp/snapshots/completions/new_expression_nested.snap b/libs/wingc/src/lsp/snapshots/completions/new_expression_nested.snap index 2f4723ceb43..a196fcee6ea 100644 --- a/libs/wingc/src/lsp/snapshots/completions/new_expression_nested.snap +++ b/libs/wingc/src/lsp/snapshots/completions/new_expression_nested.snap @@ -104,7 +104,7 @@ source: libs/wingc/src/lsp/completions.rs kind: 7 documentation: kind: markdown - value: "```wing\nclass Secret\n```\n---\nA cloud secret.\n\n### Initializer\n- `...props` — `SecretProps?`\n \n - `name?` — `str?` — The secret's name.\n### Fields\n- `node` — `Node` — The tree node.\n### Methods\n- `isConstruct` — `preflight (x: any): bool` — Checks if `x` is a construct.\n- `onLift` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this resource inflight.\n- `onLiftType` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this type inflight.\n- `toString` — `preflight (): str` — Returns a string representation of this construct.\n- `value` — `inflight (options: GetSecretValueOptions?): str` — Retrieve the value of the secret.\n- `valueJson` — `inflight (options: GetSecretValueOptions?): Json` — Retrieve the Json value of the secret." + value: "```wing\nclass Secret\n```\n---\nA cloud secret.\n\n### Initializer\n- `...props` — `SecretProps?`\n \n - `name?` — `str?` — The secret's name.\n### Fields\n- `node` — `Node` — The tree node.\n- `name?` — `str?` — Get secret name.\n### Methods\n- `isConstruct` — `preflight (x: any): bool` — Checks if `x` is a construct.\n- `onLift` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this resource inflight.\n- `onLiftType` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this type inflight.\n- `toString` — `preflight (): str` — Returns a string representation of this construct.\n- `value` — `inflight (options: GetSecretValueOptions?): str` — Retrieve the value of the secret.\n- `valueJson` — `inflight (options: GetSecretValueOptions?): Json` — Retrieve the Json value of the secret." sortText: gg|Secret insertText: Secret($1) insertTextFormat: 2 diff --git a/libs/wingc/src/lsp/snapshots/completions/partial_type_reference_annotation.snap b/libs/wingc/src/lsp/snapshots/completions/partial_type_reference_annotation.snap index ec5f074cc87..3e2a90098aa 100644 --- a/libs/wingc/src/lsp/snapshots/completions/partial_type_reference_annotation.snap +++ b/libs/wingc/src/lsp/snapshots/completions/partial_type_reference_annotation.snap @@ -59,7 +59,7 @@ source: libs/wingc/src/lsp/completions.rs kind: 7 documentation: kind: markdown - value: "```wing\nclass Secret\n```\n---\nA cloud secret.\n\n### Initializer\n- `...props` — `SecretProps?`\n \n - `name?` — `str?` — The secret's name.\n### Fields\n- `node` — `Node` — The tree node.\n### Methods\n- `isConstruct` — `preflight (x: any): bool` — Checks if `x` is a construct.\n- `onLift` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this resource inflight.\n- `onLiftType` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this type inflight.\n- `toString` — `preflight (): str` — Returns a string representation of this construct.\n- `value` — `inflight (options: GetSecretValueOptions?): str` — Retrieve the value of the secret.\n- `valueJson` — `inflight (options: GetSecretValueOptions?): Json` — Retrieve the Json value of the secret." + value: "```wing\nclass Secret\n```\n---\nA cloud secret.\n\n### Initializer\n- `...props` — `SecretProps?`\n \n - `name?` — `str?` — The secret's name.\n### Fields\n- `node` — `Node` — The tree node.\n- `name?` — `str?` — Get secret name.\n### Methods\n- `isConstruct` — `preflight (x: any): bool` — Checks if `x` is a construct.\n- `onLift` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this resource inflight.\n- `onLiftType` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this type inflight.\n- `toString` — `preflight (): str` — Returns a string representation of this construct.\n- `value` — `inflight (options: GetSecretValueOptions?): str` — Retrieve the value of the secret.\n- `valueJson` — `inflight (options: GetSecretValueOptions?): Json` — Retrieve the Json value of the secret." sortText: gg|Secret - label: Service kind: 7 diff --git a/libs/wingc/src/lsp/snapshots/completions/variable_type_annotation_namespace.snap b/libs/wingc/src/lsp/snapshots/completions/variable_type_annotation_namespace.snap index ec5f074cc87..3e2a90098aa 100644 --- a/libs/wingc/src/lsp/snapshots/completions/variable_type_annotation_namespace.snap +++ b/libs/wingc/src/lsp/snapshots/completions/variable_type_annotation_namespace.snap @@ -59,7 +59,7 @@ source: libs/wingc/src/lsp/completions.rs kind: 7 documentation: kind: markdown - value: "```wing\nclass Secret\n```\n---\nA cloud secret.\n\n### Initializer\n- `...props` — `SecretProps?`\n \n - `name?` — `str?` — The secret's name.\n### Fields\n- `node` — `Node` — The tree node.\n### Methods\n- `isConstruct` — `preflight (x: any): bool` — Checks if `x` is a construct.\n- `onLift` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this resource inflight.\n- `onLiftType` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this type inflight.\n- `toString` — `preflight (): str` — Returns a string representation of this construct.\n- `value` — `inflight (options: GetSecretValueOptions?): str` — Retrieve the value of the secret.\n- `valueJson` — `inflight (options: GetSecretValueOptions?): Json` — Retrieve the Json value of the secret." + value: "```wing\nclass Secret\n```\n---\nA cloud secret.\n\n### Initializer\n- `...props` — `SecretProps?`\n \n - `name?` — `str?` — The secret's name.\n### Fields\n- `node` — `Node` — The tree node.\n- `name?` — `str?` — Get secret name.\n### Methods\n- `isConstruct` — `preflight (x: any): bool` — Checks if `x` is a construct.\n- `onLift` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this resource inflight.\n- `onLiftType` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this type inflight.\n- `toString` — `preflight (): str` — Returns a string representation of this construct.\n- `value` — `inflight (options: GetSecretValueOptions?): str` — Retrieve the value of the secret.\n- `valueJson` — `inflight (options: GetSecretValueOptions?): Json` — Retrieve the Json value of the secret." sortText: gg|Secret - label: Service kind: 7 diff --git a/libs/wingsdk/src/core/types.ts b/libs/wingsdk/src/core/types.ts index e75c579485f..a65e1950fa2 100644 --- a/libs/wingsdk/src/core/types.ts +++ b/libs/wingsdk/src/core/types.ts @@ -8,10 +8,10 @@ export { Construct } from "constructs"; /** Flag to signify the `inflight` side of a `preflight` object */ export const INFLIGHT_SYMBOL: unique symbol = Symbol("@winglang/sdk.inflight"); -/** This symbol is not defined in cloud/secrets.ts due to circular dependencies +/** This symbol is not defined in cloud/secrets.ts due to circular dependencies * between cloud/secrets.ts and platform/platform-manager.ts, which need to be revisited * in the meantime this dependency inversion is used to avoid the circular dependency -*/ + */ export const SECRET_SYMBOL = Symbol("@winglang/sdk.cloud.Secret"); /** `preflight` representation of an `inflight` */ diff --git a/libs/wingsdk/src/platform/platform-manager.ts b/libs/wingsdk/src/platform/platform-manager.ts index f6f159e97bb..45b467acc75 100644 --- a/libs/wingsdk/src/platform/platform-manager.ts +++ b/libs/wingsdk/src/platform/platform-manager.ts @@ -130,7 +130,10 @@ export class PlatformManager { } if (secretNames.length > 0) { - writeFileSync(join(app.outdir, "secrets.json"), JSON.stringify(secretNames)); + writeFileSync( + join(app.outdir, "secrets.json"), + JSON.stringify(secretNames) + ); } let registrar = app.parameters; From 5d828615f6d4b16e9f38b70b2e3742f24b4dbce3 Mon Sep 17 00:00:00 2001 From: Hasan Date: Wed, 17 Apr 2024 17:11:55 -0400 Subject: [PATCH 20/29] docs --- docs/docs/06-tools/01-cli.md | 25 +++++++++++++++++++ libs/wingsdk/src/cloud/secret.md | 11 ++++---- libs/wingsdk/src/platform/platform-manager.ts | 5 +--- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/docs/docs/06-tools/01-cli.md b/docs/docs/06-tools/01-cli.md index 4ee92994d18..415d631e82b 100644 --- a/docs/docs/06-tools/01-cli.md +++ b/docs/docs/06-tools/01-cli.md @@ -239,6 +239,31 @@ This will compile your current Wing directory, and bundle it as a tarball that c See [Libraries](../05-libraries.md) for more details on packaging and consuming Wing libraries. +::: + +## Store Secrets: `wing secrets` + +The `wing secrets` command can be used to store secrets needed by your application. The method of storing secrets depends on the target platform. + +Take the following Wing application: + +```js +// main.w +bring cloud; + +let secret = new cloud.Secret(name: "slack-token"); +``` + +Usage: + +```sh +$ wing secrets main.w + +1 secret(s) found + +? Enter the secret value for slack-token: [hidden] +``` + ## Environment Variables For development and testing, Wing can automatically read environment variables from `.env` files in your current working directory. These environment variables can be accessed in Wing code using `util.env` and `util.tryEnv` in both preflight. In inflight these functions can also be used but note that the variables are not automatically available, if desired they must be passed explicitly when used like in `cloud.Function`. diff --git a/libs/wingsdk/src/cloud/secret.md b/libs/wingsdk/src/cloud/secret.md index f0a321a3f3d..ddd8a5129f1 100644 --- a/libs/wingsdk/src/cloud/secret.md +++ b/libs/wingsdk/src/cloud/secret.md @@ -52,15 +52,14 @@ new cloud.Function(inflight () => { ### Simulator (`sim`) -When using a secret in Wing's simulator, a secrets file must be added to your home directory at `~/.wing/secrets.json`. +When using a secret in Wing's simulator, a secrets file must be added to your project in a file called: `.env`. The simulator will look up secrets in this file by their `name`. -Secrets should be saved in a JSON format: +Secrets should be saved in a key=value format: ```json -// secrets.json -{ - "my-api-key": "1234567890" -} +// .env +my-api-key=1234567890 +secret-key=secret-value ``` ### AWS (`tf-aws` and `awscdk`) diff --git a/libs/wingsdk/src/platform/platform-manager.ts b/libs/wingsdk/src/platform/platform-manager.ts index 45b467acc75..755e8eca083 100644 --- a/libs/wingsdk/src/platform/platform-manager.ts +++ b/libs/wingsdk/src/platform/platform-manager.ts @@ -24,6 +24,7 @@ export class PlatformManager { constructor(options: PlatformManagerOptions) { this.platformPaths = options.platformPaths ?? []; this.retrieveImplicitPlatforms(); + this.createPlatformInstances(); } /** @@ -101,8 +102,6 @@ export class PlatformManager { // This method is called from preflight.cjs in order to return an App instance // that can be synthesized public createApp(appProps: AppProps): App { - this.createPlatformInstances(); - let appCall = this.platformInstances[0].newApp; if (!appCall) { @@ -121,8 +120,6 @@ export class PlatformManager { let secretNames = []; for (const c of app.node.findAll()) { - // This duplicates the symbol from ../cloud/secret.ts, we cant import it here - // due to circular dependencies, which need to be revisited if ((c as any)[SECRET_SYMBOL]) { const secret = c as any; secretNames.push(secret.name); From 961847fc34ba7cbcf532b57eb1f59530b1506e9f Mon Sep 17 00:00:00 2001 From: Hasan Date: Wed, 17 Apr 2024 17:21:01 -0400 Subject: [PATCH 21/29] tidy --- apps/wing/src/commands/secrets.test.ts | 1 - libs/wingsdk/src/target-tf-aws/platform.ts | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/wing/src/commands/secrets.test.ts b/apps/wing/src/commands/secrets.test.ts index dbc5d079ac8..b2c12f4af3e 100644 --- a/apps/wing/src/commands/secrets.test.ts +++ b/apps/wing/src/commands/secrets.test.ts @@ -1,6 +1,5 @@ import { writeFile } from "fs/promises"; import { join } from "path"; -// import inquirer from "inquirer"; import { BuiltinPlatform } from "@winglang/compiler"; import { describe, expect, test, vitest, beforeEach, afterEach, vi } from "vitest"; import { secrets } from "../commands/secrets"; diff --git a/libs/wingsdk/src/target-tf-aws/platform.ts b/libs/wingsdk/src/target-tf-aws/platform.ts index f679fcddf03..0651bf0999a 100644 --- a/libs/wingsdk/src/target-tf-aws/platform.ts +++ b/libs/wingsdk/src/target-tf-aws/platform.ts @@ -85,7 +85,6 @@ export class Platform implements IPlatform { // Attempt to retrieve the secret to check if it exists await client.send(new GetSecretValueCommand({ SecretId: name })); console.log(`Secret ${name} exists, updating it.`); - // Update the secret if it exists await client.send( new UpdateSecretCommand({ SecretId: name, @@ -103,9 +102,8 @@ export class Platform implements IPlatform { }) ); } else { - // Log other errors console.error(`Failed to store secret ${name}:`, error); - throw error; // Re-throw the error if it is not related to the secret not existing + throw error; } } } From 774c3a7d4d3a713f063712ef72035eca1204abeb Mon Sep 17 00:00:00 2001 From: Hasan Date: Wed, 17 Apr 2024 17:43:30 -0400 Subject: [PATCH 22/29] fix compatibility spy --- libs/wingsdk/src/platform/platform-manager.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libs/wingsdk/src/platform/platform-manager.ts b/libs/wingsdk/src/platform/platform-manager.ts index 755e8eca083..adceb27884a 100644 --- a/libs/wingsdk/src/platform/platform-manager.ts +++ b/libs/wingsdk/src/platform/platform-manager.ts @@ -19,7 +19,7 @@ const BUILTIN_PLATFORMS = ["tf-aws", "tf-azure", "tf-gcp", "sim"]; /** @internal */ export class PlatformManager { private readonly platformPaths: string[]; - private readonly platformInstances: IPlatform[] = []; + private platformInstances: IPlatform[] = []; constructor(options: PlatformManagerOptions) { this.platformPaths = options.platformPaths ?? []; @@ -94,6 +94,7 @@ export class PlatformManager { } private createPlatformInstances() { + this.platformInstances = []; this.platformPaths.forEach((platformPath) => { this.loadPlatformPath(platformPath); }); @@ -102,6 +103,7 @@ export class PlatformManager { // This method is called from preflight.cjs in order to return an App instance // that can be synthesized public createApp(appProps: AppProps): App { + this.createPlatformInstances(); let appCall = this.platformInstances[0].newApp; if (!appCall) { From 605910daf9a94228686036cd7d62d2b3a5499e7c Mon Sep 17 00:00:00 2001 From: "monada-bot[bot]" Date: Wed, 17 Apr 2024 21:54:26 +0000 Subject: [PATCH 23/29] chore: self mutation (build.diff) Signed-off-by: monada-bot[bot] --- docs/docs/04-standard-library/cloud/secret.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/docs/04-standard-library/cloud/secret.md b/docs/docs/04-standard-library/cloud/secret.md index 59d02cb808c..2dc62f2e8e2 100644 --- a/docs/docs/04-standard-library/cloud/secret.md +++ b/docs/docs/04-standard-library/cloud/secret.md @@ -52,15 +52,14 @@ new cloud.Function(inflight () => { ### Simulator (`sim`) -When using a secret in Wing's simulator, a secrets file must be added to your home directory at `~/.wing/secrets.json`. +When using a secret in Wing's simulator, a secrets file must be added to your project in a file called: `.env`. The simulator will look up secrets in this file by their `name`. -Secrets should be saved in a JSON format: +Secrets should be saved in a key=value format: ```json -// secrets.json -{ - "my-api-key": "1234567890" -} +// .env +my-api-key=1234567890 +secret-key=secret-value ``` ### AWS (`tf-aws` and `awscdk`) From ef611b96a1a378b5c98b21d1ffb184822ee3755d Mon Sep 17 00:00:00 2001 From: Hasan Date: Thu, 18 Apr 2024 16:30:01 -0400 Subject: [PATCH 24/29] adding const --- apps/wing/src/commands/secrets.ts | 6 +++--- libs/wingsdk/src/platform/platform-manager.ts | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/wing/src/commands/secrets.ts b/apps/wing/src/commands/secrets.ts index b15aa405d5f..e766f6ec89d 100644 --- a/apps/wing/src/commands/secrets.ts +++ b/apps/wing/src/commands/secrets.ts @@ -1,7 +1,7 @@ import fs from "fs"; import path from "path"; import { cwd } from "process"; -import { PlatformManager } from "@winglang/sdk/lib/platform"; +import { PlatformManager, SECRETS_FILE_NAME } from "@winglang/sdk/lib/platform"; import inquirer from "inquirer"; import { CompileOptions, compile } from "./compile"; @@ -12,10 +12,10 @@ export interface SecretsOptions extends CompileOptions { export async function secrets(entrypoint?: string, options?: SecretsOptions): Promise { // Compile the program to generate secrets file const outdir = await compile(entrypoint, options); - const secretsFile = path.join(outdir, "secrets.json"); + const secretsFile = path.join(outdir, SECRETS_FILE_NAME); let secretNames = fs.existsSync(secretsFile) - ? JSON.parse(fs.readFileSync(path.join(outdir, "secrets.json"), "utf-8")) + ? JSON.parse(fs.readFileSync(secretsFile, "utf-8")) : []; process.env.WING_SOURCE_DIR = cwd(); diff --git a/libs/wingsdk/src/platform/platform-manager.ts b/libs/wingsdk/src/platform/platform-manager.ts index adceb27884a..8038d7aef8a 100644 --- a/libs/wingsdk/src/platform/platform-manager.ts +++ b/libs/wingsdk/src/platform/platform-manager.ts @@ -16,6 +16,9 @@ interface PlatformManagerOptions { const BUILTIN_PLATFORMS = ["tf-aws", "tf-azure", "tf-gcp", "sim"]; +/** @internal */ +export const SECRETS_FILE_NAME = "secrets.json"; + /** @internal */ export class PlatformManager { private readonly platformPaths: string[]; @@ -130,7 +133,7 @@ export class PlatformManager { if (secretNames.length > 0) { writeFileSync( - join(app.outdir, "secrets.json"), + join(app.outdir, SECRETS_FILE_NAME), JSON.stringify(secretNames) ); } From b039defe49a1003facefb6a6dceb8c751a5db4e6 Mon Sep 17 00:00:00 2001 From: Hasan Date: Fri, 19 Apr 2024 12:16:40 -0400 Subject: [PATCH 25/29] add type --- apps/wing/src/commands/secrets.ts | 2 +- libs/wingsdk/src/platform/platform-manager.ts | 4 ++-- libs/wingsdk/src/target-sim/platform.ts | 2 +- libs/wingsdk/src/target-tf-aws/platform.ts | 4 +--- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/wing/src/commands/secrets.ts b/apps/wing/src/commands/secrets.ts index e766f6ec89d..cafe1554f53 100644 --- a/apps/wing/src/commands/secrets.ts +++ b/apps/wing/src/commands/secrets.ts @@ -20,7 +20,7 @@ export async function secrets(entrypoint?: string, options?: SecretsOptions): Pr process.env.WING_SOURCE_DIR = cwd(); - let secretValues: any = {}; + let secretValues: Record = {}; console.log(`${secretNames.length} secret(s) found\n`); if (options?.list) { diff --git a/libs/wingsdk/src/platform/platform-manager.ts b/libs/wingsdk/src/platform/platform-manager.ts index 8038d7aef8a..77816182585 100644 --- a/libs/wingsdk/src/platform/platform-manager.ts +++ b/libs/wingsdk/src/platform/platform-manager.ts @@ -147,12 +147,12 @@ export class PlatformManager { return app; } - public async storeSecrets(secretNames: string[]): Promise { + public async storeSecrets(secrets: Record): Promise { const hooks = collectHooks(this.platformInstances); if (!hooks.storeSecretsHook) { throw new Error(`No storeSecrets method found on any platform`); } - await hooks.storeSecretsHook(secretNames); + await hooks.storeSecretsHook(secrets); } } diff --git a/libs/wingsdk/src/target-sim/platform.ts b/libs/wingsdk/src/target-sim/platform.ts index 8e89f45dc00..66e7ef6223c 100644 --- a/libs/wingsdk/src/target-sim/platform.ts +++ b/libs/wingsdk/src/target-sim/platform.ts @@ -13,7 +13,7 @@ export class Platform implements IPlatform { return new App(appProps); } - public async storeSecrets(secrets: { [key: string]: string }): Promise { + public async storeSecrets(secrets: Record): Promise { let existingSecretsContent = ""; try { existingSecretsContent = fs.readFileSync("./.env", "utf8"); diff --git a/libs/wingsdk/src/target-tf-aws/platform.ts b/libs/wingsdk/src/target-tf-aws/platform.ts index 0651bf0999a..0035c269fa5 100644 --- a/libs/wingsdk/src/target-tf-aws/platform.ts +++ b/libs/wingsdk/src/target-tf-aws/platform.ts @@ -74,9 +74,7 @@ export class Platform implements IPlatform { return new App(appProps); } - public async storeSecrets(secrets: { - [name: string]: string; - }): Promise { + public async storeSecrets(secrets: Record): Promise { console.log("Storing secrets in AWS Secrets Manager"); const client = new SecretsManagerClient({}); From 95c4456af3b19027d17002306307d0258e34fa5d Mon Sep 17 00:00:00 2001 From: Hasan Date: Mon, 22 Apr 2024 15:05:08 -0400 Subject: [PATCH 26/29] updates from pr review --- libs/wingsdk/src/platform/platform-manager.ts | 2 +- .../wingsdk/src/target-sim/secret.inflight.ts | 21 +++---------------- libs/wingsdk/src/target-tf-aws/platform.ts | 19 +++++++++++------ 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/libs/wingsdk/src/platform/platform-manager.ts b/libs/wingsdk/src/platform/platform-manager.ts index 77816182585..becd17b6777 100644 --- a/libs/wingsdk/src/platform/platform-manager.ts +++ b/libs/wingsdk/src/platform/platform-manager.ts @@ -150,7 +150,7 @@ export class PlatformManager { public async storeSecrets(secrets: Record): Promise { const hooks = collectHooks(this.platformInstances); if (!hooks.storeSecretsHook) { - throw new Error(`No storeSecrets method found on any platform`); + throw new Error(`Cannot find a platform or platform extension that supports storing secrets`); } await hooks.storeSecretsHook(secrets); } diff --git a/libs/wingsdk/src/target-sim/secret.inflight.ts b/libs/wingsdk/src/target-sim/secret.inflight.ts index f3520657a4b..c6521dca11e 100644 --- a/libs/wingsdk/src/target-sim/secret.inflight.ts +++ b/libs/wingsdk/src/target-sim/secret.inflight.ts @@ -57,31 +57,16 @@ export class Secret implements ISecretClient, ISimulatorResourceInstance { timestamp: new Date().toISOString(), }); - const secretsContent = fs.readFileSync(this.secretsFile, "utf-8"); - const secrets = this.parseEnvFile(secretsContent); + const secretValue = process.env[this.name]; - if (!secrets[this.name]) { + if (!secretValue) { throw new Error(`No secret value for secret ${this.name}`); } - return secrets[this.name]; + return secretValue; } public async valueJson(): Promise { return JSON.parse(await this.value()); } - - private parseEnvFile(contents: string): { [key: string]: string } { - return contents - .split("\n") - .map((line) => line.trim()) - .filter((line) => line && !line.startsWith("#")) // Ignore empty lines and comments - .reduce((acc, line) => { - const [key, value] = line.split("=", 2); - if (key) { - acc[key.trim()] = value.trim(); - } - return acc; - }, {} as { [key: string]: string }); - } } diff --git a/libs/wingsdk/src/target-tf-aws/platform.ts b/libs/wingsdk/src/target-tf-aws/platform.ts index 0035c269fa5..89fee442a69 100644 --- a/libs/wingsdk/src/target-tf-aws/platform.ts +++ b/libs/wingsdk/src/target-tf-aws/platform.ts @@ -1,9 +1,9 @@ -import { - SecretsManagerClient, - GetSecretValueCommand, - CreateSecretCommand, - UpdateSecretCommand, -} from "@aws-sdk/client-secrets-manager"; +// import { +// SecretsManagerClient, +// GetSecretValueCommand, +// CreateSecretCommand, +// UpdateSecretCommand, +// } from "@aws-sdk/client-secrets-manager"; import { App } from "./app"; import { IPlatform } from "../platform"; @@ -75,6 +75,13 @@ export class Platform implements IPlatform { } public async storeSecrets(secrets: Record): Promise { + const { + SecretsManagerClient, + GetSecretValueCommand, + CreateSecretCommand, + UpdateSecretCommand, + } = await import("@aws-sdk/client-secrets-manager"); + console.log("Storing secrets in AWS Secrets Manager"); const client = new SecretsManagerClient({}); From 8c65fcccad8e9e1bbc4d67f00f03f80928bd7405 Mon Sep 17 00:00:00 2001 From: Hasan Date: Mon, 22 Apr 2024 15:20:58 -0400 Subject: [PATCH 27/29] fix --- libs/wingsdk/src/target-tf-aws/platform.ts | 6 ------ libs/wingsdk/test/target-sim/secret.test.ts | 13 ++++++++----- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/libs/wingsdk/src/target-tf-aws/platform.ts b/libs/wingsdk/src/target-tf-aws/platform.ts index 89fee442a69..1163e679db9 100644 --- a/libs/wingsdk/src/target-tf-aws/platform.ts +++ b/libs/wingsdk/src/target-tf-aws/platform.ts @@ -1,9 +1,3 @@ -// import { -// SecretsManagerClient, -// GetSecretValueCommand, -// CreateSecretCommand, -// UpdateSecretCommand, -// } from "@aws-sdk/client-secrets-manager"; import { App } from "./app"; import { IPlatform } from "../platform"; diff --git a/libs/wingsdk/test/target-sim/secret.test.ts b/libs/wingsdk/test/target-sim/secret.test.ts index 19899edabf6..037d8436c28 100644 --- a/libs/wingsdk/test/target-sim/secret.test.ts +++ b/libs/wingsdk/test/target-sim/secret.test.ts @@ -45,12 +45,15 @@ describe("secrets", () => { new cloud.Secret(app, "my_secret", { name: "wing-sim-test-my-secret", }); - - const secretsContent = await fs.readFile(SECRETS_FILE, "utf-8"); - - const secrets = tryParse(secretsContent) ?? {}; + await fs.writeFile(SECRETS_FILE, "wing-sim-test-my-secret=secret-value"); + const secretsContent = fs.readFileSync(SECRETS_FILE, "utf-8"); + secretsContent.split("\n").forEach((line) => { + const [key, value] = line.split("="); + process.env[key] = value; + }); + const s = await app.startSimulator(); const secretClient = s.getResource("/my_secret") as cloud.ISecretClient; @@ -68,4 +71,4 @@ function tryParse(content: string): string | undefined { } catch (err) { return undefined; } -} +} \ No newline at end of file From b6da7494fd62c47a054249b34d506ebe4926cdc0 Mon Sep 17 00:00:00 2001 From: Hasan Date: Mon, 22 Apr 2024 15:44:22 -0400 Subject: [PATCH 28/29] fix source dir issue --- apps/wing/src/commands/secrets.ts | 3 +-- libs/wingsdk/src/target-sim/platform.ts | 7 +++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/wing/src/commands/secrets.ts b/apps/wing/src/commands/secrets.ts index cafe1554f53..b9fae26ddf0 100644 --- a/apps/wing/src/commands/secrets.ts +++ b/apps/wing/src/commands/secrets.ts @@ -1,6 +1,5 @@ import fs from "fs"; import path from "path"; -import { cwd } from "process"; import { PlatformManager, SECRETS_FILE_NAME } from "@winglang/sdk/lib/platform"; import inquirer from "inquirer"; import { CompileOptions, compile } from "./compile"; @@ -18,7 +17,7 @@ export async function secrets(entrypoint?: string, options?: SecretsOptions): Pr ? JSON.parse(fs.readFileSync(secretsFile, "utf-8")) : []; - process.env.WING_SOURCE_DIR = cwd(); + process.env.WING_SOURCE_DIR = path.resolve(path.dirname(entrypoint ?? "main.w")); let secretValues: Record = {}; console.log(`${secretNames.length} secret(s) found\n`); diff --git a/libs/wingsdk/src/target-sim/platform.ts b/libs/wingsdk/src/target-sim/platform.ts index 66e7ef6223c..fdef259d76f 100644 --- a/libs/wingsdk/src/target-sim/platform.ts +++ b/libs/wingsdk/src/target-sim/platform.ts @@ -1,6 +1,7 @@ import fs from "fs"; import { App } from "./app"; import { IPlatform } from "../platform"; +import { join } from "path"; /** * Sim Platform @@ -15,8 +16,10 @@ export class Platform implements IPlatform { public async storeSecrets(secrets: Record): Promise { let existingSecretsContent = ""; + const envFile = join(process.env.WING_SOURCE_DIR!, ".env"); + try { - existingSecretsContent = fs.readFileSync("./.env", "utf8"); + existingSecretsContent = fs.readFileSync(envFile, "utf8"); } catch (error) {} const existingSecrets = existingSecretsContent @@ -36,7 +39,7 @@ export class Platform implements IPlatform { .map(([key, value]) => `${key}=${value}`) .join("\n"); - fs.writeFileSync("./.env", updatedContent); + fs.writeFileSync(envFile, updatedContent); console.log(`${Object.keys(secrets).length} secret(s) stored in .env`); } From 8a66197ad88d59f8ab820f1daa16ff24977922b9 Mon Sep 17 00:00:00 2001 From: "monada-bot[bot]" Date: Mon, 22 Apr 2024 19:55:18 +0000 Subject: [PATCH 29/29] chore: self mutation (build.diff) Signed-off-by: monada-bot[bot] --- libs/wingsdk/src/platform/platform-manager.ts | 4 +++- libs/wingsdk/src/target-sim/platform.ts | 4 ++-- libs/wingsdk/test/target-sim/secret.test.ts | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/libs/wingsdk/src/platform/platform-manager.ts b/libs/wingsdk/src/platform/platform-manager.ts index becd17b6777..f4c957272c3 100644 --- a/libs/wingsdk/src/platform/platform-manager.ts +++ b/libs/wingsdk/src/platform/platform-manager.ts @@ -150,7 +150,9 @@ export class PlatformManager { public async storeSecrets(secrets: Record): Promise { const hooks = collectHooks(this.platformInstances); if (!hooks.storeSecretsHook) { - throw new Error(`Cannot find a platform or platform extension that supports storing secrets`); + throw new Error( + `Cannot find a platform or platform extension that supports storing secrets` + ); } await hooks.storeSecretsHook(secrets); } diff --git a/libs/wingsdk/src/target-sim/platform.ts b/libs/wingsdk/src/target-sim/platform.ts index fdef259d76f..112fcd8c869 100644 --- a/libs/wingsdk/src/target-sim/platform.ts +++ b/libs/wingsdk/src/target-sim/platform.ts @@ -1,7 +1,7 @@ import fs from "fs"; +import { join } from "path"; import { App } from "./app"; import { IPlatform } from "../platform"; -import { join } from "path"; /** * Sim Platform @@ -17,7 +17,7 @@ export class Platform implements IPlatform { public async storeSecrets(secrets: Record): Promise { let existingSecretsContent = ""; const envFile = join(process.env.WING_SOURCE_DIR!, ".env"); - + try { existingSecretsContent = fs.readFileSync(envFile, "utf8"); } catch (error) {} diff --git a/libs/wingsdk/test/target-sim/secret.test.ts b/libs/wingsdk/test/target-sim/secret.test.ts index 037d8436c28..a0a94ceb83b 100644 --- a/libs/wingsdk/test/target-sim/secret.test.ts +++ b/libs/wingsdk/test/target-sim/secret.test.ts @@ -45,7 +45,7 @@ describe("secrets", () => { new cloud.Secret(app, "my_secret", { name: "wing-sim-test-my-secret", }); - + await fs.writeFile(SECRETS_FILE, "wing-sim-test-my-secret=secret-value"); const secretsContent = fs.readFileSync(SECRETS_FILE, "utf-8"); @@ -71,4 +71,4 @@ function tryParse(content: string): string | undefined { } catch (err) { return undefined; } -} \ No newline at end of file +}