Skip to content

Commit 15a4f29

Browse files
committed
feat(fastly): Add basic transaction support
Adds two functions `transact` and `dryrun` that both create a new version of the currently active service config, then apply a set of changes (specified in a function) to it and either activate the new version (`transact`) or leave it as is (`dryrun`) fixes #21
1 parent 09630f2 commit 15a4f29

File tree

5 files changed

+277
-10
lines changed

5 files changed

+277
-10
lines changed

README.md

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,31 @@ const fastly = require('@adobe/fastly-native-promises');
5656
const service_1 = fastly('token', 'service_id_1');
5757
const serivce_2 = fastly('token', 'service_id_2');
5858

59-
// read/write baseURL property
60-
console.log(service_1.request.defaults.baseURL); // https://api.fastly.com
61-
62-
// read/write timeout property
63-
console.log(service_1.request.defaults.timeout); // 3000
59+
// make changes
60+
61+
service_1.transact(async () => {
62+
return this.writeS3('test-s3', {
63+
name: 'test-s3',
64+
bucket_name: 'my_corporate_bucket',
65+
access_key: 'AKIAIOSFODNN7EXAMPLE',
66+
secret_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
67+
});
68+
});
69+
70+
71+
service_2.transact(async () => {
72+
return this.writeBigquery('test-bq', {
73+
name: 'test-bq',
74+
format: '{\n "timestamp":"%{begin:%Y-%m-%dT%H:%M:%S}t",\n "time_elapsed":%{time.elapsed.usec}V,\n "is_tls":%{if(req.is_ssl, "true", "false")}V,\n "client_ip":"%{req.http.Fastly-Client-IP}V",\n "geo_city":"%{client.geo.city}V",\n "geo_country_code":"%{client.geo.country_code}V",\n "request":"%{req.request}V",\n "host":"%{req.http.Fastly-Orig-Host}V",\n "url":"%{json.escape(req.url)}V",\n "request_referer":"%{json.escape(req.http.Referer)}V",\n "request_user_agent":"%{json.escape(req.http.User-Agent)}V",\n "request_accept_language":"%{json.escape(req.http.Accept-Language)}V",\n "request_accept_charset":"%{json.escape(req.http.Accept-Charset)}V",\n "cache_status":"%{regsub(fastly_info.state, "^(HIT-(SYNTH)|(HITPASS|HIT|MISS|PASS|ERROR|PIPE)).*", "\\\\2\\\\3") }V"\n}',
75+
user: 'fastly-bigquery-log@example-fastly-log.iam.gserviceaccount.com',
76+
project_id: 'example-fastly-log',
77+
dataset: 'fastly_log_test',
78+
table: 'fastly_logs',
79+
template_suffix: null,
80+
secret_key: '-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC7bPG9yaIYd5AL\nmvOaYvNozFJB/VWS53KWBll769kJvlmgMks6r6Xrv8w6rjxWKjZeDrnXVf7UDa0F\nckPPIFvXRxahftWFMGArw0lIvQzgT4/BlndXU5RNxfah/8m7q/GIF6oNYWzfJwvv\nzodxDUqIRH2e2JWidNRjElHuogYHLhV4O/od5pAkfDwak/ihuuh/2VA3Auwb3nph\ndX2F0JBs14oPKZUTYUUSzUQY5IMxSxYUA4Q7W4v21x1EnJt+biXOrERk1rm4ieEE\nU3WkjR5c5gvG8xcWyYod87RNFELmIhCCytI1+t5C3Em/jPsQFtLzwHpbNhdW4oEm\nn7d06n75AgMBAAECggEAWRh26lNZfOwJS5sDRlbXgu/uAnSdI1JmxC6Mhz4cVGdq\nT57Y6DLrWuA4A4UkJYm3gorZiSXWF5PQthAVb/bf8bxXY7nZYpEWhnc09SD5aAAq\nREp0vMx8aWQ709K2YUJg+zDUo7u2d3YmVH8HH5TD43c7iDFJIIsNE3N4A0p+NxZ+\nw06FFW+fz/etrWiNyhrlTsbkMbSgU+GpFFBq1pCd0ni5d1YM1rsaAaUpmkwdjgjL\noDs+M/L/HtqfEhyZNdw8JF7EJXVE1bIl7/NL0rBInhyO28FcB56t/AG5nzXKFI/c\nc+IO7d6MOOqiGRLRWZItEpnyzuV8DZo461wy1hSvqQKBgQDhSsg2cHkTrtBW8x0A\n3BwB/ygdkkxm1OIvfioT+JBneRufUPvVIM2aPZBBGKEedDAmIGn/8f9XAHhKjs8B\nEsPRgE206s4+hnrTcK7AeWWPvM9FDkrkQCoJFuJrNy9mJt8gs7AnnoBa9u/J4naW\ne1tfC8fUfsa7kdzblDhcRQ8FhwKBgQDU+N4kPzIdUuJDadd6TkBbjUNPEfZzU5+t\nIike2VSRhApxAxviUnTDsTROwJRzKik9w7gIMka8Ek+nmLNMEtds77ttcGQRdu16\n+vT1iualiCJe+/iMbl+PiJtFwhEHECLU9QfgBVS6r2lDAlZA+w6nwCRiidlrObzO\nCXqVOzN3fwKBgAsrOuu//bClHP0ChnCReO38aU+1/gWnDiOOnKVq0DXhAiaOzD1P\nqAG6hZlEkFBDMPWzq62doKv+gPgpRkfmV0DenHuYnGrrHdG3p2IxYoCSuq/QupPA\nPpU+xjDMhpQI30zuu4/rQq+/yDl4+aoSKYB3xAtb0Zxg6dMU8QpZ/hmnAoGBAIFu\nIesbcQR7O8FGkMrmxZweNNrYCtQ57R/WU/FImWm6OnJGNmsMO6Q2jJiT12RKKjg8\nOxrYGz7vTfOIDOddyAiPhXPUSyyF/3uvCrIzUUsmeeUJ8xq9dVwQ5HS3pYuKVfDg\nXYHbG4w9UJaF1A+3xEdUsYglSLouo7z/67zH9tZXAoGBAKpsdjSd3R+llaAv2HQ8\nGMlN92UTr5i9w++QMXq4qspH5NEYqz3NHbKuYthZqxEsRUZbRP50eDWU4jvxFVJl\nLBFINp6B+3AsIme0YCyOaleB/Cy0347miSinSv2I6QiH6dQxHdHzrG+x1evS/76f\nKT0KS+ySjCAEWgg4v+mjUDUV\n-----END PRIVATE KEY-----\n',
81+
response_condition: '',
82+
});
83+
});
6484
```
6585

6686
### Promises
@@ -253,6 +273,8 @@ HTTP status code can be retrieved. Known error status codes include:</p>
253273
* [.createVCL(version, data)](#Fastly+createVCL) ⇒ <code>Promise</code>
254274
* [.updateVCL(version, name, data)](#Fastly+updateVCL) ⇒ <code>Promise</code>
255275
* [.setMainVCL(version, name)](#Fastly+setMainVCL) ⇒ <code>Promise</code>
276+
* [.transact(operations, activate)](#Fastly+transact) ⇒ <code>Object</code>
277+
* [.dryrun(operations)](#Fastly+dryrun) ⇒ <code>Object</code>
256278

257279
<a name="new_Fastly_new"></a>
258280

@@ -817,6 +839,41 @@ Define a custom VCL to be the main VCL for a particular service and version.
817839
| version | <code>string</code> | The current version of a service. |
818840
| name | <code>string</code> | The name of the VCL to declare main. |
819841

842+
<a name="Fastly+transact"></a>
843+
844+
#### fastly.transact(operations, activate) ⇒ <code>Object</code>
845+
Creates a new version, runs the function `operations` and then
846+
optionally activates the newly created version. This function
847+
is useful for making modifications to a service config.
848+
849+
**Kind**: instance method of [<code>Fastly</code>](#Fastly)
850+
**Returns**: <code>Object</code> - The return value of the wrapped function.
851+
852+
| Param | Type | Default | Description |
853+
| --- | --- | --- | --- |
854+
| operations | <code>function</code> | | A function that performs changes on the service config. |
855+
| activate | <code>boolean</code> | <code>true</code> | Set to false to prevent automatic activation. |
856+
857+
**Example**
858+
```javascript
859+
await fastly.transact(async (newversion) => {
860+
await fastly.doSomething(newversion);
861+
});
862+
// new version has been activated
863+
```
864+
<a name="Fastly+dryrun"></a>
865+
866+
#### fastly.dryrun(operations) ⇒ <code>Object</code>
867+
See `transact`, but this version does not activate the created version.
868+
869+
**Kind**: instance method of [<code>Fastly</code>](#Fastly)
870+
**Returns**: <code>Object</code> - Whatever `operations` returns.
871+
**See**: #transact
872+
873+
| Param | Type | Description |
874+
| --- | --- | --- |
875+
| operations | <code>function</code> | The operations that should be applied to the cloned service config version. |
876+
820877
<a name="CreateFunction"></a>
821878

822879
### CreateFunction ⇒ <code>Promise</code>

README.md.hbs

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,31 @@ const fastly = require('@adobe/fastly-native-promises');
5656
const service_1 = fastly('token', 'service_id_1');
5757
const serivce_2 = fastly('token', 'service_id_2');
5858

59-
// read/write baseURL property
60-
console.log(service_1.request.defaults.baseURL); // https://api.fastly.com
61-
62-
// read/write timeout property
63-
console.log(service_1.request.defaults.timeout); // 3000
59+
// make changes
60+
61+
service_1.transact(async () => {
62+
return this.writeS3('test-s3', {
63+
name: 'test-s3',
64+
bucket_name: 'my_corporate_bucket',
65+
access_key: 'AKIAIOSFODNN7EXAMPLE',
66+
secret_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
67+
});
68+
});
69+
70+
71+
service_2.transact(async () => {
72+
return this.writeBigquery('test-bq', {
73+
name: 'test-bq',
74+
format: '{\n "timestamp":"%{begin:%Y-%m-%dT%H:%M:%S}t",\n "time_elapsed":%{time.elapsed.usec}V,\n "is_tls":%{if(req.is_ssl, "true", "false")}V,\n "client_ip":"%{req.http.Fastly-Client-IP}V",\n "geo_city":"%{client.geo.city}V",\n "geo_country_code":"%{client.geo.country_code}V",\n "request":"%{req.request}V",\n "host":"%{req.http.Fastly-Orig-Host}V",\n "url":"%{json.escape(req.url)}V",\n "request_referer":"%{json.escape(req.http.Referer)}V",\n "request_user_agent":"%{json.escape(req.http.User-Agent)}V",\n "request_accept_language":"%{json.escape(req.http.Accept-Language)}V",\n "request_accept_charset":"%{json.escape(req.http.Accept-Charset)}V",\n "cache_status":"%{regsub(fastly_info.state, "^(HIT-(SYNTH)|(HITPASS|HIT|MISS|PASS|ERROR|PIPE)).*", "\\\\2\\\\3") }V"\n}',
75+
user: 'fastly-bigquery-log@example-fastly-log.iam.gserviceaccount.com',
76+
project_id: 'example-fastly-log',
77+
dataset: 'fastly_log_test',
78+
table: 'fastly_logs',
79+
template_suffix: null,
80+
secret_key: '-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC7bPG9yaIYd5AL\nmvOaYvNozFJB/VWS53KWBll769kJvlmgMks6r6Xrv8w6rjxWKjZeDrnXVf7UDa0F\nckPPIFvXRxahftWFMGArw0lIvQzgT4/BlndXU5RNxfah/8m7q/GIF6oNYWzfJwvv\nzodxDUqIRH2e2JWidNRjElHuogYHLhV4O/od5pAkfDwak/ihuuh/2VA3Auwb3nph\ndX2F0JBs14oPKZUTYUUSzUQY5IMxSxYUA4Q7W4v21x1EnJt+biXOrERk1rm4ieEE\nU3WkjR5c5gvG8xcWyYod87RNFELmIhCCytI1+t5C3Em/jPsQFtLzwHpbNhdW4oEm\nn7d06n75AgMBAAECggEAWRh26lNZfOwJS5sDRlbXgu/uAnSdI1JmxC6Mhz4cVGdq\nT57Y6DLrWuA4A4UkJYm3gorZiSXWF5PQthAVb/bf8bxXY7nZYpEWhnc09SD5aAAq\nREp0vMx8aWQ709K2YUJg+zDUo7u2d3YmVH8HH5TD43c7iDFJIIsNE3N4A0p+NxZ+\nw06FFW+fz/etrWiNyhrlTsbkMbSgU+GpFFBq1pCd0ni5d1YM1rsaAaUpmkwdjgjL\noDs+M/L/HtqfEhyZNdw8JF7EJXVE1bIl7/NL0rBInhyO28FcB56t/AG5nzXKFI/c\nc+IO7d6MOOqiGRLRWZItEpnyzuV8DZo461wy1hSvqQKBgQDhSsg2cHkTrtBW8x0A\n3BwB/ygdkkxm1OIvfioT+JBneRufUPvVIM2aPZBBGKEedDAmIGn/8f9XAHhKjs8B\nEsPRgE206s4+hnrTcK7AeWWPvM9FDkrkQCoJFuJrNy9mJt8gs7AnnoBa9u/J4naW\ne1tfC8fUfsa7kdzblDhcRQ8FhwKBgQDU+N4kPzIdUuJDadd6TkBbjUNPEfZzU5+t\nIike2VSRhApxAxviUnTDsTROwJRzKik9w7gIMka8Ek+nmLNMEtds77ttcGQRdu16\n+vT1iualiCJe+/iMbl+PiJtFwhEHECLU9QfgBVS6r2lDAlZA+w6nwCRiidlrObzO\nCXqVOzN3fwKBgAsrOuu//bClHP0ChnCReO38aU+1/gWnDiOOnKVq0DXhAiaOzD1P\nqAG6hZlEkFBDMPWzq62doKv+gPgpRkfmV0DenHuYnGrrHdG3p2IxYoCSuq/QupPA\nPpU+xjDMhpQI30zuu4/rQq+/yDl4+aoSKYB3xAtb0Zxg6dMU8QpZ/hmnAoGBAIFu\nIesbcQR7O8FGkMrmxZweNNrYCtQ57R/WU/FImWm6OnJGNmsMO6Q2jJiT12RKKjg8\nOxrYGz7vTfOIDOddyAiPhXPUSyyF/3uvCrIzUUsmeeUJ8xq9dVwQ5HS3pYuKVfDg\nXYHbG4w9UJaF1A+3xEdUsYglSLouo7z/67zH9tZXAoGBAKpsdjSd3R+llaAv2HQ8\nGMlN92UTr5i9w++QMXq4qspH5NEYqz3NHbKuYthZqxEsRUZbRP50eDWU4jvxFVJl\nLBFINp6B+3AsIme0YCyOaleB/Cy0347miSinSv2I6QiH6dQxHdHzrG+x1evS/76f\nKT0KS+ySjCAEWgg4v+mjUDUV\n-----END PRIVATE KEY-----\n',
81+
response_condition: '',
82+
});
83+
});
6484
```
6585

