Skip to content

Commit d773f2a

Browse files
authored
feat(publish): Support for custom VCL logic (#893)
1 parent 86f1b62 commit d773f2a

File tree

7 files changed

+293
-7
lines changed

7 files changed

+293
-7
lines changed

src/publish.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ module.exports = function strain() {
6262
describe: 'Don\'t publish strains with names following the specified pattern, use config from master branch instead',
6363
type: 'string',
6464
})
65+
.option('custom-vcl', {
66+
alias: 'customVCL',
67+
describe: 'Path(s) to VCL file(s) to override the orginal one(s).',
68+
type: 'string',
69+
array: true,
70+
default: [],
71+
})
6572
.conflicts('only', 'exclude')
6673
.demandOption(
6774
'fastly-auth',
@@ -106,6 +113,7 @@ module.exports = function strain() {
106113
.withUpdateBotConfig(argv.updateBotConfig)
107114
.withConfigPurgeAPI(argv.apiConfigPurge)
108115
.withFilter(argv.only, argv.exclude)
116+
.withCustomVCLs(argv.customVCL)
109117
.run();
110118
},
111119
};

src/remotepublish.cmd.js

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
/* eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */
1414

1515
const request = require('request-promise-native');
16+
const fs = require('fs-extra');
17+
const path = require('path');
1618
const fastly = require('@adobe/fastly-native-promises');
1719
const chalk = require('chalk');
1820
const ProgressBar = require('progress');
@@ -35,6 +37,7 @@ class RemotePublishCommand extends AbstractCommand {
3537
this._githubToken = '';
3638
this._updateBotConfig = false;
3739
this._configPurgeAPI = 'https://app.project-helix.io/config/purge';
40+
this._vcl = null;
3841
}
3942

