Skip to content
Closed
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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ jobs:
- `service`: (Required, unless providing `metadata`) ID of the service or
fully-qualified identifier of the service.

- `job`: (Required, unless providing `metadata` or `service`) ID of the job or
fully-qualified identifier of the job. If `job` and `service` are specified
then the `service` will be updated and the `job` will be ignored. Note that
the `job` must be created first. This will only update an existing `job`, it
will not deploy/create a new job.

- `image`: (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:
Required if not using a service YAML.
required: false

job:
description: |-
ID of the job or fully qualified identifier for the job.
Required if not using a service YAML.
required: false

region:
description: |-
Region in which the resource can be found.
Expand Down
4 changes: 2 additions & 2 deletions dist/main/index.js

Large diffs are not rendered by default.

26 changes: 25 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export async function run(): Promise<void> {
// Get action inputs
const image = getInput('image'); // Image ie gcr.io/...
const service = getInput('service'); // Service name
const job = getInput('job'); // Job name
const metadata = getInput('metadata'); // YAML file
const projectId = getInput('project_id');
const gcloudVersion = await computeGcloudVersion(getInput('gcloud_version'));
Expand Down Expand Up @@ -131,6 +132,7 @@ export async function run(): Promise<void> {
if (gcloudComponent && gcloudComponent !== 'alpha' && gcloudComponent !== 'beta') {
throw new Error(`invalid input received for gcloud_component: ${gcloudComponent}`);
}
const useJob = job && !service;

// Find base command
if (revTraffic || tagTraffic) {
Expand Down Expand Up @@ -181,6 +183,26 @@ export async function run(): Promise<void> {
logWarning(`Using metadata YAML, ignoring "${key}" input`);
}
}
} else if (useJob) {
cmd = ['run', 'jobs', 'update', job, '--quiet'];

if (image) {
// Deploy job with image specified
cmd.push('--image', image);
}

// Set optional flags from inputs
const compiledEnvVars = parseKVStringAndFile(envVars, envVarsFile);
if (compiledEnvVars && Object.keys(compiledEnvVars).length > 0) {
cmd.push('--update-env-vars', kvToString(compiledEnvVars));
}
if (secrets && Object.keys(secrets).length > 0) {
cmd.push('--update-secrets', kvToString(secrets));
}

// Compile the labels
const compiledLabels = Object.assign({}, defaultLabels(), labels);
cmd.push('--update-labels', kvToString(compiledLabels));
} else {
cmd = ['run', 'deploy', service, '--quiet'];

Expand Down Expand Up @@ -216,7 +238,9 @@ export async function run(): Promise<void> {
}

// Push common flags
cmd.push('--platform', 'managed');
if (!useJob) {
cmd.push('--platform', 'managed');
}
cmd.push('--format', 'json');
if (region) cmd.push('--region', region);
if (projectId) cmd.push('--project', projectId);
Expand Down
104 changes: 102 additions & 2 deletions tests/unit/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,36 @@ import { run } from '../../src/main';
const fakeInputs: { [key: string]: string } = {
image: 'gcr.io/cloudrun/hello',
service: 'test',
job: '',
metadata: '',
project_id: 'test',
env_vars: '',
env_vars_file: '',
labels: '',
skip_default_labels: 'false',
source: '',
suffix: '',
tag: '',
timeout: '',
revision_traffic: '',
tag_traffic: '',
};

const fakeInputsJob: { [key: string]: string } = {
image: 'gcr.io/cloudrun/hello',
job: 'job-name',
metadata: '',
project_id: 'test',
env_vars: '',
env_vars_file: '',
labels: '',
skip_default_labels: 'false',
source: '',
suffix: '',
tag: '',
timeout: '',
revision_traffic: '',
tag_traffic: '',
};

const defaultMocks = (
Expand Down Expand Up @@ -70,6 +99,41 @@ const defaultMocks = (
};
};

const jobMocks = (m: typeof mock, overrideInputs?: Record<string, string>): Record<string, any> => {
const inputs = Object.assign({}, fakeInputsJob, overrideInputs);
return {
setFailed: m.method(core, 'setFailed', (msg: string) => {
throw new Error(msg);
}),
getBooleanInput: m.method(core, 'getBooleanInput', (name: string) => {
return !!inputs[name];
}),
getMultilineInput: m.method(core, 'getMultilineInput', (name: string) => {
return inputs[name];
}),
getInput: m.method(core, 'getInput', (name: string) => {
return inputs[name];
}),
getExecOutput: m.method(exec, 'getExecOutput', () => {
return { exitCode: 0, stderr: '', stdout: '{}' };
}),

authenticateGcloudSDK: m.method(setupGcloud, 'authenticateGcloudSDK', () => {}),
isAuthenticated: m.method(setupGcloud, 'isAuthenticated', () => {}),
isInstalled: m.method(setupGcloud, 'isInstalled', () => {
return true;
}),
installGcloudSDK: m.method(setupGcloud, 'installGcloudSDK', async () => {
return '1.2.3';
}),
installComponent: m.method(setupGcloud, 'installComponent', () => {}),
setProject: m.method(setupGcloud, 'setProject', () => {}),
getLatestGcloudSDKVersion: m.method(setupGcloud, 'getLatestGcloudSDKVersion', () => {
return '1.2.3';
}),
};
};

test('#run', { concurrency: true }, async (suite) => {
const originalEnv = Object.assign({}, process.env);

Expand Down Expand Up @@ -97,12 +161,12 @@ test('#run', { concurrency: true }, async (suite) => {

await suite.test('sets the project ID', async (t) => {
const mocks = defaultMocks(t.mock, {
project_id: 'my-test-project',
project_id: 'test',
});
await run();

const args = mocks.getExecOutput.mock.calls?.at(0).arguments?.at(1);
assertMembers(args, ['--project', 'my-test-project']);
assertMembers(args, ['--project', 'test']);
});

await suite.test('installs the gcloud SDK if it is not already installed', async (t) => {
Expand Down Expand Up @@ -324,6 +388,42 @@ test('#run', { concurrency: true }, async (suite) => {
{ message: /no service name set/ },
);
});

await suite.test('ignore job if job and service are both specified', async (t) => {
const mocks = defaultMocks(t.mock, {
service: 'test',
job: 'job-name',
});

await run();

const args = mocks.getExecOutput.mock.calls?.at(0).arguments?.at(1);
assertMembers(args, ['run', 'deploy', 'test']);
});

await suite.test('updates a job if job is specified and service is not', async (t) => {
const mocks = jobMocks(t.mock);

await run();

const args = mocks.getExecOutput.mock.calls?.at(0).arguments?.at(1);
assertMembers(args, ['run', 'jobs', 'update', 'job-name']);
});

await suite.test(
'updates a job if job is specified and service is an empty string',
async (t) => {
const mocks = defaultMocks(t.mock, {
service: '',
job: 'job-name',
});

await run();

const args = mocks.getExecOutput.mock.calls?.at(0).arguments?.at(1);
assertMembers(args, ['run', 'jobs', 'update', 'job-name']);
},
);
});

const splitKV = (s: string): Record<string, string> => {
Expand Down