Skip to content

Commit 9688073

Browse files
authored
Merge pull request tediousjs#4 from dhensby/pulls/install-native-client
feat: add SQL Native Client 11.0 installer
2 parents 7327e8b + dd970c4 commit 9688073

File tree

10 files changed

+170
-2
lines changed

10 files changed

+170
-2
lines changed

.github/workflows/release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ jobs:
145145
uses: ./
146146
with:
147147
sqlserver-version: ${{ matrix.sqlserver }}
148+
native-client-version: 11
148149
release:
149150
name: Release
150151
concurrency: release

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ See [action.yml](./action.yml):
1717
# Default: latest
1818
sqlserver-version: ''
1919

20+
# Version of native client to installer. Only 11 is supported.
21+
native-client-version: ''
22+
2023
# The SA user password to use.
2124
# Default: yourStrong(!)Password
2225
sa-password: ''

action.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ inputs:
88
sqlserver-version:
99
description: 'Version to use. Examples: 2008, 2012, 2014, etc. "latest" can also be used.'
1010
default: 'latest'
11+
native-client-version:
12+
description: 'Version of native client to installer. Only 11 is supported.'
1113
sa-password:
1214
description: 'The SA user password to use.'
1315
default: 'yourStrong(!)Password'

lib/main/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/install-native-client.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import * as core from '@actions/core';
2+
import * as tc from '@actions/tool-cache';
3+
import * as exec from '@actions/exec';
4+
import { downloadTool } from './utils';
5+
import { join as joinPaths } from 'path';
6+
7+
const x64_URL = 'https://download.microsoft.com/download/B/E/D/BED73AAC-3C8A-43F5-AF4F-EB4FEA6C8F3A/ENU/x64/sqlncli.msi';
8+
const x86_URL = 'https://download.microsoft.com/download/B/E/D/BED73AAC-3C8A-43F5-AF4F-EB4FEA6C8F3A/ENU/x86/sqlncli.msi';
9+
10+
export default async function installNativeClient(version: number) {
11+
if (version !== 11) {
12+
throw new Error('Unsupported Native Client version, only 11 is valid.');
13+
}
14+
const arch = process.arch === 'x64' ? 'x64' : 'x86';
15+
let path = tc.find('sqlncli', '11.0', arch);
16+
if (!path) {
17+
core.info(`Downloading client installer for ${arch}.`);
18+
path = await downloadTool(arch === 'x64' ? x64_URL : x86_URL).then((tmp) => {
19+
return tc.cacheFile(tmp, 'sqlncli.msi', 'sqlncli', '11.0', arch);
20+
});
21+
} else {
22+
core.info('Loaded client installer from cache.');
23+
}
24+
path = joinPaths(path, 'sqlncli.msi');
25+
core.info('Installing SQL Native Client 11.0');
26+
// see https://learn.microsoft.com/en-us/previous-versions/sql/sql-server-2012/ms131321(v=sql.110)
27+
await exec.exec('msiexec', [
28+
'/passive',
29+
'/i',
30+
path,
31+
'APPGUID={0CC618CE-F36A-415E-84B4-FB1BFF6967E1}',
32+
'IACCEPTSQLNCLILICENSETERMS=YES',
33+
], {
34+
windowsVerbatimArguments: true,
35+
});
36+
}

src/install.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
getOsVersion,
1414
waitForDatabase,
1515
} from './utils';
16+
import installNativeClient from './install-native-client';
1617

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

