Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

job summary and buildonly option #237

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ The definition of this GitHub Action is in [action.yml](https://github.com/Azure
path:

# optional when using a .sql script, required otherwise
# sqlpackage action on the .dacpac or .sqlproj file, supported options are: Publish, Script, DeployReport, DriftReport
# sqlpackage action on the .dacpac or .sqlproj file, supported options are: Publish, Script, DeployReport, DriftReport, BuildOnly
action:

# optional additional sqlpackage or go-sqlcmd arguments
Expand All @@ -37,6 +37,9 @@ The definition of this GitHub Action is in [action.yml](https://github.com/Azure

# optional, set this to skip checking if the runner has access to the server. Default is false.
skip-firewall-check:

# option, set this to skip adding a GitHub job summary for the SQL action. Default is false.
skip-job-summary:
```

## 🎨 Samples
Expand Down
55 changes: 36 additions & 19 deletions __tests__/AzureSqlAction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,13 @@ describe('AzureSqlAction tests', () => {
expect(getSqlPackagePathSpy).toHaveBeenCalledTimes(1);
expect(execSpy).toHaveBeenCalledTimes(1);

if (actionName == 'DriftReport') {
expect(execSpy).toHaveBeenCalledWith(`"SqlPackage.exe" /Action:${actionName} /TargetConnectionString:"${inputs.connectionConfig.EscapedConnectionString}" ${sqlpackageArgs}`);
} else {
expect(execSpy).toHaveBeenCalledWith(`"SqlPackage.exe" /Action:${actionName} /TargetConnectionString:"${inputs.connectionConfig.EscapedConnectionString}" /SourceFile:"${inputs.filePath}" ${sqlpackageArgs}`);
expect(inputs.connectionConfig).toBeDefined();
if (inputs.connectionConfig) {
if (actionName == 'DriftReport') {
expect(execSpy).toHaveBeenCalledWith(`"SqlPackage.exe" /Action:${actionName} /TargetConnectionString:"${inputs.connectionConfig.EscapedConnectionString}" ${sqlpackageArgs}`);
} else {
expect(execSpy).toHaveBeenCalledWith(`"SqlPackage.exe" /Action:${actionName} /TargetConnectionString:"${inputs.connectionConfig.EscapedConnectionString}" /SourceFile:"${inputs.filePath}" ${sqlpackageArgs}`);
}
}
});
});
Expand Down Expand Up @@ -74,13 +77,17 @@ describe('AzureSqlAction tests', () => {
expect(execSpy).toHaveBeenCalledTimes(1);
expect(execSpy).toHaveBeenCalledWith(`"${sqlcmdExe}" ${expectedSqlCmdCall}`);

// Except for AAD default, password/client secret should be set as SqlCmdPassword environment variable
if (inputs.connectionConfig.FormattedAuthentication !== 'activedirectorydefault') {
expect(exportVariableSpy).toHaveBeenCalledTimes(1);
expect(exportVariableSpy).toHaveBeenCalledWith(Constants.sqlcmdPasswordEnvVarName, "placeholder");
}
else {
expect(exportVariableSpy).not.toHaveBeenCalled();
expect(inputs.connectionConfig).toBeDefined();

if (inputs.connectionConfig) {
// Except for AAD default, password/client secret should be set as SqlCmdPassword environment variable
if (inputs.connectionConfig.FormattedAuthentication !== 'activedirectorydefault') {
expect(exportVariableSpy).toHaveBeenCalledTimes(1);
expect(exportVariableSpy).toHaveBeenCalledWith(Constants.sqlcmdPasswordEnvVarName, "placeholder");
}
else {
expect(exportVariableSpy).not.toHaveBeenCalled();
}
}
})
});
Expand Down Expand Up @@ -121,7 +128,8 @@ describe('AzureSqlAction tests', () => {
['Publish', '/p:DropPermissionsNotInSource=true'],
['Script', '/DeployScriptPath:script.sql'],
['DriftReport', '/OutputPath:report.xml'],
['DeployReport', '/OutputPath:report.xml']
['DeployReport', '/OutputPath:report.xml'],
['BuildOnly', '']
];

it.each(inputs)('Validate build and %s action with args %s', async (actionName, sqlpackageArgs) => {
Expand All @@ -138,13 +146,22 @@ describe('AzureSqlAction tests', () => {

expect(parseCommandArgumentsSpy).toHaveBeenCalledTimes(1);
expect(findArgumentSpy).toHaveBeenCalledTimes(2);
expect(getSqlPackagePathSpy).toHaveBeenCalledTimes(1);
expect(execSpy).toHaveBeenCalledTimes(2);
expect(execSpy).toHaveBeenNthCalledWith(1, `dotnet build "./TestProject.sqlproj" -p:NetCoreBuild=true --verbose --test "test value"`);
if (actionName === 'DriftReport') {
expect(execSpy).toHaveBeenNthCalledWith(2, `"SqlPackage.exe" /Action:${actionName} /TargetConnectionString:"${inputs.connectionConfig.EscapedConnectionString}" ${sqlpackageArgs}`);
expect(execSpy).toHaveBeenNthCalledWith(1, `dotnet build "./TestProject.sqlproj" -p:NetCoreBuild=true --verbose --test "test value"`, [], expect.anything());

if (actionName === 'BuildOnly') {
expect(execSpy).toHaveBeenCalledTimes(1);
} else {
expect(execSpy).toHaveBeenNthCalledWith(2, `"SqlPackage.exe" /Action:${actionName} /TargetConnectionString:"${inputs.connectionConfig.EscapedConnectionString}" /SourceFile:"${expectedDacpac}" ${sqlpackageArgs}`);
expect(getSqlPackagePathSpy).toHaveBeenCalledTimes(1);
expect(execSpy).toHaveBeenCalledTimes(2);
expect(inputs.connectionConfig).toBeDefined();

if (inputs.connectionConfig) {
if (actionName === 'DriftReport') {
expect(execSpy).toHaveBeenNthCalledWith(2, `"SqlPackage.exe" /Action:${actionName} /TargetConnectionString:"${inputs.connectionConfig.EscapedConnectionString}" ${sqlpackageArgs}`);
} else {
expect(execSpy).toHaveBeenNthCalledWith(2, `"SqlPackage.exe" /Action:${actionName} /TargetConnectionString:"${inputs.connectionConfig.EscapedConnectionString}" /SourceFile:"${expectedDacpac}" ${sqlpackageArgs}`);
}
}
}
});
});
Expand Down Expand Up @@ -265,7 +282,7 @@ function getInputsWithCustomSqlPackageAction(actionType: ActionType, sqlpackageA
case ActionType.DacpacAction: {
return {
actionType: ActionType.DacpacAction,
connectionConfig: defaultConnectionConfig,
connectionConfig: sqlpackageAction == SqlPackageAction.BuildOnly ? null : defaultConnectionConfig,
filePath: './TestPackage.dacpac',
sqlpackageAction: sqlpackageAction,
additionalArguments: additionalArguments
Expand Down
2 changes: 1 addition & 1 deletion __tests__/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ describe('main.ts tests', () => {
expect(resolveFilePathSpy).toHaveBeenCalled();
expect(getInputSpy).toHaveBeenCalled();
expect(setFailedSpy).toHaveBeenCalled();
expect(setFailedSpy).toHaveBeenCalledWith(`Action ${actionName} is invalid. Supported action types are: Publish, Script, DriftReport, or DeployReport.`);
expect(setFailedSpy).toHaveBeenCalledWith(`Action ${actionName} is invalid. Supported action types are: Publish, Script, DriftReport, DeployReport, or BuildOnly.`);
});
});

Expand Down
6 changes: 5 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: 'Deploy a database project, DACPAC, or a SQL script to Azure SQL da
inputs:
connection-string:
description: 'The connection string, including authentication information, for the Azure SQL Server database.'
required: true
required: false
path:
description: 'Path to the file used for this action. Supported file types are .sql, .dacpac, or .sqlproj.'
required: true
Expand All @@ -23,6 +23,10 @@ inputs:
description: 'Skip the firewall check when connecting to the Azure SQL Server.'
required: false
default: false
skip-job-summary:
description: 'Skip providing a job summary from this step.'
required: false
default: false
runs:
using: 'node20'
main: 'lib/main.js'
2 changes: 1 addition & 1 deletion lib/main.js

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"author": "Microsoft",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.9.1",
"@actions/core": "^1.10.1",
"@actions/exec": "^1.0.1",
"@actions/tool-cache": "^2.0.1",
"@tediousjs/connection-string": "^0.5.0",
Expand Down
105 changes: 98 additions & 7 deletions src/AzureSqlAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,18 @@ export enum ActionType {

export interface IActionInputs {
actionType: ActionType;
connectionConfig: SqlConnectionConfig;
connectionConfig?: SqlConnectionConfig;
filePath: string;
additionalArguments?: string;
skipFirewallCheck: boolean;
skipJobSummary: boolean;
}

export interface ISqlCmdInputs extends IActionInputs {
connectionConfig: SqlConnectionConfig;
}


export interface IDacpacActionInputs extends IActionInputs {
sqlpackageAction: SqlPackageAction;
sqlpackagePath?: string;
Expand All @@ -38,7 +44,8 @@ export enum SqlPackageAction {
Import,
DriftReport,
DeployReport,
Script
Script,
BuildOnly
}

export default class AzureSqlAction {
Expand All @@ -51,7 +58,7 @@ export default class AzureSqlAction {
await this._executeDacpacAction(this._inputs as IDacpacActionInputs);
}
else if (this._inputs.actionType === ActionType.SqlAction) {
await this._executeSqlFile(this._inputs);
await this._executeSqlFile(this._inputs as ISqlCmdInputs);
}
else if (this._inputs.actionType === ActionType.BuildAndPublish) {
const buildAndPublishInputs = this._inputs as IBuildAndPublishInputs;
Expand All @@ -74,6 +81,11 @@ export default class AzureSqlAction {
}

private async _executeDacpacAction(inputs: IDacpacActionInputs) {
if (inputs.sqlpackageAction === SqlPackageAction.BuildOnly) {
core.debug('Skipping sqlpackage action as action is set to BuildOnly');
return;
}

core.debug('Begin executing sqlpackage');
let sqlPackagePath = await AzureSqlActionHelper.getSqlPackagePath(inputs);
let sqlPackageArgs = this._getSqlPackageArguments(inputs);
Expand All @@ -83,7 +95,7 @@ export default class AzureSqlAction {
console.log(`Successfully executed action ${SqlPackageAction[inputs.sqlpackageAction]} on target database.`);
}

private async _executeSqlFile(inputs: IActionInputs) {
private async _executeSqlFile(inputs: ISqlCmdInputs) {
core.debug('Begin executing sql script');

let sqlcmdCall = SqlUtils.buildSqlCmdCallWithConnectionInfo(inputs.connectionConfig);
Expand Down Expand Up @@ -115,16 +127,96 @@ export default class AzureSqlAction {
outputDir = path.join(path.dirname(inputs.filePath), "bin", configuration);
}

await exec.exec(`dotnet build "${inputs.filePath}" -p:NetCoreBuild=true ${additionalBuildArguments}`);

let buildOutput = '';

try {
await exec.exec(`dotnet build "${inputs.filePath}" -p:NetCoreBuild=true ${additionalBuildArguments}`
, [], {
listeners: {
stderr: (data: Buffer) => buildOutput += data.toString(),
stdout: (data: Buffer) => buildOutput += data.toString()
}
}
);

// check for process.env.GITHUB_STEP_SUMMARY to support unit tests and older GitHub enterprise environments
if (!inputs.skipJobSummary && process.env.GITHUB_STEP_SUMMARY) {
this._projectBuildJobSummary(buildOutput);
}
} catch (error) {
if (!inputs.skipJobSummary && process.env.GITHUB_STEP_SUMMARY) {
this._projectBuildJobSummary(buildOutput);
}
throw new Error(`Failed to build project: ${error}`);
}

const dacpacPath = path.join(outputDir, projectName + Constants.dacpacExtension);
console.log(`Successfully built database project to ${dacpacPath}`);
return dacpacPath;
}

/**
* parses the build output and adds a summary to the github job
* displays errors and warnings
* @param buildOutput The output of the dotnet build exec
*/
private _projectBuildJobSummary(buildOutput: string) {
try {
if (buildOutput.includes('Build succeeded.')) {
core.summary.addHeading(':white_check_mark: SQL project build succeeded.');
} else {
core.summary.addHeading(':rotating_light: SQL project build failed.');
}
core.summary.addEOL();
core.summary.addRaw('See the full build log for more details.', true);

const lines = buildOutput.split(/\r?\n/);
let warnings = lines.filter(line => (line.includes('Build warning') || line.includes('StaticCodeAnalysis warning')));
let errorMessages = lines.filter(line => (line.includes('Build error') || line.includes('StaticCodeAnalysis error')));

if (errorMessages.length > 0) {
errorMessages = [...new Set(errorMessages)];
core.summary.addHeading(':x: Errors', 2);
core.summary.addEOL();

errorMessages.forEach(error => {
// remove [project path] from the end of the line
error = error.lastIndexOf('[') > 0 ? error.substring(0, error.lastIndexOf('[')-1) : error;

// move the file info from the beginning of the line to the end
error = '- **'+error.substring(error.indexOf(':')+2) + '** ' + error.substring(0, error.indexOf(':'));
core.summary.addRaw(error, true);
});
}

if (warnings.length > 0) {
warnings = [...new Set(warnings)];
core.summary.addHeading(':warning: Warnings', 2);
core.summary.addEOL();

warnings.forEach(warning => {
// remove [project path] from the end of the line
warning = warning.lastIndexOf('[') > 0 ? warning.substring(0, warning.lastIndexOf('[')-1) : warning;

// move the file info from the beginning of the line to the end
warning = '- **'+warning.substring(warning.indexOf(':')+2) + '** ' + warning.substring(0, warning.indexOf(':'));
core.summary.addRaw(warning, true);
});
}

core.summary.write();
} catch (err) {
core.notice(`Error parsing build output for job summary: ${err}`);
}
}

private _getSqlPackageArguments(inputs: IDacpacActionInputs) {
let args = '';

if (!inputs.connectionConfig) {
throw new Error('Connection string is required for action to call SqlPackgage');
}

switch (inputs.sqlpackageAction) {
case SqlPackageAction.Publish:
case SqlPackageAction.Script:
Expand All @@ -134,7 +226,6 @@ export default class AzureSqlAction {
case SqlPackageAction.DriftReport:
args += `/Action:${SqlPackageAction[inputs.sqlpackageAction]} /TargetConnectionString:"${inputs.connectionConfig.EscapedConnectionString}"`;
break;

default:
throw new Error(`Not supported SqlPackage action: '${SqlPackageAction[inputs.sqlpackageAction]}'`);
}
Expand Down
4 changes: 3 additions & 1 deletion src/AzureSqlActionHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,10 @@ export default class AzureSqlActionHelper {
return SqlPackageAction.DeployReport;
case 'script':
return SqlPackageAction.Script;
case 'buildonly':
return SqlPackageAction.BuildOnly;
default:
throw new Error(`Action ${action} is invalid. Supported action types are: Publish, Script, DriftReport, or DeployReport.`);
throw new Error(`Action ${action} is invalid. Supported action types are: Publish, Script, DriftReport, DeployReport, or BuildOnly.`);
}
}

Expand Down
Loading
Loading