Skip to content

Commit

Permalink
Merge of #6235
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored Apr 23, 2024
2 parents fa4a7a5 + 8a66197 commit 43251c4
Show file tree
Hide file tree
Showing 22 changed files with 423 additions and 115 deletions.
17 changes: 17 additions & 0 deletions apps/wing/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,23 @@ async function main() {
.hook("preAction", collectAnalyticsHook)
.action(runSubCommand("compile"));

program
.command("secrets")
.description("Manage secrets")
.argument("[entrypoint]", "program .w entrypoint")
.option(
"-t, --platform <platform> --platform <platform>",
"Target platform provider (builtin: sim, tf-aws, tf-azure, tf-gcp, awscdk)",
collectPlatformVariadic,
DEFAULT_PLATFORM
)
.option("-v, --value <value>", "Platform-specific value in the form KEY=VALUE", addValue, [])
.option("--values <file>", "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"));

program
.command("test")
.description(
Expand Down
61 changes: 61 additions & 0 deletions apps/wing/src/commands/secrets.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { writeFile } from "fs/promises";
import { join } from "path";
import { BuiltinPlatform } from "@winglang/compiler";
import { describe, expect, test, vitest, beforeEach, afterEach, vi } from "vitest";
import { secrets } from "../commands/secrets";
import { generateTmpDir } from "../util";

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 secret(s) found\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,
list: true,
});

expect(console.log).toHaveBeenCalledWith("2 secret(s) found\n");
});
});
47 changes: 47 additions & 0 deletions apps/wing/src/commands/secrets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import fs from "fs";
import path from "path";
import { PlatformManager, SECRETS_FILE_NAME } from "@winglang/sdk/lib/platform";
import inquirer from "inquirer";
import { CompileOptions, compile } from "./compile";

export interface SecretsOptions extends CompileOptions {
readonly list?: boolean;
}

export async function secrets(entrypoint?: string, options?: SecretsOptions): Promise<void> {
// Compile the program to generate secrets file
const outdir = await compile(entrypoint, options);
const secretsFile = path.join(outdir, SECRETS_FILE_NAME);

let secretNames = fs.existsSync(secretsFile)
? JSON.parse(fs.readFileSync(secretsFile, "utf-8"))
: [];

process.env.WING_SOURCE_DIR = path.resolve(path.dirname(entrypoint ?? "main.w"));

let secretValues: Record<string, string> = {};
console.log(`${secretNames.length} secret(s) found\n`);

if (options?.list) {
console.log("- " + secretNames.join("\n- "));
return;
}

if (secretNames.length === 0) {
return;
}

for (const secret of secretNames) {
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 });
await plaformManager.storeSecrets(secretValues);
}
24 changes: 18 additions & 6 deletions docs/docs/04-standard-library/cloud/secret.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down Expand Up @@ -189,6 +188,7 @@ other capabilities to the inflight host.
| **Name** | **Type** | **Description** |
| --- | --- | --- |
| <code><a href="#@winglang/sdk.cloud.Secret.property.node">node</a></code> | <code>constructs.Node</code> | The tree node. |
| <code><a href="#@winglang/sdk.cloud.Secret.property.name">name</a></code> | <code>str</code> | Get secret name. |

---

Expand All @@ -204,6 +204,18 @@ The tree node.

---

##### `name`<sup>Optional</sup> <a name="name" id="@winglang/sdk.cloud.Secret.property.name"></a>

```wing
name: str;
```

- *Type:* str

Get secret name.

---



## Structs <a name="Structs" id="Structs"></a>
Expand Down
25 changes: 25 additions & 0 deletions docs/docs/06-tools/01-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<str>): 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<str>): 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<str>): 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<str>): 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<str>): 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<str>): 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<str>): 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<str>): 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<str>): 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<str>): 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<str>): 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<str>): 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<str>): 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<str>): 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<str>): 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<str>): 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<str>): 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<str>): 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<str>): 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<str>): 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
Expand Down
11 changes: 5 additions & 6 deletions libs/wingsdk/src/cloud/secret.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down
14 changes: 12 additions & 2 deletions libs/wingsdk/src/cloud/secret.ts
Original file line number Diff line number Diff line change
@@ -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";

/**
Expand Down Expand Up @@ -33,6 +33,11 @@ export interface SecretProps {
export class Secret extends Resource {
/** @internal */
public [INFLIGHT_SYMBOL]?: ISecretClient;
/** @internal */
public [SECRET_SYMBOL] = true;

/** @internal */
protected _name?: string;

constructor(scope: Construct, id: string, props: SecretProps = {}) {
if (new.target === Secret) {
Expand All @@ -44,7 +49,12 @@ export class Secret extends Resource {
Node.of(this).title = "Secret";
Node.of(this).description = "A cloud secret";

props;
this._name = props.name;
}

/** Get secret name */
public get name(): string | undefined {
return this._name;
}
}

Expand Down
6 changes: 6 additions & 0 deletions libs/wingsdk/src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<F extends AsyncFunction> = IInflight & {
/** Note: This is not actually callable,
Expand Down
Loading

0 comments on commit 43251c4

Please sign in to comment.