3435
export default async function install() {
35-
const { version, password, collation, installArgs, wait, skipOsCheck } = gatherInputs();
36+
const {
37+
version,
38+
password,
39+
collation,
40+
installArgs,
41+
wait,
42+
skipOsCheck,
43+
nativeClientVersion,
44+
} = gatherInputs();
3645
// we only support windows for now. But allow crazy people to skip this check if they like...
3746
if (!skipOsCheck && os.platform() !== 'win32') {
3847
throw new Error(`setup-sqlserver only supports Windows runners, got: ${os.platform()}`);
@@ -66,6 +75,9 @@ export default async function install() {
6675
throw new Error(`Runner version windows-${osVersion} is not supported for SQL Server ${version}. ${message}`);
6776
}
6877
}
78+
if (nativeClientVersion) {
79+
await core.group('Installing SQL Native Client', () => installNativeClient(parseInt(nativeClientVersion, 10)));
80+
}
6981
// Initial checks complete - fetch the installer
7082
const toolPath = await core.group(`Fetching install media for ${version}`, () => findOrDownloadTool(config));
7183
const instanceName = 'MSSQLSERVER';

src/utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export interface Inputs {
4848
installArgs: string[];
4949
wait: boolean;
5050
skipOsCheck: boolean;
51+
nativeClientVersion: string;
5152
}
5253

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

test/install-native-client.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { match, restore, SinonStubbedInstance, stub } from 'sinon';
2+
import * as core from '@actions/core';
3+
import * as tc from '@actions/tool-cache';
4+
import * as exec from '@actions/exec';
5+
import * as utils from '../src/utils';
6+
import installNativeClient from '../src/install-native-client';
7+
import { expect, use } from 'chai';
8+
import sinonChai from 'sinon-chai';
9+
use(sinonChai);
10+
11+
describe('install-native-client', () => {
12+
// let coreStub: SinonStubbedInstance<typeof core>;
13+
let tcStub: SinonStubbedInstance<typeof tc>;
14+
let execStub: SinonStubbedInstance<typeof exec>;
15+
let utilsStub: SinonStubbedInstance<typeof utils>;
16+
let arch: PropertyDescriptor;
17+
beforeEach('stub deps', () => {
18+
stub(core);
19+
arch = Object.getOwnPropertyDescriptor(process, 'arch')!;
20+
tcStub = stub(tc);
21+
tcStub.find.returns('');
22+
execStub = stub(exec);
23+
execStub.exec.resolves();
24+
utilsStub = stub(utils);
25+
utilsStub.downloadTool.resolves('c:/tmp/downloads');
26+
});
27+
afterEach('restore stubs', () => {
28+
Object.defineProperty(process, 'arch', arch);
29+
restore();
30+
});
31+
describe('.installNativeClient()', () => {
32+
it('throws for bad version', async () => {
33+
try {
34+
await installNativeClient(10);
35+
} catch (e) {
36+
expect(e).to.have.property('message', 'Unsupported Native Client version, only 11 is valid.');
37+
return;
38+
}
39+
expect.fail('expected to throw');
40+
});
41+
it('installs from cache', async () => {
42+
tcStub.find.returns('C:/tmp/');
43+
await installNativeClient(11);
44+
expect(utilsStub.downloadTool).to.have.callCount(0);
45+
expect(execStub.exec).to.have.been.calledOnceWith('msiexec', match.array, {
46+
windowsVerbatimArguments: true,
47+
});
48+
expect(execStub.exec.firstCall.args[1]).to.contain('C:/tmp/sqlncli.msi');
49+
});
50+
it('installs from web (x64)', async () => {
51+
Object.defineProperty(process, 'arch', {
52+
value: 'x64',
53+
});
54+
tcStub.cacheFile.resolves('C:/tmp/cache/');
55+
await installNativeClient(11);
56+
expect(utilsStub.downloadTool).to.have.been.calledOnceWith('https://download.microsoft.com/download/B/E/D/BED73AAC-3C8A-43F5-AF4F-EB4FEA6C8F3A/ENU/x64/sqlncli.msi');
57+
expect(tcStub.cacheFile).to.have.callCount(1);
58+
expect(execStub.exec).to.have.been.calledOnceWith('msiexec', match.array, {
59+
windowsVerbatimArguments: true,
60+
});
61+
expect(execStub.exec.firstCall.args[1]).to.contain('C:/tmp/cache/sqlncli.msi');
62+
});
63+
it('installs from web (x32)', async () => {
64+
Object.defineProperty(process, 'arch', {
65+
value: 'x32',
66+
});
67+
tcStub.cacheFile.resolves('C:/tmp/cache/');
68+
await installNativeClient(11);
69+
expect(utilsStub.downloadTool).to.have.been.calledOnceWith('https://download.microsoft.com/download/B/E/D/BED73AAC-3C8A-43F5-AF4F-EB4FEA6C8F3A/ENU/x86/sqlncli.msi');
70+
expect(tcStub.cacheFile).to.have.callCount(1);
71+
expect(execStub.exec).to.have.been.calledOnceWith('msiexec', match.array, {
72+
windowsVerbatimArguments: true,
73+
});
74+
expect;
75+
expect(execStub.exec.firstCall.args[1]).to.contain('C:/tmp/cache/sqlncli.msi');
76+
});
77+
});
78+
});

test/install.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as core from '@actions/core';
44
import * as tc from '@actions/tool-cache';
55
import * as exec from '@actions/exec';
66
import * as versions from '../src/versions';
7+
import * as nativeClient from '../src/install-native-client';
78
import { match, restore, SinonStubbedInstance, stub, useFakeTimers } from 'sinon';
89
import * as utils from '../src/utils';
910
import install from '../src/install';
@@ -20,7 +21,9 @@ describe('install', () => {
2021
let utilsStub: SinonStubbedInstance<typeof utils>;
2122
let tcStub: SinonStubbedInstance<typeof tc>;
2223
let execStub: SinonStubbedInstance<typeof exec>;
24+
let stubNc: SinonStubbedInstance<typeof nativeClient>;
2325
beforeEach('stub deps', () => {
26+
stubNc = stub(nativeClient);
2427
versionStub = stub(versions.VERSIONS);
2528
versionStub.keys.returns(['box', 'exe', 'maxOs', 'minOs', 'minMaxOs'][Symbol.iterator]());
2629
versionStub.has.callsFake((name) => {
@@ -65,6 +68,7 @@ describe('install', () => {
6568
installArgs: [],
6669
wait: true,
6770
skipOsCheck: false,
71+
nativeClientVersion: '',
6872
});
6973
utilsStub.getOsVersion.resolves(2022);
7074
utilsStub.gatherSummaryFiles.resolves([]);
@@ -105,6 +109,7 @@ describe('install', () => {
105109
installArgs: [],
106110
wait: true,
107111
skipOsCheck: false,
112+
nativeClientVersion: '',
108113
});
109114
try {
110115
await install();
@@ -126,6 +131,7 @@ describe('install', () => {
126131
installArgs: [],
127132
wait: true,
128133
skipOsCheck: false,
134+
nativeClientVersion: '',
129135
});
130136
await install();
131137
expect(execStub.exec).to.have.been.calledWith('"C:/tmp/exe/setup.exe"', match.array, { windowsVerbatimArguments: true });
@@ -143,6 +149,7 @@ describe('install', () => {
143149
installArgs: [],
144150
wait: true,
145151
skipOsCheck: false,
152+
nativeClientVersion: '',
146153
});
147154
try {
148155
await install();
@@ -161,6 +168,7 @@ describe('install', () => {
161168
installArgs: [],
162169
wait: true,
163170
skipOsCheck: false,
171+
nativeClientVersion: '',
164172
});
165173
try {
166174
await install();
@@ -179,6 +187,7 @@ describe('install', () => {
179187
installArgs: [],
180188
wait: true,
181189
skipOsCheck: false,
190+
nativeClientVersion: '',
182191
});
183192
try {
184193
await install();
@@ -197,6 +206,7 @@ describe('install', () => {
197206
installArgs: [],
198207
wait: true,
199208
skipOsCheck: false,
209+
nativeClientVersion: '',
200210
});
201211
await install();
202212
expect(execStub.exec).to.have.been.calledWith('"C:/tmp/exe/setup.exe"', match.array, { windowsVerbatimArguments: true });
@@ -210,6 +220,7 @@ describe('install', () => {
210220
installArgs: [],
211221
wait: true,
212222
skipOsCheck: false,
223+
nativeClientVersion: '',
213224
});
214225
await install();
215226
expect(execStub.exec).to.have.been.calledWith('"C:/tmp/exe/setup.exe"', match.array, { windowsVerbatimArguments: true });
@@ -222,6 +233,7 @@ describe('install', () => {
222233
installArgs: [],
223234
wait: true,
224235
skipOsCheck: true,
236+
nativeClientVersion: '',
225237
});
226238
await install();
227239
expect(execStub.exec).to.have.been.calledWith('"C:/tmp/exe/setup.exe"', match.array, { windowsVerbatimArguments: true });
@@ -256,6 +268,7 @@ describe('install', () => {
256268
installArgs: [],
257269
wait: false,
258270
skipOsCheck: false,
271+
nativeClientVersion: '',
259272
});
260273
const stubReadfile = stub(fs, 'readFile');
261274
stubReadfile.resolves(Buffer.from('test data'));
@@ -281,4 +294,17 @@ describe('install', () => {
281294
}
282295
expect.fail('expected to throw');
283296
});
297+
it('installs native client if needed', async () => {
298+
utilsStub.gatherInputs.returns({
299+
version: 'box',
300+
password: 'secret password',
301+
collation: 'SQL_Latin1_General_CP1_CI_AS',
302+
installArgs: [],
303+
wait: false,
304+
skipOsCheck: false,
305+
nativeClientVersion: '11',
306+
});
307+
await install();
308+
expect(stubNc.default).to.have.been.calledOnceWith(11);
309+
});
284310
});

test/utils.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ describe('utils', () => {
187187
coreStub.getInput.withArgs('sqlserver-version').returns('sql-2022');
188188
coreStub.getInput.withArgs('sa-password').returns('secret password');
189189
coreStub.getInput.withArgs('db-collation').returns('SQL_Latin1_General_CP1_CI_AS');
190+
coreStub.getInput.withArgs('native-client-version').returns('');
190191
coreStub.getMultilineInput.withArgs('install-arguments').returns([]);
191192
coreStub.getBooleanInput.withArgs('wait-for-ready').returns(true);
192193
coreStub.getBooleanInput.withArgs('skip-os-check').returns(false);
@@ -198,12 +199,14 @@ describe('utils', () => {
198199
installArgs: [],
199200
wait: true,
200201
skipOsCheck: false,
202+
nativeClientVersion: '',
201203
});
202204
});
203205
it('constructs input object with no sql- prefix', () => {
204206
coreStub.getInput.withArgs('sqlserver-version').returns('2022');
205207
coreStub.getInput.withArgs('sa-password').returns('secret password');
206208
coreStub.getInput.withArgs('db-collation').returns('SQL_Latin1_General_CP1_CI_AS');
209+
coreStub.getInput.withArgs('native-client-version').returns('');
207210
coreStub.getMultilineInput.withArgs('install-arguments').returns([]);
208211
coreStub.getBooleanInput.withArgs('wait-for-ready').returns(true);
209212
coreStub.getBooleanInput.withArgs('skip-os-check').returns(false);
@@ -215,12 +218,14 @@ describe('utils', () => {
215218
installArgs: [],
216219
wait: true,
217220
skipOsCheck: false,
221+
nativeClientVersion: '',
218222
});
219223
});
220224
it('constructs input object with "latest" version', () => {
221225
coreStub.getInput.withArgs('sqlserver-version').returns('latest');
222226
coreStub.getInput.withArgs('sa-password').returns('secret password');
223227
coreStub.getInput.withArgs('db-collation').returns('SQL_Latin1_General_CP1_CI_AS');
228+
coreStub.getInput.withArgs('native-client-version').returns('');
224229
coreStub.getMultilineInput.withArgs('install-arguments').returns([]);
225230
coreStub.getBooleanInput.withArgs('wait-for-ready').returns(true);
226231
coreStub.getBooleanInput.withArgs('skip-os-check').returns(false);
@@ -232,12 +237,14 @@ describe('utils', () => {
232237
installArgs: [],
233238
wait: true,
234239
skipOsCheck: false,
240+
nativeClientVersion: '',
235241
});
236242
});
237243
it('constructs input object with default version', () => {
238244
coreStub.getInput.withArgs('sqlserver-version').returns('');
239245
coreStub.getInput.withArgs('sa-password').returns('secret password');
240246
coreStub.getInput.withArgs('db-collation').returns('SQL_Latin1_General_CP1_CI_AS');
247+
coreStub.getInput.withArgs('native-client-version').returns('');
241248
coreStub.getMultilineInput.withArgs('install-arguments').returns([]);
242249
coreStub.getBooleanInput.withArgs('wait-for-ready').returns(true);
243250
coreStub.getBooleanInput.withArgs('skip-os-check').returns(false);
@@ -249,6 +256,7 @@ describe('utils', () => {
249256
installArgs: [],
250257
wait: true,
251258
skipOsCheck: false,
259+
nativeClientVersion: '',
252260
});
253261
});
254262
});

0 commit comments

Comments
 (0)