Gehel has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/395478 )
Change subject: initial commit ...................................................................... initial commit Change-Id: Iffebc2da34e0a1c64a02d12a5cf0a0a0f1216c61 --- A .dockerignore A .gitignore A .gitreview A app.js A config.dev.yaml A package.json A server.js A targets.yaml 8 files changed, 412 insertions(+), 0 deletions(-) Approvals: Gehel: Verified; Looks good to me, approved diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..563df11 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.git +coverage +node_modules diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a0a1c47 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +Dockerfile +.idea/ +coverage +config.yaml +node_modules +npm-debug.log +vectors +variables.yaml diff --git a/.gitreview b/.gitreview new file mode 100644 index 0000000..fd59be6 --- /dev/null +++ b/.gitreview @@ -0,0 +1,6 @@ +[gerrit] +host=gerrit.wikimedia.org +port=29418 +project=maps/tilerator/package.git +defaultbranch=master +defaultrebase=0 diff --git a/app.js b/app.js new file mode 100644 index 0000000..f866804 --- /dev/null +++ b/app.js @@ -0,0 +1,203 @@ +'use strict'; + + +var http = require('http'); +var BBPromise = require('bluebird'); +var express = require('express'); +var compression = require('compression'); +var bodyParser = require('body-parser'); +var fs = BBPromise.promisifyAll(require('fs')); +var sUtil = require('./lib/util'); +var packageInfo = require('./package.json'); +var yaml = require('js-yaml'); + + +/** + * Creates an express app and initialises it + * @param {Object} options the options to initialise the app with + * @return {bluebird} the promise resolving to the app object + */ +function initApp(options) { + + // the main application object + var app = express(); + + // get the options and make them available in the app + app.logger = options.logger; // the logging device + app.metrics = options.metrics; // the metrics + app.conf = options.config; // this app's config options + app.info = packageInfo; // this app's package info + + // ensure some sane defaults + if(!app.conf.port) { app.conf.port = 8888; } + if(!app.conf.interface) { app.conf.interface = '0.0.0.0'; } + if(app.conf.compression_level === undefined) { app.conf.compression_level = 3; } + if(app.conf.cors === undefined) { app.conf.cors = '*'; } + if(app.conf.csp === undefined) { + app.conf.csp = + "default-src 'self'; object-src 'none'; media-src *; img-src *; style-src *; frame-ancestors 'self'"; + } + + // set outgoing proxy + if(app.conf.proxy) { + process.env.HTTP_PROXY = app.conf.proxy; + // if there is a list of domains which should + // not be proxied, set it + if(app.conf.no_proxy_list) { + if(Array.isArray(app.conf.no_proxy_list)) { + process.env.NO_PROXY = app.conf.no_proxy_list.join(','); + } else { + process.env.NO_PROXY = app.conf.no_proxy_list; + } + } + } + + // set up header whitelisting for logging + if(!app.conf.log_header_whitelist) { + app.conf.log_header_whitelist = [ + 'cache-control', 'content-type', 'content-length', 'if-match', + 'user-agent', 'x-request-id' + ]; + } + app.conf.log_header_whitelist = new RegExp('^(?:' + app.conf.log_header_whitelist.map(function(item) { + return item.trim(); + }).join('|') + ')$', 'i'); + + // set up the spec + if(!app.conf.spec) { + app.conf.spec = __dirname + '/spec.yaml'; + } + if(app.conf.spec.constructor !== Object) { + try { + app.conf.spec = yaml.safeLoad(fs.readFileSync(app.conf.spec)); + } catch(e) { + app.logger.log('warn/spec', 'Could not load the spec: ' + e); + app.conf.spec = {}; + } + } + if(!app.conf.spec.swagger) { + app.conf.spec.swagger = '2.0'; + } + if(!app.conf.spec.info) { + app.conf.spec.info = { + version: app.info.version, + title: app.info.name, + description: app.info.description + }; + } + app.conf.spec.info.version = app.info.version; + if(!app.conf.spec.paths) { + app.conf.spec.paths = {}; + } + + // set the CORS and CSP headers + app.all('*', function(req, res, next) { + // + // Tilerator is an admin app, there is no point to set app.conf.cors and app.conf.csp + // + sUtil.initAndLogRequest(req, app); + next(); + }); + + // disable the X-Powered-By header + app.set('x-powered-by', false); + // disable the ETag header - users should provide them! + app.set('etag', false); + // enable compression + app.use(compression({level: app.conf.compression_level})); + // use the JSON body parser + app.use(bodyParser.json()); + // use the application/x-www-form-urlencoded parser + app.use(bodyParser.urlencoded({extended: true})); + + return BBPromise.resolve(app); + +} + + +/** + * Loads all routes declared in routes/ into the app + * @param {Application} app the application object to load routes into + * @returns {bluebird} a promise resolving to the app object + */ +function loadRoutes (app) { + + // get the list of files in routes/ + return fs.readdirAsync(__dirname + '/routes').map(function(fname) { + return BBPromise.try(function() { + // ... and then load each route + // but only if it's a js file + if(!/\.js$/.test(fname)) { + return undefined; + } + // import the route file + var route = require(__dirname + '/routes/' + fname); + return route(app); + }).then(function(route) { + if(route === undefined) { + return undefined; + } + // check that the route exports the object we need + if(route.constructor !== Object || !route.path || !route.router || !(route.api_version || route.skip_domain)) { + throw new TypeError('routes/' + fname + ' does not export the correct object!'); + } + // wrap the route handlers with Promise.try() blocks + sUtil.wrapRouteHandlers(route.router); + // determine the path prefix + var prefix = ''; + if(!route.skip_domain) { + prefix = '/:domain/v' + route.api_version; + } + // all good, use that route + app.use(prefix + route.path, route.router); + }); + }).then(function () { + // catch errors + sUtil.setErrorHandler(app); + // route loading is now complete, return the app object + return BBPromise.resolve(app); + }); + +} + + +/** + * Creates and start the service's web server + * @param {Application} app the app object to use in the service + * @returns {bluebird} a promise creating the web server + */ +function createServer(app) { + + // return a promise which creates an HTTP server, + // attaches the app to it, and starts accepting + // incoming client requests + var server; + return new BBPromise(function (resolve) { + server = http.createServer(app).listen( + app.conf.port, + app.conf.interface, + resolve + ); + }).then(function () { + app.logger.log('info', + 'Worker ' + process.pid + ' listening on ' + app.conf.interface + ':' + app.conf.port); + return server; + }); + +} + + +/** + * The service's entry point. It takes over the configuration + * options and the logger- and metrics-reporting objects from + * service-runner and starts an HTTP server, attaching the application + * object to it. + */ +module.exports = function(options) { + + return initApp(options) + .then(loadRoutes) + .then(createServer); + +}; + diff --git a/config.dev.yaml b/config.dev.yaml new file mode 100644 index 0000000..4f10557 --- /dev/null +++ b/config.dev.yaml @@ -0,0 +1,75 @@ +# Number of worker processes to spawn. +# Set to 0 to run everything in a single process without clustering. +# Use 'ncpu' to run as many workers as there are CPU units +num_workers: 0 + +# Log error messages and gracefully restart a worker if v8 reports that it +# uses more heap (note: not RSS) than this many mb. +worker_heap_limit_mb: 250 + +# Logger info +logging: + level: info + streams: + - type: debug +# streams: +# # Use gelf-stream -> logstash +# - type: gelf +# host: logstash1003.eqiad.wmnet +# port: 12201 + +# Statsd metrics reporter +metrics: + #type: log + #host: localhost + #port: 8125 + +services: + - name: tilerator + # a relative path or the name of an npm package, if different from name + module: ./app.js + # optionally, a version constraint of the npm package + # version: ^0.4.0 + # per-service config + conf: + port: 16534 + + # restrict to localhost access only + interface: localhost + + # more per-service config settings + # the location of the spec, defaults to spec.yaml if not specified + spec: ./spec.template.yaml + # allow cross-domain requests to the API (default '*') + cors: '*' + # to disable use: + # cors: false + # to restrict to a particular domain, use: + # cors: restricted.domain.org + # content for the CSP headers + # csp: false # uncomment this line to disable sending them + # URL of the outbound proxy to use (complete with protocol) + # proxy: http://my.proxy.org:8080 + # the list of domains for which not to use the proxy defined above + # no_proxy_list: + # - domain1.com + # - domain2.org + # the list of incoming request headers that can be logged; if left empty, + # the following headers are allowed: cache-control, content-length, + # content-type, if-match, user-agent, x-request-id + # log_header_whitelist: + # - cache-control + # - content-length + # - content-type + # - if-match + # - user-agent + # - x-request-id + + sources: ../sources.yaml + + # If true, do not enable admin interface + daemonOnly: false + + # If true, runs this instance without processing tiles + # This could be good for queue management + uiOnly: false diff --git a/package.json b/package.json new file mode 100644 index 0000000..8ff4c54 --- /dev/null +++ b/package.json @@ -0,0 +1,102 @@ +{ + "name": "tilerator-package", + "version": "0.0.1", + "description": "Map tiles pre-generation service - packaging for WMF", + "main": "./app.js", + "scripts": { + "start": "service-runner", + "test": "mocha", + "docker-start": "service-runner docker-start", + "docker-test": "service-runner docker-test", + "coverage": "istanbul cover _mocha -- -R spec" + }, + "repository": { + "type": "git", + "url": "https://gerrit.wikimedia.org/r/maps/tilerator/package" + }, + "keywords": [ + "REST", + "API", + "service template", + "MediaWiki" + ], + "author": "Guillaume Lederrey <ge...@wikimedia.org>", + "contributors": [ + "Paul Norman <pnor...@wikimedia.org>" + ], + "license": "Apache-2.0", + "bugs": "https://github.com/kartotherian/tilerator/issues", + "homepage": "https://github.com/kartotherian/tilerator", + "kartotherian": { + "registerSourceLibs": [ + "tilelive-bridge", + "tilelive-vector", + "@kartotherian/autogen", + "@kartotherian/cassandra", + "@kartotherian/demultiplexer", + "@kartotherian/layermixer", + "@kartotherian/overzoom", + "@kartotherian/postgres", + "@kartotherian/substantial" + ] + }, + "dependencies": { + "kartotherian": "git+https://github.com/kartotherian/tilerator.git#master", + "service-runner": "^2.4.6" + }, + "optionalDependencies": { + "bunyan-prettystream": "*" + }, + "devDependencies": { + "extend": "^3.0.0", + "istanbul": "^0.4.3", + "mocha": "^3.2.0", + "mocha-jshint": "^2.3.1", + "mocha-lcov-reporter": "^1.2.0", + "swagger-router": "^0.4.2" + }, + "deploy": { + "node": "6.9.1", + "target": "debian", + "install_opts": [ + "--build-from-source=mapnik", + "--fallback-to-build=false" + ], + "dependencies": { + "_all": [ + "libcairo2-dev", + "libgif-dev", + "libpango1.0-dev" + ], + "ubuntu": [ + "libjpeg62-dev" + ], + "debian": [ + "libjpeg62-turbo-dev", + "fonts-dejavu", + "libboost-filesystem-dev", + "libboost-program-options-dev", + "libboost-regex-dev", + "libboost-system-dev", + "libboost-thread-dev", + "libgdal-dev", + "libicu-dev", + "libpq-dev", + "libcurl4-gnutls-dev", + "libproj-dev", + "libtiff-dev", + "libwebp5", + { + "repo_url": "https://apt.wikimedia.org/wikimedia", + "release": "jessie-wikimedia", + "pool": "backports", + "packages": [ + "libmapbox-variant-dev", + "libmapnik-dev", + "mapnik-utils" + ] + } + ] + } + } +} diff --git a/server.js b/server.js new file mode 100755 index 0000000..da9fad8 --- /dev/null +++ b/server.js @@ -0,0 +1,12 @@ +#!/usr/bin/env node + +'use strict'; + +// Service entry point. Try node server --help for commandline options. + +// Start the service by running service-runner, which in turn loads the config +// (config.yaml by default, specify other path with -c). It requires the +// module(s) specified in the config 'services' section (app.js in this +// example). +var ServiceRunner = require('service-runner'); +return new ServiceRunner().run(); diff --git a/targets.yaml b/targets.yaml new file mode 100644 index 0000000..6a16a18 --- /dev/null +++ b/targets.yaml @@ -0,0 +1,3 @@ +debian: 'debian:jessie' +ubuntu: 'ubuntu:14.04' + -- To view, visit https://gerrit.wikimedia.org/r/395478 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: Iffebc2da34e0a1c64a02d12a5cf0a0a0f1216c61 Gerrit-PatchSet: 2 Gerrit-Project: maps/tilerator/package Gerrit-Branch: master Gerrit-Owner: Gehel <guillaume.leder...@wikimedia.org> Gerrit-Reviewer: Gehel <guillaume.leder...@wikimedia.org> Gerrit-Reviewer: Pnorman <penor...@mac.com> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits