Skip to content

Commit e8ced9e

Browse files
authored
Always read back the deployment to get metadata (#290)
This fixes an issue where --no-promote does not include all the URL information. Closes #288
1 parent dfe0faf commit e8ced9e

File tree

7 files changed

+341
-88
lines changed

7 files changed

+341
-88
lines changed

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,21 @@ for more information.
8585

8686
## Outputs
8787

88-
- `url`: The URL of your App Engine Application.
88+
- `name`: The fully-qualified resource name of the deployment. This will be of
89+
the format "apps/<project>/services/<service>/versions/<version>".
90+
91+
- `runtime`: The computed deployment runtime.
92+
93+
- `service_account_email`: The email address of the runtime service account.
94+
95+
- `serving_status`: The current serving status. The value is usually
96+
"SERVING", unless the deployment failed to start.
97+
98+
- `version_id`: Unique identifier for the version, or the specified version if
99+
one was given.
100+
101+
- `version_url`: URL of the version of the AppEngine service that was
102+
deployed.
89103

90104
## Authorization
91105

action.yml

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,37 @@ inputs:
5151
required: false
5252

5353
outputs:
54+
name:
55+
description: |-
56+
The fully-qualified resource name of the deployment. This will be of the
57+
format "apps/<project>/services/<service>/versions/<version>".
58+
59+
runtime:
60+
description: |-
61+
The computed deployment runtime.
62+
63+
service_account_email:
64+
description: |-
65+
The email address of the runtime service account.
66+
67+
serving_status:
68+
description: |-
69+
The current serving status. The value is usually "SERVING", unless the
70+
deployment failed to start.
71+
72+
version_id:
73+
description: |-
74+
Unique identifier for the version, or the specified version if one was
75+
given.
76+
77+
version_url:
78+
description: |-
79+
URL of the version of the AppEngine service that was deployed.
80+
5481
url:
55-
description: URL of your App Engine Application
82+
description: |-
83+
DEPRECATED: Use "version_url" instead. URL of the version of the AppEngine
84+
service that was deployed.
5685
5786
branding:
5887
icon: 'code'

dist/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main.ts

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@ import fs from 'fs';
1919
import {
2020
getInput,
2121
info as logInfo,
22+
debug as logDebug,
2223
warning as logWarning,
2324
setFailed,
2425
setOutput,
2526
} from '@actions/core';
2627
import { getExecOutput } from '@actions/exec';
27-
import { parseDeployResponse } from './output-parser';
28+
import { parseDeployResponse, parseDescribeResponse } from './output-parser';
2829

2930
import {
3031
getLatestGcloudSDKVersion,
@@ -43,7 +44,8 @@ import {
4344
stubEnv,
4445
} from '@google-github-actions/actions-utils';
4546

46-
// Do not listen to the linter - this can NOT be rewritten as an ES6 import statement.
47+
// Do not listen to the linter - this can NOT be rewritten as an ES6 import
48+
// statement.
4749
// eslint-disable-next-line @typescript-eslint/no-var-requires
4850
const { version: appVersion } = require('../package.json');
4951

@@ -60,7 +62,7 @@ export async function run(): Promise<void> {
6062

6163
// Warn if pinned to HEAD
6264
if (isPinnedToHead()) {
63-
logWarning(pinnedToHeadWarning('v0'));
65+
logWarning(pinnedToHeadWarning('v1'));
6466
}
6567

6668
try {
@@ -136,26 +138,53 @@ export async function run(): Promise<void> {
136138
}
137139

138140
const options = { silent: true, ignoreReturnCode: true };
139-
const commandString = `${toolCommand} ${appDeployCmd.join(' ')}`;
140-
logInfo(`Running: ${commandString}`);
141+
const deployCommandString = `${toolCommand} ${appDeployCmd.join(' ')}`;
142+
logInfo(`Running: ${deployCommandString}`);
141143

142144
// Get output of gcloud cmd.
143-
const output = await getExecOutput(toolCommand, appDeployCmd, options);
144-
if (output.exitCode !== 0) {
145-
const errMsg = output.stderr || `command exited ${output.exitCode}, but stderr had no output`;
146-
throw new Error(`failed to execute gcloud command \`${commandString}\`: ${errMsg}`);
145+
const deployOutput = await getExecOutput(toolCommand, appDeployCmd, options);
146+
if (deployOutput.exitCode !== 0) {
147+
const errMsg =
148+
deployOutput.stderr || `command exited ${deployOutput.exitCode}, but stderr had no output`;
149+
throw new Error(`failed to execute gcloud command \`${deployCommandString}\`: ${errMsg}`);
147150
}
148151

149-
// Set url as output.
150-
const response = parseDeployResponse(output.stdout);
151-
if (response) {
152-
setOutput('name', response.name);
153-
setOutput('serviceAccountEmail', response.serviceAccountEmail);
154-
setOutput('versionURL', response.versionURL);
155-
setOutput('url', response.versionURL);
156-
} else {
157-
logWarning(`no outputs were set, this usually happens with --no-promote`);
152+
// Extract the version from the response.
153+
const deployResponse = parseDeployResponse(deployOutput.stdout);
154+
logDebug(`Deployed new version: ${JSON.stringify(deployResponse)}`);
155+
156+
// Look up the new version to get metadata.
157+
const appVersionsDescribeCmd = ['app', 'versions', 'describe', '--quiet', '--format', 'json'];
158+
appVersionsDescribeCmd.push('--project', deployResponse.project);
159+
appVersionsDescribeCmd.push('--service', deployResponse.service);
160+
appVersionsDescribeCmd.push(deployResponse.versionID);
161+
162+
const describeCommandString = `${toolCommand} ${appVersionsDescribeCmd.join(' ')}`;
163+
logInfo(`Running: ${describeCommandString}`);
164+
165+
const describeOutput = await getExecOutput(toolCommand, appVersionsDescribeCmd, options);
166+
if (describeOutput.exitCode !== 0) {
167+
const errMsg =
168+
describeOutput.stderr ||
169+
`command exited ${describeOutput.exitCode}, but stderr had no output`;
170+
throw new Error(`failed to execute gcloud command \`${describeCommandString}\`: ${errMsg}`);
158171
}
172+
173+
// Parse the describe response.
174+
const describeResponse = parseDescribeResponse(describeOutput.stdout);
175+
176+
// Set outputs.
177+
setOutput('name', describeResponse.name);
178+
setOutput('runtime', describeResponse.runtime);
179+
setOutput('service_account_email', describeResponse.serviceAccountEmail);
180+
setOutput('serving_status', describeResponse.servingStatus);
181+
setOutput('version_id', describeResponse.versionID);
182+
setOutput('version_url', describeResponse.versionURL);
183+
184+
// Backwards compatability.
185+
setOutput('serviceAccountEmail', describeResponse.serviceAccountEmail);
186+
setOutput('versionURL', describeResponse.versionURL);
187+
setOutput('url', describeResponse.versionURL);
159188
} catch (err) {
160189
const msg = errorMessage(err);
161190
setFailed(`google-github-actions/deploy-appengine failed with: ${msg}`);

src/output-parser.ts

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,42 +16,101 @@
1616

1717
import { errorMessage, presence } from '@google-github-actions/actions-utils';
1818

19-
export interface DeployOutput {
19+
/**
20+
* DeployResponse is the output from a "gcloud app deploy".
21+
*/
22+
export interface DeployResponse {
23+
// project is the project ID returned from the deployment.
24+
project: string;
25+
26+
// service is the name of the deployed service.
27+
service: string;
28+
29+
// versionID is the unique version ID.
30+
versionID: string;
31+
}
32+
33+
/**
34+
* parseDeployResponse parses the JSON stdout from a deployment.
35+
*
36+
* @param string Standard output in JSON format.
37+
*/
38+
export function parseDeployResponse(stdout: string | undefined): DeployResponse {
39+
try {
40+
stdout = presence(stdout);
41+
if (!stdout) {
42+
throw new Error(`empty response`);
43+
}
44+
45+
const outputJSON = JSON.parse(stdout);
46+
const version = outputJSON?.versions?.at(0);
47+
if (!version) {
48+
throw new Error(`missing or empty "versions"`);
49+
}
50+
51+
return {
52+
project: version['project'],
53+
service: version['service'],
54+
versionID: version['id'],
55+
};
56+
} catch (err) {
57+
const msg = errorMessage(err);
58+
throw new Error(`failed to parse deploy response: ${msg}, stdout: ${stdout}`);
59+
}
60+
}
61+
62+
/**
63+
* DescribeResponse is the response from a "gcloud app versions describe".
64+
*/
65+
export interface DescribeResponse {
2066
// name is the full resource name of the deployment (e.g.
2167
// projects/p/services/default/versions/123).
2268
name: string;
2369

70+
// runtime is the decided runtime.
71+
runtime: string;
72+
2473
// serviceAccountEmail is the email address of the runtime service account for
2574
// the deployment.
2675
serviceAccountEmail: string;
2776

77+
// servingStatus is the current serving status.
78+
servingStatus: string;
79+
80+
// id is the unique version ID.
81+
versionID: string;
82+
2883
// versionURL is the full HTTPS URL to the version.
2984
versionURL: string;
3085
}
3186

3287
/**
33-
* parseDeployResponse parses the JSON output from a "gcloud app deploy" output.
88+
* parseDescribeResponse parses the output from a description.
89+
*
90+
* @param string Standard output in JSON format.
3491
*/
35-
export function parseDeployResponse(stdout: string | undefined): DeployOutput | null {
92+
export function parseDescribeResponse(stdout: string | undefined): DescribeResponse {
3693
try {
3794
stdout = presence(stdout);
3895
if (!stdout || stdout === '{}' || stdout === '[]') {
39-
return null;
96+
throw new Error(`empty response`);
4097
}
4198

42-
const outputJSON = JSON.parse(stdout);
43-
const version = outputJSON.versions?.at(0)?.version;
99+
const version = JSON.parse(stdout);
44100
if (!version) {
45-
return null;
101+
throw new Error(`empty JSON response`);
46102
}
47103

48104
return {
49-
name: version.name,
50-
serviceAccountEmail: version.serviceAccount,
51-
versionURL: version.versionUrl,
105+
name: version['name'],
106+
runtime: version['runtime'],
107+
serviceAccountEmail: version['serviceAccount'],
108+
servingStatus: version['servingStatus'],
109+
versionID: version['id'],
110+
versionURL: version['versionUrl'],
52111
};
53112
} catch (err) {
54113
const msg = errorMessage(err);
55-
throw new Error(`failed to parse deploy response: ${msg}, stdout: ${stdout}`);
114+
throw new Error(`failed to parse describe response: ${msg}, stdout: ${stdout}`);
56115
}
57116
}

0 commit comments

Comments
 (0)