4043
tick(ticks = 1, message, name) {
@@ -155,6 +158,31 @@ class RemotePublishCommand extends AbstractCommand {
155158
return this;
156159
}
157160

161+
/**
162+
* Adds a list of VCL files to the publish command. Each VCL file is represented
163+
* by a path relative to the current command (e.g. ['vcl/extensions.vcl']).
164+
* @param {array} value List of vcl files to add as vcl extensions
165+
* @returns {RemotePublishCommand} the current instance
166+
*/
167+
withCustomVCLs(vcls) {
168+
if (vcls && vcls.length > 0) {
169+
const vcl = {};
170+
vcls.forEach((file) => {
171+
try {
172+
const fullPath = path.resolve(this.directory, file);
173+
const name = path.basename(fullPath, '.vcl');
174+
const content = fs.readFileSync(fullPath).toString();
175+
vcl[name] = content;
176+
} catch (error) {
177+
this.log.error(`Cannot read provided custom vcl file ${file}`);
178+
throw error;
179+
}
180+
});
181+
this._vcl = vcl;
182+
}
183+
return this;
184+
}
185+
158186
showNextStep(dryrun) {
159187
this.progressBar().terminate();
160188
if (dryrun) {
@@ -235,15 +263,20 @@ ${e}`);
235263
throw new Error('Unable to merge configurations for selective publishing');
236264
}
237265
}
266+
const body = {
267+
configuration: this.config.toJSON(),
268+
service: this._fastly_namespace,
269+
token: this._fastly_auth,
270+
version: this._version,
271+
};
272+
273+
if (this._vcl) {
274+
body.vcl = this._vcl;
275+
}
238276

239277
return request.post(this._publishAPI, {
240278
json: true,
241-
body: {
242-
configuration: this.config.toJSON(),
243-
service: this._fastly_namespace,
244-
token: this._fastly_auth,
245-
version: this._version,
246-
},
279+
body,
247280
}).then(() => {
248281
this.tick(9, 'set service config up for Helix', true);
249282
return true;

test/fixtures/vcl/another.vcl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#
2+
# Copyright 2019 Adobe. All rights reserved.
3+
# This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License. You may obtain a copy
5+
# of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software distributed under
8+
# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
# OF ANY KIND, either express or implied. See the License for the specific language
10+
# governing permissions and limitations under the License.
11+
#
12+
13+
sub hlx_type_pipeline_before {
14+
# THIS WILL DO SOME CUSTOM CODE
15+
}
16+
17+
sub hlx_type_pipeline_after {
18+
# THIS WILL DO SOME CUSTOM CODE
19+
}

test/fixtures/vcl/extensions.vcl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#
2+
# Copyright 2019 Adobe. All rights reserved.
3+
# This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License. You may obtain a copy
5+
# of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software distributed under
8+
# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
# OF ANY KIND, either express or implied. See the License for the specific language
10+
# governing permissions and limitations under the License.
11+
#
12+
13+
sub hlx_type_pipeline_before {
14+
# THIS WILL DO SOME CUSTOM CODE
15+
}
16+
17+
sub hlx_type_pipeline_after {
18+
# THIS WILL DO SOME CUSTOM CODE
19+
}

test/testPublishCli.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ describe('hlx publish', () => {
4040
mockPublish.withUpdateBotConfig.returnsThis();
4141
mockPublish.withGithubToken.returnsThis();
4242
mockPublish.withFilter.returnsThis();
43+
mockPublish.withCustomVCLs.returnsThis();
4344
mockPublish.run.returnsThis();
4445
});
4546

@@ -51,7 +52,7 @@ describe('hlx publish', () => {
5152
new CLI()
5253
.withCommandExecutor('publish', mockPublish)
5354
.onFail((err) => {
54-
assert.ok(err.indexOf('required'));
55+
assert.ok(!err || err.indexOf('required'));
5556
done();
5657
})
5758
.run(['publish']);
@@ -71,6 +72,7 @@ describe('hlx publish', () => {
7172
sinon.assert.calledWith(mockPublish.withFastlyAuth, 'foobar');
7273
sinon.assert.calledWith(mockPublish.withPublishAPI, 'foobar.api');
7374
sinon.assert.calledWith(mockPublish.withDryRun, true);
75+
sinon.assert.calledWith(mockPublish.withCustomVCLs, []);
7476
});
7577

7678
it('hlx publish works with minimal arguments', () => {
@@ -88,6 +90,7 @@ describe('hlx publish', () => {
8890
sinon.assert.calledWith(mockPublish.withWskNamespace, 'hlx');
8991
sinon.assert.calledWith(mockPublish.withFastlyNamespace, 'hlx'); // TODO !!
9092
sinon.assert.calledWith(mockPublish.withFastlyAuth, 'secret-key');
93+
sinon.assert.calledWith(mockPublish.withCustomVCLs, []);
9194
sinon.assert.calledOnce(mockPublish.run);
9295
});
9396

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/*
2+
* Copyright 2018 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
/* eslint-env mocha */
14+
15+
const assert = require('assert');
16+
const fs = require('fs-extra');
17+
const nock = require('nock');
18+
const path = require('path');
19+
const proxyquire = require('proxyquire');
20+
const sinon = require('sinon');
21+
const shell = require('shelljs');
22+
const { Logger } = require('@adobe/helix-shared');
23+
const { createTestRoot } = require('./utils.js');
24+
25+
const RemotePublishCommand = require('../src/remotepublish.cmd');
26+
27+
describe('hlx publish --custom-vcl (check params)', () => {
28+
it('hlx publish (no custom-vcl)', () => {
29+
const remote = new RemotePublishCommand()
30+
.withCustomVCLs();
31+
32+
// eslint-disable-next-line no-underscore-dangle
33+
assert.equal(remote._vcl, null);
34+
});
35+
36+
it('hlx publish --custom-vcl fixtures/vcl/extensions.vcl', async () => {
37+
const e = path.resolve(__dirname, 'fixtures/vcl/extensions.vcl');
38+
const remote = new RemotePublishCommand()
39+
.withCustomVCLs([e]);
40+
41+
// eslint-disable-next-line no-underscore-dangle
42+
assert.deepEqual(remote._vcl, {
43+
extensions: (await fs.readFile(e, 'utf8')).toString(),
44+
});
45+
});
46+
47+
it('hlx publish --custom-vcl fixtures/vcl/extensions.vcl --custom-vcl fixtures/vcl/another.vcl', async () => {
48+
const e = path.resolve(__dirname, 'fixtures/vcl/extensions.vcl');
49+
const a = path.resolve(__dirname, 'fixtures/vcl/another.vcl');
50+
const remote = new RemotePublishCommand()
51+
.withCustomVCLs([e, a]);
52+
53+
// eslint-disable-next-line no-underscore-dangle
54+
assert.deepEqual(remote._vcl, {
55+
extensions: (await fs.readFile(e, 'utf8')).toString(),
56+
another: (await fs.readFile(e, 'utf8')).toString(),
57+
});
58+
});
59+
60+
it('hlx publish --custom-vcl fixtures/vcl/unexisting.vcl', async () => {
61+
const e = path.resolve(__dirname, 'fixtures/vcl/unexisting.vcl');
62+
function throwsError() {
63+
new RemotePublishCommand().withCustomVCLs([e]);
64+
}
65+
assert.throws(throwsError);
66+
});
67+
});
68+
69+
describe('hlx publish --custom-vcl (check requests)', () => {
70+
let ProxiedRemotePublishCommand;
71+
let writeDictItem;
72+
let purgeAll;
73+
let testRoot;
74+
let pwd;
75+
let vcl;
76+
let scope;
77+
let remote;
78+
79+
beforeEach('Setting up Fake Server', async function bef() {
80+
this.timeout(5000);
81+
writeDictItem = sinon.fake.resolves(true);
82+
purgeAll = sinon.fake.resolves(true);
83+
84+
ProxiedRemotePublishCommand = proxyquire('../src/remotepublish.cmd', {
85+
'@adobe/fastly-native-promises': () => ({
86+
transact: fn => fn(3),
87+
writeDictItem,
88+
purgeAll,
89+
}),
90+
});
91+
92+
93+
// ensure to reset nock to avoid conflicts with PollyJS
94+
nock.restore();
95+
nock.cleanAll();
96+
nock.activate();
97+
98+
scope = nock('https://adobeioruntime.net')
99+
.post('/api/v1/web/helix/default/publish', (body) => {
100+
({ vcl } = body);
101+
return true;
102+
})
103+
.reply(200, {})
104+
.post('/api/v1/web/helix/default/addlogger')
105+
.reply(200, {});
106+
107+
// set up a fake git repo.
108+
testRoot = await createTestRoot();
109+
await fs.copy(path.resolve(__dirname, 'fixtures/filtered-master.yaml'), path.resolve(testRoot, 'helix-config.yaml'));
110+
111+
// throw a Javascript error when any shell.js command encounters an error
112+
shell.config.fatal = true;
113+
114+
// init git repo
115+
pwd = shell.pwd();
116+
shell.cd(testRoot);
117+
shell.exec('git init');
118+
shell.exec('git add -A');
119+
shell.exec('git commit -m"initial commit."');
120+
121+
// set up command
122+
const logger = Logger.getTestLogger();
123+
remote = await new ProxiedRemotePublishCommand(logger)
124+
.withWskAuth('fakeauth')
125+
.withWskNamespace('fakename')
126+
.withFastlyAuth('fake_auth')
127+
.withFastlyNamespace('fake_name')
128+
.withWskHost('doesn.t.matter')
129+
.withPublishAPI('https://adobeioruntime.net/api/v1/web/helix/default/publish')
130+
.withConfigFile(path.resolve(__dirname, 'fixtures/filtered.yaml'))
131+
.withDryRun(false);
132+
});
133+
134+
it('hlx publish (no custom-vcl)', async () => {
135+
await remote.run();
136+
assert.equal(vcl, null);
137+
});
138+
139+
it('hlx publish --custom-vcl fixtures/vcl/extensions.vcl', async () => {
140+
const e = path.resolve(__dirname, 'fixtures/vcl/extensions.vcl');
141+
remote.withCustomVCLs([e]);
142+
await remote.run();
143+
144+
assert.ok(vcl);
145+
assert.deepEqual(vcl, {
146+
extensions: fs.readFileSync(e).toString(),
147+
});
148+
});
149+
150+
it('hlx publish --custom-vcl fixtures/vcl/extensions.vcl --custom-vcl fixtures/vcl/another.vcl', async () => {
151+
const e = path.resolve(__dirname, 'fixtures/vcl/extensions.vcl');
152+
const a = path.resolve(__dirname, 'fixtures/vcl/another.vcl');
153+
remote.withCustomVCLs([e, a]);
154+
await remote.run();
155+
156+
assert.ok(vcl);
157+
assert.deepEqual(vcl, {
158+
extensions: fs.readFileSync(e).toString(),
159+
another: fs.readFileSync(a).toString(),
160+
});
161+
});
162+
163+
afterEach(async () => {
164+
scope.done();
165+
nock.restore();
166+
shell.cd(pwd);
167+
await fs.remove(testRoot);
168+
});
169+
});

test/testRemotePublishCmd.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
/* eslint-env mocha */
1414

15+
const assert = require('assert');
1516
const nock = require('nock');
1617
const path = require('path');
1718
const proxyquire = require('proxyquire');
@@ -66,6 +67,40 @@ describe('hlx publish --remote (default)', () => {
6667
scope.done();
6768
});
6869

70+
it('publishing sends expected parameters', async () => {
71+
let publishBody;
72+
const scope = nock('https://adobeioruntime.net')
73+
.post('/api/v1/web/helix/default/publish', (body) => {
74+
publishBody = body;
75+
return true;
76+
})
77+
.reply(200, {})
78+
.post('/api/v1/web/helix/default/addlogger')
79+
.reply(200, {});
80+
81+
const remote = await new RemotePublishCommand()
82+
.withWskAuth('fakeauth')
83+
.withWskNamespace('fakename')
84+
.withFastlyAuth('fake_auth')
85+
.withFastlyNamespace('fake_name')
86+
.withWskHost('doesn.t.matter')
87+
.withPublishAPI('https://adobeioruntime.net/api/v1/web/helix/default/publish')
88+
.withConfigFile(path.resolve(__dirname, 'fixtures/deployed.yaml'))
89+
.withFilter()
90+
.withDryRun(false);
91+
await remote.run();
92+
93+
assert.ok(publishBody);
94+
assert.equal(publishBody.service, 'fake_name');
95+
assert.equal(publishBody.token, 'fake_auth');
96+
assert.equal(typeof (publishBody.configuration), 'object');
97+
assert.ok(Array.isArray(publishBody.configuration.strains));
98+
assert.equal(publishBody.configuration.strains.length, 4);
99+
assert.equal(publishBody.vcl, undefined);
100+
101+
scope.done();
102+
});
103+
69104
it('publishing stops of no strains with package info', async () => {
70105
const remote = await new RemotePublishCommand()
71106
.withWskAuth('fakeauth')

0 commit comments

Comments
 (0)