Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions src/deploy/functions/build.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,88 @@ describe("applyEnvSecretBindings", () => {
});
});

describe("matchSecretEnvVars", () => {
it("updates secretEnvVars in a Build with resource IDs from Secret params", () => {
const testBuild: build.Build = {
endpoints: {
func: {
region: "us-central1",
project: "test-project",
platform: "gcfv2",
runtime: "nodejs18",
entryPoint: "func1",
httpsTrigger: {},
secretEnvironmentVariables: [
{
key: "foo",
secret: "foo",
projectId: "test-project",
},
],
},
},
params: [
{
type: "secret",
name: "foo",
resourceId: "bar",
version: "2",
inLocalEnvironment: true,
},
],
requiredAPIs: [],
};
build.matchSecretEnvVars(testBuild);
expect(testBuild.endpoints["func"].secretEnvironmentVariables).to.deep.equal([
{
key: "foo",
secret: "bar",
projectId: "test-project",
},
]);
});

it("is case-insensitive when matching secret names and env vars", () => {
const testBuild: build.Build = {
endpoints: {
func: {
region: "us-central1",
project: "test-project",
platform: "gcfv2",
runtime: "nodejs18",
entryPoint: "func1",
httpsTrigger: {},
secretEnvironmentVariables: [
{
key: "foo",
secret: "foo",
projectId: "test-project",
},
],
},
},
params: [
{
type: "secret",
name: "FOO",
resourceId: "bar",
version: "2",
inLocalEnvironment: true,
},
],
requiredAPIs: [],
};
build.matchSecretEnvVars(testBuild);
expect(testBuild.endpoints["func"].secretEnvironmentVariables).to.deep.equal([
{
key: "foo",
secret: "bar",
projectId: "test-project",
},
]);
});
});

describe("applyPrefix", () => {
const createTestBuild = (): build.Build => ({
endpoints: {
Expand Down
39 changes: 38 additions & 1 deletion src/deploy/functions/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,8 @@
opts.isEmulator,
opts.force,
);
// build.secretEnvVars and build.params may be out of sync after param resolution, if new secret resources were created during it
matchSecretEnvVars(opts.build);

return { backend: toBackend(opts.build, paramValues), envs: paramValues, secretRefs: secretRefs };
}
Expand Down Expand Up @@ -514,7 +516,7 @@
// List param, we try resolving a String param instead.
try {
regions = params.resolveList(bdEndpoint.region, paramValues);
} catch (err: any) {

Check warning on line 519 in src/deploy/functions/build.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unexpected any. Specify a different type
if (err instanceof ExprParseError) {
regions = [params.resolveString(bdEndpoint.region, paramValues)];
} else {
Expand Down Expand Up @@ -804,7 +806,7 @@
* /version can be omitted and will cause the secret to resolve to whatever the latest version was at time of deploy.
*
* For each binding imported from the .env file,
* 1) TODO: Check if a conflicting SecretParam with the same name exists. If so, override the param so that the prompting flow will look in the right place when deciding whether or not to create a new Secret.
* 1) Check if a conflicting SecretParam with the same name exists. If so, override the param so that the prompting flow will look in the right place when deciding whether or not to create a new Secret.
* 2) Upsert the binding directly into the Build's SecretEnvVars, which will cause it to be actually available in process.ENV
*/
export function applyEnvSecretBindings(
Expand Down Expand Up @@ -863,6 +865,41 @@
}
}

/**
* Updates the SecretEnvVars of a Build to use the same backing resources as its Secret Params.
*
* This is usually ensured by applyEnvSecretBindings, which reconciles the state of SecretEnvVars and
* Secrets before param handling. However, param handling itself can change the Secrets in the case where
* the user is prompted to create a new Secret resource, and selects a non-default resource ID.
*
* This function is a no-op in all other cases.
*/
export function matchSecretEnvVars(build: Build): void {
for (const param of build.params) {
if (param.type !== "secret") {
continue;
}
const secretParam = param;
if (!secretParam.resourceId) {
continue;
}

for (const endpoint of Object.values(build.endpoints)) {
for (const envVar of endpoint.secretEnvironmentVariables ?? []) {
if (envVar.key.toUpperCase() !== secretParam.name.toUpperCase()) {
continue;
}
if (envVar.secret !== secretParam.resourceId) {
logger.debug(
`Inserted newly created secret into build.SecretEnvVars: ${envVar.key}=${secretParam.resourceId}`,
);
envVar.secret = secretParam.resourceId;
}
}
}
}
Comment on lines +877 to +900

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jetski thinks we can flatten this more:

export function matchSecretEnvVars(build: Build): void {
  const secretParamsByKey = new Map<string, SecretParam>();
  for (const param of build.params) {
    if (param.type === "secret" && param.resourceId) {
      secretParamsByKey.set(param.name.toUpperCase(), param);
    }
  }
  if (secretParamsByKey.size === 0) return;

  for (const endpoint of Object.values(build.endpoints)) {
    for (const envVar of endpoint.secretEnvironmentVariables ?? []) {
      const secretParam = secretParamsByKey.get(envVar.key.toUpperCase());
      if (secretParam && envVar.secret !== secretParam.resourceId) {
        logger.debug(
          `Inserted newly created secret into build.SecretEnvVars: ${envVar.key}=${secretParam.resourceId}`,
        );
        envVar.secret = secretParam.resourceId;
      }
    }
  }
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure Record is preferred to Map in this repo?

More seriously, I don't agree with Jetski here and I don't even think this suggestion would compile without some fairly ugly surgery: in the context of this file, SecretParam is build.SecretParam which contains just the wire-level representation of a secret that we get from the SDK. For the types to line up here the Map would have to be declared with the params.SecretParam type, which isn't currently exported.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point on both counts! I didn't catch that SecretParam in build.ts was a local type collision with params.ts. Since you have already pushed the test cases, going ahead with the approval

}
Comment on lines +877 to +901

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but it came through applyEnvSecretBindings, so we know--whatever, fine


/**
* Parses any of the supported formats used to refer to a Secret in .env:
* API_KEY=<secret-id>
Expand Down
Loading