Compare commits
33 Commits
php_rating
...
tanzu
Author | SHA1 | Date | |
---|---|---|---|
|
a363a2485b | ||
|
f3e849e90b | ||
|
4470ebfa1c | ||
|
2d8e8e92ed | ||
|
2d1aa58274 | ||
|
bdce77fbd0 | ||
|
ab51044e65 | ||
|
480da51039 | ||
|
656054e928 | ||
|
136a67166f | ||
|
bf06964db3 | ||
|
68fe665eea | ||
|
0f12ad30d7 | ||
|
f389523bbf | ||
|
497bfe6acc | ||
|
3b7b948f96 | ||
|
c51acba9ab | ||
|
934f95deb0 | ||
|
7ef5c22ba3 | ||
|
632d2a85b3 | ||
|
00401f6b80 | ||
|
e6cbae39a2 | ||
|
bcfd10d9be | ||
|
733c943108 | ||
|
0db4344a24 | ||
|
da3cde42f5 | ||
|
43040713a7 | ||
|
33235306ae | ||
|
d931967c1f | ||
|
e76fd26775 | ||
|
e44ddda513 | ||
|
eefe45a645 | ||
|
48032a561b |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,4 +2,5 @@
|
||||
.DS_Store
|
||||
*-private.*
|
||||
vendor/
|
||||
Gopkg.lock
|
||||
Gopkg.lock
|
||||
.idea
|
||||
|
134
CF/README.md
Normal file
134
CF/README.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# RobotShop on PCF [WIP]
|
||||
|
||||
## Requirements
|
||||
|
||||
The following tiles up and running in the PCF foundation:
|
||||
|
||||
- [Pivotal Application Service](https://network.pivotal.io/products/elastic-runtime)
|
||||
- [RabbitMQ for Pivotal Platform](https://network.pivotal.io/products/p-rabbitmq/)
|
||||
- [Redis for Pivotal Platform](https://network.pivotal.io/products/p-redis/)
|
||||
- [MySQL for Pivotal Platform](https://network.pivotal.io/products/pivotal-mysql/)
|
||||
- [MongoDB Enterprise Service for Pivotal Platform](https://network.pivotal.io/products/mongodb-enterprise-service/)
|
||||
- [Instana Microservices Application Monitoring for Pivotal Platform](https://network.pivotal.io/products/instana-microservices-application-monitoring/)
|
||||
|
||||
## Create an organization and a space
|
||||
|
||||
```sh
|
||||
cf create-org stan
|
||||
cf create-space -o stan robotshop
|
||||
```
|
||||
|
||||
## Ensure routes are available
|
||||
|
||||
The following routes must be available for use:
|
||||
|
||||
- `web.<domain>`
|
||||
- `ratings.<domain>`
|
||||
- `cart.<domain>`
|
||||
- `catalogue.<domain>`
|
||||
- `shipping.<domain>`
|
||||
- `user.<domain>`
|
||||
- `payment.<domain>`
|
||||
- `dispatch.<domain>`
|
||||
|
||||
## Set up the services
|
||||
|
||||
```sh
|
||||
cf target -o stan -s robotshop
|
||||
```
|
||||
|
||||
## Build applications
|
||||
|
||||
Build the Java apps, from the repository root:
|
||||
|
||||
```sh
|
||||
pushd shipping && ./mvnw clean package && popd
|
||||
```
|
||||
|
||||
## First app push
|
||||
|
||||
RobotShop relies on specific binding names between services and apps, so we first push the apps without creating instances (all instance counts in the manifest are `0`).
|
||||
|
||||
From the root of the repo:
|
||||
|
||||
```sh
|
||||
cf push -f CF/manifest.yml
|
||||
```
|
||||
|
||||
## Create the services
|
||||
|
||||
```sh
|
||||
cf cs p.mysql db-small mysql-cities
|
||||
cf cs p.mysql db-small mysql-ratings
|
||||
cf cs mongodb-odb replica_set_small mongodb
|
||||
cf cs p.redis cache-small redis
|
||||
cf cs p.rabbitmq single-node-3.7 rabbitmq
|
||||
```
|
||||
|
||||
## Init MongoDB
|
||||
|
||||
```sh
|
||||
cf bind-service mongo-init mongodb --binding-name catalogue_database
|
||||
cf run-task mongo-init 'node init-db.js' --name "Init MongoDB"
|
||||
```
|
||||
|
||||
## Init MySQL databases
|
||||
|
||||
### Ratings database
|
||||
|
||||
```sh
|
||||
cf bind-service mysql-init mysql-ratings --binding-name ratings_database
|
||||
cf run-task mysql-init 'node init-db.js init-ratings.sql ratings_database' --name "Init Ratings database"
|
||||
```
|
||||
|
||||
### Shipping database
|
||||
|
||||
This one is not automated yet, as the `mysql-init` task app chokes on the large SQL init file.
|
||||
Something that works is to:
|
||||
|
||||
1) `bosh ssh` into the MySQL virtual machine
|
||||
2) Find out admin password from `/var/vcap/jobs/mysql/config/mylogin.cnf`
|
||||
3) Download the ["cities" init sql file](https://github.com/mmanciop/robot-shop/raw/master/mysql/scripts/10-dump.sql.gz) and `gunzip` it
|
||||
4) `/var/vcap/packages/percona-server/bin/mysql -u admin -p<adminpwd> -P 3306 -D service_instance_db < <sql_file>`
|
||||
|
||||
## Bind the services
|
||||
|
||||
Now that we have both apps and services, we can bind the former to the latter:
|
||||
|
||||
```sh
|
||||
cf bind-service mysql-init mysql-ratings --binding-name ratings_database
|
||||
cf bind-service ratings mysql-ratings --binding-name ratings_database
|
||||
cf bind-service catalogue mongodb --binding-name catalogue_database
|
||||
cf bind-service cart redis --binding-name cart_cache
|
||||
cf bind-service shipping mysql-cities --binding-name shipping_database
|
||||
cf bind-service user redis --binding-name users_cache
|
||||
cf bind-service user mongodb --binding-name users_database
|
||||
cf bind-service payment rabbitmq --binding-name dispatch_queue
|
||||
cf bind-service dispatch rabbitmq --binding-name dispatch_queue
|
||||
```
|
||||
|
||||
## Configure EUM
|
||||
|
||||
Create a website in Instana.
|
||||
Edit the `web/static/eum.html` file accordingly, specifically replacing the values of the `reportingUrl` and `key` ienums.
|
||||
|
||||
Push again the `web` application (or the entire manifest, whatever is easier for you):
|
||||
|
||||
```sh
|
||||
cf push -f CF/manifest.yml
|
||||
```
|
||||
|
||||
## Spin up the containers
|
||||
|
||||
**Note:** Feel free to replace the value after `-i` with how many container of any one kind you want.
|
||||
|
||||
```sh
|
||||
cf scale -i 1 web
|
||||
cf scale -i 1 ratings
|
||||
cf scale -i 1 cart
|
||||
cf scale -i 1 catalogue
|
||||
cf scale -i 1 shipping
|
||||
cf scale -i 1 user
|
||||
cf scale -i 1 payment
|
||||
cf scale -i 1 dispatch
|
||||
```
|
139
CF/manifest.yml
Normal file
139
CF/manifest.yml
Normal file
@@ -0,0 +1,139 @@
|
||||
---
|
||||
applications:
|
||||
|
||||
###
|
||||
### "Task" applications to setup services, see https://docs.cloudfoundry.org/devguide/using-tasks.html
|
||||
###
|
||||
|
||||
- name: mongo-init
|
||||
path: ../CF/mongo-init
|
||||
memory: 128M
|
||||
no-route: true # It's a CF Task, does not need a route
|
||||
health-check-type: process
|
||||
instances: 0
|
||||
|
||||
- name: mysql-init
|
||||
path: ../CF/mysql-init
|
||||
memory: 512M
|
||||
no-route: true # It's a CF Task, does not need a route
|
||||
health-check-type: process
|
||||
instances: 0
|
||||
|
||||
###
|
||||
### Productive apps
|
||||
###
|
||||
|
||||
- name: cart
|
||||
path: ../cart/
|
||||
memory: 128M
|
||||
env:
|
||||
CATALOGUE_HTTPS: true
|
||||
instances: 0
|
||||
|
||||
- name: catalogue
|
||||
path: ../catalogue/
|
||||
instances: 0
|
||||
|
||||
- name: dispatch
|
||||
path: ../dispatch/src
|
||||
memory: 128M
|
||||
health-check-type: process
|
||||
env:
|
||||
GOPACKAGENAME: github.com/instana/robot-shop/dispatch
|
||||
instances: 0
|
||||
|
||||
- name: payment
|
||||
path: ../payment/
|
||||
memory: 256M
|
||||
env:
|
||||
AUTOWRAPT_BOOTSTRAP: instana
|
||||
instances: 0
|
||||
|
||||
- name: ratings
|
||||
path: ../ratings/app
|
||||
memory: 128MB
|
||||
env:
|
||||
CATALOGUE_USE_HTTPS: true
|
||||
instances: 0
|
||||
|
||||
- name: shipping
|
||||
path: ../shipping/target/shipping-1.0-jar-with-dependencies.jar
|
||||
memory: 1G
|
||||
instances: 0
|
||||
|
||||
- name: user
|
||||
path: ../user/
|
||||
memory: 128MB
|
||||
instances: 0
|
||||
|
||||
- name: web
|
||||
path: ../web/
|
||||
memory: 128M
|
||||
buildpacks:
|
||||
- https://github.com/mmanciop/nginx-buildpack.git#nginx-1.17.2
|
||||
instances: 0
|
||||
|
||||
- name: loadgeneration
|
||||
path: ../load-gen
|
||||
buildpacks:
|
||||
- https://github.com/cloudfoundry/python-buildpack.git#v1.6.36
|
||||
no-route: true
|
||||
health-check-type: process
|
||||
memory: 1GB
|
||||
env:
|
||||
HOST: http://web.apps.pcf264.qainfra.instana.io
|
||||
CLIENTS: 5
|
||||
instances: 5
|
||||
|
||||
# cf cs p.mysql db-small mysql-cities
|
||||
# cf cs p.mysql db-small mysql-ratings
|
||||
# cf cs mongodb-odb replica_set_small mongodb
|
||||
# cf cs p.redis cache-small redis
|
||||
# cf cs p.rabbitmq single-node-3.7 rabbitmq
|
||||
|
||||
# cf bind-service mongo-init mongodb --binding-name catalogue_database
|
||||
# cf run-task mongo-init 'node init-db.js' --name "Init MongoDB"
|
||||
|
||||
# cf bind-service mysql-init mysql-ratings --binding-name ratings_database
|
||||
# cf run-task mysql-init 'node init-db.js init-ratings.sql ratings_database' --name "Init Ratings database"
|
||||
|
||||
###
|
||||
### DOES NOT WORK DUE TO THE SIZE OF THE DATABASE TO BE IMPORTED
|
||||
### MUST IMPORT DATA MANUALLY:
|
||||
### 1) BOSH SSH into the MySQL VM
|
||||
### 2) Find out admin password from /var/vcap/jobs/mysql/config/mylogin.cnf
|
||||
### 3) Download init sql from https://github.com/mmanciop/robot-shop/raw/master/mysql/scripts/10-dump.sql.gz and gunzip it
|
||||
### 4) /var/vcap/packages/percona-server/bin/mysql -u admin -p<adminpwd> -P 3306 -D service_instance_db < init.sql
|
||||
# cf bind-service mysql-init mysql-ratings --binding-name shipping_database
|
||||
# cf run-task mysql-init 'node init-db.js init-cities.sql shipping_database' --name "Init Shipping database"
|
||||
|
||||
# cf bind-service ratings mysql-ratings --binding-name ratings_database
|
||||
# cf scale -i 1 ratings
|
||||
# cf restage ratings
|
||||
|
||||
# cf bind-service catalogue mongodb --binding-name catalogue_database
|
||||
# cf scale -i 1 catalogue
|
||||
# cf restage catalogue
|
||||
|
||||
# cf bind-service cart redis --binding-name cart_cache
|
||||
# cf scale -i 1 cart
|
||||
# cf restage cart
|
||||
|
||||
# cf bind-service shipping mysql --binding-name shipping_database
|
||||
# cf scale -i 1 shipping
|
||||
# cf restage shipping
|
||||
|
||||
# cf bind-service user redis --binding-name users_cache
|
||||
# cf bind-service user mongodb --binding-name users_database
|
||||
# cf scale -i 1 user
|
||||
# cf restage user
|
||||
|
||||
# cf bind-service payment rabbitmq --binding-name dispatch_queue
|
||||
# cf scale -i 1 payment
|
||||
# cf restage payment
|
||||
|
||||
# cf bind-service dispatch rabbitmq --binding-name dispatch_queue
|
||||
# cf scale -i 1 dispatch
|
||||
# cf restage dispatch
|
||||
|
||||
# cf scale -i 1 web
|
90
CF/mongo-init/init-db.js
Normal file
90
CF/mongo-init/init-db.js
Normal file
@@ -0,0 +1,90 @@
|
||||
/*jshint esversion: 8 */
|
||||
|
||||
const mongoClient = require('mongodb').MongoClient;
|
||||
|
||||
new Promise((resolve, reject) => {
|
||||
if (process.env.VCAP_SERVICES) {
|
||||
const bindingName = 'catalogue_database';
|
||||
|
||||
connectionDetails = null;
|
||||
|
||||
for (let [key, value] of Object.entries(JSON.parse(process.env.VCAP_SERVICES))) {
|
||||
connectionDetails = value.find(function(binding) {
|
||||
return bindingName == binding.binding_name && binding.credentials;
|
||||
}).credentials;
|
||||
|
||||
if (!connectionDetails) {
|
||||
reject(`Cannot find a service binding with name '${bindingName}'`);
|
||||
}
|
||||
|
||||
const username = connectionDetails.username;
|
||||
const password = connectionDetails.password;
|
||||
|
||||
const normalizedAuthDetails = `${encodeURIComponent(username)}:${encodeURIComponent(password)}`;
|
||||
|
||||
const mongoUrl = connectionDetails.uri.replace(`${username}:${password}`, normalizedAuthDetails);
|
||||
|
||||
resolve(mongoUrl);
|
||||
}
|
||||
} else if (process.env.MONGO_URL) {
|
||||
resolve(process.env.MONGO_URL);
|
||||
} else {
|
||||
reject('MongoDB connection data missing');
|
||||
}
|
||||
}).then(function (mongoUrl) {
|
||||
console.log(`Connecting to ${mongoUrl}`);
|
||||
|
||||
return mongoClient.connect(mongoUrl);
|
||||
}).then(function(db) {
|
||||
const products = db.collection('products');
|
||||
const users = db.collection('users');
|
||||
|
||||
return products
|
||||
.deleteMany({})
|
||||
.then(products.insertMany([
|
||||
{sku: 'HAL-1', name: 'HAL', description: 'Sorry Dave, I cant do that', price: 2001, instock: 2, categories: ['AI']},
|
||||
{sku: 'PB-1', name: 'Positronic Brain', description: 'Highly advanced sentient processing unit with the laws of robotics burned in', price: 200, instock: 0, categories: ['AI']},
|
||||
{sku: 'ROB-1', name: 'Robbie', description: 'Large mechanical workhorse, crude but effective. Comes in handy when you are lost in space', price: 1200, instock: 12, categories: ['Robot']},
|
||||
{sku: 'EVE-1', name: 'Eve', description: 'Extraterrestrial Vegetation Evaluator', price: 5000, instock: 10, categories: ['Robot']},
|
||||
{sku: 'C3P0', name: 'C3P0', description: 'Protocol android', price: 700, instock: 1, categories: ['Robot']},
|
||||
{sku: 'R2D2', name: 'R2D2', description: 'R2 maintenance robot and secret messenger. Help me Obi Wan', price: 1400, instock: 1, categories: ['Robot']},
|
||||
{sku: 'K9', name: 'K9', description: 'Time travelling companion at heel', price: 300, instock: 12, categories: ['Robot']},
|
||||
{sku: 'RD-10', name: 'Kryten', description: 'Red Drawf crew member', price: 700, instock: 5, categories: ['Robot']},
|
||||
{sku: 'HHGTTG', name: 'Marvin', description: 'Marvin, your paranoid android. Brain the size of a planet', price: 42, instock: 48, categories: ['Robot']},
|
||||
{sku: 'STAN-1', name: 'Stan', description: 'APM guru', price: 50, instock: 1000, categories: ['Robot', 'AI']},
|
||||
{sku: 'STNG', name: 'Mr Data', description: 'Could be R. Daneel Olivaw? Protype positronic brain android', price: 1000, instock: 0, categories: ['Robot']}
|
||||
]))
|
||||
// full text index for searching
|
||||
.then(products.createIndex({
|
||||
name: 'text',
|
||||
description: 'text'
|
||||
}))
|
||||
// unique index for product sku
|
||||
.then(products.createIndex(
|
||||
{ sku: 1 },
|
||||
{ unique: true }
|
||||
))
|
||||
.then(function() {
|
||||
console.log('Populated \'products\' collection');
|
||||
})
|
||||
.then(users.deleteMany({}))
|
||||
.then(users.insertMany([
|
||||
{name: 'user', password: 'password', email: 'user@me.com'},
|
||||
{name: 'stan', password: 'bigbrain', email: 'stan@instana.com'}
|
||||
]))
|
||||
.then(users.createIndex(
|
||||
{name: 1},
|
||||
{unique: true}
|
||||
))
|
||||
.then(function () {
|
||||
console.log('Populated \'users\' collection');
|
||||
})
|
||||
})
|
||||
.then(function() {
|
||||
console.log('All done');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.log(`Error while importing data into the MongoDB database: ${err}`);
|
||||
process.exit(42);
|
||||
});
|
14
CF/mongo-init/package.json
Normal file
14
CF/mongo-init/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "mongodb-init",
|
||||
"version": "1.0.0",
|
||||
"description": "Init RobotShop's MongoDB database",
|
||||
"main": "init-db.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "SteveW",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"mongodb": "2.2.33"
|
||||
}
|
||||
}
|
44
CF/mysql-init/init-db.js
Normal file
44
CF/mysql-init/init-db.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/*jshint esversion: 6 */
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
const initArgv = process.argv.slice(2);
|
||||
|
||||
if (initArgv.length != 2) {
|
||||
console.error(`Usage: ${process.argv[0]} ${process.argv[1]} <sql-file> <mysql-service-binding-name>\n\nProgram arguments: ${process.argv.join(' ')}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const sqlFile = initArgv[0];
|
||||
const bindingName = initArgv[1];
|
||||
|
||||
const appEnv = require('cfenv').getAppEnv();
|
||||
|
||||
if (appEnv.isLocal) {
|
||||
throw new Error('Not running in Cloud Foundry (\'VCAP_SERVICES\' env var not found)');
|
||||
}
|
||||
|
||||
var connectionDetails = appEnv.getServiceCreds(bindingName);
|
||||
|
||||
if (!connectionDetails) {
|
||||
throw new Error(`No service binding with name '${bindingName}' found`);
|
||||
}
|
||||
|
||||
if (fs.existsSync(sqlFile)) {
|
||||
console.log('Starting database import');
|
||||
|
||||
require('mysql-import').config({
|
||||
host: connectionDetails.hostname,
|
||||
user: connectionDetails.username,
|
||||
password: connectionDetails.password,
|
||||
database: connectionDetails.name,
|
||||
onerror: err=>console.log(err.message)
|
||||
}).import(sqlFile)
|
||||
.then(() => console.log('Database imported'));
|
||||
} else {
|
||||
throw new Error(`File '${sqlFile}' not found!`);
|
||||
}
|
||||
|
||||
}());
|
8
CF/mysql-init/init-ratings.sql
Normal file
8
CF/mysql-init/init-ratings.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
DROP TABLE IF EXISTS ratings;
|
||||
|
||||
CREATE TABLE ratings (
|
||||
sku varchar(80) NOT NULL,
|
||||
avg_rating DECIMAL(3, 2) NOT NULL,
|
||||
rating_count INT NOT NULL,
|
||||
PRIMARY KEY (sku)
|
||||
) ENGINE=InnoDB;
|
15
CF/mysql-init/package.json
Normal file
15
CF/mysql-init/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "mysql-init",
|
||||
"version": "1.0.0",
|
||||
"description": "Init RobotShop's MySQL database",
|
||||
"main": "init-db.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "SteveW",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"mysql-import": "^2.0.2",
|
||||
"cfenv": "1.2.2"
|
||||
}
|
||||
}
|
@@ -69,7 +69,7 @@ To deploy the Instana agent to Kubernetes, just use the [helm](https://github.co
|
||||
|
||||
If you are having difficulties get helm running with your K8s install it is most likely due to RBAC, most K8s now have RBAC enabled by default. Therefore helm requires a [service account](https://github.com/helm/helm/blob/master/docs/rbac.md) to have permission to do stuff.
|
||||
|
||||
## Acessing the Store
|
||||
## Accessing the Store
|
||||
If you are running the store locally via *docker-compose up* then, the store front is available on localhost port 8080 [http://localhost:8080](http://localhost:8080/)
|
||||
|
||||
If you are running the store on Kubernetes via minikube then, to make the store front accessible edit the *web* service definition and change the type to *NodePort* and add a port entry *nodePort: 30080*.
|
||||
|
@@ -16,6 +16,6 @@
|
||||
"pino": "^5.10.8",
|
||||
"express-pino-logger": "^4.0.0",
|
||||
"pino-pretty": "^2.5.0",
|
||||
"instana-nodejs-sensor": "^1.28.0"
|
||||
"@instana/collector": "^1.28.0"
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,6 @@
|
||||
const instana = require('instana-nodejs-sensor');
|
||||
/*jshint esversion: 6 */
|
||||
|
||||
const instana = require('@instana/collector');
|
||||
// init tracing
|
||||
// MUST be done before loading anything else!
|
||||
instana({
|
||||
@@ -16,8 +18,63 @@ const expPino = require('express-pino-logger');
|
||||
|
||||
var redisConnected = false;
|
||||
|
||||
var redisHost = process.env.REDIS_HOST || 'redis'
|
||||
var catalogueHost = process.env.CATALOGUE_HOST || 'catalogue'
|
||||
var redisHost;
|
||||
var redisPassword;
|
||||
var redisPort;
|
||||
|
||||
if (process.env.VCAP_SERVICES) {
|
||||
connectionDetails = null;
|
||||
|
||||
console.log('Env var \'VCAP_SERVICES\' found, scanning for \'cart_cache\' service binding');
|
||||
|
||||
for (let [key, value] of Object.entries(JSON.parse(process.env.VCAP_SERVICES))) {
|
||||
try {
|
||||
binding = value.find(function(binding) {
|
||||
return 'cart_cache' == binding.binding_name && binding.credentials;
|
||||
});
|
||||
|
||||
if (!binding) {
|
||||
continue;
|
||||
}
|
||||
|
||||
connectionDetails = binding.credentials;
|
||||
|
||||
if (connectionDetails) {
|
||||
redisHost = connectionDetails.host;
|
||||
redisPassword = connectionDetails.password;
|
||||
redisPort = connectionDetails.port;
|
||||
|
||||
console.log('Redis URI for \'cart_cache\' service binding found in \'VCAP_SERVICES\'');
|
||||
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Cannot process key \'' + key + '\' of \'VCAP_SERVICES\'', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
} else if (process.env.REDIS_HOST) {
|
||||
redisHost = process.env.REDIS_HOST;
|
||||
redisPort = 6379;
|
||||
|
||||
console.log('Redis host found in \'REDIS_HOST\': ' + redisHost);
|
||||
} else {
|
||||
redisHost = 'redis';
|
||||
redisPort = 6379;
|
||||
|
||||
console.log('Using default Redis host and port');
|
||||
}
|
||||
|
||||
var catalogueHost = process.env.CATALOGUE_HOST || 'catalogue';
|
||||
if (process.env.VCAP_APPLICATION) {
|
||||
vcapApplication = JSON.parse(process.env.VCAP_APPLICATION);
|
||||
|
||||
applicationName = vcapApplication.application_name;
|
||||
applicationUri = vcapApplication.application_uris[0];
|
||||
catalogueHost = 'catalogue' + applicationUri.substring(applicationUri.indexOf('.'));
|
||||
}
|
||||
|
||||
const catalogueUrl = (process.env.CATALOGUE_HTTPS ? 'https' : 'http') + '://' + catalogueHost;
|
||||
|
||||
const logger = pino({
|
||||
level: 'info',
|
||||
@@ -183,7 +240,7 @@ app.get('/update/:id/:sku/:qty', (req, res) => {
|
||||
// check quantity
|
||||
var qty = parseInt(req.params.qty);
|
||||
if(isNaN(qty)) {
|
||||
req.log.warn('quanity not a number');
|
||||
req.log.warn('quantity not a number');
|
||||
res.status(400).send('quantity must be a number');
|
||||
return;
|
||||
} else if(qty < 0) {
|
||||
@@ -328,7 +385,7 @@ function calcTax(total) {
|
||||
|
||||
function getProduct(sku) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request('http://' + catalogueHost + ':8080/product/' + sku, (err, res, body) => {
|
||||
request(catalogueUrl + '/product/' + sku, (err, res, body) => {
|
||||
if(err) {
|
||||
reject(err);
|
||||
} else if(res.statusCode != 200) {
|
||||
@@ -355,9 +412,15 @@ function saveCart(id, cart) {
|
||||
});
|
||||
}
|
||||
|
||||
if (!redisHost) {
|
||||
throw new Error('Redis connection data missing');
|
||||
}
|
||||
|
||||
// connect to Redis
|
||||
var redisClient = redis.createClient({
|
||||
host: redisHost
|
||||
host: redisHost,
|
||||
password: redisPassword,
|
||||
port: redisPort
|
||||
});
|
||||
|
||||
redisClient.on('error', (e) => {
|
||||
@@ -369,7 +432,7 @@ redisClient.on('ready', (r) => {
|
||||
});
|
||||
|
||||
// fire it up!
|
||||
const port = process.env.CART_SERVER_PORT || '8080';
|
||||
const port = process.env.SERVER_PORT || '8080';
|
||||
app.listen(port, () => {
|
||||
logger.info('Started on port', port);
|
||||
});
|
||||
|
@@ -11,10 +11,11 @@
|
||||
"dependencies": {
|
||||
"body-parser": "^1.18.1",
|
||||
"express": "^4.15.4",
|
||||
"mongodb": "^2.2.33",
|
||||
"mongodb": "2.2.33",
|
||||
"pino": "^5.10.8",
|
||||
"express-pino-logger": "^4.0.0",
|
||||
"pino-pretty": "^2.5.0",
|
||||
"instana-nodejs-sensor": "^1.28.0"
|
||||
"@instana/collector": "^1.28.0",
|
||||
"mathjs": "^6.6.4"
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,6 @@
|
||||
const instana = require('instana-nodejs-sensor');
|
||||
/*jshint esversion: 6 */
|
||||
|
||||
const instana = require('@instana/collector');
|
||||
// init tracing
|
||||
// MUST be done before loading anything else!
|
||||
instana({
|
||||
@@ -8,7 +10,6 @@ instana({
|
||||
});
|
||||
|
||||
const mongoClient = require('mongodb').MongoClient;
|
||||
const mongoObjectID = require('mongodb').ObjectID;
|
||||
const bodyParser = require('body-parser');
|
||||
const express = require('express');
|
||||
const pino = require('pino');
|
||||
@@ -24,9 +25,81 @@ const expLogger = expPino({
|
||||
});
|
||||
|
||||
// MongoDB
|
||||
var db;
|
||||
var collection;
|
||||
var mongoConnected = false;
|
||||
var productsCollection;
|
||||
|
||||
const port = process.env.CATALOGUE_SERVER_PORT || '8080';
|
||||
|
||||
// set up Mongo
|
||||
function mongoConnectionPromise() {
|
||||
return new Promise(function() {
|
||||
var mongoURL;
|
||||
|
||||
if (process.env.VCAP_SERVICES) {
|
||||
const bindingName = 'catalogue_database';
|
||||
|
||||
connectionDetails = null;
|
||||
|
||||
console.log('Env var \'VCAP_SERVICES\' found, scanning for \'catalogue_database\' service binding');
|
||||
|
||||
for (let [key, value] of Object.entries(JSON.parse(process.env.VCAP_SERVICES))) {
|
||||
try {
|
||||
connectionDetails = value.find(function(binding) {
|
||||
return bindingName == binding.binding_name && binding.credentials;
|
||||
}).credentials;
|
||||
|
||||
if (!connectionDetails) {
|
||||
throw new Error(`Cannot find a service binding with name '${bindingName}'`);
|
||||
}
|
||||
|
||||
const username = connectionDetails.username;
|
||||
const password = connectionDetails.password;
|
||||
|
||||
const normalizedAuthDetails = `${encodeURIComponent(username)}:${encodeURIComponent(password)}`;
|
||||
|
||||
mongoURL = connectionDetails.uri.replace(`${username}:${password}`, normalizedAuthDetails);
|
||||
} catch (err) {
|
||||
console.log('Cannot process key \'' + key + '\' of \'VCAP_SERVICES\'', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
} else if (process.env.MONGO_URL) {
|
||||
mongoURL = process.env.MONGO_URL;
|
||||
|
||||
console.log('MongoDB URI found in \'MONGO_URL\': ' + mongoURL);
|
||||
} else {
|
||||
mongoURL = 'mongodb://mongodb:27017/catalogue';
|
||||
|
||||
console.log('Using default MongoDB URI');
|
||||
}
|
||||
|
||||
if (!mongoURL) {
|
||||
throw new Error('MongoDB connection data missing');
|
||||
}
|
||||
|
||||
return mongoClient.connect(mongoURL);
|
||||
})
|
||||
.then(function(db) {
|
||||
productsCollection = db.collection('products');
|
||||
|
||||
db.on('close', function() {
|
||||
logger.error('Disconnected from the MongoDB database, reconnecting...', e);
|
||||
|
||||
mongoConnect();
|
||||
})
|
||||
})
|
||||
.then(function() {
|
||||
logger.info('MongoDB connected');
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.log(`Cannot connect to MongoDB: ${err}`);
|
||||
});
|
||||
};
|
||||
|
||||
async function mongoConnect() {
|
||||
await mongoConnectionPromise();
|
||||
}
|
||||
|
||||
mongoConnect();
|
||||
|
||||
const app = express();
|
||||
|
||||
@@ -51,120 +124,64 @@ app.get('/health', (req, res) => {
|
||||
|
||||
// all products
|
||||
app.get('/products', (req, res) => {
|
||||
if(mongoConnected) {
|
||||
collection.find({}).toArray().then((products) => {
|
||||
res.json(products);
|
||||
}).catch((e) => {
|
||||
req.log.error('ERROR', e);
|
||||
res.status(500).send(e);
|
||||
});
|
||||
} else {
|
||||
req.log.error('database not available');
|
||||
res.status(500).send('database not avaiable');
|
||||
}
|
||||
productsCollection.find({}).toArray().then((products) => {
|
||||
res.json(products);
|
||||
}).catch((e) => {
|
||||
req.log.error('ERROR', e);
|
||||
res.status(500).send(e);
|
||||
});
|
||||
});
|
||||
|
||||
// product by SKU
|
||||
app.get('/product/:sku', (req, res) => {
|
||||
if(mongoConnected) {
|
||||
collection.findOne({sku: req.params.sku}).then((product) => {
|
||||
req.log.info('product', product);
|
||||
if(product) {
|
||||
res.json(product);
|
||||
} else {
|
||||
res.status(404).send('SKU not found');
|
||||
}
|
||||
}).catch((e) => {
|
||||
req.log.error('ERROR', e);
|
||||
res.status(500).send(e);
|
||||
});
|
||||
} else {
|
||||
req.log.error('database not available');
|
||||
res.status(500).send('database not available');
|
||||
}
|
||||
productsCollection.findOne({sku: req.params.sku}).then((product) => {
|
||||
req.log.info('product', product);
|
||||
if(product) {
|
||||
res.json(product);
|
||||
} else {
|
||||
res.status(404).send('SKU not found');
|
||||
}
|
||||
}).catch((e) => {
|
||||
req.log.error('ERROR', e);
|
||||
res.status(500).send(e);
|
||||
});
|
||||
});
|
||||
|
||||
// products in a category
|
||||
app.get('/products/:cat', (req, res) => {
|
||||
if(mongoConnected) {
|
||||
collection.find({ categories: req.params.cat }).sort({ name: 1 }).toArray().then((products) => {
|
||||
if(products) {
|
||||
res.json(products);
|
||||
} else {
|
||||
res.status(404).send('No products for ' + req.params.cat);
|
||||
}
|
||||
}).catch((e) => {
|
||||
req.log.error('ERROR', e);
|
||||
res.status(500).send(e);
|
||||
});
|
||||
} else {
|
||||
req.log.error('database not available');
|
||||
res.status(500).send('database not avaiable');
|
||||
}
|
||||
productsCollection.find({ categories: req.params.cat }).sort({ name: 1 }).toArray().then((products) => {
|
||||
if(products) {
|
||||
res.json(products);
|
||||
} else {
|
||||
res.status(404).send('No products for ' + req.params.cat);
|
||||
}
|
||||
}).catch((e) => {
|
||||
req.log.error('ERROR', e);
|
||||
res.status(500).send(e);
|
||||
});
|
||||
});
|
||||
|
||||
// all categories
|
||||
app.get('/categories', (req, res) => {
|
||||
if(mongoConnected) {
|
||||
collection.distinct('categories').then((categories) => {
|
||||
res.json(categories);
|
||||
}).catch((e) => {
|
||||
req.log.error('ERROR', e);
|
||||
res.status(500).send(e);
|
||||
});
|
||||
} else {
|
||||
req.log.error('database not available');
|
||||
res.status(500).send('database not available');
|
||||
}
|
||||
productsCollection.distinct('categories').then((categories) => {
|
||||
res.json(categories);
|
||||
}).catch((e) => {
|
||||
req.log.error('ERROR', e);
|
||||
res.status(500).send(e);
|
||||
});
|
||||
});
|
||||
|
||||
// search name and description
|
||||
app.get('/search/:text', (req, res) => {
|
||||
if(mongoConnected) {
|
||||
collection.find({ '$text': { '$search': req.params.text }}).toArray().then((hits) => {
|
||||
res.json(hits);
|
||||
}).catch((e) => {
|
||||
req.log.error('ERROR', e);
|
||||
res.status(500).send(e);
|
||||
});
|
||||
} else {
|
||||
req.log.error('database not available');
|
||||
res.status(500).send('database not available');
|
||||
}
|
||||
productsCollection.find({ '$text': { '$search': req.params.text }}).toArray().then((hits) => {
|
||||
res.json(hits);
|
||||
}).catch((e) => {
|
||||
req.log.error('ERROR', e);
|
||||
res.status(500).send(e);
|
||||
});
|
||||
});
|
||||
|
||||
// set up Mongo
|
||||
function mongoConnect() {
|
||||
return new Promise((resolve, reject) => {
|
||||
var mongoURL = process.env.MONGO_URL || 'mongodb://mongodb:27017/catalogue';
|
||||
mongoClient.connect(mongoURL, (error, _db) => {
|
||||
if(error) {
|
||||
reject(error);
|
||||
} else {
|
||||
db = _db;
|
||||
collection = db.collection('products');
|
||||
resolve('connected');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// mongodb connection retry loop
|
||||
function mongoLoop() {
|
||||
mongoConnect().then((r) => {
|
||||
mongoConnected = true;
|
||||
logger.info('MongoDB connected');
|
||||
}).catch((e) => {
|
||||
logger.error('ERROR', e);
|
||||
setTimeout(mongoLoop, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
mongoLoop();
|
||||
|
||||
// fire it up!
|
||||
const port = process.env.CATALOGUE_SERVER_PORT || '8080';
|
||||
// Fire up
|
||||
app.listen(port, () => {
|
||||
logger.info('Started on port', port);
|
||||
});
|
||||
|
||||
|
@@ -8,12 +8,16 @@ import (
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"errors"
|
||||
|
||||
"github.com/streadway/amqp"
|
||||
"github.com/instana/go-sensor"
|
||||
ot "github.com/opentracing/opentracing-go"
|
||||
ext "github.com/opentracing/opentracing-go/ext"
|
||||
otlog "github.com/opentracing/opentracing-go/log"
|
||||
|
||||
"github.com/cloudfoundry-community/go-cfenv"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -94,7 +98,7 @@ func getOrderId(order []byte) string {
|
||||
return id
|
||||
}
|
||||
|
||||
func createSpan(headers map[string]interface{}, order string) {
|
||||
func createSpan(headers map[string]interface{}, amqpUri string, order string) {
|
||||
// headers is map[string]interface{}
|
||||
// carrier is map[string]string
|
||||
carrier := make(ot.TextMapCarrier)
|
||||
@@ -116,9 +120,10 @@ func createSpan(headers map[string]interface{}, order string) {
|
||||
span = tracer.StartSpan("getOrder", ot.ChildOf(spanContext))
|
||||
span.SetTag(string(ext.SpanKind), ext.SpanKindConsumerEnum)
|
||||
span.SetTag(string(ext.MessageBusDestination), "robot-shop")
|
||||
span.SetTag("messaging_bus.address", amqpUri)
|
||||
span.SetTag("exchange", "robot-shop")
|
||||
span.SetTag("sort", "consume")
|
||||
span.SetTag("address", "rabbitmq")
|
||||
//span.SetTag("address", "rabbitmq")
|
||||
span.SetTag("key", "orders")
|
||||
span.LogFields(otlog.String("orderid", order))
|
||||
defer span.Finish()
|
||||
@@ -160,7 +165,50 @@ func main() {
|
||||
if !ok {
|
||||
amqpHost = "rabbitmq"
|
||||
}
|
||||
amqpUri = fmt.Sprintf("amqp://guest:guest@%s:5672/", amqpHost)
|
||||
amqpUser, ok := os.LookupEnv("AMQP_USER")
|
||||
if !ok {
|
||||
amqpHost = "guest"
|
||||
}
|
||||
amqpPwd, ok := os.LookupEnv("AMQP_PWD")
|
||||
if !ok {
|
||||
amqpPwd = "guest"
|
||||
}
|
||||
amqpPort, ok := os.LookupEnv("AMQP_PORT")
|
||||
if !ok {
|
||||
amqpPort = "5672"
|
||||
}
|
||||
|
||||
amqpUri = fmt.Sprintf("amqp://%s:%s@%s:%s/", amqpUser, amqpPwd , amqpHost, amqpPort)
|
||||
|
||||
if cfenv.IsRunningOnCF() {
|
||||
appEnv, err := cfenv.Current()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
log.Printf("Cloud Foundry detected")
|
||||
|
||||
bindingFound := false
|
||||
|
||||
services := appEnv.Services
|
||||
for _, service := range services {
|
||||
for _, serviceBinding := range service {
|
||||
bindingName := serviceBinding.Name
|
||||
|
||||
if strings.Compare(bindingName, "dispatch_queue") == 0 {
|
||||
bindingFound = true
|
||||
|
||||
log.Printf("RabbitMQ service binding '%s' found", bindingName)
|
||||
|
||||
amqpUri, _ = serviceBinding.CredentialString("uri")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !bindingFound {
|
||||
panic(errors.New("Service binding 'dispatch_queue' not found!"))
|
||||
}
|
||||
}
|
||||
|
||||
// get error threshold from environment
|
||||
errorPercent = 0
|
||||
@@ -203,7 +251,7 @@ func main() {
|
||||
log.Printf("Order %s\n", d.Body)
|
||||
log.Printf("Headers %v\n", d.Headers)
|
||||
id := getOrderId(d.Body)
|
||||
go createSpan(d.Headers, id)
|
||||
go createSpan(d.Headers, amqpUri, id)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
1
load-gen/.profile
Normal file
1
load-gen/.profile
Normal file
@@ -0,0 +1 @@
|
||||
export PATH=$PATH:/home/vcap/deps/0/python/bin
|
1
load-gen/Procfile
Normal file
1
load-gen/Procfile
Normal file
@@ -0,0 +1 @@
|
||||
web: locust -f robot-shop.py --no-web --host "$HOST" -c "$CLIENTS" -r 1
|
@@ -1 +1 @@
|
||||
locustio
|
||||
locustio==0.11.0
|
||||
|
1
load-gen/runtime.txt
Normal file
1
load-gen/runtime.txt
Normal file
@@ -0,0 +1 @@
|
||||
python-3.7.x
|
@@ -3,8 +3,8 @@
|
||||
//
|
||||
db = db.getSiblingDB('catalogue');
|
||||
db.products.insertMany([
|
||||
{sku: 'HAL-1', name: 'HAL', description: 'Sorry Dave, I cant do that', price: 2001, instock: 2, categories: ['Artificial Intelligence']},
|
||||
{sku: 'PB-1', name: 'Positronic Brain', description: 'Highly advanced sentient processing unit with the laws of robotics burned in', price: 200, instock: 0, categories: ['Artificial Intelligence']},
|
||||
{sku: 'HAL-1', name: 'HAL', description: 'Sorry Dave, I cant do that', price: 2001, instock: 2, categories: ['AI']},
|
||||
{sku: 'PB-1', name: 'Positronic Brain', description: 'Highly advanced sentient processing unit with the laws of robotics burned in', price: 200, instock: 0, categories: ['AI']},
|
||||
{sku: 'ROB-1', name: 'Robbie', description: 'Large mechanical workhorse, crude but effective. Comes in handy when you are lost in space', price: 1200, instock: 12, categories: ['Robot']},
|
||||
{sku: 'EVE-1', name: 'Eve', description: 'Extraterrestrial Vegetation Evaluator', price: 5000, instock: 10, categories: ['Robot']},
|
||||
{sku: 'C3P0', name: 'C3P0', description: 'Protocol android', price: 700, instock: 1, categories: ['Robot']},
|
||||
@@ -12,7 +12,7 @@ db.products.insertMany([
|
||||
{sku: 'K9', name: 'K9', description: 'Time travelling companion at heel', price: 300, instock: 12, categories: ['Robot']},
|
||||
{sku: 'RD-10', name: 'Kryten', description: 'Red Drawf crew member', price: 700, instock: 5, categories: ['Robot']},
|
||||
{sku: 'HHGTTG', name: 'Marvin', description: 'Marvin, your paranoid android. Brain the size of a planet', price: 42, instock: 48, categories: ['Robot']},
|
||||
{sku: 'STAN-1', name: 'Stan', description: 'APM guru', price: 50, instock: 1000, categories: ['Robot', 'Artificial Intelligence']},
|
||||
{sku: 'STAN-1', name: 'Stan', description: 'APM guru', price: 50, instock: 1000, categories: ['Robot', 'AI']},
|
||||
{sku: 'STNG', name: 'Mr Data', description: 'Could be R. Daneel Olivaw? Protype positronic brain android', price: 1000, instock: 0, categories: ['Robot']}
|
||||
]);
|
||||
|
||||
|
1
payment/Procfile
Normal file
1
payment/Procfile
Normal file
@@ -0,0 +1 @@
|
||||
web: python payment.py
|
@@ -6,12 +6,14 @@ import uuid
|
||||
import json
|
||||
import requests
|
||||
import traceback
|
||||
import re
|
||||
import opentracing as ot
|
||||
import opentracing.ext.tags as tags
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
from flask import jsonify
|
||||
from rabbitmq import Publisher
|
||||
from cfenv import AppEnv
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@@ -41,9 +43,20 @@ def pay(id):
|
||||
span.log_kv({'id': id})
|
||||
span.log_kv({'cart': cart})
|
||||
|
||||
user_service_uri = 'http://user:8080'
|
||||
cart_service_uri = 'http://cart:8080'
|
||||
if 'VCAP_SERVICES' in os.environ:
|
||||
env = AppEnv()
|
||||
application_name = env.app['application_name']
|
||||
application_uri = env.app['application_uris'][0]
|
||||
hostname = re.sub(r'^\w+\.', '', application_uri)
|
||||
|
||||
user_service_uri = 'https://user.{hostname}'.format(hostname=hostname)
|
||||
cart_service_uri = 'https://cart.{hostname}'.format(hostname=hostname)
|
||||
|
||||
# check user exists
|
||||
try:
|
||||
req = requests.get('http://{user}:8080/check/{id}'.format(user=USER, id=id))
|
||||
req = requests.get('{uri}/check/{id}'.format(uri=user_service_uri, id=id), verify=False)
|
||||
except requests.exceptions.RequestException as err:
|
||||
app.logger.error(err)
|
||||
return str(err), 500
|
||||
@@ -69,7 +82,7 @@ def pay(id):
|
||||
app.logger.error(err)
|
||||
return str(err), 500
|
||||
if req.status_code != 200:
|
||||
return 'payment error', req.status_code
|
||||
return 'Payment error; \'{payment_gateway}\' answered with a status code \'{status_code}\''.format(payment_gateway=PAYMENT_GATEWAY, status_code=req.status_code), 503
|
||||
|
||||
# Generate order id
|
||||
orderid = str(uuid.uuid4())
|
||||
@@ -78,9 +91,10 @@ def pay(id):
|
||||
# add to order history
|
||||
if not anonymous_user:
|
||||
try:
|
||||
req = requests.post('http://{user}:8080/order/{id}'.format(user=USER, id=id),
|
||||
req = requests.post('{user_uri}/order/{id}'.format(user_uri=user_service_uri, id=id),
|
||||
data=json.dumps({'orderid': orderid, 'cart': cart}),
|
||||
headers={'Content-Type': 'application/json'})
|
||||
headers={'Content-Type': 'application/json'},
|
||||
verify=False)
|
||||
app.logger.info('order history returned {}'.format(req.status_code))
|
||||
except requests.exceptions.RequestException as err:
|
||||
app.logger.error(err)
|
||||
@@ -88,13 +102,13 @@ def pay(id):
|
||||
|
||||
# delete cart
|
||||
try:
|
||||
req = requests.delete('http://{cart}:8080/cart/{id}'.format(cart=CART, id=id));
|
||||
req = requests.delete('{cart_uri}/cart/{id}'.format(cart_uri=cart_service_uri, id=id), verify=False);
|
||||
app.logger.info('cart delete returned {}'.format(req.status_code))
|
||||
except requests.exceptions.RequestException as err:
|
||||
app.logger.error(err)
|
||||
return str(err), 500
|
||||
if req.status_code != 200:
|
||||
return 'order history update error', req.status_code
|
||||
return 'Cannot update order history', 500
|
||||
|
||||
return jsonify({ 'orderid': orderid })
|
||||
|
||||
@@ -115,9 +129,10 @@ def queueOrder(order):
|
||||
tscope.span.log_kv({'orderid': order.get('orderid')})
|
||||
with ot.tracer.start_active_span('rabbitmq', child_of=tscope.span,
|
||||
tags={
|
||||
# 'address': Publisher.HOST,
|
||||
'messaging_bus.address': publisher._uri,
|
||||
'exchange': Publisher.EXCHANGE,
|
||||
'sort': 'publish',
|
||||
'address': Publisher.HOST,
|
||||
'key': Publisher.ROUTING_KEY
|
||||
}
|
||||
) as scope:
|
||||
|
@@ -1,25 +1,54 @@
|
||||
import json
|
||||
import pika
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pika.exceptions as exceptions
|
||||
|
||||
from cfenv import AppEnv
|
||||
from random import randrange
|
||||
|
||||
class Publisher:
|
||||
HOST = os.getenv('AMQP_HOST', 'rabbitmq')
|
||||
VIRTUAL_HOST = '/'
|
||||
EXCHANGE='robot-shop'
|
||||
TYPE='direct'
|
||||
ROUTING_KEY = 'orders'
|
||||
|
||||
error_rate = 0
|
||||
|
||||
if 'ERROR_RATE' in os.environ:
|
||||
error_rate = int(os.getenv('ERROR_RATE'))
|
||||
|
||||
def __init__(self, logger):
|
||||
self._logger = logger
|
||||
self._params = pika.connection.ConnectionParameters(
|
||||
host=self.HOST,
|
||||
virtual_host=self.VIRTUAL_HOST,
|
||||
credentials=pika.credentials.PlainCredentials('guest', 'guest'))
|
||||
|
||||
if 'VCAP_SERVICES' in os.environ:
|
||||
self._logger.info('Cloud Foundry detected')
|
||||
|
||||
if int(os.getenv('CF_INSTANCE_INDEX')) > 2:
|
||||
# Crash horribly to show how we detect these scenarios
|
||||
sys.exit(42)
|
||||
|
||||
env = AppEnv()
|
||||
|
||||
amqp_service = env.get_service(binding_name='dispatch_queue')
|
||||
|
||||
self._logger.info('Service binding \'{binding_name}\' found'.format(binding_name='dispatch_queue'))
|
||||
|
||||
self._uri = amqp_service.credentials.get('uri')
|
||||
else:
|
||||
self._uri = 'ampq://{user}:{pwd}@{host}:{port}/{vhost}'.format(
|
||||
host=os.getenv('AMQP_HOST', 'rabbitmq'),
|
||||
port=os.getenv('AMQP_PORT', '5672'),
|
||||
vhost='/',
|
||||
user=os.getenv('AMQP_USER', 'guest'),
|
||||
pwd=os.getenv('AMQP_PWD', 'guest'))
|
||||
|
||||
self._params = pika.URLParameters(self._uri)
|
||||
self._conn = None
|
||||
self._channel = None
|
||||
|
||||
def _connect(self):
|
||||
if not self._conn or self._conn.is_closed:
|
||||
if not self._conn or self._conn.is_closed or self._channel is None or self._channel.is_closed:
|
||||
self._conn = pika.BlockingConnection(self._params)
|
||||
self._channel = self._conn.channel()
|
||||
self._channel.exchange_declare(exchange=self.EXCHANGE, exchange_type=self.TYPE, durable=True)
|
||||
@@ -34,14 +63,17 @@ class Publisher:
|
||||
|
||||
#Publish msg, reconnecting if necessary.
|
||||
def publish(self, msg, headers):
|
||||
if self._channel is None:
|
||||
if self._channel is None or self._channel.is_closed or self._conn is None or self._conn.is_closed:
|
||||
self._connect()
|
||||
|
||||
if error_rate > randrange(100):
|
||||
raise exceptions.ConnectionWrongStateError(
|
||||
'Connection is not open')
|
||||
|
||||
try:
|
||||
self._publish(msg, headers)
|
||||
except pika.exceptions.ConnectionClosed:
|
||||
self._logger.info('reconnecting to queue')
|
||||
self._connect()
|
||||
self._publish(msg, headers)
|
||||
self._logger.error('Connection to queue is closed')
|
||||
|
||||
def close(self):
|
||||
if self._conn and self._conn.is_open:
|
||||
|
@@ -1,4 +1,5 @@
|
||||
Flask
|
||||
requests
|
||||
pika
|
||||
instana
|
||||
cfenv
|
||||
instana==1.15.3
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Use composer to install dependencies
|
||||
FROM composer AS build
|
||||
|
||||
COPY composer.json /app/
|
||||
COPY app/composer.json /app/
|
||||
|
||||
RUN composer install
|
||||
|
||||
@@ -13,7 +13,7 @@ FROM php:7.2-apache
|
||||
RUN docker-php-ext-install pdo_mysql
|
||||
|
||||
# relax permissions on status
|
||||
COPY status.conf /etc/apache2/mods-available/status.conf
|
||||
COPY app/status.conf /etc/apache2/mods-available/status.conf
|
||||
# Enable Apache mod_rewrite and status
|
||||
RUN a2enmod rewrite && a2enmod status
|
||||
|
||||
@@ -22,5 +22,4 @@ WORKDIR /var/www/html
|
||||
# copy dependencies from previous step
|
||||
COPY --from=build /app/vendor/ /var/www/html/vendor/
|
||||
|
||||
COPY html/ /var/www/html
|
||||
|
||||
COPY html/ /var/www/html
|
5
ratings/app/.bp-config/options.json
Normal file
5
ratings/app/.bp-config/options.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"PHP_VERSION": "{PHP_72_LATEST}",
|
||||
"LIBDIR": "htdocs/vendor",
|
||||
"COMPOSER_VENDOR_DIR": "htdocs/vendor"
|
||||
}
|
2
ratings/app/.bp-config/php/php.ini.d/pdo.ini
Normal file
2
ratings/app/.bp-config/php/php.ini.d/pdo.ini
Normal file
@@ -0,0 +1,2 @@
|
||||
extension=pdo.so
|
||||
extension=pdo_mysql.so
|
2
ratings/app/.bp-config/php/php.ini.d/zzz-instana.ini
Normal file
2
ratings/app/.bp-config/php/php.ini.d/zzz-instana.ini
Normal file
@@ -0,0 +1,2 @@
|
||||
#extension=instana-x64-1.3.2.so
|
||||
instana.socket=10.0.4.8:16816
|
@@ -2,5 +2,5 @@
|
||||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule api/(.*)$ api.php?request=$1 [QSA,NC,L]
|
||||
RewriteRule (.*)$ api.php?request=$1 [QSA,NC,L]
|
||||
</IfModule>
|
220
ratings/app/api.php
Normal file
220
ratings/app/api.php
Normal file
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
require_once 'API.class.php';
|
||||
|
||||
use Monolog\Logger;
|
||||
|
||||
class RatingsAPI extends API {
|
||||
public function __construct($request, $origin) {
|
||||
parent::__construct($request);
|
||||
// Logging
|
||||
$this->logger = new Logger('RatingsAPI');
|
||||
$this->logger->pushHandler($this->logHandler);
|
||||
}
|
||||
|
||||
protected function health() {
|
||||
$this->logger->info('health OK');
|
||||
return 'OK';
|
||||
}
|
||||
|
||||
protected function dump() {
|
||||
$data = array();
|
||||
$data['method'] = $this->method;
|
||||
$data['verb'] = $this->verb;
|
||||
$data = array_merge($data, array('args' => $this->args));
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
// GET ratings/fetch/sku
|
||||
protected function fetch() {
|
||||
if($this->method == 'GET' && isset($this->verb) && count($this->args) == 0) {
|
||||
$sku = $this->verb;
|
||||
if(! $this->_checkSku($sku)) {
|
||||
throw new Exception("$sku not found", 404);
|
||||
}
|
||||
$data = $this->_getRating($sku);
|
||||
return $data;
|
||||
} else {
|
||||
$this->logger->warn('fetch rating - bad request');
|
||||
throw new Exception('Bad request: fetch', 400);
|
||||
}
|
||||
}
|
||||
|
||||
// POST ratings/rate/sku/score
|
||||
protected function rate() {
|
||||
if($this->method == 'POST' && isset($this->verb) && count($this->args) == 1) {
|
||||
$sku = $this->verb;
|
||||
$score = intval($this->args[0]);
|
||||
$score = min(max(1, $score), 5);
|
||||
|
||||
if(! $this->_checkSku($sku)) {
|
||||
throw new Exception("$sku not found", 404);
|
||||
}
|
||||
|
||||
$rating = $this->_getRating($sku);
|
||||
if($rating['avg_rating'] == 0) {
|
||||
// not rated yet
|
||||
$this->_insertRating($sku, $score);
|
||||
} else {
|
||||
// iffy maths
|
||||
$newAvg = (($rating['avg_rating'] * $rating['rating_count']) + $score) / ($rating['rating_count'] + 1);
|
||||
$this->_updateRating($sku, $newAvg, $rating['rating_count'] + 1);
|
||||
}
|
||||
} else {
|
||||
$this->logger->warn('set rating - bad request');
|
||||
throw new Exception('Bad request: ' . implode($this->args), 400);
|
||||
}
|
||||
|
||||
return 'OK';
|
||||
}
|
||||
|
||||
private function _getRating($sku) {
|
||||
$db = $this->_dbConnect();
|
||||
if($db) {
|
||||
$stmt = $db->prepare('select avg_rating, rating_count from ratings where sku = ?');
|
||||
if($stmt->execute(array($sku))) {
|
||||
$data = $stmt->fetch();
|
||||
if($data) {
|
||||
// for some reason avg_rating is return as a string
|
||||
$data['avg_rating'] = floatval($data['avg_rating']);
|
||||
return $data;
|
||||
} else {
|
||||
// nicer to return an empty record than throw 404
|
||||
return array('avg_rating' => 0, 'rating_count' => 0);
|
||||
}
|
||||
} else {
|
||||
$this->logger->error('failed to query data');
|
||||
throw new Exception('Failed to query data', 500);
|
||||
}
|
||||
} else {
|
||||
$this->logger->error('database connection error');
|
||||
throw new Exception('Database connection error', 500);
|
||||
}
|
||||
}
|
||||
|
||||
private function _updateRating($sku, $score, $count) {
|
||||
$db = $this->_dbConnect();
|
||||
if($db) {
|
||||
$stmt = $db->prepare('update ratings set avg_rating = ?, rating_count = ? where sku = ?');
|
||||
if(! $stmt->execute(array($score, $count, $sku))) {
|
||||
$this->logger->error('failed to update rating');
|
||||
throw new Exception('Failed to update data', 500);
|
||||
}
|
||||
} else {
|
||||
$this->logger->error('database connection error');
|
||||
throw new Exception('Database connection error', 500);
|
||||
}
|
||||
}
|
||||
|
||||
private function _insertRating($sku, $score) {
|
||||
$db = $this->_dbConnect();
|
||||
if($db) {
|
||||
$stmt = $db->prepare('insert into ratings(sku, avg_rating, rating_count) values(?, ?, ?)');
|
||||
if(! $stmt->execute(array($sku, $score, 1))) {
|
||||
$this->logger->error('failed to insert data');
|
||||
throw new Exception('Failed to insert data', 500);
|
||||
}
|
||||
} else {
|
||||
$this->logger->error('database connection error');
|
||||
throw new Exception('Database connection error', 500);
|
||||
}
|
||||
}
|
||||
|
||||
private function _dbConnect() {
|
||||
$dsn = getenv('PDO_URL') ? getenv('PDO_URL') : 'mysql:host=mysql;dbname=ratings;charset=utf8mb4';
|
||||
$username = 'ratings';
|
||||
$password = 'iloveit';
|
||||
|
||||
if($vcapServicesJson = getenv('VCAP_SERVICES')) {
|
||||
$vcapServices = json_decode($vcapServicesJson, true);
|
||||
|
||||
foreach ($vcapServices as $key => $value) {
|
||||
foreach ($value as $serviceBinding){
|
||||
if ($serviceBinding['binding_name'] == 'ratings_database') {
|
||||
$credentials = $serviceBinding['credentials'];
|
||||
$dsn = 'mysql:host=' . $credentials['hostname'] . ';port=' . $credentials['port'] . ';dbname=' . $credentials['name'];
|
||||
$username = $credentials['username'];
|
||||
$password = $credentials['password'];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$opt = array(
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false
|
||||
);
|
||||
|
||||
$db = false;
|
||||
try {
|
||||
$db = new PDO($dsn, $username, $password, $opt);
|
||||
} catch (PDOException $e) {
|
||||
$msg = $e->getMessage();
|
||||
$this->logger->error("Database error $msg");
|
||||
$db = false;
|
||||
}
|
||||
|
||||
return $db;
|
||||
}
|
||||
|
||||
private function _endsWith($subject, $token) {
|
||||
$tokenLength = strlen($token);
|
||||
if ($tokenLength == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (substr($subject, -$tokenLength) === $token);
|
||||
}
|
||||
|
||||
// check sku exists in product catalogue
|
||||
private function _checkSku($sku) {
|
||||
$url = getenv('CATALOGUE_URL') ? getenv('CATALOGUE_URL') : 'http://catalogue:8080/';
|
||||
|
||||
if ($vcapApplicationJson = getenv('VCAP_APPLICATION')) {
|
||||
$vcapApplication = json_decode($vcapApplicationJson, true);
|
||||
$applicationName = $vcapApplication['application_name'];
|
||||
$applicationUri = $vcapApplication['application_uris'][0];
|
||||
|
||||
$count = 1;
|
||||
$url = str_replace($applicationName, "catalogue", $applicationUri, $count);
|
||||
if (!$this->_endsWith($url, '/')) {
|
||||
$url = (getenv('CATALOGUE_USE_HTTPS') ? 'https' : 'http') . '://' . $url . '/';
|
||||
}
|
||||
}
|
||||
|
||||
$url = $url . 'product/' . $sku;
|
||||
|
||||
$opt = array(
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
);
|
||||
$curl = curl_init($url);
|
||||
curl_setopt_array($curl, $opt);
|
||||
|
||||
$data = curl_exec($curl);
|
||||
if(! $data) {
|
||||
$this->logger->error('failed to connect to catalogue');
|
||||
throw new Exception('Failed to connect to catalogue at: ' . $url, 500);
|
||||
}
|
||||
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
|
||||
$this->logger->info("catalogue status $status");
|
||||
|
||||
curl_close($curl);
|
||||
|
||||
return $status == 200;
|
||||
}
|
||||
}
|
||||
|
||||
if(!array_key_exists('HTTP_ORIGIN', $_SERVER)) {
|
||||
$_SERVER['HTTP_ORIGIN'] = $_SERVER['SERVER_NAME'];
|
||||
}
|
||||
|
||||
try {
|
||||
$API = new RatingsAPI($_REQUEST['request'], $_SERVER['HTTP_ORIGIN']);
|
||||
echo $API->processAPI();
|
||||
} catch(Exception $e) {
|
||||
echo json_encode(Array('error' => $e->getMessage()));
|
||||
}
|
||||
?>
|
143
ratings/app/composer.lock
generated
Normal file
143
ratings/app/composer.lock
generated
Normal file
@@ -0,0 +1,143 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "8511edda340e04039f72f12feb7f3171",
|
||||
"packages": [
|
||||
{
|
||||
"name": "monolog/monolog",
|
||||
"version": "1.24.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Seldaek/monolog.git",
|
||||
"reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266",
|
||||
"reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0",
|
||||
"psr/log": "~1.0"
|
||||
},
|
||||
"provide": {
|
||||
"psr/log-implementation": "1.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
|
||||
"doctrine/couchdb": "~1.0@dev",
|
||||
"graylog2/gelf-php": "~1.0",
|
||||
"jakub-onderka/php-parallel-lint": "0.9",
|
||||
"php-amqplib/php-amqplib": "~2.4",
|
||||
"php-console/php-console": "^3.1.3",
|
||||
"phpunit/phpunit": "~4.5",
|
||||
"phpunit/phpunit-mock-objects": "2.3.0",
|
||||
"ruflin/elastica": ">=0.90 <3.0",
|
||||
"sentry/sentry": "^0.13",
|
||||
"swiftmailer/swiftmailer": "^5.3|^6.0"
|
||||
},
|
||||
"suggest": {
|
||||
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
|
||||
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
|
||||
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
|
||||
"ext-mongo": "Allow sending log messages to a MongoDB server",
|
||||
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
|
||||
"mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
|
||||
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
|
||||
"php-console/php-console": "Allow sending log messages to Google Chrome",
|
||||
"rollbar/rollbar": "Allow sending log messages to Rollbar",
|
||||
"ruflin/elastica": "Allow sending log messages to an Elastic Search server",
|
||||
"sentry/sentry": "Allow sending log messages to a Sentry server"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Monolog\\": "src/Monolog"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jordi Boggiano",
|
||||
"email": "j.boggiano@seld.be",
|
||||
"homepage": "http://seld.be"
|
||||
}
|
||||
],
|
||||
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
|
||||
"homepage": "http://github.com/Seldaek/monolog",
|
||||
"keywords": [
|
||||
"log",
|
||||
"logging",
|
||||
"psr-3"
|
||||
],
|
||||
"time": "2018-11-05T09:00:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/log",
|
||||
"version": "1.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/log.git",
|
||||
"reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
|
||||
"reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Log\\": "Psr/Log/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "http://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for logging libraries",
|
||||
"homepage": "https://github.com/php-fig/log",
|
||||
"keywords": [
|
||||
"log",
|
||||
"psr",
|
||||
"psr-3"
|
||||
],
|
||||
"time": "2018-11-20T15:27:04+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": []
|
||||
}
|
BIN
ratings/app/php/lib/php/extensions/instana-x64-1.3.2.so
Normal file
BIN
ratings/app/php/lib/php/extensions/instana-x64-1.3.2.so
Normal file
Binary file not shown.
11
shipping/.gitignore
vendored
Normal file
11
shipping/.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
target/
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.next
|
||||
release.properties
|
||||
dependency-reduced-pom.xml
|
||||
buildNumber.properties
|
||||
.mvn/timing.properties
|
||||
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
|
||||
.mvn/wrapper/maven-wrapper.jar
|
117
shipping/.mvn/wrapper/MavenWrapperDownloader.java
vendored
Normal file
117
shipping/.mvn/wrapper/MavenWrapperDownloader.java
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2007-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import java.net.*;
|
||||
import java.io.*;
|
||||
import java.nio.channels.*;
|
||||
import java.util.Properties;
|
||||
|
||||
public class MavenWrapperDownloader {
|
||||
|
||||
private static final String WRAPPER_VERSION = "0.5.6";
|
||||
/**
|
||||
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
|
||||
*/
|
||||
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
|
||||
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
|
||||
|
||||
/**
|
||||
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
|
||||
* use instead of the default one.
|
||||
*/
|
||||
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
|
||||
".mvn/wrapper/maven-wrapper.properties";
|
||||
|
||||
/**
|
||||
* Path where the maven-wrapper.jar will be saved to.
|
||||
*/
|
||||
private static final String MAVEN_WRAPPER_JAR_PATH =
|
||||
".mvn/wrapper/maven-wrapper.jar";
|
||||
|
||||
/**
|
||||
* Name of the property which should be used to override the default download url for the wrapper.
|
||||
*/
|
||||
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
|
||||
|
||||
public static void main(String args[]) {
|
||||
System.out.println("- Downloader started");
|
||||
File baseDirectory = new File(args[0]);
|
||||
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
|
||||
|
||||
// If the maven-wrapper.properties exists, read it and check if it contains a custom
|
||||
// wrapperUrl parameter.
|
||||
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
|
||||
String url = DEFAULT_DOWNLOAD_URL;
|
||||
if(mavenWrapperPropertyFile.exists()) {
|
||||
FileInputStream mavenWrapperPropertyFileInputStream = null;
|
||||
try {
|
||||
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
|
||||
Properties mavenWrapperProperties = new Properties();
|
||||
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
|
||||
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
|
||||
} catch (IOException e) {
|
||||
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
|
||||
} finally {
|
||||
try {
|
||||
if(mavenWrapperPropertyFileInputStream != null) {
|
||||
mavenWrapperPropertyFileInputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignore ...
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println("- Downloading from: " + url);
|
||||
|
||||
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
|
||||
if(!outputFile.getParentFile().exists()) {
|
||||
if(!outputFile.getParentFile().mkdirs()) {
|
||||
System.out.println(
|
||||
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
|
||||
}
|
||||
}
|
||||
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
|
||||
try {
|
||||
downloadFileFromURL(url, outputFile);
|
||||
System.out.println("Done");
|
||||
System.exit(0);
|
||||
} catch (Throwable e) {
|
||||
System.out.println("- Error downloading");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
|
||||
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
|
||||
String username = System.getenv("MVNW_USERNAME");
|
||||
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
|
||||
Authenticator.setDefault(new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(username, password);
|
||||
}
|
||||
});
|
||||
}
|
||||
URL website = new URL(urlString);
|
||||
ReadableByteChannel rbc;
|
||||
rbc = Channels.newChannel(website.openStream());
|
||||
FileOutputStream fos = new FileOutputStream(destination);
|
||||
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
|
||||
fos.close();
|
||||
rbc.close();
|
||||
}
|
||||
|
||||
}
|
2
shipping/.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
2
shipping/.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
|
310
shipping/mvnw
vendored
Executable file
310
shipping/mvnw
vendored
Executable file
@@ -0,0 +1,310 @@
|
||||
#!/bin/sh
|
||||
# ----------------------------------------------------------------------------
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Maven Start Up Batch script
|
||||
#
|
||||
# Required ENV vars:
|
||||
# ------------------
|
||||
# JAVA_HOME - location of a JDK home dir
|
||||
#
|
||||
# Optional ENV vars
|
||||
# -----------------
|
||||
# M2_HOME - location of maven2's installed home dir
|
||||
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
# e.g. to debug Maven itself, use
|
||||
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
if [ -z "$MAVEN_SKIP_RC" ] ; then
|
||||
|
||||
if [ -f /etc/mavenrc ] ; then
|
||||
. /etc/mavenrc
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/.mavenrc" ] ; then
|
||||
. "$HOME/.mavenrc"
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
# OS specific support. $var _must_ be set to either true or false.
|
||||
cygwin=false;
|
||||
darwin=false;
|
||||
mingw=false
|
||||
case "`uname`" in
|
||||
CYGWIN*) cygwin=true ;;
|
||||
MINGW*) mingw=true;;
|
||||
Darwin*) darwin=true
|
||||
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
|
||||
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
if [ -x "/usr/libexec/java_home" ]; then
|
||||
export JAVA_HOME="`/usr/libexec/java_home`"
|
||||
else
|
||||
export JAVA_HOME="/Library/Java/Home"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
if [ -r /etc/gentoo-release ] ; then
|
||||
JAVA_HOME=`java-config --jre-home`
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$M2_HOME" ] ; then
|
||||
## resolve links - $0 may be a link to maven's home
|
||||
PRG="$0"
|
||||
|
||||
# need this for relative symlinks
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG="`dirname "$PRG"`/$link"
|
||||
fi
|
||||
done
|
||||
|
||||
saveddir=`pwd`
|
||||
|
||||
M2_HOME=`dirname "$PRG"`/..
|
||||
|
||||
# make it fully qualified
|
||||
M2_HOME=`cd "$M2_HOME" && pwd`
|
||||
|
||||
cd "$saveddir"
|
||||
# echo Using m2 at $M2_HOME
|
||||
fi
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched
|
||||
if $cygwin ; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME=`cygpath --unix "$M2_HOME"`
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
|
||||
fi
|
||||
|
||||
# For Mingw, ensure paths are in UNIX format before anything is touched
|
||||
if $mingw ; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME="`(cd "$M2_HOME"; pwd)`"
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
javaExecutable="`which javac`"
|
||||
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
|
||||
# readlink(1) is not available as standard on Solaris 10.
|
||||
readLink=`which readlink`
|
||||
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
|
||||
if $darwin ; then
|
||||
javaHome="`dirname \"$javaExecutable\"`"
|
||||
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
|
||||
else
|
||||
javaExecutable="`readlink -f \"$javaExecutable\"`"
|
||||
fi
|
||||
javaHome="`dirname \"$javaExecutable\"`"
|
||||
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
|
||||
JAVA_HOME="$javaHome"
|
||||
export JAVA_HOME
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$JAVACMD" ] ; then
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
else
|
||||
JAVACMD="`which java`"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
echo "Error: JAVA_HOME is not defined correctly." >&2
|
||||
echo " We cannot execute $JAVACMD" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
echo "Warning: JAVA_HOME environment variable is not set."
|
||||
fi
|
||||
|
||||
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
|
||||
|
||||
# traverses directory structure from process work directory to filesystem root
|
||||
# first directory with .mvn subdirectory is considered project base directory
|
||||
find_maven_basedir() {
|
||||
|
||||
if [ -z "$1" ]
|
||||
then
|
||||
echo "Path not specified to find_maven_basedir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
basedir="$1"
|
||||
wdir="$1"
|
||||
while [ "$wdir" != '/' ] ; do
|
||||
if [ -d "$wdir"/.mvn ] ; then
|
||||
basedir=$wdir
|
||||
break
|
||||
fi
|
||||
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
|
||||
if [ -d "${wdir}" ]; then
|
||||
wdir=`cd "$wdir/.."; pwd`
|
||||
fi
|
||||
# end of workaround
|
||||
done
|
||||
echo "${basedir}"
|
||||
}
|
||||
|
||||
# concatenates all lines of a file
|
||||
concat_lines() {
|
||||
if [ -f "$1" ]; then
|
||||
echo "$(tr -s '\n' ' ' < "$1")"
|
||||
fi
|
||||
}
|
||||
|
||||
BASE_DIR=`find_maven_basedir "$(pwd)"`
|
||||
if [ -z "$BASE_DIR" ]; then
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
##########################################################################################
|
||||
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
# This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
##########################################################################################
|
||||
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found .mvn/wrapper/maven-wrapper.jar"
|
||||
fi
|
||||
else
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
|
||||
fi
|
||||
if [ -n "$MVNW_REPOURL" ]; then
|
||||
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||
else
|
||||
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||
fi
|
||||
while IFS="=" read key value; do
|
||||
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
|
||||
esac
|
||||
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Downloading from: $jarUrl"
|
||||
fi
|
||||
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
|
||||
if $cygwin; then
|
||||
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
|
||||
fi
|
||||
|
||||
if command -v wget > /dev/null; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found wget ... using wget"
|
||||
fi
|
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||
wget "$jarUrl" -O "$wrapperJarPath"
|
||||
else
|
||||
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
|
||||
fi
|
||||
elif command -v curl > /dev/null; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found curl ... using curl"
|
||||
fi
|
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||
curl -o "$wrapperJarPath" "$jarUrl" -f
|
||||
else
|
||||
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
|
||||
fi
|
||||
|
||||
else
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Falling back to using Java to download"
|
||||
fi
|
||||
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
|
||||
# For Cygwin, switch paths to Windows format before running javac
|
||||
if $cygwin; then
|
||||
javaClass=`cygpath --path --windows "$javaClass"`
|
||||
fi
|
||||
if [ -e "$javaClass" ]; then
|
||||
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo " - Compiling MavenWrapperDownloader.java ..."
|
||||
fi
|
||||
# Compiling the Java class
|
||||
("$JAVA_HOME/bin/javac" "$javaClass")
|
||||
fi
|
||||
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
||||
# Running the downloader
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo " - Running MavenWrapperDownloader.java ..."
|
||||
fi
|
||||
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
##########################################################################################
|
||||
# End of extension
|
||||
##########################################################################################
|
||||
|
||||
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo $MAVEN_PROJECTBASEDIR
|
||||
fi
|
||||
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME=`cygpath --path --windows "$M2_HOME"`
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
|
||||
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
|
||||
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
|
||||
fi
|
||||
|
||||
# Provide a "standardized" way to retrieve the CLI args that will
|
||||
# work with both Windows and non-Windows executions.
|
||||
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
|
||||
export MAVEN_CMD_LINE_ARGS
|
||||
|
||||
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
exec "$JAVACMD" \
|
||||
$MAVEN_OPTS \
|
||||
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
|
||||
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
|
||||
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
|
182
shipping/mvnw.cmd
vendored
Normal file
182
shipping/mvnw.cmd
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||
@REM or more contributor license agreements. See the NOTICE file
|
||||
@REM distributed with this work for additional information
|
||||
@REM regarding copyright ownership. The ASF licenses this file
|
||||
@REM to you under the Apache License, Version 2.0 (the
|
||||
@REM "License"); you may not use this file except in compliance
|
||||
@REM with the License. You may obtain a copy of the License at
|
||||
@REM
|
||||
@REM http://www.apache.org/licenses/LICENSE-2.0
|
||||
@REM
|
||||
@REM Unless required by applicable law or agreed to in writing,
|
||||
@REM software distributed under the License is distributed on an
|
||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
@REM KIND, either express or implied. See the License for the
|
||||
@REM specific language governing permissions and limitations
|
||||
@REM under the License.
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Maven Start Up Batch script
|
||||
@REM
|
||||
@REM Required ENV vars:
|
||||
@REM JAVA_HOME - location of a JDK home dir
|
||||
@REM
|
||||
@REM Optional ENV vars
|
||||
@REM M2_HOME - location of maven2's installed home dir
|
||||
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
|
||||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
|
||||
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
@REM e.g. to debug Maven itself, use
|
||||
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
|
||||
@echo off
|
||||
@REM set title of command window
|
||||
title %0
|
||||
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
|
||||
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
|
||||
|
||||
@REM set %HOME% to equivalent of $HOME
|
||||
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
|
||||
|
||||
@REM Execute a user defined script before this one
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
||||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
|
||||
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
|
||||
:skipRcPre
|
||||
|
||||
@setlocal
|
||||
|
||||
set ERROR_CODE=0
|
||||
|
||||
@REM To isolate internal variables from possible post scripts, we use another setlocal
|
||||
@setlocal
|
||||
|
||||
@REM ==== START VALIDATION ====
|
||||
if not "%JAVA_HOME%" == "" goto OkJHome
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME not found in your environment. >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
:OkJHome
|
||||
if exist "%JAVA_HOME%\bin\java.exe" goto init
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME is set to an invalid directory. >&2
|
||||
echo JAVA_HOME = "%JAVA_HOME%" >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
@REM ==== END VALIDATION ====
|
||||
|
||||
:init
|
||||
|
||||
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
|
||||
@REM Fallback to current working directory if not found.
|
||||
|
||||
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
|
||||
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
|
||||
|
||||
set EXEC_DIR=%CD%
|
||||
set WDIR=%EXEC_DIR%
|
||||
:findBaseDir
|
||||
IF EXIST "%WDIR%"\.mvn goto baseDirFound
|
||||
cd ..
|
||||
IF "%WDIR%"=="%CD%" goto baseDirNotFound
|
||||
set WDIR=%CD%
|
||||
goto findBaseDir
|
||||
|
||||
:baseDirFound
|
||||
set MAVEN_PROJECTBASEDIR=%WDIR%
|
||||
cd "%EXEC_DIR%"
|
||||
goto endDetectBaseDir
|
||||
|
||||
:baseDirNotFound
|
||||
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
|
||||
cd "%EXEC_DIR%"
|
||||
|
||||
:endDetectBaseDir
|
||||
|
||||
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
|
||||
|
||||
@setlocal EnableExtensions EnableDelayedExpansion
|
||||
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
|
||||
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
|
||||
|
||||
:endReadAdditionalConfig
|
||||
|
||||
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
||||
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
|
||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||
|
||||
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
|
||||
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
|
||||
)
|
||||
|
||||
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
if exist %WRAPPER_JAR% (
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Found %WRAPPER_JAR%
|
||||
)
|
||||
) else (
|
||||
if not "%MVNW_REPOURL%" == "" (
|
||||
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||
)
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Couldn't find %WRAPPER_JAR%, downloading it ...
|
||||
echo Downloading from: %DOWNLOAD_URL%
|
||||
)
|
||||
|
||||
powershell -Command "&{"^
|
||||
"$webclient = new-object System.Net.WebClient;"^
|
||||
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
|
||||
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
|
||||
"}"^
|
||||
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
|
||||
"}"
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Finished downloading %WRAPPER_JAR%
|
||||
)
|
||||
)
|
||||
@REM End of extension
|
||||
|
||||
@REM Provide a "standardized" way to retrieve the CLI args that will
|
||||
@REM work with both Windows and non-Windows executions.
|
||||
set MAVEN_CMD_LINE_ARGS=%*
|
||||
|
||||
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
|
||||
if ERRORLEVEL 1 goto error
|
||||
goto end
|
||||
|
||||
:error
|
||||
set ERROR_CODE=1
|
||||
|
||||
:end
|
||||
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
||||
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
|
||||
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
|
||||
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
|
||||
:skipRcPost
|
||||
|
||||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
||||
if "%MAVEN_BATCH_PAUSE%" == "on" pause
|
||||
|
||||
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
|
||||
|
||||
exit /B %ERROR_CODE%
|
39
shipping/shipping.iml
Normal file
39
shipping/shipping.iml
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
|
||||
<output url="file://$MODULE_DIR$/target/classes" />
|
||||
<output-test url="file://$MODULE_DIR$/target/test-classes" />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Maven: com.sparkjava:spark-core:2.7.2" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.13" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-server:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: javax.servlet:javax.servlet-api:3.1.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-http:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-util:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-io:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-webapp:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-xml:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-servlet:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-security:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty.websocket:websocket-server:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty.websocket:websocket-common:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty.websocket:websocket-client:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-client:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty.websocket:websocket-servlet:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty.websocket:websocket-api:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.slf4j:slf4j-simple:1.7.25" level="project" />
|
||||
<orderEntry type="library" name="Maven: c3p0:c3p0:0.9.1.2" level="project" />
|
||||
<orderEntry type="library" name="Maven: mysql:mysql-connector-java:5.1.45" level="project" />
|
||||
<orderEntry type="library" name="Maven: commons-dbutils:commons-dbutils:1.7" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.google.code.gson:gson:2.8.2" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpclient:4.5.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpcore:4.4.9" level="project" />
|
||||
<orderEntry type="library" name="Maven: commons-logging:commons-logging:1.2" level="project" />
|
||||
<orderEntry type="library" name="Maven: commons-codec:commons-codec:1.10" level="project" />
|
||||
</component>
|
||||
</module>
|
@@ -1,5 +1,6 @@
|
||||
package org.steveww.spark;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.mchange.v2.c3p0.ComboPooledDataSource;
|
||||
import spark.Spark;
|
||||
|
||||
@@ -16,48 +17,75 @@ import org.apache.http.params.HttpParams;
|
||||
import org.apache.commons.dbutils.QueryRunner;
|
||||
import org.apache.commons.dbutils.handlers.MapListHandler;
|
||||
import org.apache.commons.dbutils.DbUtils;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.Types;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class Main {
|
||||
private static String CART_URL = null;
|
||||
private static String JDBC_URL = null;
|
||||
private static Logger logger = LoggerFactory.getLogger(Main.class);
|
||||
private static ComboPooledDataSource cpds = null;
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Main.class);
|
||||
private static final ComboPooledDataSource CPDS = new ComboPooledDataSource();
|
||||
|
||||
public static void main(String[] args) {
|
||||
// Get ENV configuration values
|
||||
CART_URL = String.format("http://%s/shipping/", System.getenv("CART_ENDPOINT") != null ? System.getenv("CART_ENDPOINT") : "cart");
|
||||
JDBC_URL = String.format("jdbc:mysql://%s/cities?useSSL=false&autoReconnect=true", System.getenv("DB_HOST") != null ? System.getenv("DB_HOST") : "mysql");
|
||||
final String cartUrl;
|
||||
final String jdbcUrl;
|
||||
|
||||
final boolean isCloudFoundry = System.getenv("VCAP_APPLICATION") != null;
|
||||
|
||||
if (isCloudFoundry) {
|
||||
// Cloud Foundry
|
||||
final JsonParser jsonParser = new JsonParser();
|
||||
|
||||
jdbcUrl = getJdbcURLFromVCAPServices(jsonParser);
|
||||
|
||||
if (System.getenv("CART_ENDPOINT") != null) {
|
||||
cartUrl = System.getenv("CART_ENDPOINT");
|
||||
} else {
|
||||
final JsonObject vcapApplication = jsonParser.parse(System.getenv("VCAP_APPLICATION")).getAsJsonObject();
|
||||
|
||||
final JsonArray appUris = vcapApplication.get("application_uris").getAsJsonArray();
|
||||
if (appUris == null || appUris.size() == 0) {
|
||||
throw new IllegalStateException("Cannot retrieve application URIs from 'VCAP_APPLICATION'");
|
||||
}
|
||||
|
||||
final String applicationName = vcapApplication.get("application_name").getAsString();
|
||||
final String applicationURI = appUris.get(0).getAsString();
|
||||
|
||||
cartUrl = "https://" + applicationURI.replace(applicationName, "cart") + "/shipping/";
|
||||
}
|
||||
} else {
|
||||
cartUrl = String.format("http://%s/shipping/", System.getenv("CART_ENDPOINT") != null ? System.getenv("CART_ENDPOINT") : "cart");
|
||||
jdbcUrl = String.format("jdbc:mysql://%s/cities?useSSL=false&autoReconnect=true", System.getenv("DB_HOST") != null ? System.getenv("DB_HOST") : "mysql");
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Create database connector
|
||||
// TODO - might need a retry loop here
|
||||
//
|
||||
try {
|
||||
cpds = new ComboPooledDataSource();
|
||||
cpds.setDriverClass( "com.mysql.jdbc.Driver" ); //loads the jdbc driver
|
||||
cpds.setJdbcUrl( JDBC_URL );
|
||||
cpds.setUser("shipping");
|
||||
cpds.setPassword("secret");
|
||||
CPDS.setDriverClass( "com.mysql.jdbc.Driver" ); //loads the jdbc driver
|
||||
CPDS.setJdbcUrl( jdbcUrl );
|
||||
|
||||
if (!isCloudFoundry) {
|
||||
CPDS.setUser("shipping");
|
||||
CPDS.setPassword("secret");
|
||||
}
|
||||
|
||||
// some config
|
||||
cpds.setMinPoolSize(5);
|
||||
cpds.setAcquireIncrement(5);
|
||||
cpds.setMaxPoolSize(20);
|
||||
cpds.setMaxStatements(180);
|
||||
CPDS.setMinPoolSize(5);
|
||||
CPDS.setAcquireIncrement(5);
|
||||
CPDS.setMaxPoolSize(20);
|
||||
CPDS.setMaxStatements(180);
|
||||
}
|
||||
catch(Exception e) {
|
||||
logger.error("Database Exception", e);
|
||||
LOGGER.error("Database Exception", e);
|
||||
}
|
||||
|
||||
// Spark
|
||||
@@ -71,7 +99,7 @@ public class Main {
|
||||
data = queryToJson("select count(*) as count from cities");
|
||||
res.header("Content-Type", "application/json");
|
||||
} catch(Exception e) {
|
||||
logger.error("count", e);
|
||||
LOGGER.error("count", e);
|
||||
res.status(500);
|
||||
data = "ERROR";
|
||||
}
|
||||
@@ -86,7 +114,7 @@ public class Main {
|
||||
data = queryToJson(query);
|
||||
res.header("Content-Type", "application/json");
|
||||
} catch(Exception e) {
|
||||
logger.error("codes", e);
|
||||
LOGGER.error("codes", e);
|
||||
res.status(500);
|
||||
data = "ERROR";
|
||||
}
|
||||
@@ -99,11 +127,11 @@ public class Main {
|
||||
String data;
|
||||
try {
|
||||
String query = "select uuid, name from cities where country_code = ?";
|
||||
logger.info("Query " + query);
|
||||
LOGGER.info("Query " + query);
|
||||
data = queryToJson(query, req.params(":code"));
|
||||
res.header("Content-Type", "application/json");
|
||||
} catch(Exception e) {
|
||||
logger.error("cities", e);
|
||||
LOGGER.error("cities", e);
|
||||
res.status(500);
|
||||
data = "ERROR";
|
||||
}
|
||||
@@ -115,11 +143,11 @@ public class Main {
|
||||
String data;
|
||||
try {
|
||||
String query = "select uuid, name from cities where country_code = ? and city like ? order by name asc limit 10";
|
||||
logger.info("Query " + query);
|
||||
LOGGER.info("Query " + query);
|
||||
data = queryToJson(query, req.params(":code"), req.params(":text") + "%");
|
||||
res.header("Content-Type", "application/json");
|
||||
} catch(Exception e) {
|
||||
logger.error("match", e);
|
||||
LOGGER.error("match", e);
|
||||
res.status(500);
|
||||
data = "ERROR";
|
||||
}
|
||||
@@ -145,7 +173,7 @@ public class Main {
|
||||
data = new Gson().toJson(ship);
|
||||
} else {
|
||||
data = "no location";
|
||||
logger.warn(data);
|
||||
LOGGER.warn(data);
|
||||
res.status(400);
|
||||
}
|
||||
|
||||
@@ -153,9 +181,9 @@ public class Main {
|
||||
});
|
||||
|
||||
Spark.post("/confirm/:id", (req, res) -> {
|
||||
logger.info("confirm " + req.params(":id") + " - " + req.body());
|
||||
String cart = addToCart(req.params(":id"), req.body());
|
||||
logger.info("new cart " + cart);
|
||||
LOGGER.info("confirm " + req.params(":id") + " - " + req.body());
|
||||
String cart = addToCart(cartUrl, req.params(":id"), req.body());
|
||||
LOGGER.info("new cart " + cart);
|
||||
|
||||
if(cart.equals("")) {
|
||||
res.status(404);
|
||||
@@ -166,18 +194,35 @@ public class Main {
|
||||
return cart;
|
||||
});
|
||||
|
||||
logger.info("Ready");
|
||||
LOGGER.info("Ready");
|
||||
}
|
||||
|
||||
private static String getJdbcURLFromVCAPServices(final JsonParser jsonParser) {
|
||||
final JsonObject vcapServices = jsonParser.parse(System.getenv("VCAP_SERVICES")).getAsJsonObject();
|
||||
|
||||
for (Map.Entry<String, JsonElement> boundServices : vcapServices.entrySet()) {
|
||||
final JsonArray bindings = boundServices.getValue().getAsJsonArray();
|
||||
|
||||
for (JsonElement boundService : bindings) {
|
||||
final JsonObject bindingDetails = boundService.getAsJsonObject();
|
||||
if (bindingDetails.has("binding_name") && "shipping_database".equals(bindingDetails.get("binding_name").getAsString())) {
|
||||
final JsonObject credentials = bindingDetails.get("credentials").getAsJsonObject();
|
||||
return credentials.get("jdbcUrl").getAsString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Cannot retrieve the 'shipping_database' service binding.");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Query to Json - QED
|
||||
**/
|
||||
private static String queryToJson(String query, Object ... args) {
|
||||
List<Map<String, Object>> listOfMaps = null;
|
||||
List<Map<String, Object>> listOfMaps;
|
||||
try {
|
||||
QueryRunner queryRunner = new QueryRunner(cpds);
|
||||
QueryRunner queryRunner = new QueryRunner(CPDS);
|
||||
listOfMaps = queryRunner.query(query, new MapListHandler(), args);
|
||||
} catch (SQLException se) {
|
||||
throw new RuntimeException("Couldn't query the database.", se);
|
||||
@@ -197,7 +242,7 @@ public class Main {
|
||||
String query = "select latitude, longitude from cities where uuid = ?";
|
||||
|
||||
try {
|
||||
conn = cpds.getConnection();
|
||||
conn = CPDS.getConnection();
|
||||
stmt = conn.prepareStatement(query);
|
||||
stmt.setInt(1, Integer.parseInt(uuid));
|
||||
rs = stmt.executeQuery();
|
||||
@@ -206,7 +251,7 @@ public class Main {
|
||||
break;
|
||||
}
|
||||
} catch(Exception e) {
|
||||
logger.error("Location exception", e);
|
||||
LOGGER.error("Location exception", e);
|
||||
} finally {
|
||||
DbUtils.closeQuietly(conn, stmt, rs);
|
||||
}
|
||||
@@ -214,7 +259,7 @@ public class Main {
|
||||
return location;
|
||||
}
|
||||
|
||||
private static String addToCart(String id, String data) {
|
||||
private static String addToCart(String cartUrl, String id, String data) {
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
DefaultHttpClient httpClient = null;
|
||||
@@ -224,7 +269,7 @@ public class Main {
|
||||
HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
|
||||
|
||||
httpClient = new DefaultHttpClient(httpParams);
|
||||
HttpPost postRequest = new HttpPost(CART_URL + id);
|
||||
HttpPost postRequest = new HttpPost(cartUrl + id);
|
||||
StringEntity payload = new StringEntity(data);
|
||||
payload.setContentType("application/json");
|
||||
postRequest.setEntity(payload);
|
||||
@@ -237,10 +282,10 @@ public class Main {
|
||||
buffer.append(line);
|
||||
}
|
||||
} else {
|
||||
logger.warn("Failed with code: " + res.getStatusLine().getStatusCode());
|
||||
LOGGER.warn("Failed with code: " + res.getStatusLine().getStatusCode());
|
||||
}
|
||||
} catch(Exception e) {
|
||||
logger.error("http client exception", e);
|
||||
LOGGER.error("http client exception", e);
|
||||
} finally {
|
||||
if(httpClient != null) {
|
||||
httpClient.getConnectionManager().shutdown();
|
||||
|
BIN
shipping/target/classes/org/steveww/spark/Location.class
Normal file
BIN
shipping/target/classes/org/steveww/spark/Location.class
Normal file
Binary file not shown.
BIN
shipping/target/classes/org/steveww/spark/Main.class
Normal file
BIN
shipping/target/classes/org/steveww/spark/Main.class
Normal file
Binary file not shown.
BIN
shipping/target/classes/org/steveww/spark/Ship.class
Normal file
BIN
shipping/target/classes/org/steveww/spark/Ship.class
Normal file
Binary file not shown.
5
shipping/target/maven-archiver/pom.properties
Normal file
5
shipping/target/maven-archiver/pom.properties
Normal file
@@ -0,0 +1,5 @@
|
||||
#Generated by Maven
|
||||
#Mon Aug 12 12:33:39 CEST 2019
|
||||
groupId=steveww
|
||||
artifactId=shipping
|
||||
version=1.0
|
@@ -0,0 +1,3 @@
|
||||
org/steveww/spark/Ship.class
|
||||
org/steveww/spark/Main.class
|
||||
org/steveww/spark/Location.class
|
@@ -0,0 +1,3 @@
|
||||
/Users/michele/git/instana/robot-shop/shipping/src/main/java/org/steveww/spark/Ship.java
|
||||
/Users/michele/git/instana/robot-shop/shipping/src/main/java/org/steveww/spark/Main.java
|
||||
/Users/michele/git/instana/robot-shop/shipping/src/main/java/org/steveww/spark/Location.java
|
BIN
shipping/target/shipping-1.0-jar-with-dependencies.jar
Normal file
BIN
shipping/target/shipping-1.0-jar-with-dependencies.jar
Normal file
Binary file not shown.
BIN
shipping/target/shipping-1.0.jar
Normal file
BIN
shipping/target/shipping-1.0.jar
Normal file
Binary file not shown.
63
user/package-lock.json
generated
Normal file
63
user/package-lock.json
generated
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"name": "user",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"complex.js": {
|
||||
"version": "2.0.11",
|
||||
"resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.0.11.tgz",
|
||||
"integrity": "sha512-6IArJLApNtdg1P1dFtn3dnyzoZBEF0MwMnrfF1exSBRpZYoy4yieMkpZhQDC0uwctw48vii0CFVyHfpgZ/DfGw=="
|
||||
},
|
||||
"decimal.js": {
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.0.tgz",
|
||||
"integrity": "sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw=="
|
||||
},
|
||||
"escape-latex": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz",
|
||||
"integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw=="
|
||||
},
|
||||
"fraction.js": {
|
||||
"version": "4.0.12",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.0.12.tgz",
|
||||
"integrity": "sha512-8Z1K0VTG4hzYY7kA/1sj4/r1/RWLBD3xwReT/RCrUCbzPszjNQCCsy3ktkU/eaEqX3MYa4pY37a52eiBlPMlhA=="
|
||||
},
|
||||
"javascript-natural-sort": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
|
||||
"integrity": "sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k="
|
||||
},
|
||||
"mathjs": {
|
||||
"version": "6.6.4",
|
||||
"resolved": "https://registry.npmjs.org/mathjs/-/mathjs-6.6.4.tgz",
|
||||
"integrity": "sha512-fvmP89ujJbDAC8ths7FZh7PWdA71dfA5WJVAzJbQhSDCHK1aBk8WRf1XcTw51ERs+sKx9nYBGsRshqmb/oe8Ag==",
|
||||
"requires": {
|
||||
"complex.js": "^2.0.11",
|
||||
"decimal.js": "^10.2.0",
|
||||
"escape-latex": "^1.2.0",
|
||||
"fraction.js": "^4.0.12",
|
||||
"javascript-natural-sort": "^0.7.1",
|
||||
"seed-random": "^2.2.0",
|
||||
"tiny-emitter": "^2.1.0",
|
||||
"typed-function": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"seed-random": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz",
|
||||
"integrity": "sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ="
|
||||
},
|
||||
"tiny-emitter": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
|
||||
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
|
||||
},
|
||||
"typed-function": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/typed-function/-/typed-function-1.1.1.tgz",
|
||||
"integrity": "sha512-RbN7MaTQBZLJYzDENHPA0nUmWT0Ex80KHItprrgbTPufYhIlTePvCXZxyQK7wgn19FW5bnuaBIKcBb5mRWjB1Q=="
|
||||
}
|
||||
}
|
||||
}
|
@@ -9,13 +9,14 @@
|
||||
"author": "SteveW",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"body-parser": "^1.18.1",
|
||||
"express": "^4.15.4",
|
||||
"mongodb": "^2.2.33",
|
||||
"redis": "^2.8.0",
|
||||
"pino": "^5.10.8",
|
||||
"express-pino-logger": "^4.0.0",
|
||||
"pino-pretty": "^2.5.0",
|
||||
"instana-nodejs-sensor": "^1.28.0"
|
||||
"body-parser": "^1.18.1",
|
||||
"express": "^4.15.4",
|
||||
"express-pino-logger": "^4.0.0",
|
||||
"instana-nodejs-sensor": "^1.28.0",
|
||||
"mathjs": "^6.6.4",
|
||||
"mongodb": "^2.2.33",
|
||||
"pino": "^5.10.8",
|
||||
"pino-pretty": "^2.5.0",
|
||||
"redis": "^2.8.0"
|
||||
}
|
||||
}
|
||||
|
157
user/server.js
157
user/server.js
@@ -1,4 +1,6 @@
|
||||
const instana = require('instana-nodejs-sensor');
|
||||
/*jshint esversion: 6 */
|
||||
|
||||
const instana = require('@instana/collector');
|
||||
// init tracing
|
||||
// MUST be done before loading anything else!
|
||||
instana({
|
||||
@@ -8,12 +10,15 @@ instana({
|
||||
});
|
||||
|
||||
const mongoClient = require('mongodb').MongoClient;
|
||||
const mongoObjectID = require('mongodb').ObjectID;
|
||||
const redis = require('redis');
|
||||
const bodyParser = require('body-parser');
|
||||
const express = require('express');
|
||||
const pino = require('pino');
|
||||
const expPino = require('express-pino-logger');
|
||||
const { randomInt } = require('mathjs');
|
||||
|
||||
const errorRate = parseInt(process.env.ERROR_RATE) || 0
|
||||
|
||||
|
||||
// MongoDB
|
||||
var db;
|
||||
@@ -54,6 +59,11 @@ app.get('/health', (req, res) => {
|
||||
|
||||
// use REDIS INCR to track anonymous users
|
||||
app.get('/uniqueid', (req, res) => {
|
||||
if (errorRate > randomInt(1, 100)) {
|
||||
res.status(500).send('Cannot process the uniqueid request');
|
||||
return;
|
||||
}
|
||||
|
||||
req.log.error('Unique ID test');
|
||||
// get number from Redis
|
||||
redisClient.incr('anonymous-counter', (err, r) => {
|
||||
@@ -70,6 +80,11 @@ app.get('/uniqueid', (req, res) => {
|
||||
|
||||
// check user exists
|
||||
app.get('/check/:id', (req, res) => {
|
||||
if (errorRate > randomInt(1, 100)) {
|
||||
res.status(500).send('Cannot process the check user request');
|
||||
return;
|
||||
}
|
||||
|
||||
if(mongoConnected) {
|
||||
usersCollection.findOne({name: req.params.id}).then((user) => {
|
||||
if(user) {
|
||||
@@ -103,6 +118,11 @@ app.get('/users', (req, res) => {
|
||||
});
|
||||
|
||||
app.post('/login', (req, res) => {
|
||||
if (errorRate > randomInt(1, 100)) {
|
||||
res.status(500).send('Cannot process the login request');
|
||||
return;
|
||||
}
|
||||
|
||||
req.log.info('login', req.body);
|
||||
if(req.body.name === undefined || req.body.password === undefined) {
|
||||
req.log.warn('credentails not complete');
|
||||
@@ -133,6 +153,11 @@ app.post('/login', (req, res) => {
|
||||
|
||||
// TODO - validate email address format
|
||||
app.post('/register', (req, res) => {
|
||||
if (errorRate > randomInt(1, 100)) {
|
||||
res.status(500).send('Cannot process the registration request');
|
||||
return;
|
||||
}
|
||||
|
||||
req.log.info('register', req.body);
|
||||
if(req.body.name === undefined || req.body.password === undefined || req.body.email === undefined) {
|
||||
req.log.warn('insufficient data');
|
||||
@@ -168,6 +193,11 @@ app.post('/register', (req, res) => {
|
||||
});
|
||||
|
||||
app.post('/order/:id', (req, res) => {
|
||||
if (errorRate > randomInt(1, 100)) {
|
||||
res.status(500).send('Cannot process the order request');
|
||||
return;
|
||||
}
|
||||
|
||||
req.log.info('order', req.body);
|
||||
// only for registered users
|
||||
if(mongoConnected) {
|
||||
@@ -222,6 +252,11 @@ app.post('/order/:id', (req, res) => {
|
||||
});
|
||||
|
||||
app.get('/history/:id', (req, res) => {
|
||||
if (errorRate > randomInt(1, 100)) {
|
||||
res.status(500).send('Cannot process the history request');
|
||||
return;
|
||||
}
|
||||
|
||||
if(mongoConnected) {
|
||||
ordersCollection.findOne({
|
||||
name: req.params.id
|
||||
@@ -241,9 +276,62 @@ app.get('/history/:id', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
var redisHost;
|
||||
var redisPassword;
|
||||
var redisPort;
|
||||
|
||||
if (process.env.VCAP_SERVICES) {
|
||||
connectionDetails = null;
|
||||
|
||||
console.log('Env var \'VCAP_SERVICES\' found, scanning for \'users_cache\' service binding');
|
||||
|
||||
for (let [key, value] of Object.entries(JSON.parse(process.env.VCAP_SERVICES))) {
|
||||
try {
|
||||
binding = value.find(function(binding) {
|
||||
return 'users_cache' == binding.binding_name && binding.credentials;
|
||||
});
|
||||
|
||||
if (!binding) {
|
||||
continue;
|
||||
}
|
||||
|
||||
connectionDetails = binding.credentials;
|
||||
|
||||
if (connectionDetails) {
|
||||
redisHost = connectionDetails.host;
|
||||
redisPassword = connectionDetails.password;
|
||||
redisPort = connectionDetails.port;
|
||||
|
||||
console.log('Redis URI for \'users_cache\' service binding found in \'VCAP_SERVICES\'');
|
||||
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Cannot process key \'' + key + '\' of \'VCAP_SERVICES\'', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
} else if (process.env.REDIS_HOST) {
|
||||
redisHost = process.env.REDIS_HOST;
|
||||
redisPort = 6379;
|
||||
|
||||
console.log('Redis host found in \'REDIS_HOST\': ' + redisHost);
|
||||
} else {
|
||||
redisHost = 'redis';
|
||||
redisPort = 6379;
|
||||
|
||||
console.log('Using default Redis host and port');
|
||||
}
|
||||
|
||||
if (!redisHost) {
|
||||
throw new Error('Redis connection data missing');
|
||||
}
|
||||
|
||||
// connect to Redis
|
||||
var redisClient = redis.createClient({
|
||||
host: process.env.REDIS_HOST || 'redis'
|
||||
host: redisHost,
|
||||
password: redisPassword,
|
||||
port: redisPort
|
||||
});
|
||||
|
||||
redisClient.on('error', (e) => {
|
||||
@@ -256,15 +344,66 @@ redisClient.on('ready', (r) => {
|
||||
// set up Mongo
|
||||
function mongoConnect() {
|
||||
return new Promise((resolve, reject) => {
|
||||
var mongoURL = process.env.MONGO_URL || 'mongodb://mongodb:27017/users';
|
||||
mongoClient.connect(mongoURL, (error, _db) => {
|
||||
if(error) {
|
||||
reject(error);
|
||||
} else {
|
||||
db = _db;
|
||||
var mongoURL;
|
||||
|
||||
if (process.env.VCAP_SERVICES) {
|
||||
connectionDetails = null;
|
||||
|
||||
console.log('Env var \'VCAP_SERVICES\' found, scanning for \'users_database\' service binding');
|
||||
|
||||
for (let [key, value] of Object.entries(JSON.parse(process.env.VCAP_SERVICES))) {
|
||||
try {
|
||||
binding = value.find(function(binding) {
|
||||
return 'users_database' == binding.binding_name && binding.credentials;
|
||||
});
|
||||
|
||||
if (!binding) {
|
||||
continue;
|
||||
}
|
||||
|
||||
connectionDetails = binding.credentials;
|
||||
|
||||
if (connectionDetails && connectionDetails.uri) {
|
||||
mongoURL = connectionDetails.uri;
|
||||
|
||||
console.log('MongoDB URI for \'users_database\' service binding found in \'VCAP_SERVICES\'');
|
||||
|
||||
break;
|
||||
} else {
|
||||
throw new Error('Service binding \'users_database\' found, but cannot retrieve the URI from the credentials');
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Cannot process key \'' + key + '\' of \'VCAP_SERVICES\'', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
} else if (process.env.MONGO_URL) {
|
||||
mongoURL = process.env.MONGO_URL;
|
||||
|
||||
console.log('MongoDB URI found in \'MONGO_URL\': ' + mongoURL);
|
||||
} else {
|
||||
mongoURL = 'mongodb://mongodb:27017/catalogue';
|
||||
|
||||
console.log('Using default MongoDB URI');
|
||||
}
|
||||
|
||||
if (!mongoURL) {
|
||||
throw new Error('MongoDB connection data missing');
|
||||
}
|
||||
|
||||
mongoClient.connect(mongoURL, (error, db) => {
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
try {
|
||||
usersCollection = db.collection('users');
|
||||
ordersCollection = db.collection('orders');
|
||||
|
||||
resolve('connected');
|
||||
} catch (err) {
|
||||
console.log('Cannot connecto to MongoDB databases', err);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
3
web/buildpack.yml
Normal file
3
web/buildpack.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
nginx:
|
||||
version: 1.17.2
|
5
web/instana/instana-config.json
Normal file
5
web/instana/instana-config.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"service": "web",
|
||||
"agent_host": "instana-agent",
|
||||
"agent_port": 42699
|
||||
}
|
BIN
web/instana/libinstana_sensor.so
Normal file
BIN
web/instana/libinstana_sensor.so
Normal file
Binary file not shown.
78
web/mime.types
Normal file
78
web/mime.types
Normal file
@@ -0,0 +1,78 @@
|
||||
types {
|
||||
text/html html htm shtml;
|
||||
text/css css;
|
||||
text/xml xml;
|
||||
image/gif gif;
|
||||
image/jpeg jpeg jpg;
|
||||
application/x-javascript js;
|
||||
application/atom+xml atom;
|
||||
application/rss+xml rss;
|
||||
font/ttf ttf;
|
||||
font/woff woff;
|
||||
font/woff2 woff2;
|
||||
text/mathml mml;
|
||||
text/plain txt;
|
||||
text/vnd.sun.j2me.app-descriptor jad;
|
||||
text/vnd.wap.wml wml;
|
||||
text/x-component htc;
|
||||
text/cache-manifest manifest;
|
||||
image/png png;
|
||||
image/tiff tif tiff;
|
||||
image/vnd.wap.wbmp wbmp;
|
||||
image/x-icon ico;
|
||||
image/x-jng jng;
|
||||
image/x-ms-bmp bmp;
|
||||
image/svg+xml svg svgz;
|
||||
image/webp webp;
|
||||
application/java-archive jar war ear;
|
||||
application/mac-binhex40 hqx;
|
||||
application/msword doc;
|
||||
application/pdf pdf;
|
||||
application/postscript ps eps ai;
|
||||
application/rtf rtf;
|
||||
application/vnd.ms-excel xls;
|
||||
application/vnd.ms-powerpoint ppt;
|
||||
application/vnd.wap.wmlc wmlc;
|
||||
application/vnd.google-earth.kml+xml kml;
|
||||
application/vnd.google-earth.kmz kmz;
|
||||
application/x-7z-compressed 7z;
|
||||
application/x-cocoa cco;
|
||||
application/x-java-archive-diff jardiff;
|
||||
application/x-java-jnlp-file jnlp;
|
||||
application/x-makeself run;
|
||||
application/x-perl pl pm;
|
||||
application/x-pilot prc pdb;
|
||||
application/x-rar-compressed rar;
|
||||
application/x-redhat-package-manager rpm;
|
||||
application/x-sea sea;
|
||||
application/x-shockwave-flash swf;
|
||||
application/x-stuffit sit;
|
||||
application/x-tcl tcl tk;
|
||||
application/x-x509-ca-cert der pem crt;
|
||||
application/x-xpinstall xpi;
|
||||
application/xhtml+xml xhtml;
|
||||
application/zip zip;
|
||||
application/octet-stream bin exe dll;
|
||||
application/octet-stream deb;
|
||||
application/octet-stream dmg;
|
||||
application/octet-stream eot;
|
||||
application/octet-stream iso img;
|
||||
application/octet-stream msi msp msm;
|
||||
application/json json;
|
||||
audio/midi mid midi kar;
|
||||
audio/mpeg mp3;
|
||||
audio/ogg ogg;
|
||||
audio/x-m4a m4a;
|
||||
audio/x-realaudio ra;
|
||||
video/3gpp 3gpp 3gp;
|
||||
video/mp4 mp4;
|
||||
video/mpeg mpeg mpg;
|
||||
video/quicktime mov;
|
||||
video/webm webm;
|
||||
video/x-flv flv;
|
||||
video/x-m4v m4v;
|
||||
video/x-mng mng;
|
||||
video/x-ms-asf asx asf;
|
||||
video/x-ms-wmv wmv;
|
||||
video/x-msvideo avi;
|
||||
}
|
BIN
web/modules/ngx_http_module.so
Normal file
BIN
web/modules/ngx_http_module.so
Normal file
Binary file not shown.
76
web/nginx.conf
Normal file
76
web/nginx.conf
Normal file
@@ -0,0 +1,76 @@
|
||||
load_module modules/ngx_http_module.so;
|
||||
|
||||
worker_processes 1;
|
||||
daemon off;
|
||||
|
||||
error_log stderr;
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
pid /tmp/nginx.pid;
|
||||
|
||||
http {
|
||||
opentracing on;
|
||||
|
||||
opentracing_load_tracer instana/libinstana_sensor.so instana/instana-config.json;
|
||||
|
||||
charset utf-8;
|
||||
log_format cloudfoundry 'NginxLog "$request" $status $body_bytes_sent';
|
||||
access_log /dev/stdout cloudfoundry;
|
||||
default_type application/octet-stream;
|
||||
sendfile on;
|
||||
|
||||
tcp_nopush on;
|
||||
keepalive_timeout 30;
|
||||
port_in_redirect off;
|
||||
|
||||
# Calculate CF app names reusing the CF apps domain as seen by the client
|
||||
map $server_name $domain {
|
||||
~^[^\.]*(.*)$ $1;
|
||||
}
|
||||
|
||||
server {
|
||||
listen {{port}};
|
||||
server_name ~^(?<name>\w+)\.(?<domain>.*)$;
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
# location = /50x.html {
|
||||
# root /usr/share/nginx/html;
|
||||
# }
|
||||
|
||||
location ~ ^/api/([a-zA-Z]+)/(.+)$ {
|
||||
# We need a resolver, since we need to use variables, so we need to specify a DNS server.
|
||||
# Google, HALP!
|
||||
resolver 8.8.8.8;
|
||||
|
||||
proxy_pass https://$1.$domain/$2;
|
||||
|
||||
opentracing_trace_locations off;
|
||||
opentracing_propagate_context;
|
||||
opentracing_tag "resource.name" $1/$2;
|
||||
opentracing_tag "endpoint" $1;
|
||||
}
|
||||
|
||||
location /nginx_status {
|
||||
stub_status on;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location / {
|
||||
root static;
|
||||
index index.html;
|
||||
ssi on;
|
||||
|
||||
include mime.types;
|
||||
|
||||
opentracing_trace_locations off;
|
||||
opentracing_propagate_context;
|
||||
opentracing_tag "resource.name" "static_resources";
|
||||
opentracing_tag "endpoint" "static_resources";
|
||||
|
||||
add_header "server-timing" "intid;desc=${opentracing_context_x_instana_t}";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
<!-- EUM include -->
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['InstanaEumObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//eum.instana.io/eum.min.js','ineum');
|
||||
ineum('apiKey', 'INSTANA_EUM_KEY');
|
||||
ineum('page', 'Shop');
|
||||
</script>
|
||||
<!-- EUM include end -->
|
||||
|
10
web/static/eum.html
Normal file
10
web/static/eum.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<script>
|
||||
(function(c,e,f,k,g,h,b,a,d){c[g]||(c[g]=h,b=c[h]=function(){
|
||||
b.q.push(arguments)},b.q=[],b.l=1*new Date,a=e.createElement(f),a.async=1,
|
||||
a.src=k,a.setAttribute("crossorigin", "anonymous"),d=e.getElementsByTagName(f)[0],
|
||||
d.parentNode.insertBefore(a,d))})(window,document,"script",
|
||||
"//eum.instana.io/eum.min.js","InstanaEumObject","ineum");
|
||||
ineum('reportingUrl', 'TODO EDIT ME');
|
||||
ineum('key', 'TODO EDIT ME');
|
||||
ineum('page', 'RobotShop');
|
||||
</script>
|
@@ -1,7 +1,9 @@
|
||||
/*jshint esversion: 6 */
|
||||
|
||||
(function(angular) {
|
||||
'use strict';
|
||||
|
||||
var robotshop = angular.module('robotshop', ['ngRoute'])
|
||||
var robotshop = angular.module('robotshop', ['ngRoute']);
|
||||
|
||||
// Share user between controllers
|
||||
robotshop.factory('currentUser', function() {
|
||||
@@ -211,10 +213,10 @@
|
||||
|
||||
$scope.rateProduct = function(score) {
|
||||
console.log('rate product', $scope.data.product.sku, score);
|
||||
var url = '/api/ratings/api/rate/' + $scope.data.product.sku + '/' + score;
|
||||
var url = '/api/ratings/rate/' + $scope.data.product.sku + '/' + score;
|
||||
$http({
|
||||
url: url,
|
||||
method: 'PUT'
|
||||
method: 'POST'
|
||||
}).then((res) => {
|
||||
$scope.data.message = 'Thankyou for your feedback';
|
||||
$timeout(clearMessage, 3000);
|
||||
@@ -246,7 +248,7 @@
|
||||
|
||||
function loadRating(sku) {
|
||||
$http({
|
||||
url: '/api/ratings/api/fetch/' + sku,
|
||||
url: '/api/ratings/fetch/' + sku,
|
||||
method: 'GET'
|
||||
}).then((res) => {
|
||||
$scope.data.rating = res.data;
|
||||
|
Reference in New Issue
Block a user