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
81 changes: 81 additions & 0 deletions src/gcp/runv2.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,13 @@
httpsTrigger: {},
environmentVariables: { FOO: "bar" },
};
const expectedServiceInput = JSON.parse(

Check warning on line 104 in src/gcp/runv2.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe assignment of an `any` value
JSON.stringify({
...BASE_RUN_SERVICE,
name: `projects/${PROJECT_ID}/locations/${LOCATION}/services/${FUNCTION_ID.toLowerCase()}`,
}),
);
expectedServiceInput.template.containers[0].env.unshift({ name: "FOO", value: "bar" });

Check warning on line 110 in src/gcp/runv2.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe call of an `any` typed value

Check warning on line 110 in src/gcp/runv2.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe member access .template on an `any` value

expect(runv2.serviceFromEndpoint(endpoint, IMAGE_URI)).to.deep.equal(expectedServiceInput);
});
Expand All @@ -120,13 +120,13 @@
{ key: "MY_SECRET", secret: "secret-name", projectId: PROJECT_ID, version: "1" },
],
};
const expectedServiceInput = JSON.parse(

Check warning on line 123 in src/gcp/runv2.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe assignment of an `any` value
JSON.stringify({
...BASE_RUN_SERVICE,
name: `projects/${PROJECT_ID}/locations/${LOCATION}/services/${FUNCTION_ID.toLowerCase()}`,
}),
);
expectedServiceInput.template.containers[0].env.unshift({

Check warning on line 129 in src/gcp/runv2.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe call of an `any` typed value

Check warning on line 129 in src/gcp/runv2.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe member access .template on an `any` value
name: "MY_SECRET",
valueSource: { secretKeyRef: { secret: "secret-name", version: "1" } },
});
Expand All @@ -140,13 +140,13 @@
minInstances: 1,
maxInstances: 10,
};
const expectedServiceInput = JSON.parse(

Check warning on line 143 in src/gcp/runv2.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe assignment of an `any` value
JSON.stringify({
...BASE_RUN_SERVICE,
name: `projects/${PROJECT_ID}/locations/${LOCATION}/services/${FUNCTION_ID.toLowerCase()}`,
}),
);
expectedServiceInput.scaling = {

Check warning on line 149 in src/gcp/runv2.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe member access .scaling on an `any` value
minInstanceCount: 1,
maxInstanceCount: 10,
};
Expand All @@ -160,13 +160,13 @@
httpsTrigger: {},
concurrency: 50,
};
const expectedServiceInput = JSON.parse(

Check warning on line 163 in src/gcp/runv2.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe assignment of an `any` value
JSON.stringify({
...BASE_RUN_SERVICE,
name: `projects/${PROJECT_ID}/locations/${LOCATION}/services/${FUNCTION_ID.toLowerCase()}`,
}),
);
expectedServiceInput.template.maxInstanceRequestConcurrency = 50;

Check warning on line 169 in src/gcp/runv2.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe member access .template on an `any` value

expect(runv2.serviceFromEndpoint(endpoint, IMAGE_URI)).to.deep.equal(expectedServiceInput);
});
Expand Down Expand Up @@ -470,6 +470,87 @@

expect(runv2.endpointFromService(service)).to.deep.equal(expectedEndpoint);
});

it("should normalize ID for GCF v2 services to use dashes", () => {
const service: Omit<runv2.Service, runv2.ServiceOutputFields> = {
...BASE_RUN_SERVICE,
name: `projects/${PROJECT_ID}/locations/${LOCATION}/services/${SERVICE_ID}`,
labels: {
[runv2.RUNTIME_LABEL]: latest("nodejs"),
[runv2.CLIENT_NAME_LABEL]: "cloud-functions",
},
annotations: {
[runv2.FUNCTION_ID_ANNOTATION]: "v2.helloWorldNode",
},
template: {
containers: [
{
name: "worker",
image: IMAGE_URI,
resources: { limits: { cpu: "1", memory: "256Mi" } },
},
],
},
};

const result = runv2.endpointFromService(service);
expect(result.platform).to.equal("gcfv2");
expect(result.id).to.equal("v2-helloWorldNode");
});

