Skip to content

Commit

Permalink
add fun validate subcommand (alibaba#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
vangie authored and JacksonTian committed Jun 26, 2018
1 parent 165b5de commit c518b3b
Show file tree
Hide file tree
Showing 9 changed files with 410 additions and 17 deletions.
3 changes: 3 additions & 0 deletions bin/fun.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ These are common Fun commands used in various situations:
start a working area
config Configure the fun
validate Validate a fun template
deploy Deploy a project to AliCloud
build Build the dependencies
help Print help information
Expand All @@ -35,6 +36,8 @@ var handle = function (err) {

if (subcommand === 'config') {
require('../lib/commands/config')(...args).catch(handle);
} else if (subcommand === 'validate') {
require('../lib/commands/validate')(...args).catch(handle);
} else if (subcommand === 'deploy') {
require('../lib/commands/deploy')(...args).catch(handle);
} else if (subcommand === 'build') {
Expand Down
1 change: 0 additions & 1 deletion docs/specs/2018-04-03.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,6 @@ When the logical ID of this resource is provided to the [Ref build-in function](
##### Example: Aliyun::Serverless::Api

```yaml
StageName: prod
DefinitionUri: swagger.yml
```

Expand Down
2 changes: 1 addition & 1 deletion examples/datahub/template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Resources:
Type: 'Aliyun::Serverless::Function'
Properties:
Handler: datahub.index
Runtime: nodejs6.10
Runtime: nodejs6
CodeUri: './'
Events:
DhsTrigger:
Expand Down
1 change: 1 addition & 0 deletions examples/segment/template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Resources:
Handler: index.doSegment
CodeUri: './'
Description: 'do segment'
Runtime: nodejs8
Events:
GetApi:
Type: API
Expand Down
2 changes: 1 addition & 1 deletion examples/timer/template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Resources:
Type: 'Aliyun::Serverless::Function'
Properties:
Handler: index.handler
Runtime: nodejs6.10
Runtime: nodejs8
Description: 'send hangzhou weather'
CodeUri: './'
Events:
Expand Down
301 changes: 301 additions & 0 deletions lib/commands/validate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
'use strict';

const fs = require('fs');
const util = require('util');

const yaml = require('js-yaml');
const Ajv = require('ajv');

const exists = util.promisify(fs.exists);
const readFile = util.promisify(fs.readFile);

/**
* JSON Schema for template.yml
* http://json-schema.org/
*/
const rosSchema = {
'title': 'fun template',
'type': 'object',
'properties': {
'ROSTemplateFormatVersion': {
'type': 'string',
'enum': ['2015-09-01']
},
'Transform': {
'type': 'string',
'enum': ['Aliyun::Serverless-2018-04-03']
},
'Resources': {
'type': 'object',
'patternProperties': {
'^[a-zA-Z_][a-zA-Z0-9_-]{0,127}$': {
anyOf: [
{'$ref': '/Resources/Service'},
{'$ref': '/Resources/Api'}
]
}
}
}
},
'required': ['ROSTemplateFormatVersion', 'Resources']
};
const serviceResourceSchema = {
'type': 'object',
'description': 'Service',
'properties': {
'Type': {
'type': 'string',
'const': 'Aliyun::Serverless::Service'
},
'Properties': {
'type': 'object',
'properties': {
'Description': {
'type': 'string'
}
}
}
},
'patternProperties': {
'^(?!Type|Properties)[a-zA-Z_][a-zA-Z0-9_-]{0,127}$': {
'$ref': '/Resources/Service/Function'
}
},
'required': ['Type']
};

const functionSchema = {
'type': 'object',
'description': 'Function',
'properties': {
'Type': {
'type': 'string',
'const': 'Aliyun::Serverless::Function'
},
'Properties': {
'type': 'object',
'properties': {
'Handler': {
'type': 'string'
},
'Runtime': {
'type': 'string',
'enum': ['nodejs6', 'nodejs8', 'python2.7', 'python3', 'java8'],
},
'CodeUri': {
'type': 'string'
},
'Description': {
'type': 'string'
}
}
},
'Events': {
'type': 'object',
'patternProperties': {
'^[a-zA-Z_][a-zA-Z0-9_-]{0,127}$': {
'anyOf': [
{ '$ref': '/Resources/Service/Function/Events/Datahub' },
{ '$ref': '/Resources/Service/Function/Events/API' },
{ '$ref': '/Resources/Service/Function/Events/OTS' },
{ '$ref': '/Resources/Service/Function/Events/Timer' },
]

}
}
},
},
'required': ['Type']
};

const datahubEventSchema = {
'type': 'object',
'properties': {
'Type': {
'type': 'string',
'const': 'Datahub'
},
'Properties': {
'type': 'object',
'properties': {
'Topic': {
'type': 'string'
},
'StartingPosition': {
'type': 'string',
'enum': ['LATEST', 'OLDEST', 'SYSTEM_TIME']
},
'BatchSize': {
'type': 'integer',
'minimum': 1
}
},
'required': ['Topic', 'StartingPosition']
}
}
};

const apiEventSchema = {
'type': 'object',
'properties': {
'Type': {
'type': 'string',
'const': 'API'
},
'Properties': {
'type': 'object',
'properties': {
'Path': {
'type': 'string'
},
'Method': {
'type': 'string',
'enum': ['get', 'head', 'post', 'put', 'delete', 'connect', 'options', 'trace', 'patch',
'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH',
'Get', 'Head', 'Post', 'Put', 'Delete', 'Connect', 'Options', 'Trace', 'Patch'
]
},
'RestApiId': {
'oneOf': [
{ 'type': 'string' },
{
'type': 'object',
'properties': {
'Ref': { 'type': 'string' }
}
}
]

}
},
'required': ['Path', 'Method']
}
}
};

const apiRescoureSchema = {
'type': 'object',
'description': 'API',
'properties': {
'Type': {
'type': 'string',
'const': 'Aliyun::Serverless::Api'
},
'Properties': {
'type': 'object',
'properties': {
'Name': {
'type': 'string'
},
'DefinitionBody': {
'type': 'object'
},
'DefinitionUri': {
'type': 'string'
},
'Cors': {
oneOf: [
{'type': 'string'},
{'$ref': '/CORS'}
]

}
}
}
},
'required': ['Type']
};

const corsSchema = {
'type': 'object',
'properties': {
'AllowMethods':{
'type': 'string'
},
'AllowHeaders':{
'type': 'string'
},
'AllowOrigin':{
'type': 'string'
},
'MaxAge':{
'type': 'string'
}
},
'required': ['AllowOrigin']
};

const otsEventSchema = {
'type': 'object',
'properties': {
'Type': {
'type': 'string',
'const': 'OTS'
},
'Properties': {
'type': 'object',
'properties': {
'Stream': {
'type': 'string'
}
},
'required': ['Stream']
}
}
};

const timerEventSchema = {
'type': 'object',
'properties': {
'Type': {
'type': 'string',
'const': 'Timer'
},
'Properties': {
'type': 'object',
'properties': {
'Payload': {
'type': 'string'
},
'CronExpression': {
'type': 'string'
},
'Enable': {
'type': 'boolean'
},
},
'required': ['CronExpression']
}
}
};


async function validate() {
if (!(await exists('template.yml'))) {
console.error('Can\'t found template.yml in current dir.');
return;
}

const tplContent = await readFile('template.yml', 'utf8');
const tpl = yaml.safeLoad(tplContent);
const ajv = new Ajv();
const valid = ajv.addSchema(datahubEventSchema, '/Resources/Service/Function/Events/Datahub')
.addSchema(apiEventSchema, '/Resources/Service/Function/Events/API')
.addSchema(otsEventSchema, '/Resources/Service/Function/Events/OTS')
.addSchema(timerEventSchema, '/Resources/Service/Function/Events/Timer')
.addSchema(functionSchema, '/Resources/Service/Function')
.addSchema(serviceResourceSchema, '/Resources/Service')
.addSchema(apiRescoureSchema, '/Resources/Api')
.addSchema(corsSchema, '/CORS')
.validate(rosSchema, tpl);


if (valid) {
console.log(JSON.stringify(tpl, null, 2));
} else {
console.error(JSON.stringify(ajv.errorsText(), null, 2));
}
return valid;
}

module.exports = validate;
Loading

0 comments on commit c518b3b

Please sign in to comment.