6686
### Promises

src/index.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,43 @@ class Fastly {
778778
async setMainVCL(version, name) {
779779
return this.request.put(`/service/${this.service_id}/version/${await this.getVersion(version, 'current')}/vcl/${name}/main`, {});
780780
}
781+
782+
/**
783+
* Creates a new version, runs the function `operations` and then
784+
* optionally activates the newly created version. This function
785+
* is useful for making modifications to a service config.
786+
*
787+
* @example
788+
* ```javascript
789+
* await fastly.transact(async (newversion) => {
790+
* await fastly.doSomething(newversion);
791+
* });
792+
* // new version has been activated
793+
* ```
794+
* @param {Function} operations - A function that performs changes on the service config.
795+
* @param {boolean} activate - Set to false to prevent automatic activation.
796+
* @returns {Object} The return value of the wrapped function.
797+
*/
798+
async transact(operations, activate = true) {
799+
const newversion = (await this.cloneVersion()).data.number;
800+
const result = await operations.apply(this, [newversion]);
801+
if (activate) {
802+
await this.activateVersion(newversion);
803+
}
804+
return result;
805+
}
806+
807+
/**
808+
* See `transact`, but this version does not activate the created version.
809+
*
810+
* @see #transact
811+
* @param {Function} operations - The operations that should be applied to the
812+
* cloned service config version.
813+
* @returns {Object} Whatever `operations` returns.
814+
*/
815+
async dryrun(operations) {
816+
return this.transact(operations, false);
817+
}
781818
}
782819

