Adding books microservice for GE 8.6

This commit is contained in:
Ravi Srinivasan 2019-07-02 17:33:29 +05:30
parent 1deac4dc9b
commit f3b01722a2
17 changed files with 4273 additions and 0 deletions

34
books/.eslintrc Normal file
View file

@ -0,0 +1,34 @@
{
"extends": "airbnb-base",
"parserOptions": {
"sourceType": "script",
},
"rules": {
"strict": 0,
"no-param-reassign": [
"error",
{ "props": false }
],
"func-names": 0,
"comma-dangle": 0,
"prefer-template": 0,
"camelcase": 0,
"no-underscore-dangle": 0,
"consistent-return": 0,
"no-bitwise": ["error", { "allow": ["~"] }]
},
"globals": {
"describe": true,
"it": true,
"after": true,
"afterEach": true,
"before": true,
"beforeEach": true,
"should": true,
"reqServer": true
},
"env": {
"node": true,
"mocha": true
}
}

115
books/.gitignore vendored Normal file
View file

@ -0,0 +1,115 @@
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/

29
books/data/authors.js Normal file
View file

@ -0,0 +1,29 @@
const authors = [
{
id: 1,
name: 'James Joyce',
dob: 1882
},
{
id: 2,
name: 'F Scott Fitzgerald',
dob: 1896
},
{
id: 3,
name: 'Aldous Huxley',
dob: 1894
},
{
id: 4,
name: 'Vladimir Nabokov',
dob: 1899
},
{
id: 5,
name: 'William Faulkner',
dob: 1897
}
];
module.exports.books = authors;

39
books/data/books.js Normal file
View file

@ -0,0 +1,39 @@
const books = [
{
ranking: 1,
name: 'ULYSSES',
Author: 'James Joyce',
country: 'Ireland',
yearPublished: '1922'
},
{
ranking: 2,
name: 'THE GREAT GATSBY',
Author: 'F. Scott Fitzgerald',
country: 'USA',
yearPublished: '1925'
},
{
ranking: 3,
name: 'A PORTRAIT OF THE ARTIST AS A YOUNG MAN',
Author: 'James Joyce',
country: 'Ireland',
yearPublished: '1916'
},
{
ranking: 4,
name: 'BRAVE NEW WORLD',
Author: 'Aldous Huxley',
country: 'UK',
yearPublished: '1932'
},
{
ranking: 5,
name: 'THE SOUND AND THE FURY',
Author: 'William Faulkner',
country: 'USA',
yearPublished: '1929'
}
];
module.exports.books = books;

90
books/exec/www Executable file
View file

