Skip to content

Commit

Permalink
feat: add SQL Native Client 11.0 installer
Browse files Browse the repository at this point in the history
  • Loading branch information
dhensby committed Sep 4, 2023
1 parent 7327e8b commit dd970c4
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 2 deletions.
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ jobs:
uses: ./
with:
sqlserver-version: ${{ matrix.sqlserver }}
native-client-version: 11
release:
name: Release
concurrency: release
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ See [action.yml](./action.yml):
# Default: latest
sqlserver-version: ''

# Version of native client to installer. Only 11 is supported.
native-client-version: ''

# The SA user password to use.
# Default: yourStrong(!)Password
sa-password: ''
Expand Down
2 changes: 2 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ inputs:
sqlserver-version:
description: 'Version to use. Examples: 2008, 2012, 2014, etc. "latest" can also be used.'
default: 'latest'
native-client-version:
description: 'Version of native client to installer. Only 11 is supported.'
sa-password:
description: 'The SA user password to use.'
default: 'yourStrong(!)Password'
Expand Down
2 changes: 1 addition & 1 deletion lib/main/index.js

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions src/install-native-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as core from '@actions/core';
import * as tc from '@actions/tool-cache';
import * as exec from '@actions/exec';
import { downloadTool } from './utils';
import { join as joinPaths } from 'path';

const x64_URL = 'https://download.microsoft.com/download/B/E/D/BED73AAC-3C8A-43F5-AF4F-EB4FEA6C8F3A/ENU/x64/sqlncli.msi';
const x86_URL = 'https://download.microsoft.com/download/B/E/D/BED73AAC-3C8A-43F5-AF4F-EB4FEA6C8F3A/ENU/x86/sqlncli.msi';

export default async function installNativeClient(version: number) {
if (version !== 11) {
throw new Error('Unsupported Native Client version, only 11 is valid.');
}
const arch = process.arch === 'x64' ? 'x64' : 'x86';
let path = tc.find('sqlncli', '11.0', arch);
if (!path) {
core.info(`Downloading client installer for ${arch}.`);
path = await downloadTool(arch === 'x64' ? x64_URL : x86_URL).then((tmp) => {
return tc.cacheFile(tmp, 'sqlncli.msi', 'sqlncli', '11.0', arch);
});
} else {
core.info('Loaded client installer from cache.');
}
path = joinPaths(path, 'sqlncli.msi');
core.info('Installing SQL Native Client 11.0');
// see https://learn.microsoft.com/en-us/previous-versions/sql/sql-server-2012/ms131321(v=sql.110)
await exec.exec('msiexec', [
'/passive',
'/i',
path,
'APPGUID={0CC618CE-F36A-415E-84B4-FB1BFF6967E1}',
'IACCEPTSQLNCLILICENSETERMS=YES',
], {
windowsVerbatimArguments: true,
});
}
14 changes: 13 additions & 1 deletion src/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
getOsVersion,
waitForDatabase,
} from './utils';
import installNativeClient from './install-native-client';