783820
/**

test/dryrunS3.update.test.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
'use strict';
2+
3+
/* eslint-env mocha */
4+
5+
const nock = require('nock');
6+
const expect = require('expect');
7+
const config = require('../src/config');
8+
const fastlyPromises = require('../src/index');
9+
const versionresponse = require('./response/cloneVersion.response');
10+
const getversionsresponse = require('./response/readVersions.response');
11+
const readresponse = require('./response/readS3.response');
12+
const updateresponse = require('./response/updateS3.response');
13+
14+
describe('#transactS3.update', () => {
15+
const fastly = fastlyPromises('923b6bd5266a7f932e41962755bd4254', 'SU1Z0isxPaozGVKXdv0eY');
16+
let res;
17+
18+
nock(config.mainEntryPoint)
19+
.get('/service/SU1Z0isxPaozGVKXdv0eY/version')
20+
.reply(200, getversionsresponse.readVersions)
21+
.put('/service/SU1Z0isxPaozGVKXdv0eY/version/1/clone')
22+
.reply(200, versionresponse.cloneVersionDefault)
23+
.get('/service/SU1Z0isxPaozGVKXdv0eY/version/2/logging/s3/test-s3')
24+
.reply(200, readresponse.readS3)
25+
.put('/service/SU1Z0isxPaozGVKXdv0eY/version/2/logging/s3/test-s3')
26+
.reply(200, updateresponse.updateS3);
27+
28+
before(async () => {
29+
res = await fastly.dryrun(version => fastly.writeS3(version, 'test-s3', {
30+
name: 'test-s3',
31+
bucket_name: 'my_corporate_bucket',
32+
access_key: 'AKIAIOSFODNN7EXAMPLE',
33+
secret_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
34+
}));
35+
});
36+
37+
it('response should be a status 200', () => {
38+
expect(res.status).toBe(200);
39+
});
40+
41+
it('response body should exist', () => {
42+
expect(res.data).toBeTruthy();
43+
});
44+
45+
it('response body should be an object', () => {
46+
expect(typeof res.data).toBe('object');
47+
});
48+
49+
it('response body should contain all properties', () => {
50+
[
51+
'access_key',
52+
'bucket_name',
53+
'created_at',
54+
'deleted_at',
55+
'domain',
56+
'format',
57+
'format_version',
58+
'gzip_level',
59+
'message_type',
60+
'name',
61+
'path',
62+
'period',
63+
'placement',
64+
'redundancy',
65+
'response_condition',
66+
'secret_key',
67+
'service_id',
68+
'timestamp_format',
69+
'updated_at',
70+
'version',
71+
].forEach((e) => {
72+
expect(Object.keys(res.data)).toContain(e);
73+
});
74+
});
75+
});