@ -0,0 +1,90 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../server');
var debug = require('debug')('movies:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '8080');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}

152
books/jenkins/Jenkinsfile vendored Normal file
View file

@ -0,0 +1,152 @@
pipeline {
options {
// set a timeout of 30 minutes for this pipeline
timeout(time: 30, unit: 'MINUTES')
}
agent {
node {
label 'nodejs'
}
}
environment {
DEV_PROJECT = "youruser-books-dev"
STAGE_PROJECT = "youruser-books-stage"
APP_GIT_URL = "https://github.com/youruser/DO288-apps"
NEXUS_SERVER = "http://nexus-common.apps.cluster.domain.example.com/repository/nodejs"
// DO NOT CHANGE THE GLOBAL VARS BELOW THIS LINE
APP_NAME = "books"
}
stages {
stage('NPM Install') {
steps {
echo '### Installing NPM dependencies ###'
sh '''
npm config set registry ${NEXUS_SERVER}
cd books
npm install
'''
}
}
stage('Run Unit Tests') {
steps {
echo '### Running unit tests ###'
sh 'cd books; npm test'
junit 'books/reports/server/mocha/test-results.xml'
}
}
stage('Run Linting Tools') {
steps {
echo '### Running eslint on code ###'
sh 'cd books; npm run lint'
}
}
stage('Launch new app in DEV env') {
steps {
echo '### Cleaning existing resources in DEV env ###'
sh '''
oc project ${DEV_PROJECT}
oc delete all -l app=${APP_NAME}
sleep 5
'''
echo '### Creating a new app in DEV env ###'
sh '''
oc project ${DEV_PROJECT}
oc new-app --name books nodejs:8~${APP_GIT_URL} --build-env npm_config_registry=${NEXUS_SERVER} --context-dir ${APP_NAME}
oc expose svc/${APP_NAME}
'''
}
}
stage('Wait for S2I build to complete') {
steps {
script {
openshift.withCluster() {
openshift.withProject( "${DEV_PROJECT}" ) {
def bc = openshift.selector("bc", "${APP_NAME}")
bc.logs('-f')
def builds = bc.related('builds')
builds.untilEach(1) {
return (it.object().status.phase == "Complete")
}
}
}
}
}
}
stage('Wait for deployment in DEV env') {
steps {
script {
openshift.withCluster() {
openshift.withProject( "${DEV_PROJECT}" ) {
def deployment = openshift.selector("dc", "${APP_NAME}").rollout()
openshift.selector("dc", "${APP_NAME}").related('pods').untilEach(1) {
return (it.object().status.phase == "Running")
}
}
}
}
}
}
stage('Promote to Staging Env') {
steps {
timeout(time: 60, unit: 'MINUTES') {
input message: "Promote to Staging?"
}
script {
openshift.withCluster() {
openshift.tag("${DEV_PROJECT}/books:latest", "${STAGE_PROJECT}/books:stage")
}
}
}
}
stage('Deploy to Staging Env') {
steps {
echo '### Cleaning existing resources in Staging ###'
sh '''
oc project ${STAGE_PROJECT}
oc delete all -l app=${APP_NAME}
sleep 5
'''
echo '### Creating a new app in Staging ###'
sh '''
oc project ${STAGE_PROJECT}
oc new-app --name books -i books:stage
oc expose svc/${APP_NAME}
'''
}
}
stage('Wait for deployment in Staging') {
steps {
sh "oc get route ${APP_NAME} -n ${STAGE_PROJECT} -o jsonpath='{ .spec.host }' --loglevel=4 > routehost"
script {
routeHost = readFile('routehost').trim()
openshift.withCluster() {
openshift.withProject( "${STAGE_PROJECT}" ) {
def deployment = openshift.selector("dc", "${APP_NAME}").rollout()
openshift.selector("dc", "${APP_NAME}").related('pods').untilEach(1) {
return (it.object().status.phase == "Running")
}
}
echo "Deployment to Staging env is complete. Access the app at the URL http://${routeHost}."
}
}
}
}
}
}

3637
books/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

30
books/package.json Normal file
View file

@ -0,0 +1,30 @@
{
"name": "books",
"version": "1.0.0",
"private": true,
"scripts": {
"start": "node ./exec/www",
"test": "export MOCHA_FILE='reports/server/mocha/test-results.xml' && IP=0.0.0.0 PORT=3030 node_modules/.bin/nyc --reporter=text node_modules/.bin/mocha tests/*_test.js -R mocha-junit-reporter",
"lint": "node_modules/.bin/eslint . --ext .js"
},
"dependencies": {
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
"express": "~4.16.1",
"express-prettify": "^0.1.1",
"hbs": "~4.0.4",
"http-errors": "~1.6.3",
"morgan": "~1.9.1"
},
"devDependencies": {
"chai": "^4.2.0",
"chai-http": "^4.3.0",
"eslint": "^5.16.0",
"eslint-config-airbnb-base": "^13.1.0",
"eslint-plugin-import": "^2.18.0",
"mocha": "^6.1.4",
"mocha-junit-reporter": "^1.23.0",
"nyc": "^14.1.1",
"request": "^2.88.0"
}
}

View file

@ -0,0 +1,8 @@
body {
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
a {
color: #00B7FF;
}

13
books/routes/authors.js Normal file
View file

@ -0,0 +1,13 @@
const express = require('express');
const router = express.Router();
const authors = require('../data/authors');
/* GET users listing. */
router.get('/', (req, res) => {
res.json(authors);
});
module.exports = router;

12
books/routes/books.js Normal file
View file

@ -0,0 +1,12 @@
const express = require('express');
const router = express.Router();
const books = require('../data/books');
/* GET users listing. */
router.get('/', (req, res) => {
res.json(books);
});
module.exports = router;

10
books/routes/index.js Normal file
View file

@ -0,0 +1,10 @@
const express = require('express');
const router = express.Router();
/* GET home page. */
router.get('/', (req, res) => {
res.render('index', { title: 'The Book List' });
});
module.exports = router;

45
books/server.js Normal file
View file

@ -0,0 +1,45 @@
const createError = require('http-errors');
const express = require('express');
const pretty = require('express-prettify');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const indexRouter = require('./routes/index');
const booksRouter = require('./routes/books');
const authorsRouter = require('./routes/authors');
const app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'hbs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(pretty({ query: 'pretty' }));
app.use('/', indexRouter);
app.use('/books', booksRouter);
app.use('/authors', authorsRouter);
// catch 404 and forward to error handler
app.use((req, res, next) => {
next(createError(404));
});
// error handler
app.use((err, req, res) => {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;

41
books/tests/app_test.js Normal file
View file

@ -0,0 +1,41 @@
const chai = require('chai');
const chaiHTTP = require('chai-http');
const server = require('../server');
const { expect } = chai;
chai.use(chaiHTTP);
reqServer = process.env.HTTP_TEST_SERVER || server;
describe('Books App routes test', () => {
it('GET to / should return 200', (done) => {
chai.request(reqServer)
.get('/')
.end((err, res) => {
expect(res).to.have.status(200);
expect(res.text).to.include('Welcome');
done();
});
});
it('GET to /books should return 200', (done) => {
chai.request(reqServer)
.get('/books')
.end((err, res) => {
expect(res).to.have.status(200);
expect(res.text).to.include('ULYSSES');
done();
});
});
it('GET to /authors should return 200', (done) => {
chai.request(reqServer)
.get('/authors')
.end((err, res) => {
expect(res).to.have.status(200);
expect(res.text).to.include('James Joyce');
done();
});
});
});

3
books/views/error.hbs Normal file
View file

@ -0,0 +1,3 @@
<h1>{{message}}</h1>
<h2>{{error.status}}</h2>
<pre>{{error.stack}}</pre>

5
books/views/index.hbs Normal file
View file

@ -0,0 +1,5 @@
<h1>{{title}}</h1>
<p>Welcome to {{title}}</p>
<p>The List of books is <a href="/books">here</a>.</p>
<p>The List of authors is <a href="/authors">here</a>.</p>

10
books/views/layout.hbs Normal file
View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
{{{body}}}
</body>
</html>