Skip to content

Commit 5527975

Browse files
committed
feat: add cumulative update installation for supported versions
1 parent 200d7c9 commit 5527975

File tree

10 files changed

+2038
-2615
lines changed

10 files changed

+2038
-2615
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ See [action.yml](./action.yml):
3939
# action. A maximum of 10 attempts is made.
4040
# Default: true
4141
wait-for-ready: true
42+
43+
# Attempt to install latest cumulative updates during the installation process
44+
# (not available for all versions).
45+
# Default: false
46+
install-updates: false
4247
```
4348
<!-- end usage -->
4449

action.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ inputs:
2323
wait-for-ready:
2424
description: 'Wait for the database to respond successfully to queries before completing the action. A maximum of 10 attempts is made.'
2525
default: 'true'
26+
install-updates:
27+
description: 'Attempt to install latest cumulative updates during the installation process (not available for all versions).'
28+
default: 'false'
2629
outputs:
2730
sa-password:
2831
description: 'The SA password, this will be the same as the input, but can be useful when relying on the default value.'

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.

package-lock.json

Lines changed: 1940 additions & 2612 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"@actions/core": "^1.10.0",
5858
"@actions/exec": "^1.1.1",
5959
"@actions/glob": "^0.4.0",
60+
"@actions/http-client": "^2.2.1",
6061
"@actions/io": "^1.1.3",
6162
"@actions/tool-cache": "^2.0.1"
6263
}

src/install.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import os from 'os';
2-
import { basename, join as joinPaths } from 'path';
2+
import { basename, dirname, join as joinPaths } from 'path';
33
import { readFile } from 'fs/promises';
44
import * as core from '@actions/core';
55
import * as exec from '@actions/exec';
@@ -8,6 +8,7 @@ import { VersionConfig, VERSIONS } from './versions';
88
import {
99
downloadBoxInstaller,
1010
downloadExeInstaller,
11+
downloadUpdateInstaller,
1112
gatherInputs,
1213
gatherSummaryFiles,
1314
getOsVersion,
@@ -33,6 +34,15 @@ function findOrDownloadTool(config: VersionConfig): Promise<string> {
3334
return downloadExeInstaller(config);
3435
}
3536

37+
function findOrDownloadUpdates(config: VersionConfig): Promise<string> {
38+
const toolPath = tc.find('sqlupdate', config.version);
39+
if (toolPath) {
40+
core.info(`Found in cache @ ${toolPath}`);
41+
return Promise.resolve(joinPaths(toolPath, 'sqlupdate.exe'));
42+
}
43+
return downloadUpdateInstaller(config);
44+
}
45+
3646
export default async function install() {
3747
let threw = false;
3848
const {
@@ -44,6 +54,7 @@ export default async function install() {
4454
skipOsCheck,
4555
nativeClientVersion,
4656
odbcVersion,
57+
installUpdates,
4758
} = gatherInputs();
4859
// we only support windows for now. But allow crazy people to skip this check if they like...
4960
if (!skipOsCheck && os.platform() !== 'win32') {
@@ -86,6 +97,16 @@ export default async function install() {
8697
}
8798
// Initial checks complete - fetch the installer
8899
const toolPath = await core.group(`Fetching install media for ${version}`, () => findOrDownloadTool(config));
100+
if (installUpdates) {
101+
if (!config.updateUrl) {
102+
core.info('Skipping update installation - version not supported');
103+
} else {
104+
const updatePath = await core.group(`Fetching cumulative updates for ${version}`, () => findOrDownloadUpdates(config));
105+
if (updatePath) {
106+
installArgs.push('/UPDATEENABLED=1', `/UpdateSource=${dirname(updatePath)}`);
107+
}
108+
}
109+
}
89110
const instanceName = 'MSSQLSERVER';
90111
try {
91112
// @todo - make sure that the arguments are unique / don't conflict

src/utils.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as exec from '@actions/exec';
22
import * as core from '@actions/core';
33
import * as tc from '@actions/tool-cache';
44
import * as io from '@actions/io';
5+
import * as http from '@actions/http-client';
56
import { basename, extname, dirname, join as joinPaths } from 'path';
67
import { VersionConfig } from './versions';
78
import { generateFileHash } from './crypto';
@@ -50,6 +51,7 @@ export interface Inputs {
5051
skipOsCheck: boolean;
5152
nativeClientVersion: string;
5253
odbcVersion: string;
54+
installUpdates: boolean;
5355
}
5456

5557
/**
@@ -68,6 +70,7 @@ export function gatherInputs(): Inputs {
6870
skipOsCheck: core.getBooleanInput('skip-os-check'),
6971
nativeClientVersion: core.getInput('native-client-version'),
7072
odbcVersion: core.getInput('odbc-version'),
73+
installUpdates: core.getBooleanInput('install-updates'),
7174
};
7275
}
7376

@@ -170,7 +173,44 @@ export async function downloadExeInstaller(config: VersionConfig): Promise<strin
170173
return joinPaths(toolPath, 'setup.exe');
171174
}
172175

173-
176+
/**
177+
* Downloads cumulative updates for supported versions.
178+
*
179+
* @param {VersionConfig} config
180+
* @returns {Promise<string>}
181+
*/
182+
export async function downloadUpdateInstaller(config: VersionConfig): Promise<string> {
183+
if (!config.updateUrl) {
184+
throw new Error('No update url provided');
185+
}
186+
// resolve download url
187+
let downloadLink: string | null = null;
188+
if (!config.updateUrl.endsWith('.exe')) {
189+
const client = new http.HttpClient();
190+
const res = await client.get(config.updateUrl);
191+
if (res.message.statusCode && res.message.statusCode >= 200 && res.message.statusCode < 300) {
192+
const body = await res.readBody();
193+
const [, link] = body.match(/\s+href\s*=\s*["'](https:\/\/download\.microsoft\.com\/.*\.exe)['"]/) ?? [];
194+
if (link) {
195+
core.info(`Found download link: ${link}`);
196+
downloadLink = link;
197+
}
198+
}
199+
if (!downloadLink) {
200+
core.warning('Unable to download cumulative updates');
201+
return '';
202+
}
203+
}
204+
const updatePath = await downloadTool(downloadLink ?? config.updateUrl);
205+
if (core.isDebug()) {
206+
const hash = await generateFileHash(updatePath);
207+
core.debug(`Got update file with hash SHA256=${hash.toString('base64')}`);
208+
}
209+
core.info('Adding to the cache');
210+
const toolPath = await tc.cacheFile(updatePath, 'sqlupdate.exe', 'sqlupdate', config.version);
211+
core.debug(`Cached @ ${toolPath}`);
212+
return joinPaths(toolPath, 'sqlupdate.exe');
213+
}
174214

175215
/**
176216
* Gather installation summary file. Used after installation to output summary data.

src/versions.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ interface Config {
1212
export interface VersionConfig extends Config {
1313
exeUrl: string;
1414
boxUrl?: string;
15+
updateUrl?: string;
1516
}
1617

1718
export const VERSIONS = new Map<string, VersionConfig>(
@@ -20,21 +21,25 @@ export const VERSIONS = new Map<string, VersionConfig>(
2021
version: '2022',
2122
exeUrl: 'https://download.microsoft.com/download/3/8/d/38de7036-2433-4207-8eae-06e247e17b25/SQLServer2022-DEV-x64-ENU.exe',
2223
boxUrl: 'https://download.microsoft.com/download/3/8/d/38de7036-2433-4207-8eae-06e247e17b25/SQLServer2022-DEV-x64-ENU.box',
24+
updateUrl: 'https://www.microsoft.com/en-us/download/details.aspx?id=105013',
2325
}],
2426
['2019', {
2527
version: '2019',
2628
exeUrl: 'https://download.microsoft.com/download/8/4/c/84c6c430-e0f5-476d-bf43-eaaa222a72e0/SQLServer2019-DEV-x64-ENU.exe',
2729
boxUrl: 'https://download.microsoft.com/download/8/4/c/84c6c430-e0f5-476d-bf43-eaaa222a72e0/SQLServer2019-DEV-x64-ENU.box',
30+
updateUrl: 'https://www.microsoft.com/en-us/download/details.aspx?id=100809',
2831
}],
2932
['2017', {
3033
version: '2017',
3134
exeUrl: 'https://download.microsoft.com/download/E/F/2/EF23C21D-7860-4F05-88CE-39AA114B014B/SQLServer2017-DEV-x64-ENU.exe',
3235
boxUrl: 'https://download.microsoft.com/download/E/F/2/EF23C21D-7860-4F05-88CE-39AA114B014B/SQLServer2017-DEV-x64-ENU.box',
36+
updateUrl: 'https://www.microsoft.com/en-us/download/details.aspx?id=56128',
3337
}],
3438
['2016', {
3539
version: '2016',
3640
exeUrl: 'https://download.microsoft.com/download/4/1/A/41AD6EDE-9794-44E3-B3D5-A1AF62CD7A6F/sql16_sp2_dlc/en-us/SQLServer2016SP2-FullSlipstream-DEV-x64-ENU.exe',
3741
boxUrl: 'https://download.microsoft.com/download/4/1/A/41AD6EDE-9794-44E3-B3D5-A1AF62CD7A6F/sql16_sp2_dlc/en-us/SQLServer2016SP2-FullSlipstream-DEV-x64-ENU.box',
42+
updateUrl: 'https://download.microsoft.com/download/a/7/7/a77b5753-8fe7-4804-bfc5-591d9a626c98/SQLServer2016SP3-KB5003279-x64-ENU.exe',
3843
}],
3944
['2014', {
4045
osSupport: {

test/install.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ describe('install', () => {
7373
skipOsCheck: false,
7474
nativeClientVersion: '',
7575
odbcVersion: '',
76+
installUpdates: false,
7677
});
7778
utilsStub.getOsVersion.resolves(2022);
7879
utilsStub.gatherSummaryFiles.resolves([]);
@@ -115,6 +116,7 @@ describe('install', () => {
115116
skipOsCheck: false,
116117
nativeClientVersion: '',
117118
odbcVersion: '',
119+
installUpdates: false,
118120
});
119121
try {
120122
await install();
@@ -138,6 +140,7 @@ describe('install', () => {
138140
skipOsCheck: false,
139141
nativeClientVersion: '',
140142
odbcVersion: '',
143+
installUpdates: false,
141144
});
142145
await install();
143146
expect(execStub.exec).to.have.been.calledWith('"C:/tmp/exe/setup.exe"', match.array, { windowsVerbatimArguments: true });
@@ -157,6 +160,7 @@ describe('install', () => {
157160
skipOsCheck: false,
158161
nativeClientVersion: '',
159162
odbcVersion: '',
163+
installUpdates: false,
160164
});
161165
try {
162166
await install();
@@ -177,6 +181,7 @@ describe('install', () => {
177181
skipOsCheck: false,
178182
nativeClientVersion: '',
179183
odbcVersion: '',
184+
installUpdates: false,
180185
});
181186
try {
182187
await install();
@@ -197,6 +202,7 @@ describe('install', () => {
197202
skipOsCheck: false,
198203
nativeClientVersion: '',
199204
odbcVersion: '',
205+
installUpdates: false,
200206
});
201207
try {
202208
await install();
@@ -217,6 +223,7 @@ describe('install', () => {
217223
skipOsCheck: false,
218224
nativeClientVersion: '',
219225
odbcVersion: '',
226+
installUpdates: false,
220227
});
221228
await install();
222229
expect(execStub.exec).to.have.been.calledWith('"C:/tmp/exe/setup.exe"', match.array, { windowsVerbatimArguments: true });
@@ -232,6 +239,7 @@ describe('install', () => {
232239
skipOsCheck: false,
233240
nativeClientVersion: '',
234241
odbcVersion: '',
242+
installUpdates: false,
235243
});
236244
await install();
237245
expect(execStub.exec).to.have.been.calledWith('"C:/tmp/exe/setup.exe"', match.array, { windowsVerbatimArguments: true });
@@ -246,6 +254,7 @@ describe('install', () => {
246254
skipOsCheck: true,
247255
nativeClientVersion: '',
248256
odbcVersion: '',
257+
installUpdates: false,
249258
});
250259
await install();
251260
expect(execStub.exec).to.have.been.calledWith('"C:/tmp/exe/setup.exe"', match.array, { windowsVerbatimArguments: true });
@@ -282,6 +291,7 @@ describe('install', () => {
282291
skipOsCheck: false,
283292
nativeClientVersion: '',
284293
odbcVersion: '',
294+
installUpdates: false,
285295
});
286296
const stubReadfile = stub(fs, 'readFile');
287297
stubReadfile.resolves(Buffer.from('test data'));
@@ -327,6 +337,7 @@ describe('install', () => {
327337
skipOsCheck: false,
328338
nativeClientVersion: '11',
329339
odbcVersion: '',
340+
installUpdates: false,
330341
});
331342
await install();
332343
expect(stubNc.default).to.have.been.calledOnceWith('11');
@@ -341,6 +352,7 @@ describe('install', () => {
341352
skipOsCheck: false,
342353
nativeClientVersion: '11',
343354
odbcVersion: '18',
355+
installUpdates: false,
344356
});
345357
await install();
346358
expect(stubNc.default).to.have.been.calledOnceWith('11');

test/utils.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ describe('utils', () => {
192192
coreStub.getMultilineInput.withArgs('install-arguments').returns([]);
193193
coreStub.getBooleanInput.withArgs('wait-for-ready').returns(true);
194194
coreStub.getBooleanInput.withArgs('skip-os-check').returns(false);
195+
coreStub.getBooleanInput.withArgs('install-updates').returns(false);
195196
const res = utils.gatherInputs();
196197
expect(res).to.deep.equal({
197198
version: '2022',
@@ -202,6 +203,7 @@ describe('utils', () => {
202203
skipOsCheck: false,
203204
nativeClientVersion: '',
204205
odbcVersion: '',
206+
installUpdates: false,
205207
});
206208
});
207209
it('constructs input object with no sql- prefix', () => {
@@ -213,6 +215,7 @@ describe('utils', () => {
213215
coreStub.getMultilineInput.withArgs('install-arguments').returns([]);
214216
coreStub.getBooleanInput.withArgs('wait-for-ready').returns(true);
215217
coreStub.getBooleanInput.withArgs('skip-os-check').returns(false);
218+
coreStub.getBooleanInput.withArgs('install-updates').returns(false);
216219
const res = utils.gatherInputs();
217220
expect(res).to.deep.equal({
218221
version: '2022',
@@ -223,6 +226,7 @@ describe('utils', () => {
223226
skipOsCheck: false,
224227
nativeClientVersion: '',
225228
odbcVersion: '',
229+
installUpdates: false,
226230
});
227231
});
228232
it('constructs input object with "latest" version', () => {
@@ -234,6 +238,7 @@ describe('utils', () => {
234238
coreStub.getMultilineInput.withArgs('install-arguments').returns([]);
235239
coreStub.getBooleanInput.withArgs('wait-for-ready').returns(true);
236240
coreStub.getBooleanInput.withArgs('skip-os-check').returns(false);
241+
coreStub.getBooleanInput.withArgs('install-updates').returns(false);
237242
const res = utils.gatherInputs();
238243
expect(res).to.deep.equal({
239244
version: '2022',
@@ -244,6 +249,7 @@ describe('utils', () => {
244249
skipOsCheck: false,
245250
nativeClientVersion: '',
246251
odbcVersion: '',
252+
installUpdates: false,
247253
});
248254
});
249255
it('constructs input object with default version', () => {
@@ -255,6 +261,7 @@ describe('utils', () => {
255261
coreStub.getMultilineInput.withArgs('install-arguments').returns([]);
256262
coreStub.getBooleanInput.withArgs('wait-for-ready').returns(true);
257263
coreStub.getBooleanInput.withArgs('skip-os-check').returns(false);
264+
coreStub.getBooleanInput.withArgs('install-updates').returns(false);
258265
const res = utils.gatherInputs();
259266
expect(res).to.deep.equal({
260267
version: '2022',
@@ -265,6 +272,7 @@ describe('utils', () => {
265272
skipOsCheck: false,
266273
nativeClientVersion: '',
267274
odbcVersion: '',
275+
installUpdates: false,
268276
});
269277
});
270278
});

0 commit comments

Comments
 (0)