Skip to content

Commit 822c6a6

Browse files
committed
template: Add dataFile parameter property for including arbitrary text from files
1 parent f8a438c commit 822c6a6

File tree

11 files changed

+534
-20
lines changed

11 files changed

+534
-20
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# v0.19.0
2+
## Added
3+
template: Add dataFile parameter property for including arbitrary text from files
4+
15
# v0.18.0
26
## Added
37
* Add a GitHubSchemaProvider and GitHubTemplateProvider

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,40 @@ fast.Template.loadYaml(yamldata)
287287
.then(template => template.forwardHttp()); // POST "foo" to http://example.com/resource
288288
```
289289
290+
### Template Data Files
291+
292+
Sometimes it is desirable to keep a portion of a template in a separate file and include it into the template text.
293+
This can be done with parameters and the `dataFile` property:
294+
295+
```javascript
296+
const fast = require('@f5devcentral/f5-fast-core');
297+
298+
const templatesPath = '/path/to/templatesdir'; // directory containing example.data
299+
const dataProvider = new fast.FsDataProvider(templatesPath);
300+
const yamldata = `
301+
definitions:
302+
var:
303+
dataFile: example
304+
template: |
305+
{{var}}
306+
`;
307+
308+
fast.Template.loadYaml(yamldata, { dataProvider })
309+
.then((template) => {
310+
console.log(template.getParametersSchema());
311+
console.log(template.render({virtual_port: 443});
312+
});
313+
```
314+
The `FsDataProvider` will pick up on any files with the `.data` extension in the template set directory.
315+
When referencing the file in a template, use the filename (without the extension) as a key.
316+
317+
Parameters with a `dataFile` property:
318+
319+
* are removed from `required`
320+
* have their `default` set to the contents of the file
321+
* given a default `format` of `hidden`
322+
323+
290324
## Development
291325
292326
* To check for lint errors run `npm run lint`

lib/data_provider.js

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/* Copyright 2021 F5 Networks, Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
'use strict';
17+
18+
const fs = require('fs');
19+
20+
const ResourceCache = require('./resource_cache').ResourceCache;
21+
22+
/**
23+
* DataProvider that fetches data from the file system
24+
*/
25+
class FsDataProvider {
26+
/**
27+
* @param {string} dataRootPath - a path to a directory containing data files
28+
*/
29+
constructor(dataRootPath) {
30+
this.data_path = dataRootPath;
31+
this.cache = new ResourceCache(dataName => new Promise((resolve, reject) => {
32+
fs.readFile(`${dataRootPath}/${dataName}.data`, (err, data) => {
33+
if (err) return reject(err);
34+
return resolve(data.toString('utf8'));
35+
});
36+
}));
37+
}
38+
39+
/**
40+
* Get the data file contents associated with the supplied key
41+
*
42+
* @param {string} key
43+
* @returns {object}
44+
*/
45+
fetch(key) {
46+
return this.cache.fetch(key);
47+
}
48+
49+
/**
50+
* List all data files known to the provider
51+
*
52+
* @returns {string[]}
53+
*/
54+
list() {
55+
return new Promise((resolve, reject) => {
56+
fs.readdir(this.data_path, (err, data) => {
57+
if (err) return reject(err);
58+
59+
const list = data.filter(x => x.endsWith('.data'))
60+
.map(x => x.replace(/.data$/, ''));
61+
return resolve(list);
62+
});
63+
});
64+
}
65+
}
66+
67+
/**
68+
* DataProvider that fetches data from an atg-storage DataStore
69+
*/
70+
class DataStoreDataProvider {
71+
/**
72+
* @param {object} datastore - an atg-storage DataStore
73+
* @param {string} tsName - the key to use to access the data file contents in the provided DataStore
74+
*/
75+
constructor(datastore, tsName) {
76+
this.storage = datastore;
77+
this.tsName = tsName;
78+
this.cache = new ResourceCache(
79+
dataName => this.storage.hasItem(this.tsName)
80+
.then((result) => {
81+
if (result) {
82+
return Promise.resolve();
83+
}
84+
return Promise.reject(new Error(`Could not find template set "${this.tsName}" in data store`));
85+
})
86+
.then(() => this.storage.getItem(this.tsName))
87+
.then(ts => ts.dataFiles[dataName])
88+
.then((data) => {
89+
if (typeof data === 'undefined') {
90+
return Promise.reject(new Error(`Failed to find data file named "${dataName}"`));
91+
}
92+
return Promise.resolve(data);
93+
})
94+
);
95+
}
96+
97+
/**
98+
* Get the data file contents associated with the supplied key
99+
*
100+
* @param {string} key
101+
* @returns {object}
102+
*/
103+
fetch(key) {
104+
return this.cache.fetch(key);
105+
}
106+
107+
/**
108+
* List all data files known to the provider
109+
*
110+
* @returns {string[]}
111+
*/
112+
list() {
113+
return this.storage.hasItem(this.tsName)
114+
.then((result) => {
115+
if (result) {
116+
return Promise.resolve();
117+
}
118+
return Promise.reject(new Error(`Could not find template set "${this.tsName}" in data store`));
119+
})
120+
.then(() => this.storage.getItem(this.tsName))
121+
.then(ts => Object.keys(ts.dataFiles));
122+
}
123+
}
124+
125+
module.exports = {
126+
FsDataProvider,
127+
DataStoreDataProvider
128+
};

lib/github_provider.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,48 @@ class GitHubSchemaProvider {
112112
}
113113
}
114114

115+
/**
116+
* DataProvider that fetches data from a GitHub repository
117+
*/
118+
class GitHubDataProvider {
119+
/**
120+
* @param {string} dataRootPath - a path to a directory containing data files
121+
*/
122+
constructor(repo, dataRootPath, options) {
123+
options = options || {};
124+
125+
this._rootDir = `/${dataRootPath}`;
126+
this._contentsApi = new GitHubContentsApi(repo, {
127+
apiToken: options.apiToken
128+
});
129+
this.cache = new ResourceCache(dataName => Promise.resolve()
130+
.then(() => this._contentsApi.getContentsData(`${this._rootDir}/${dataName}.data`)));
131+
}
132+
133+
/**
134+
* Get the data file contents associated with the supplied key
135+
*
136+
* @param {string} key
137+
* @returns {object}
138+
*/
139+
fetch(key) {
140+
return this.cache.fetch(key);
141+
}
142+
143+
/**
144+
* List all data files known to the provider
145+
*
146+
* @returns {string[]}
147+
*/
148+
list() {
149+
return Promise.resolve()
150+
.then(() => this._contentsApi.getContentsByType(this._rootDir, 'file'))
151+
.then(files => files
152+
.filter(x => x.endsWith('.data'))
153+
.map(x => stripExtension(x)));
154+
}
155+
}
156+
115157
/**
116158
* TemplateProvider that fetches data from a GitHub repository
117159
*/
@@ -127,6 +169,7 @@ class GitHubTemplateProvider extends BaseTemplateProvider {
127169
this._apiToken = options.apiToken;
128170

129171
this._schemaProviders = {};
172+
this._dataProviders = {};
130173

131174
this._contentsApi = new GitHubContentsApi(this.repo, {
132175
apiToken: this._apiToken
@@ -139,6 +182,7 @@ class GitHubTemplateProvider extends BaseTemplateProvider {
139182
const tmplName = tmplParts[tmplParts.length - 1];
140183

141184
const schemaProvider = this._getSchemaProvider(tmplDir);
185+
const dataProvider = this._getDataProvider(tmplDir);
142186
return Promise.resolve()
143187
.then(() => this._contentsApi.getContentsByType(tmplDir, 'file'))
144188
.then((files) => {
@@ -162,6 +206,7 @@ class GitHubTemplateProvider extends BaseTemplateProvider {
162206
.then(() => this._contentsApi.getContentsData(fname))
163207
.then(tmpldata => Template[useMst ? 'loadMst' : 'loadYaml'](tmpldata, {
164208
schemaProvider,
209+
dataProvider,
165210
templateProvider: this
166211
}));
167212
});
@@ -175,6 +220,14 @@ class GitHubTemplateProvider extends BaseTemplateProvider {
175220
return this._schemaProviders[tsName];
176221
}
177222

223+
_getDataProvider(tsName) {
224+
if (!this._dataProviders[tsName]) {
225+
this._dataProviders[tsName] = new GitHubDataProvider(this.repo, tsName, { apiToken: this._apiToken });
226+
}
227+
228+
return this._dataProviders[tsName];
229+
}
230+
178231
/**
179232
* Get a list of set names known to the provider
180233
*
@@ -250,6 +303,35 @@ class GitHubTemplateProvider extends BaseTemplateProvider {
250303
)))
251304
.then(() => schemas);
252305
}
306+
307+
/**
308+
* Get all data files known to the provider (optionally filtered by the supplied set name)
309+
*
310+
* @param {string} [filteredSetName] - only return data for this template set (instead of all template sets)
311+
* @returns {Promise} Promise resolves to an object containing data files
312+
*/
313+
getDataFiles(filteredSetName) {
314+
const dataFiles = {};
315+
return Promise.resolve()
316+
.then(() => (filteredSetName ? [filteredSetName] : this.listSets()))
317+
.then(setList => Promise.all(setList.map(
318+
tsName => this._getDataProvider(tsName).list()
319+
.then(dataFileList => Promise.all(dataFileList.map(
320+
dataName => this._getDataProvider(tsName).fetch(dataName)
321+
.then((data) => {
322+
const name = `${tsName}/${dataName}`;
323+
const dataHash = crypto.createHash('sha256');
324+
dataHash.update(data);
325+
dataFiles[name] = {
326+
name,
327+
data,
328+
hash: dataHash.digest('hex')
329+
};
330+
})
331+
)))
332+
)))
333+
.then(() => dataFiles);
334+
}
253335
}
254336

255337
module.exports = {

0 commit comments

Comments
 (0)