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
98 changes: 98 additions & 0 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -321,3 +321,101 @@ jobs:
"commit-sha": "${{ github.sha }}",
"managed-by": "github-actions"
}

worker_pools:
runs-on: 'ubuntu-latest'
timeout-minutes: 7

steps:
- uses: 'actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683' # ratchet:actions/checkout@v4

- name: 'Compute worker_pool name'
run: |-
echo "WORKER_POOL_NAME=${GITHUB_JOB}-worker-pool-${GITHUB_SHA::7}-${GITHUB_RUN_NUMBER}" >> "${GITHUB_ENV}"

- uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4
with:
node-version-file: 'package.json'

- run: 'npm ci && npm run build'

- uses: 'google-github-actions/auth@v3' # ratchet:exclude
with:
workload_identity_provider: '${{ vars.WIF_PROVIDER_NAME }}'
service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}'

- id: 'deploy-cloudrun'
name: 'Deploy'
uses: './'
with:
image: 'us-docker.pkg.dev/cloudrun/container/worker-pool:latest'
gcloud_component: 'beta'
worker_pool: '${{ env.WORKER_POOL_NAME }}'
env_vars: |-
FOO=bar
ZIP=zap\,with|separators\,and&stuff
secrets: |-
MY_SECRET=${{ vars.SECRET_NAME }}:latest
MY_SECOND_SECRET=${{ vars.SECRET_NAME }}:1
labels: |-
label1=value1
label2=value2
skip_default_labels: true
flags: '--cpu=2'

- name: 'Run initial deploy tests'
run: 'npm run e2e-tests'
env:
PROJECT_ID: '${{ vars.PROJECT_ID }}'
WORKER_POOL: '${{ env.WORKER_POOL_NAME }}'
ENV: |-
{
"FOO": "bar",
"ZIP": "zap,with|separators,and&stuff"
}
SECRET_ENV: |-
{
"MY_SECRET": "${{ vars.SECRET_NAME }}:latest",
"MY_SECOND_SECRET": "${{ vars.SECRET_NAME }}:1"
}
LABELS: |-
{
"label1": "value1",
"label2": "value2"
}

- id: 'deploy-cloudrun-again'
name: 'Deploy again'
uses: './'
with:
image: 'us-docker.pkg.dev/cloudrun/container/worker-pool:latest'
gcloud_component: 'beta'
worker_pool: '${{ env.WORKER_POOL_NAME }}'
env_vars: |-
ABC=123
DEF=456
env_vars_update_strategy: 'overwrite'
secrets: |-
/api/secrets/my-secret=${{ vars.SECRET_NAME }}:latest

- name: 'Run re-deploy tests'
run: 'npm run e2e-tests'
env:
PROJECT_ID: '${{ vars.PROJECT_ID }}'
WORKER_POOL: '${{ env.WORKER_POOL_NAME }}'
ENV: |-
{
"ABC": "123",
"DEF": "456"
}
SECRET_VOLUMES: |-
{
"/api/secrets/my-secret": "${{ vars.SECRET_NAME }}:latest"
}
LABELS: |-
{
"label1": "value1",
"label2": "value2",
"commit-sha": "${{ github.sha }}",
"managed-by": "github-actions"
}
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,16 @@ jobs:
<!-- BEGIN_AUTOGEN_INPUTS -->

- <a name="__input_service"></a><a href="#user-content-__input_service"><code>service</code></a>: _(Optional)_ ID of the service or fully-qualified identifier of the service. This is
required unless providing `metadata` or `job`.
required unless providing `metadata`, `worker-pool` or `job`.

- <a name="__input_job"></a><a href="#user-content-__input_job"><code>job</code></a>: _(Optional)_ ID of the job or fully-qualified identifier of the job. This is required
unless providing `metadata` or `service`.
unless providing `metadata`, `worker-pool` or `service`.

- <a name="__input_worker_pool"></a><a href="#user-content-__input_service"><code>worker-pool</code></a>: _(Optional)_ ID of the service or fully-qualified identifier of the worker-pool. This is
required unless providing `metadata`, `service` or `job`.

