/
validation.js
executable file
·191 lines (161 loc) · 5.94 KB
/
validation.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
// Validation methods
import fs from 'fs';
import jsonfile from 'jsonfile';
import yaml from 'js-yaml';
import winston from 'winston';
import nth from 'lodash.nth';
import dropRight from 'lodash.dropright';
import Joi from '@hapi/joi';
import commandExists from 'command-exists';
export function validateGCloud() {
// Ensure gcloud CLI is installed
winston.debug('check gcloud is installed');
if (commandExists.sync('gcloud') === false) {
throw new Error('gcloud is not installed');
}
}
export function validateMeteor() {
let release;
// Ensure Meteor CLI is installed
winston.debug('check Meteor is installed');
if (commandExists.sync('meteor') === false) {
throw new Error('Meteor is not installed');
}
// Determine current release/packages from '.meteor' directory
try {
release = fs.readFileSync('.meteor/release', 'utf8');
} catch (error) {
/* Abort the program if files are not found, this is a strong
indication we may not be in the root project directory */
throw new Error('You must be in a Meteor project directory');
}
// Determine major/minor version numbers by stripping non-numeric characters from release
const versionNumbers = release.replace(/[^0-9]/g, '');
const majorVersion = Number.parseInt(versionNumbers.charAt(0), 10);
const minorVersion = Number.parseInt(versionNumbers.charAt(1), 10);
// Ensure current Meteor release is >= 1.4
winston.debug('check current Meteor release >= 1.4');
if (majorVersion < 1 || minorVersion < 4) {
throw new Error('Meteor version must be >= 1.4');
}
}
export function validateSettings(filePath) {
let settingsFile;
winston.info(`Validating settings file (${filePath})`);
// Ensure valid json exists
winston.debug('check valid json exists');
try {
settingsFile = jsonfile.readFileSync(filePath);
} catch (error) {
throw new Error(`Could not read settings file at '${filePath}'`);
}
// Define schema
const meteorGoogleCloudConfig = Joi.object({
project: Joi.string(),
}).unknown(true);
const schema = Joi.object({
'meteor-google-cloud': meteorGoogleCloudConfig,
}).unknown(true);
// Ensure settings data follows schema
winston.debug('check data follows schema');
Joi.validate(settingsFile, schema, { presence: 'required' }, (error) => {
if (error) {
// Pull error from bottom of stack to get most specific/useful details
const lastError = nth(error.details, -1);
// Locate parent of non-compliant field, or otherwise mark as top level
let pathToParent = 'top level';
if (lastError.path.length > 1) {
pathToParent = `"${dropRight(lastError.path).join('.')}"`;
}
// Report user-friendly error with relevant complaint/context to errors
throw new Error(`Settings file (${filePath}): ${lastError.message} in ${pathToParent}`);
}
});
return settingsFile;
}
export function validateApp(filePath) {
let appFile;
winston.info(`Validating app.yml file (${filePath})`);
// Ensure valid json exists
winston.debug('check app yaml exists');
try {
appFile = yaml.safeLoad(fs.readFileSync(filePath));
} catch (error) {
throw new Error(`Could not read app.yml file at '${filePath}'`);
}
// Define schema
const schema = Joi.object({
service: Joi.string(),
runtime: Joi.string(),
env: Joi.string(),
threadsafe: Joi.boolean(),
automatic_scaling: Joi.object({
max_num_instances: Joi.number().min(1),
}).optional().unknown(true),
resources: Joi.object({
cpu: Joi.number().min(1),
memory_gb: Joi.number(),
disk_size_gb: Joi.number().min(10),
}).optional().unknown(true),
network: Joi.object({
session_affinity: Joi.boolean(),
}),
}).unknown(true);
// allow unknown keys (at the top level) for extra settings
// (https://cloud.google.com/appengine/docs/admin-api/reference/rest/v1/apps.services.versions)
// Ensure settings app yaml follows schema
winston.debug('check app yaml follows schema');
Joi.validate(appFile, schema, { presence: 'required' }, (error) => {
if (error) {
// Pull error from bottom of stack to get most specific/useful details
const lastError = nth(error.details, -1);
// Locate parent of non-compliant field, or otherwise mark as top level
let pathToParent = 'top level';
if (lastError.path.length > 1) {
pathToParent = `"${dropRight(lastError.path).join('.')}"`;
}
// Report user-friendly error with relevant complaint/context to errors
throw new Error(`App.yaml file (${filePath}): ${lastError.message} in ${pathToParent}`);
}
});
// Make sure threadsafe is always true otherwise Meteor will not work properly
if (!appFile.threadsafe) {
winston.debug('found threadsafe false, change threadsafe to true');
Object.assign(appFile, {
threadsafe: true,
});
}
return appFile;
}
export function getDocker(filePath) {
let dockerFile;
winston.info(`Reading Dockerfile (${filePath})`);
// Ensure file exists
winston.debug('check dockerfile exists');
try {
dockerFile = fs.readFileSync(filePath, 'utf8');
} catch (error) {
throw new Error(`Could not read Dockerfile at '${filePath}'`);
}
return dockerFile;
}
export function validateEnv(settings, app) {
winston.debug('check either settings.json or app.yaml contain the required env');
const appSchema = Joi.object({
env_variables: Joi.object({
ROOT_URL: Joi.string(),
MONGO_URL: Joi.string(),
}).unknown(true),
}).unknown(true);
const settingsValidation = Joi.validate(settings, Joi.object({
'meteor-google-cloud': appSchema,
}).unknown(true), { presence: 'required' });
const appValidation = Joi.validate(app, appSchema, { presence: 'required' });
if (settingsValidation.error === null) {
return settings['meteor-google-cloud'].env_variables;
}
if (appValidation.error === null) {
return app.env_variables;
}
throw new Error('neither app.yaml, nor settings.json did contain the env_variables');
}