test/transactS3.update.test.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
'use strict';
2+
3+
/* eslint-env mocha */
4+
5+
const nock = require('nock');
6+
const expect = require('expect');
7+
const config = require('../src/config');
8+
const fastlyPromises = require('../src/index');
9+
const versionresponse = require('./response/cloneVersion.response');
10+
const activateresponse = require('./response/activateVersion.response');
11+
const getversionsresponse = require('./response/readVersions.response');
12+
const readresponse = require('./response/readS3.response');
13+
const updateresponse = require('./response/updateS3.response');
14+
15+
describe('#transactS3.update', () => {
16+
const fastly = fastlyPromises('923b6bd5266a7f932e41962755bd4254', 'SU1Z0isxPaozGVKXdv0eY');
17+
let res;
18+
19+
nock(config.mainEntryPoint)
20+
.get('/service/SU1Z0isxPaozGVKXdv0eY/version')
21+
.reply(200, getversionsresponse.readVersions)
22+
.put('/service/SU1Z0isxPaozGVKXdv0eY/version/1/clone')
23+
.reply(200, versionresponse.cloneVersionDefault)
24+
.get('/service/SU1Z0isxPaozGVKXdv0eY/version/2/logging/s3/test-s3')
25+
.reply(200, readresponse.readS3)
26+
.put('/service/SU1Z0isxPaozGVKXdv0eY/version/2/logging/s3/test-s3')
27+
.reply(200, updateresponse.updateS3)
28+
.put('/service/SU1Z0isxPaozGVKXdv0eY/version/2/activate')
29+
.reply(200, activateresponse.activateVersion);
30+
31+
before(async () => {
32+
res = await fastly.transact(version => fastly.writeS3(version, 'test-s3', {
33+
name: 'test-s3',
34+
bucket_name: 'my_corporate_bucket',
35+
access_key: 'AKIAIOSFODNN7EXAMPLE',
36+
secret_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
37+
}));
38+
});
39+
40+
it('response should be a status 200', () => {
41+
expect(res.status).toBe(200);
42+
});
43+
44+
it('response body should exist', () => {
45+
expect(res.data).toBeTruthy();
46+
});
47+
48+
it('response body should be an object', () => {
49+
expect(typeof res.data).toBe('object');
50+
});
51+
52+
it('response body should contain all properties', () => {
53+
[
54+
'access_key',
55+
'bucket_name',
56+
'created_at',
57+
'deleted_at',
58+
'domain',
59+
'format',
60+
'format_version',
61+
'gzip_level',
62+
'message_type',
63+
'name',
64+
'path',
65+
'period',
66+
'placement',
67+
'redundancy',
68+
'response_condition',
69+
'secret_key',
70+
'service_id',
71+
'timestamp_format',
72+
'updated_at',
73+
'version',
74+
].forEach((e) => {
75+
expect(Object.keys(res.data)).toContain(e);
76+
});
77+
});
78+
});

0 commit comments

Comments
 (0)