- <a name="__input_metadata"></a><a href="#user-content-__input_metadata"><code>metadata</code></a>: _(Optional)_ YAML service description for the Cloud Run service. This is required
unless providing `service` or `job`.
unless providing `service`, `worker-pool` or `job`.

- <a name="__input_image"></a><a href="#user-content-__input_image"><code>image</code></a>: _(Optional)_ (Required, unless providing `metadata` or `source`) Fully-qualified name
of the container image to deploy. For example:
Expand Down
6 changes: 6 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ inputs:
unless providing `metadata` or `service`.
required: false

worker_pool:
description: |-
ID of the worker pool or fully-qualified identifier of the worker pool. This is required
unless providing `metadata`, `service` or `job`.
required: false

metadata:
description: |-
YAML service description for the Cloud Run service. This is required
Expand Down
2 changes: 1 addition & 1 deletion dist/main/index.js

Large diffs are not rendered by default.

42 changes: 36 additions & 6 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export async function run(): Promise<void> {
const image = getInput('image'); // Image ie us-docker.pkg.dev/...
let service = getInput('service'); // Service name
const job = getInput('job'); // Job name
const workerPool = getInput('worker_pool'); // Worker pool name
const metadata = getInput('metadata'); // YAML file
const projectId = getInput('project_id');
const gcloudVersion = await computeGcloudVersion(getInput('gcloud_version'));
Expand Down Expand Up @@ -132,8 +133,9 @@ export async function run(): Promise<void> {
if (source && image) {
throw new Error('Only one of `source` or `image` inputs can be set.');
}
if (service && job) {
throw new Error('Only one of `service` or `job` inputs can be set.');
const exclusiveKindInputs = [service, job, workerPool].filter(Boolean);
if (exclusiveKindInputs.length > 1) {
throw new Error('Only one of `service`, `job`, or `worker_pool` inputs can be set.');
}

// Validate gcloud component input
Expand Down Expand Up @@ -164,6 +166,8 @@ export async function run(): Promise<void> {
deployCmd = ['run', 'services', 'replace', metadata];
} else if (kind === 'Job') {
deployCmd = ['run', 'jobs', 'replace', metadata];
} else if (kind === 'WorkerPool') {
deployCmd = ['run', 'worker-pools', 'replace', metadata];
} else {
throw new Error(`Unkown metadata type "${kind}", expected "Job" or "Service"`);
}
Expand Down Expand Up @@ -206,6 +210,29 @@ export async function run(): Promise<void> {
if (compiledLabels && Object.keys(compiledLabels).length > 0) {
deployCmd.push('--labels', joinKVStringForGCloud(compiledLabels));
}
} else if (workerPool) {
logWarning(
`Support for Cloud Run worker pools in this GitHub Action is in beta and is ` +
`not covered by the semver backwards compatibility guarantee.`,
);
deployCmd = ['run', 'worker-pools', 'deploy', workerPool];

if (image) {
deployCmd.push('--image', image);
} else if (source) {
deployCmd.push('--source', source);
}

// Set optional flags from inputs
setEnvVarsFlags(deployCmd, envVars, envVarsUpdateStrategy);
setSecretsFlags(deployCmd, secrets, secretsUpdateStrategy);

// Compile the labels
const defLabels = skipDefaultLabels ? {} : defaultLabels();
const compiledLabels = Object.assign({}, defLabels, labels);
if (compiledLabels && Object.keys(compiledLabels).length > 0) {
deployCmd.push('--update-labels', joinKVStringForGCloud(compiledLabels));
}
} else {
deployCmd = ['run', 'deploy', service];

Expand Down Expand Up @@ -243,7 +270,10 @@ export async function run(): Promise<void> {
deployCmd.push('--format', 'json');
updateTrafficCmd.push('--format', 'json');

if (region?.length > 0) {
// Worker pool replace doesn't have region flag https://cloud.google.com/sdk/gcloud/reference/beta/run/worker-pools/replace
const isWorkerPoolMetadata = metadata && deployCmd.includes('worker-pools');

if (region?.length > 0 && !isWorkerPoolMetadata) {
const regions = region
.flat()
.filter((e) => e !== undefined && e !== null && e !== '')
Expand Down Expand Up @@ -310,7 +340,7 @@ export async function run(): Promise<void> {
const errMsg =
deployCmdExec.stderr ||
`command exited ${deployCmdExec.exitCode}, but stderr had no output`;
throw new Error(`failed to deploy: ${errMsg}, full command:\n\t${commandString}`);
throw new Error(`failed to execute gcloud command \`${commandString}\`: ${errMsg}`);
}
setActionOutputs(parseDeployResponse(deployCmdExec.stdout, { tag: tag }));

Expand All @@ -321,7 +351,7 @@ export async function run(): Promise<void> {
const errMsg =
updateTrafficExec.stderr ||
`command exited ${updateTrafficExec.exitCode}, but stderr had no output`;
throw new Error(`failed to update traffic: ${errMsg}, full command:\n\t${commandString}`);
throw new Error(`failed to execute gcloud command \`${commandString}\`: ${errMsg}`);
}
setActionOutputs(parseUpdateTrafficResponse(updateTrafficExec.stdout));
}
Expand Down Expand Up @@ -374,7 +404,7 @@ async function computeGcloudVersion(str: string): Promise<string> {
}

function setEnvVarsFlags(cmd: string[], envVars: string, strategy: string) {
const compiledEnvVars = parseKVString(envVars);
const compiledEnvVars = parseKVStringAndFile(envVars);
if (compiledEnvVars && Object.keys(compiledEnvVars).length > 0) {
let flag = '';
if (strategy === 'overwrite') {
Expand Down
33 changes: 32 additions & 1 deletion tests/e2e/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ test(
async (suite) => {
let service: run_v1.Schema$Service;
let job: run_v1.Schema$Job;
let worker_pool: run_v1.Schema$WorkerPool;
let metadata: run_v1.Schema$ObjectMeta;
let spec: run_v1.Schema$TaskSpec | run_v1.Schema$RevisionSpec;

Expand Down Expand Up @@ -73,8 +74,28 @@ test(
}
metadata = service.spec!.template!.metadata!;
spec = service.spec!.template!.spec!;
} else if (process.env.WORKER_POOL) {
const output = await getExecOutput('gcloud', [
'beta',
'run',
'worker-pools',
'describe',
process.env.WORKER_POOL!,
'--project',
process.env.PROJECT_ID!,
'--format',
'json',
'--region',
'us-central1',
]);
worker_pool = JSON.parse(output.stdout) as run_v1.Schema$Service;
if (!worker_pool) {
throw new Error('failed to find worker_pool definition');
}
metadata = worker_pool.spec!.template!.metadata!;
spec = worker_pool.spec!.template!.spec!;
} else {
throw new Error(`missing service or job`);
throw new Error(`missing service, job or worker_pool`);
}
});

Expand Down Expand Up @@ -189,6 +210,16 @@ test(
assert.deepStrictEqual(actual, expected);
});

await suite.test(
'has the worker_pool name',
{ skip: skipIfMissingEnv('WORKER_POOL') },
async () => {
const expected = process.env.WORKER_POOL! as string;
const actual = worker_pool.metadata?.name;
assert.deepStrictEqual(actual, expected);
},
);

await suite.test('has the job name', { skip: skipIfMissingEnv('JOB') }, async () => {
const expected = process.env.JOB! as string;
const actual = job.metadata?.name;
Expand Down
19 changes: 19 additions & 0 deletions tests/fixtures/worker-pools.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
apiVersion: 'run.googleapis.com/v1'
kind: 'WorkerPool'
metadata:
name: 'worker-pool'
labels:
cloud.googleapis.com/location: 'us-east1'
spec:
template:
metadata:
annotations:
run.googleapis.com/execution-environment: 'gen2'
spec:
containers:
- name: 'worker-pool-1'
image: 'us-docker.pkg.dev/cloudrun/container/worker-pool:latest'
resources:
limits:
cpu: '1'
memory: '512Mi'
Loading