Skip to content

Commit

Permalink
Always read back the deployment to get metadata
Browse files Browse the repository at this point in the history
This fixes an issue where --no-promote does not include all the URL information.

Closes #288
  • Loading branch information
sethvargo committed Dec 15, 2022
1 parent dfe0faf commit 26784fd
Show file tree
Hide file tree
Showing 7 changed files with 336 additions and 86 deletions.
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,21 @@ for more information.

## Outputs

- `url`: The URL of your App Engine Application.
- `name`: The fully-qualified resource name of the deployment. This will be of
the format "apps/<project>/services/<service>/versions/<version>".

- `runtime`: The computed deployment runtime.

- `service_account_email`: The email address of the runtime service account.

- `serving_status`: The current serving status. The value is usually
"SERVING", unless the deployment failed to start.

- `version_id`: Unique identifier for the version, or the specified version if
one was given.

- `version_url`: URL of the version of the AppEngine service that was
deployed.

## Authorization

Expand Down
31 changes: 30 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,37 @@ inputs:
required: false

outputs:
name:
description: |-
The fully-qualified resource name of the deployment. This will be of the
format "apps/<project>/services/<service>/versions/<version>".
runtime:
description: |-
The computed deployment runtime.
service_account_email:
description: |-
The email address of the runtime service account.
serving_status:
description: |-
The current serving status. The value is usually "SERVING", unless the
deployment failed to start.
version_id:
description: |-
Unique identifier for the version, or the specified version if one was
given.
version_url:
description: |-
URL of the version of the AppEngine service that was deployed.
url:
description: URL of your App Engine Application
description: |-
DEPRECATED: Use "version_url" instead. URL of the version of the AppEngine
service that was deployed.
branding:
icon: 'code'
Expand Down
2 changes: 1 addition & 1 deletion dist/index.js

Large diffs are not rendered by default.

58 changes: 42 additions & 16 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ import fs from 'fs';
import {
getInput,
info as logInfo,
debug as logDebug,
warning as logWarning,
setFailed,
setOutput,
} from '@actions/core';
import { getExecOutput } from '@actions/exec';
import { parseDeployResponse } from './output-parser';
import { parseDeployResponse, parseDescribeResponse } from './output-parser';

