From a6ebc7dca7a11afc2a5047729634beb61b2f64a6 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Fri, 10 May 2024 11:36:36 -0400 Subject: [PATCH] Support jobs metadata too (#519) Closes https://github.com/google-github-actions/deploy-cloudrun/issues/508 --- .github/workflows/integration.yml | 4 +- README.md | 4 +- package-lock.json | 27 ++++++----- package.json | 3 +- src/main.ts | 70 ++++++++------------------- tests/fixtures/job.yaml | 25 ++++++++++ tests/{unit => fixtures}/service.yaml | 12 ++--- tests/unit/main.test.ts | 18 +++++-- 8 files changed, 87 insertions(+), 76 deletions(-) create mode 100644 tests/fixtures/job.yaml rename tests/{unit => fixtures}/service.yaml (66%) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 61742ed7..51577d9e 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -163,7 +163,7 @@ jobs: - name: 'Set service name in metadata YAML' run: |- - sed -i "s/run-full-yaml/${{ env.SERVICE_NAME }}/" ./tests/unit/service.yaml + sed -i "s/run-full-yaml/${{ env.SERVICE_NAME }}/" ./tests/fixtures/service.yaml - uses: 'actions/setup-node@v4' with: @@ -180,7 +180,7 @@ jobs: name: 'Deploy' uses: './' with: - metadata: './tests/unit/service.yaml' + metadata: './tests/fixtures/service.yaml' - name: 'Run initial deploy tests' run: 'npm run e2e-tests' diff --git a/README.md b/README.md index 2526c50c..bb8c43ee 100644 --- a/README.md +++ b/README.md @@ -262,7 +262,7 @@ jobs: ### Custom metadata YAML For advanced use cases, you can define a custom Cloud Run metadata file. This is -a YAML description of the Cloud Run service. This allows you to customize your +a YAML description of the Cloud Run service or job. This allows you to customize your service configuration, such as [memory limits](https://cloud.google.com/run/docs/configuring/memory-limits), [CPU allocation](https://cloud.google.com/run/docs/configuring/cpu), [max @@ -271,7 +271,7 @@ instances](https://cloud.google.com/run/docs/configuring/max-instances), and **⚠️ When using a custom metadata YAML file, all other inputs are ignored!** -- `metadata`: (Optional) The path to a Cloud Run service metadata file. +- `metadata`: (Optional) The path to a Cloud Run service or job metadata file. To [deploying a new service](https://cloud.google.com/run/docs/deploying#yaml) to create a new YAML service definition: diff --git a/package-lock.json b/package-lock.json index 7b30e015..bab9cee2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,8 @@ "@actions/exec": "^1.1.1", "@actions/tool-cache": "^2.0.1", "@google-github-actions/actions-utils": "^0.7.5", - "@google-github-actions/setup-cloud-sdk": "^1.1.6" + "@google-github-actions/setup-cloud-sdk": "^1.1.6", + "yaml": "^2.4.2" }, "devDependencies": { "@types/node": "^20.12.11", @@ -202,9 +203,9 @@ } }, "node_modules/@google-github-actions/setup-cloud-sdk/node_modules/semver": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.1.tgz", - "integrity": "sha512-f/vbBsu+fOiYt+lmwZV0rVwJScl46HppnOA1ZvIuBWKOTlllpyJ3bfVax76/OrhCH38dyxoDIA8K7uB963IYgA==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "bin": { "semver": "bin/semver.js" }, @@ -420,9 +421,9 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.1.tgz", - "integrity": "sha512-f/vbBsu+fOiYt+lmwZV0rVwJScl46HppnOA1ZvIuBWKOTlllpyJ3bfVax76/OrhCH38dyxoDIA8K7uB963IYgA==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -545,9 +546,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.1.tgz", - "integrity": "sha512-f/vbBsu+fOiYt+lmwZV0rVwJScl46HppnOA1ZvIuBWKOTlllpyJ3bfVax76/OrhCH38dyxoDIA8K7uB963IYgA==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -582,9 +583,9 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.1.tgz", - "integrity": "sha512-f/vbBsu+fOiYt+lmwZV0rVwJScl46HppnOA1ZvIuBWKOTlllpyJ3bfVax76/OrhCH38dyxoDIA8K7uB963IYgA==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true, "bin": { "semver": "bin/semver.js" diff --git a/package.json b/package.json index b6bd03f3..bdb4083f 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ "@actions/exec": "^1.1.1", "@actions/tool-cache": "^2.0.1", "@google-github-actions/actions-utils": "^0.7.5", - "@google-github-actions/setup-cloud-sdk": "^1.1.6" + "@google-github-actions/setup-cloud-sdk": "^1.1.6", + "yaml": "^2.4.2" }, "devDependencies": { "@types/node": "^20.12.11", diff --git a/src/main.ts b/src/main.ts index f3528569..990ae246 100644 --- a/src/main.ts +++ b/src/main.ts @@ -27,6 +27,9 @@ import { } from '@actions/core'; import { getExecOutput } from '@actions/exec'; import * as toolCache from '@actions/tool-cache'; +import { readFile } from 'fs/promises'; +import { parse as parseYAML } from 'yaml'; + import { errorMessage, isPinnedToHead, @@ -146,45 +149,17 @@ export async function run(): Promise { cmd = ['run', 'services', 'update-traffic', service]; if (revTraffic) cmd.push('--to-revisions', revTraffic); if (tagTraffic) cmd.push('--to-tags', tagTraffic); - - const providedButIgnored: Record = { - image: image !== '', - metadata: metadata !== '', - source: source !== '', - env_vars: envVars !== '', - no_traffic: noTraffic, - secrets: Object.keys(secrets).length > 0, - suffix: suffix !== '', - tag: tag !== '', - labels: Object.keys(labels).length > 0, - timeout: timeout !== '', - }; - for (const key in providedButIgnored) { - if (providedButIgnored[key]) { - logWarning(`Updating traffic, ignoring "${key}" input`); - } - } } else if (metadata) { - cmd = ['run', 'services', 'replace', metadata]; - - const providedButIgnored: Record = { - image: image !== '', - service: service !== '', - source: source !== '', - env_vars: envVars !== '', - no_traffic: noTraffic, - secrets: Object.keys(secrets).length > 0, - suffix: suffix !== '', - tag: tag !== '', - revision_traffic: revTraffic !== '', - tag_traffic: revTraffic !== '', - labels: Object.keys(labels).length > 0, - timeout: timeout !== '', - }; - for (const key in providedButIgnored) { - if (providedButIgnored[key]) { - logWarning(`Using metadata YAML, ignoring "${key}" input`); - } + const contents = await readFile(metadata, 'utf8'); + const parsed = parseYAML(contents); + + const kind = parsed?.kind; + if (kind === 'Service') { + cmd = ['run', 'services', 'replace', metadata]; + } else if (kind === 'Job') { + cmd = ['run', 'jobs', 'replace', metadata]; + } else { + throw new Error(`Unkown metadata type "${kind}", expected "Job" or "Service"`); } } else if (job) { logWarning( @@ -249,17 +224,14 @@ export async function run(): Promise { // Push common flags cmd.push('--format', 'json'); - if (region) { - switch (region.length) { - case 0: - break; - case 1: - cmd.push('--region', region[0]); - break; - default: - cmd.push('--region', region.join(',')); - break; - } + if (region?.length > 0) { + cmd.push( + '--region', + region + .flat() + .filter((e) => e !== undefined && e !== null && e !== '') + .join(','), + ); } if (projectId) cmd.push('--project', projectId); diff --git a/tests/fixtures/job.yaml b/tests/fixtures/job.yaml new file mode 100644 index 00000000..b54da4cb --- /dev/null +++ b/tests/fixtures/job.yaml @@ -0,0 +1,25 @@ +apiVersion: 'run.googleapis.com/v1' +kind: 'Job' +metadata: + name: 'job' + labels: + cloud.googleapis.com/location: 'us-east1' +spec: + template: + metadata: + annotations: + run.googleapis.com/execution-environment: 'gen2' + spec: + parallelism: 1 + taskCount: 1 + template: + spec: + containers: + - image: 'gcr.io/cloudrun/hello' + imagePullPolicy: 'Always' + resources: + limits: + cpu: '1000m' + memory: '512Mi' + maxRetries: 0 + timeoutSeconds: '3600' diff --git a/tests/unit/service.yaml b/tests/fixtures/service.yaml similarity index 66% rename from tests/unit/service.yaml rename to tests/fixtures/service.yaml index d6f8569d..3ef34c34 100644 --- a/tests/unit/service.yaml +++ b/tests/fixtures/service.yaml @@ -1,22 +1,22 @@ -apiVersion: serving.knative.dev/v1 -kind: Service +apiVersion: 'serving.knative.dev/v1' +kind: 'Service' metadata: - name: run-full-yaml + name: 'run-full-yaml' spec: template: metadata: labels: - test_label: test_value + test_label: 'test_value' annotations: run.googleapis.com/cloudsql-instances: 'test-project:us-central1:my-test-instance' spec: containerConcurrency: 20 containers: - - image: gcr.io/cloudrun/hello + - image: 'gcr.io/cloudrun/hello' ports: - containerPort: 8080 resources: limits: cpu: '2' - memory: 1Gi + memory: '1Gi' timeoutSeconds: 300 diff --git a/tests/unit/main.test.ts b/tests/unit/main.test.ts index 7d4a7ceb..7251ddc9 100644 --- a/tests/unit/main.test.ts +++ b/tests/unit/main.test.ts @@ -271,16 +271,28 @@ test('#run', { concurrency: true }, async (suite) => { assertMembers(args, ['--source', 'example-app']); }); - await suite.test('sets metadata if given', async (t) => { + await suite.test('sets service metadata if given', async (t) => { const mocks = defaultMocks(t.mock, { - metadata: 'yaml', + metadata: 'tests/fixtures/service.yaml', image: '', }); await run(); const args = mocks.getExecOutput.mock.calls?.at(0).arguments?.at(1); - assertMembers(args, ['services', 'replace', 'yaml']); + assertMembers(args, ['services', 'replace']); + }); + + await suite.test('sets job metadata if given', async (t) => { + const mocks = defaultMocks(t.mock, { + metadata: 'tests/fixtures/job.yaml', + image: '', + }); + + await run(); + + const args = mocks.getExecOutput.mock.calls?.at(0).arguments?.at(1); + assertMembers(args, ['jobs', 'replace']); }); await suite.test('sets timeout if given', async (t) => {