/**
* Attempt to load the installer from the tool-cache, otherwise, fetch it.
Expand All @@ -32,7 +33,15 @@ function findOrDownloadTool(config: VersionConfig): Promise<string> {
}

export default async function install() {
const { version, password, collation, installArgs, wait, skipOsCheck } = gatherInputs();
const {
version,
password,
collation,
installArgs,
wait,
skipOsCheck,
nativeClientVersion,
} = gatherInputs();
// we only support windows for now. But allow crazy people to skip this check if they like...
if (!skipOsCheck && os.platform() !== 'win32') {
throw new Error(`setup-sqlserver only supports Windows runners, got: ${os.platform()}`);
Expand Down Expand Up @@ -66,6 +75,9 @@ export default async function install() {
throw new Error(`Runner version windows-${osVersion} is not supported for SQL Server ${version}. ${message}`);
}
}
if (nativeClientVersion) {
await core.group('Installing SQL Native Client', () => installNativeClient(parseInt(nativeClientVersion, 10)));
}
// Initial checks complete - fetch the installer
const toolPath = await core.group(`Fetching install media for ${version}`, () => findOrDownloadTool(config));
const instanceName = 'MSSQLSERVER';
Expand Down
2 changes: 2 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface Inputs {
installArgs: string[];
wait: boolean;
skipOsCheck: boolean;
nativeClientVersion: string;
}

/**
Expand All @@ -64,6 +65,7 @@ export function gatherInputs(): Inputs {
installArgs: core.getMultilineInput('install-arguments'),
wait: core.getBooleanInput('wait-for-ready'),
skipOsCheck: core.getBooleanInput('skip-os-check'),
nativeClientVersion: core.getInput('native-client-version'),
};
}

Expand Down
78 changes: 78 additions & 0 deletions test/install-native-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { match, restore, SinonStubbedInstance, stub } from 'sinon';
import * as core from '@actions/core';
import * as tc from '@actions/tool-cache';
import * as exec from '@actions/exec';
import * as utils from '../src/utils';
import installNativeClient from '../src/install-native-client';
import { expect, use } from 'chai';
import sinonChai from 'sinon-chai';
use(sinonChai);

describe('install-native-client', () => {
// let coreStub: SinonStubbedInstance<typeof core>;
let tcStub: SinonStubbedInstance<typeof tc>;
let execStub: SinonStubbedInstance<typeof exec>;
let utilsStub: SinonStubbedInstance<typeof utils>;
let arch: PropertyDescriptor;
beforeEach('stub deps', () => {
stub(core);
arch = Object.getOwnPropertyDescriptor(process, 'arch')!;
tcStub = stub(tc);
tcStub.find.returns('');
execStub = stub(exec);
execStub.exec.resolves();
utilsStub = stub(utils);
utilsStub.downloadTool.resolves('c:/tmp/downloads');
});
afterEach('restore stubs', () => {
Object.defineProperty(process, 'arch', arch);
restore();
});
describe('.installNativeClient()', () => {
it('throws for bad version', async () => {
try {
await installNativeClient(10);
} catch (e) {
expect(e).to.have.property('message', 'Unsupported Native Client version, only 11 is valid.');
return;
}
expect.fail('expected to throw');
});
it('installs from cache', async () => {
tcStub.find.returns('C:/tmp/');
await installNativeClient(11);
expect(utilsStub.downloadTool).to.have.callCount(0);
expect(execStub.exec).to.have.been.calledOnceWith('msiexec', match.array, {
windowsVerbatimArguments: true,
});
expect(execStub.exec.firstCall.args[1]).to.contain('C:/tmp/sqlncli.msi');
});
it('installs from web (x64)', async () => {
Object.defineProperty(process, 'arch', {
value: 'x64',
});
tcStub.cacheFile.resolves('C:/tmp/cache/');
await installNativeClient(11);
expect(utilsStub.downloadTool).to.have.been.calledOnceWith('https://download.microsoft.com/download/B/E/D/BED73AAC-3C8A-43F5-AF4F-EB4FEA6C8F3A/ENU/x64/sqlncli.msi');
expect(tcStub.cacheFile).to.have.callCount(1);
expect(execStub.exec).to.have.been.calledOnceWith('msiexec', match.array, {
windowsVerbatimArguments: true,
});
expect(execStub.exec.firstCall.args[1]).to.contain('C:/tmp/cache/sqlncli.msi');
});
it('installs from web (x32)', async () => {
Object.defineProperty(process, 'arch', {
value: 'x32',
});
tcStub.cacheFile.resolves('C:/tmp/cache/');
await installNativeClient(11);
expect(utilsStub.downloadTool).to.have.been.calledOnceWith('https://download.microsoft.com/download/B/E/D/BED73AAC-3C8A-43F5-AF4F-EB4FEA6C8F3A/ENU/x86/sqlncli.msi');
expect(tcStub.cacheFile).to.have.callCount(1);
expect(execStub.exec).to.have.been.calledOnceWith('msiexec', match.array, {
windowsVerbatimArguments: true,
});
expect;
expect(execStub.exec.firstCall.args[1]).to.contain('C:/tmp/cache/sqlncli.msi');
});
});
});
26 changes: 26 additions & 0 deletions test/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as core from '@actions/core';
import * as tc from '@actions/tool-cache';
import * as exec from '@actions/exec';
import * as versions from '../src/versions';
import * as nativeClient from '../src/install-native-client';
import { match, restore, SinonStubbedInstance, stub, useFakeTimers } from 'sinon';
import * as utils from '../src/utils';
import install from '../src/install';
Expand All @@ -20,7 +21,9 @@ describe('install', () => {
let utilsStub: SinonStubbedInstance<typeof utils>;
let tcStub: SinonStubbedInstance<typeof tc>;
let execStub: SinonStubbedInstance<typeof exec>;
let stubNc: SinonStubbedInstance<typeof nativeClient>;
beforeEach('stub deps', () => {
stubNc = stub(nativeClient);
versionStub = stub(versions.VERSIONS);
versionStub.keys.returns(['box', 'exe', 'maxOs', 'minOs', 'minMaxOs'][Symbol.iterator]());
versionStub.has.callsFake((name) => {
Expand Down Expand Up @@ -65,6 +68,7 @@ describe('install', () => {
installArgs: [],
wait: true,
skipOsCheck: false,
nativeClientVersion: '',
});
utilsStub.getOsVersion.resolves(2022);
utilsStub.gatherSummaryFiles.resolves([]);
Expand Down Expand Up @@ -105,6 +109,7 @@ describe('install', () => {
installArgs: [],
wait: true,
skipOsCheck: false,
nativeClientVersion: '',
});
try {
await install();
Expand All @@ -126,6 +131,7 @@ describe('install', () => {
installArgs: [],
wait: true,
skipOsCheck: false,
nativeClientVersion: '',
});
await install();
expect(execStub.exec).to.have.been.calledWith('"C:/tmp/exe/setup.exe"', match.array, { windowsVerbatimArguments: true });
Expand All @@ -143,6 +149,7 @@ describe('install', () => {
installArgs: [],
wait: true,
skipOsCheck: false,
nativeClientVersion: '',
});
try {
await install();
Expand All @@ -161,6 +168,7 @@ describe('install', () => {
installArgs: [],
wait: true,
skipOsCheck: false,
nativeClientVersion: '',
});
try {
await install();
Expand All @@ -179,6 +187,7 @@ describe('install', () => {
installArgs: [],
wait: true,
skipOsCheck: false,
nativeClientVersion: '',
});
try {
await install();
Expand All @@ -197,6 +206,7 @@ describe('install', () => {
installArgs: [],
wait: true,
skipOsCheck: false,
nativeClientVersion: '',
});
await install();
expect(execStub.exec).to.have.been.calledWith('"C:/tmp/exe/setup.exe"', match.array, { windowsVerbatimArguments: true });
Expand All @@ -210,6 +220,7 @@ describe('install', () => {
installArgs: [],
wait: true,
skipOsCheck: false,
nativeClientVersion: '',
});
await install();
expect(execStub.exec).to.have.been.calledWith('"C:/tmp/exe/setup.exe"', match.array, { windowsVerbatimArguments: true });
Expand All @@ -222,6 +233,7 @@ describe('install', () => {
installArgs: [],
wait: true,
skipOsCheck: true,
nativeClientVersion: '',
});
await install();
expect(execStub.exec).to.have.been.calledWith('"C:/tmp/exe/setup.exe"', match.array, { windowsVerbatimArguments: true });
Expand Down Expand Up @@ -256,6 +268,7 @@ describe('install', () => {
installArgs: [],
wait: false,
skipOsCheck: false,
nativeClientVersion: '',
});
const stubReadfile = stub(fs, 'readFile');
stubReadfile.resolves(Buffer.from('test data'));
Expand All @@ -281,4 +294,17 @@ describe('install', () => {
}
expect.fail('expected to throw');
});
it('installs native client if needed', async () => {
utilsStub.gatherInputs.returns({
version: 'box',
password: 'secret password',
collation: 'SQL_Latin1_General_CP1_CI_AS',
installArgs: [],
wait: false,
skipOsCheck: false,
nativeClientVersion: '11',
});
await install();
expect(stubNc.default).to.have.been.calledOnceWith(11);
});
});
8 changes: 8 additions & 0 deletions test/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ describe('utils', () => {
coreStub.getInput.withArgs('sqlserver-version').returns('sql-2022');
coreStub.getInput.withArgs('sa-password').returns('secret password');
coreStub.getInput.withArgs('db-collation').returns('SQL_Latin1_General_CP1_CI_AS');
coreStub.getInput.withArgs('native-client-version').returns('');
coreStub.getMultilineInput.withArgs('install-arguments').returns([]);
coreStub.getBooleanInput.withArgs('wait-for-ready').returns(true);
coreStub.getBooleanInput.withArgs('skip-os-check').returns(false);
Expand All @@ -198,12 +199,14 @@ describe('utils', () => {
installArgs: [],
wait: true,
skipOsCheck: false,
nativeClientVersion: '',
});
});
it('constructs input object with no sql- prefix', () => {
coreStub.getInput.withArgs('sqlserver-version').returns('2022');
coreStub.getInput.withArgs('sa-password').returns('secret password');
coreStub.getInput.withArgs('db-collation').returns('SQL_Latin1_General_CP1_CI_AS');
coreStub.getInput.withArgs('native-client-version').returns('');
coreStub.getMultilineInput.withArgs('install-arguments').returns([]);
coreStub.getBooleanInput.withArgs('wait-for-ready').returns(true);
coreStub.getBooleanInput.withArgs('skip-os-check').returns(false);
Expand All @@ -215,12 +218,14 @@ describe('utils', () => {
installArgs: [],
wait: true,
skipOsCheck: false,
nativeClientVersion: '',
});
});
it('constructs input object with "latest" version', () => {
coreStub.getInput.withArgs('sqlserver-version').returns('latest');
coreStub.getInput.withArgs('sa-password').returns('secret password');
coreStub.getInput.withArgs('db-collation').returns('SQL_Latin1_General_CP1_CI_AS');
coreStub.getInput.withArgs('native-client-version').returns('');
coreStub.getMultilineInput.withArgs('install-arguments').returns([]);
coreStub.getBooleanInput.withArgs('wait-for-ready').returns(true);
coreStub.getBooleanInput.withArgs('skip-os-check').returns(false);
Expand All @@ -232,12 +237,14 @@ describe('utils', () => {
installArgs: [],
wait: true,
skipOsCheck: false,
nativeClientVersion: '',
});
});
it('constructs input object with default version', () => {
coreStub.getInput.withArgs('sqlserver-version').returns('');
coreStub.getInput.withArgs('sa-password').returns('secret password');
coreStub.getInput.withArgs('db-collation').returns('SQL_Latin1_General_CP1_CI_AS');
coreStub.getInput.withArgs('native-client-version').returns('');
coreStub.getMultilineInput.withArgs('install-arguments').returns([]);
coreStub.getBooleanInput.withArgs('wait-for-ready').returns(true);
coreStub.getBooleanInput.withArgs('skip-os-check').returns(false);
Expand All @@ -249,6 +256,7 @@ describe('utils', () => {
installArgs: [],
wait: true,
skipOsCheck: false,
nativeClientVersion: '',
});
});
});
Expand Down

0 comments on commit dd970c4

Please sign in to comment.