import {
getLatestGcloudSDKVersion,
Expand Down Expand Up @@ -136,26 +137,51 @@ export async function run(): Promise<void> {
}

const options = { silent: true, ignoreReturnCode: true };
const commandString = `${toolCommand} ${appDeployCmd.join(' ')}`;
logInfo(`Running: ${commandString}`);
const deployCommandString = `${toolCommand} ${appDeployCmd.join(' ')}`;
logInfo(`Running: ${deployCommandString}`);

// Get output of gcloud cmd.
const output = await getExecOutput(toolCommand, appDeployCmd, options);
if (output.exitCode !== 0) {
const errMsg = output.stderr || `command exited ${output.exitCode}, but stderr had no output`;
throw new Error(`failed to execute gcloud command \`${commandString}\`: ${errMsg}`);
const deployOutput = await getExecOutput(toolCommand, appDeployCmd, options);
if (deployOutput.exitCode !== 0) {
const errMsg =
deployOutput.stderr || `command exited ${deployOutput.exitCode}, but stderr had no output`;
throw new Error(`failed to execute gcloud command \`${deployCommandString}\`: ${errMsg}`);
}

// Set url as output.
const response = parseDeployResponse(output.stdout);
if (response) {
setOutput('name', response.name);
setOutput('serviceAccountEmail', response.serviceAccountEmail);
setOutput('versionURL', response.versionURL);
setOutput('url', response.versionURL);
} else {
logWarning(`no outputs were set, this usually happens with --no-promote`);
// Extract the version from the response.
const deployResponse = parseDeployResponse(deployOutput.stdout);
logDebug(`Deployed new version: ${JSON.stringify(deployResponse)}`);

// Look up the new version to get metadata.
const appVersionsDescribeCmd = ['app', 'versions', 'describe', '--quiet', '--format', 'json'];
appVersionsDescribeCmd.push('--project', deployResponse.project);
appVersionsDescribeCmd.push('--service', deployResponse.service);
appVersionsDescribeCmd.push(deployResponse.versionID);

const describeCommandString = `${toolCommand} ${appVersionsDescribeCmd.join(' ')}`;
logInfo(`Running: ${describeCommandString}`);

const describeOutput = await getExecOutput(toolCommand, appVersionsDescribeCmd, options);
if (describeOutput.exitCode !== 0) {
const errMsg =
describeOutput.stderr ||
`command exited ${describeOutput.exitCode}, but stderr had no output`;
throw new Error(`failed to execute gcloud command \`${describeCommandString}\`: ${errMsg}`);
}

// Parse the describe response.
const describeResponse = parseDescribeResponse(describeOutput.stdout);

// Set outputs.
setOutput('name', describeResponse.name);
setOutput('runtime', describeResponse.runtime);
setOutput('service_account_email', describeResponse.serviceAccountEmail);
setOutput('serving_status', describeResponse.servingStatus);
setOutput('version_id', describeResponse.versionID);
setOutput('version_url', describeResponse.versionURL);

// Backwards compatability.
setOutput('url', describeResponse.versionURL);
} catch (err) {
const msg = errorMessage(err);
setFailed(`google-github-actions/deploy-appengine failed with: ${msg}`);
Expand Down
81 changes: 70 additions & 11 deletions src/output-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,42 +16,101 @@

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

export interface DeployOutput {
/**
* DeployResponse is the output from a "gcloud app deploy".
*/
export interface DeployResponse {
// project is the project ID returned from the deployment.
project: string;

// service is the name of the deployed service.
service: string;

// versionID is the unique version ID.
versionID: string;
}

/**
* parseDeployResponse parses the JSON stdout from a deployment.
*
* @param string Standard output in JSON format.
*/
export function parseDeployResponse(stdout: string | undefined): DeployResponse {
try {
stdout = presence(stdout);
if (!stdout) {
throw new Error(`empty response`);
}

const outputJSON = JSON.parse(stdout);
const version = outputJSON?.versions?.at(0);
if (!version) {
throw new Error(`missing or empty "versions"`);
}

return {
project: version['project'],
service: version['service'],
versionID: version['id'],
};
} catch (err) {
const msg = errorMessage(err);
throw new Error(`failed to parse deploy response: ${msg}, stdout: ${stdout}`);
}
}

/**
* DescribeResponse is the response from a "gcloud app versions describe".
*/
export interface DescribeResponse {
// name is the full resource name of the deployment (e.g.
// projects/p/services/default/versions/123).
name: string;

// runtime is the decided runtime.
runtime: string;

// serviceAccountEmail is the email address of the runtime service account for
// the deployment.
serviceAccountEmail: string;

// servingStatus is the current serving status.
servingStatus: string;

// id is the unique version ID.
versionID: string;

// versionURL is the full HTTPS URL to the version.
versionURL: string;
}

/**
* parseDeployResponse parses the JSON output from a "gcloud app deploy" output.
* parseDescribeResponse parses the output from a description.
*
* @param string Standard output in JSON format.
*/
export function parseDeployResponse(stdout: string | undefined): DeployOutput | null {
export function parseDescribeResponse(stdout: string | undefined): DescribeResponse {
try {
stdout = presence(stdout);
if (!stdout || stdout === '{}' || stdout === '[]') {
return null;
throw new Error(`empty response`);
}

const outputJSON = JSON.parse(stdout);
const version = outputJSON.versions?.at(0)?.version;
const version = JSON.parse(stdout);
if (!version) {
return null;
throw new Error(`empty JSON response`);
}

return {
name: version.name,
serviceAccountEmail: version.serviceAccount,
versionURL: version.versionUrl,
name: version['name'],
runtime: version['runtime'],
serviceAccountEmail: version['serviceAccount'],
servingStatus: version['servingStatus'],
versionID: version['id'],
versionURL: version['versionUrl'],
};
} catch (err) {
const msg = errorMessage(err);
throw new Error(`failed to parse deploy response: ${msg}, stdout: ${stdout}`);
throw new Error(`failed to parse describe response: ${msg}, stdout: ${stdout}`);
}
}
Loading

0 comments on commit 26784fd

Please sign in to comment.