it("should NOT normalize underscores in ID for GCF v2 services", () => {
const service: Omit<runv2.Service, runv2.ServiceOutputFields> = {
...BASE_RUN_SERVICE,
name: `projects/${PROJECT_ID}/locations/${LOCATION}/services/${SERVICE_ID}`,
labels: {
[runv2.RUNTIME_LABEL]: latest("nodejs"),
[runv2.CLIENT_NAME_LABEL]: "cloud-functions",
},
annotations: {
[runv2.FUNCTION_ID_ANNOTATION]: "v2_helloWorldNode",
},
template: {
containers: [
{
name: "worker",
image: IMAGE_URI,
resources: { limits: { cpu: "1", memory: "256Mi" } },
},
],
},
};

const result = runv2.endpointFromService(service);
expect(result.platform).to.equal("gcfv2");
expect(result.id).to.equal("v2_helloWorldNode");
});

it("should NOT normalize ID for non-GCF services", () => {
const service: Omit<runv2.Service, runv2.ServiceOutputFields> = {
...BASE_RUN_SERVICE,
name: `projects/${PROJECT_ID}/locations/${LOCATION}/services/${SERVICE_ID}`,
labels: {
[runv2.RUNTIME_LABEL]: latest("nodejs"),
[runv2.CLIENT_NAME_LABEL]: "firebase-functions",
},
annotations: {
[runv2.FUNCTION_ID_ANNOTATION]: "v2.helloWorldNode",
},
template: {
containers: [
{
name: "worker",
image: IMAGE_URI,
resources: { limits: { cpu: "1", memory: "256Mi" } },
},
],
},
};

const result = runv2.endpointFromService(service);
expect(result.platform).to.equal("run");
expect(result.id).to.equal("v2.helloWorldNode");
});
});

describe("listServices", () => {
Expand Down
22 changes: 16 additions & 6 deletions src/gcp/runv2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -585,23 +585,33 @@ export function endpointFromService(service: Omit<Service, ServiceOutputFields>)
(e) => "value" in e,
) as [PlaintextEnvVar[], SecretEnvVar[]];

const id =
const platform =
service.labels?.[CLIENT_NAME_LABEL] === "cloud-functions" ||
service.labels?.[CLIENT_NAME_LABEL] === "cloudfunctions"
? "gcfv2"
: "run";

const rawId =
metadata.functionId ||
service.annotations?.[FUNCTION_ID_ANNOTATION] ||
service.annotations?.[FUNCTION_TARGET_ANNOTATION] ||
env.find((e) => e.name === FUNCTION_TARGET_ENV)?.value ||
svcId;
// GCF v2 API normalizes function IDs by replacing dots with dashes (e.g. 'v2.helloWorld'
// -> 'v2-helloWorld') because GCF resource names do not allow dots.
// We must normalize IDs loaded from Cloud Run services for GCF v2 to match this behavior,
// preventing duplicate endpoints in the existing backend. We do NOT normalize for 'run'
// platform (like Dart functions) because their local specs preserve the original IDs.
// Note: GCF v2 does support underscores, so we only normalize dots.
const id = (platform === "gcfv2" ? rawId?.replace(/\./g, "-") : rawId) ?? "";

const memory = mebibytes(service.template.containers![0]!.resources!.limits!.memory!);
if (!backend.isValidMemoryOption(memory)) {
logger.debug("Converting a service to an endpoint with an invalid memory option", memory);
}
const cpu = Number(service.template.containers![0]!.resources!.limits!.cpu);
const endpoint: backend.Endpoint = {
platform:
service.labels?.[CLIENT_NAME_LABEL] === "cloud-functions" ||
service.labels?.[CLIENT_NAME_LABEL] === "cloudfunctions"
? "gcfv2"
: "run",
platform,
id,
project,
labels: { ...service.labels, "deployment-tool": "cli-firebase" },
Expand Down
Loading