├── .dockerignore
├── .env
├── .gitignore
├── Dockerfile
├── Dockerfile.test
├── Jenkinsfile
├── README.md
├── Vagrantfile
├── ansible
├── dev.yml
└── roles
│ └── docker
│ ├── defaults
│ └── main.yml
│ └── tasks
│ └── main.yml
├── books-ms.iml
├── bootstrap.sh
├── build.sbt
├── client
├── README.md
├── bower.json
├── components
│ └── tc-books
│ │ ├── demo
│ │ └── index.html
│ │ ├── tc-book-form.html
│ │ └── tc-books.html
├── gulpfile.js
├── package.json
├── test.html
├── test
│ ├── index.html
│ ├── tc-book-form.html
│ └── tc-books.html
├── wct-complete.conf.js
└── wct.conf.js
├── consul_check.ctmpl
├── docker-compose-dev-v2.yml
├── docker-compose-dev.yml
├── docker-compose-jenkins-v2.yml
├── docker-compose-jenkins.yml
├── docker-compose-logging-v2.yml
├── docker-compose-logging.yml
├── docker-compose-no-links-v2.yml
├── docker-compose-no-links.yml
├── docker-compose-swarm-v2.yml
├── docker-compose-swarm.yml
├── docker-compose-v2.yml
├── docker-compose.yml
├── haproxy.ctmpl
├── img
├── book-form-demo.png
├── book-form-sc.png
├── book-form-styled.png
├── book-form.ora
├── book-form.png
├── books-sc.png
├── books.ora
├── books.png
├── demo-styled.png
├── fe-monolith.ora
├── fe-monolith.png
├── first-test-failure.png
├── first-test-success.png
├── idea-code-tests.png
├── ms.ora
├── ms.png
├── tests-console.png
└── toast.png
├── mesos.json
├── nginx-includes.conf
├── nginx-upstreams-blue.ctmpl
├── nginx-upstreams-green.ctmpl
├── nginx-upstreams.ctmpl
├── preload.sh
├── project
├── assembly.sbt
├── build.properties
└── plugins.sbt
├── run.sh
├── run_tests.sh
└── src
├── main
└── scala
│ └── com
│ └── technologyconversations
│ └── api
│ ├── Service.scala
│ └── ServiceActor.scala
└── test
├── resources
└── application.conf
└── scala
└── com
└── technologyconversations
└── api
├── ServiceInteg.scala
└── ServiceSpec.scala
/.dockerignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .vagrant
3 | ansible
4 | client/node_modules
5 | client/bower_components
6 | img
7 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | COMPOSE_HTTP_TIMEOUT=300
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | /target
3 | /.vagrant
4 | /*.log
5 | /client/node_modules
6 | /client/bower_components
7 | /client/*.log
8 | /db
9 | /.ivy2
10 | /*.box
11 | /*.iml
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM openjdk
2 | MAINTAINER Viktor Farcic "viktor@farcic.com"
3 |
4 | ENV DB_DBNAME books
5 | ENV DB_COLLECTION books
6 | ENV DB_HOST localhost
7 |
8 | COPY run.sh /run.sh
9 | RUN chmod +x /run.sh
10 |
11 | COPY target/scala-2.10/books-ms-assembly-1.0.jar /bs.jar
12 | COPY client/components /client/components
13 |
14 | CMD ["/run.sh"]
15 |
16 | EXPOSE 8080
17 |
--------------------------------------------------------------------------------
/Dockerfile.test:
--------------------------------------------------------------------------------
1 | FROM debian:jessie
2 | MAINTAINER Viktor Farcic "viktor@farcic.com"
3 |
4 | ENV VERSION 1.0
5 |
6 | RUN apt-get update
7 |
8 | # CUrl
9 | RUN apt-get -y install curl
10 |
11 | # Dependencies
12 | RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - && \
13 | echo "deb http://dl.bintray.com/sbt/debian /" | tee -a /etc/apt/sources.list.d/sbt.list && \
14 | curl -sL https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \
15 | echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list && \
16 | echo "deb http://packages.linuxmint.com debian import" >> /etc/apt/sources.list
17 |
18 | # Mongo, NodeJS, Git, SBT, xvfb, FireFox, Chrome
19 | RUN apt-get update && \
20 | apt-get -y --fix-missing install wget bzip2 make g++ && \
21 | apt-get -y --force-yes --fix-missing install --no-install-recommends mongodb git sbt=0.13.13 xvfb nodejs firefox google-chrome-stable && \
22 | apt-get clean && \
23 | rm -rf /var/lib/apt/lists/*
24 |
25 | # Scala
26 | RUN curl -O -q http://downloads.typesafe.com/scala/2.11.5/scala-2.11.5.deb && \
27 | dpkg -i scala-2.11.5.deb && \
28 | rm scala-2.11.5.deb
29 |
30 | # Gulp, bower
31 | RUN npm install -g gulp bower
32 |
33 | # Dirs
34 | RUN mkdir /source
35 | RUN mkdir -p /data/db
36 |
37 | ADD project /source/project
38 | ADD build.sbt /source/build.sbt
39 | ADD client/bower.json /source/client/bower.json
40 | ADD client/gulpfile.js /source/client/gulpfile.js
41 | ADD client/package.json /source/client/package.json
42 | ADD client/wct.conf.js /source/client/wct.conf.js
43 | ADD client/test.html /source/client/test.html
44 | ADD run_tests.sh /source/run_tests.sh
45 |
46 | # Dependencies
47 | RUN cd /source && sbt update
48 | RUN cd /source/client && npm install && bower install --allow-root --config.interactive=false -s
49 |
50 | # Envs
51 | ENV TEST_TYPE "spec"
52 | ENV DOMAIN "http://172.17.42.1"
53 | ENV DISPLAY ":1.0"
54 | ENV DB_HOST localhost
55 |
56 | WORKDIR /source
57 | VOLUME ["/source", "/source/target/scala-2.10", "/root/.ivy2/cache", "/data/db"]
58 |
59 | CMD ["/source/run_tests.sh"]
60 |
61 | EXPOSE 8080
62 |
--------------------------------------------------------------------------------
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | node("cd") {
2 | def serviceName = "books-ms"
3 | def prodIp = "10.100.192.200"
4 | def proxyIp = "10.100.192.200"
5 | def swarmNode = "swarm-master"
6 | def proxyNode = "swarm-master"
7 | def registryIpPort = "10.100.198.200:5000"
8 | def swarmPlaybook = "swarm-healing.yml"
9 | def proxyPlaybook = "swarm-proxy.yml"
10 | def instances = 1
11 |
12 | def flow = load "/data/scripts/workflow-util.groovy"
13 |
14 | git url: "https://github.com/vfarcic/${serviceName}.git"
15 | flow.provision(swarmPlaybook)
16 | flow.provision(proxyPlaybook)
17 | flow.buildTests(serviceName, registryIpPort)
18 | flow.runTests(serviceName, "tests", "")
19 | flow.buildService(serviceName, registryIpPort)
20 |
21 | def currentColor = flow.getCurrentColor(serviceName, prodIp)
22 | def nextColor = flow.getNextColor(currentColor)
23 |
24 | flow.deploySwarm(serviceName, prodIp, nextColor, instances)
25 | flow.runBGPreIntegrationTests(serviceName, prodIp, nextColor)
26 | flow.updateBGProxy(serviceName, proxyNode, nextColor)
27 | flow.runBGPostIntegrationTests(serviceName, prodIp, proxyIp, proxyNode, currentColor, nextColor)
28 | flow.updateChecks(serviceName, swarmNode)
29 | }
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Docker
2 | ============
3 |
4 | Build Tests
5 | -----------
6 |
7 | ```bash
8 | sudo docker build -t vfarcic/books-ms-tests -f Dockerfile.test .
9 |
10 | sudo docker push vfarcic/books-ms-tests
11 | ```
12 |
13 | Test and Build
14 | --------------
15 |
16 | ```bash
17 | sudo docker-compose -f docker-compose-dev.yml run testsLocal
18 |
19 | sudo docker build -t vfarcic/books-ms .
20 |
21 | sudo docker push vfarcic/books-ms
22 | ```
23 |
24 | Run Front-End Tests Watcher
25 | ---------------------------
26 |
27 | ```bash
28 | sudo docker-compose -f docker-compose-dev.yml up feTests
29 | ```
30 |
31 | Run Integration Tests
32 | ---------------------
33 |
34 | ```bash
35 | sudo docker-compose -f docker-compose-dev.yml up integ
36 | ```
37 |
38 |
--------------------------------------------------------------------------------
/Vagrantfile:
--------------------------------------------------------------------------------
1 | # -*- mode: ruby -*-
2 | # vi: set ft=ruby :
3 |
4 | Vagrant.configure(2) do |config|
5 | config.vm.box = "ubuntu/trusty64"
6 | if (/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil
7 | config.vm.synced_folder ".", "/vagrant", mount_options: ["dmode=700,fmode=600"]
8 | else
9 | config.vm.synced_folder ".", "/vagrant"
10 | end
11 | config.vm.provider "virtualbox" do |v|
12 | v.memory = 2048
13 | end
14 | config.vm.define :dev do |dev|
15 | dev.vm.network "private_network", ip: "10.100.199.200"
16 | dev.vm.provision :shell, path: "bootstrap.sh"
17 | dev.vm.provision :shell,
18 | inline: 'PYTHONUNBUFFERED=1 ansible-playbook \
19 | /vagrant/ansible/dev.yml -c local'
20 | end
21 | if Vagrant.has_plugin?("vagrant-cachier")
22 | config.cache.scope = :box
23 | end
24 | if Vagrant.has_plugin?("vagrant-vbguest")
25 | config.vbguest.auto_update = false
26 | config.vbguest.no_install = true
27 | config.vbguest.no_remote = true
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/ansible/dev.yml:
--------------------------------------------------------------------------------
1 | - hosts: localhost
2 | remote_user: vagrant
3 | become_user: yes
4 | roles:
5 | - docker
6 |
--------------------------------------------------------------------------------
/ansible/roles/docker/defaults/main.yml:
--------------------------------------------------------------------------------
1 | fig_version: "1.0.1"
--------------------------------------------------------------------------------
/ansible/roles/docker/tasks/main.yml:
--------------------------------------------------------------------------------
1 | - name: Add Docker repository and update apt cache
2 | apt_repository:
3 | repo: deb https://apt.dockerproject.org/repo ubuntu-trusty main
4 | update_cache: yes
5 | state: present
6 | tags: [docker]
7 |
8 | - name: Docker is present
9 | apt:
10 | name: docker-engine
11 | state: latest
12 | force: yes
13 | tags: [docker]
14 |
15 | - name: Python-pip is present
16 | apt: name=python-pip state=present
17 | tags: [docker]
18 |
19 | - name: Docker-py is present
20 | pip: name=docker-py version=1.6.0 state=present
21 | tags: [docker]
22 |
23 | - name: Docker Compose is present
24 | get_url:
25 | url: https://github.com/docker/compose/releases/download/1.11.1/docker-compose-Linux-x86_64
26 | dest: /usr/local/bin/docker-compose
27 | timeout: 60
28 | tags: [docker]
29 |
30 | - name: Docker Compose permissions are set
31 | file:
32 | path: /usr/local/bin/docker-compose
33 | mode: 0755
34 | tags: [docker]
35 |
--------------------------------------------------------------------------------
/books-ms.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/bootstrap.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo "Installing Ansible..."
4 | apt-get install -y software-properties-common
5 | apt-add-repository ppa:ansible/ansible
6 | apt-get update
7 | apt-get install -y --force-yes ansible
8 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | name := "books-ms"
2 |
3 | version := "1.0"
4 |
5 | resolvers += "spray" at "http://repo.spray.io/"
6 |
7 | libraryDependencies ++= {
8 | val akkaV = "2.3.0"
9 | val sprayV = "1.3.1"
10 | val specs2V = "2.3.12"
11 | Seq(
12 | "io.spray" % "spray-can" % sprayV,
13 | "io.spray" % "spray-routing" % sprayV,
14 | "io.spray" %% "spray-json" % "1.2.6",
15 | "com.typesafe.akka" %% "akka-actor" % akkaV,
16 | "org.mongodb" %% "casbah" % "2.7.2",
17 | "com.novus" %% "salat" % "1.9.8",
18 | "org.slf4j" % "slf4j-api" % "1.7.7",
19 | "ch.qos.logback" % "logback-classic" % "1.0.3",
20 | "io.spray" % "spray-testkit" % sprayV % "test",
21 | "org.specs2" %% "specs2-core" % specs2V % "test",
22 | "org.scalaj" %% "scalaj-http" % "1.1.4"
23 | )
24 | }
25 |
26 | test in assembly := {}
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | Development
2 | ===========
3 |
4 | Install dependencies
5 | --------------------
6 |
7 | With [Node.js](http://nodejs.org) and npm installed, run:
8 |
9 | ```bash
10 | npm run deps
11 | ```
12 |
13 | Run tests
14 | =========
15 |
16 | ```sh
17 | gulp watch
18 | ```
--------------------------------------------------------------------------------
/client/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "polymer-starter-kit",
3 | "version": "0.0.0",
4 | "license": "http://polymer.github.io/LICENSE.txt",
5 | "dependencies": {
6 | "iron-elements": "PolymerElements/iron-elements#1.0.0",
7 | "paper-elements": "PolymerElements/paper-elements#1.0.1",
8 | "platinum-elements": "PolymerElements/platinum-elements#1.0.0",
9 | "page": "visionmedia/page.js#~1.6.3"
10 | },
11 | "devDependencies": {
12 | "web-component-tester": "*",
13 | "test-fixture": "PolymerElements/test-fixture#^1.0.0"
14 | },
15 | "resolutions": {
16 | "test-fixture": "^3.0.0",
17 | "polymer": "^1.0.0",
18 | "webcomponentsjs": "^0.7.15",
19 | "iron-ajax": "^1.0.0",
20 | "promise-polyfill": "^1.0.0",
21 | "iron-autogrow-textarea": "^1.0.0",
22 | "iron-validatable-behavior": "^1.0.0",
23 | "iron-behaviors": "^1.0.0",
24 | "iron-a11y-keys-behavior": "^1.0.0",
25 | "iron-collapse": "^1.0.0",
26 | "iron-component-page": "^1.0.0",
27 | "paper-toolbar": "^1.0.0",
28 | "paper-header-panel": "^1.0.0",
29 | "paper-styles": "^1.0.0",
30 | "iron-doc-viewer": "^1.0.1",
31 | "marked-element": "^1.0.0",
32 | "prism-element": "^1.0.2",
33 | "iron-fit-behavior": "^1.0.0",
34 | "iron-flex-layout": "^1.0.0",
35 | "iron-icon": "^1.0.0",
36 | "iron-icons": "^1.0.0",
37 | "iron-iconset": "^1.0.0",
38 | "iron-iconset-svg": "^1.0.0",
39 | "iron-image": "^1.0.0",
40 | "iron-input": "^1.0.0",
41 | "iron-jsonp-library": "^1.0.0",
42 | "iron-localstorage": "^1.0.0",
43 | "iron-media-query": "^1.0.0",
44 | "iron-meta": "^1.0.0",
45 | "iron-overlay-behavior": "^1.0.0",
46 | "iron-pages": "^1.0.0",
47 | "iron-resizable-behavior": "^1.0.0",
48 | "iron-selector": "^1.0.0",
49 | "iron-signals": "^1.0.0",
50 | "iron-test-helpers": "^1.0.0",
51 | "paper-behaviors": "^1.0.0",
52 | "paper-button": "^1.0.0",
53 | "paper-checkbox": "^1.0.0",
54 | "paper-dialog-scrollable": "^1.0.0",
55 | "paper-drawer-panel": "^1.0.0",
56 | "paper-fab": "^1.0.0",
57 | "paper-icon-button": "^1.0.0",
58 | "paper-input": "^1.0.0",
59 | "iron-form-element-behavior": "^1.0.0",
60 | "paper-item": "^1.0.0",
61 | "paper-material": "^1.0.0",
62 | "paper-menu": "^1.0.0",
63 | "iron-menu-behavior": "^1.0.0",
64 | "paper-radio-button": "^1.0.0",
65 | "paper-radio-group": "^1.0.0",
66 | "paper-ripple": "^1.0.0",
67 | "paper-spinner": "^1.0.0",
68 | "paper-tabs": "^1.0.0",
69 | "paper-toast": "^1.0.0",
70 | "iron-a11y-announcer": "^1.0.0",
71 | "paper-toggle-button": "^1.0.0",
72 | "platinum-sw": "~1.0.0"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/client/components/tc-books/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | TechnologyConversations.com - Books
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
43 |
44 |
45 |
46 |
47 |
Books List
48 |
49 |
50 |
51 |
52 |
53 |
Book Form
54 |
55 |
56 |
57 |
58 |
59 |
76 |
77 |
--------------------------------------------------------------------------------
/client/components/tc-books/tc-book-form.html:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
16 |
17 |
23 |
24 |
28 |
29 |
67 |
68 |
69 |
70 |
157 |
--------------------------------------------------------------------------------
/client/components/tc-books/tc-books.html:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
18 |
19 |
20 |
[[newBookText]]
24 |
25 |
26 |
31 |
32 |
33 |
34 |
35 |
36 |
69 |
--------------------------------------------------------------------------------
/client/gulpfile.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
3 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
4 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
5 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
6 | Code distributed by Google as part of the polymer project is also
7 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
8 | */
9 |
10 | 'use strict';
11 |
12 | // Include Gulp & Tools We'll Use
13 | var gulp = require('gulp');
14 | var $ = require('gulp-load-plugins')();
15 | var del = require('del');
16 | var runSequence = require('run-sequence');
17 | var browserSync = require('browser-sync');
18 | var reload = browserSync.reload;
19 | var merge = require('merge-stream');
20 | var path = require('path');
21 | var fs = require('fs');
22 | var glob = require('glob');
23 |
24 | var AUTOPREFIXER_BROWSERS = [
25 | 'ie >= 10',
26 | 'ie_mob >= 10',
27 | 'ff >= 30',
28 | 'chrome >= 34',
29 | 'safari >= 7',
30 | 'opera >= 23',
31 | 'ios >= 7',
32 | 'android >= 4.4',
33 | 'bb >= 10'
34 | ];
35 |
36 | var styleTask = function (stylesPath, srcs) {
37 | return gulp.src(srcs.map(function(src) {
38 | return path.join('app', stylesPath, src);
39 | }))
40 | .pipe($.changed(stylesPath, {extension: '.css'}))
41 | .pipe($.autoprefixer(AUTOPREFIXER_BROWSERS))
42 | .pipe(gulp.dest('.tmp/' + stylesPath))
43 | .pipe($.if('*.css', $.cssmin()))
44 | .pipe(gulp.dest('dist/' + stylesPath))
45 | .pipe($.size({title: stylesPath}));
46 | };
47 |
48 | // Compile and Automatically Prefix Stylesheets
49 | gulp.task('styles', function () {
50 | return styleTask('styles', ['**/*.css']);
51 | });
52 |
53 | gulp.task('elements', function () {
54 | return styleTask('elements', ['**/*.css']);
55 | });
56 |
57 | // Lint JavaScript
58 | gulp.task('jshint', function () {
59 | return gulp.src([
60 | 'app/scripts/**/*.js',
61 | 'app/elements/**/*.js',
62 | 'app/elements/**/*.html'
63 | ])
64 | .pipe(reload({stream: true, once: true}))
65 | .pipe($.jshint.extract()) // Extract JS from .html files
66 | .pipe($.jshint())
67 | .pipe($.jshint.reporter('jshint-stylish'))
68 | .pipe($.if(!browserSync.active, $.jshint.reporter('fail')));
69 | });
70 |
71 | // Optimize Images
72 | gulp.task('images', function () {
73 | return gulp.src('app/images/**/*')
74 | .pipe($.cache($.imagemin({
75 | progressive: true,
76 | interlaced: true
77 | })))
78 | .pipe(gulp.dest('dist/images'))
79 | .pipe($.size({title: 'images'}));
80 | });
81 |
82 | // Copy All Files At The Root Level (app)
83 | gulp.task('copy', function () {
84 | var app = gulp.src([
85 | 'app/*',
86 | '!app/test',
87 | '!app/precache.json'
88 | ], {
89 | dot: true
90 | }).pipe(gulp.dest('dist'));
91 |
92 | var bower = gulp.src([
93 | 'bower_components/**/*'
94 | ]).pipe(gulp.dest('dist/bower_components'));
95 |
96 | var elements = gulp.src(['app/elements/**/*.html'])
97 | .pipe(gulp.dest('dist/elements'));
98 |
99 | var swBootstrap = gulp.src(['bower_components/platinum-sw/bootstrap/*.js'])
100 | .pipe(gulp.dest('dist/elements/bootstrap'));
101 |
102 | var swToolbox = gulp.src(['bower_components/sw-toolbox/*.js'])
103 | .pipe(gulp.dest('dist/sw-toolbox'));
104 |
105 | var vulcanized = gulp.src(['app/elements/elements.html'])
106 | .pipe($.rename('elements.vulcanized.html'))
107 | .pipe(gulp.dest('dist/elements'));
108 |
109 | return merge(app, bower, elements, vulcanized, swBootstrap, swToolbox)
110 | .pipe($.size({title: 'copy'}));
111 | });
112 |
113 | // Copy Web Fonts To Dist
114 | gulp.task('fonts', function () {
115 | return gulp.src(['app/fonts/**'])
116 | .pipe(gulp.dest('dist/fonts'))
117 | .pipe($.size({title: 'fonts'}));
118 | });
119 |
120 | // Scan Your HTML For Assets & Optimize Them
121 | gulp.task('html', function () {
122 | var assets = $.useref.assets({searchPath: ['.tmp', 'app', 'dist']});
123 |
124 | return gulp.src(['app/**/*.html', '!app/{elements,test}/**/*.html'])
125 | // Replace path for vulcanized assets
126 | .pipe($.if('*.html', $.replace('elements/elements.html', 'elements/elements.vulcanized.html')))
127 | .pipe(assets)
128 | // Concatenate And Minify JavaScript
129 | .pipe($.if('*.js', $.uglify({preserveComments: 'some'})))
130 | // Concatenate And Minify Styles
131 | // In case you are still using useref build blocks
132 | .pipe($.if('*.css', $.cssmin()))
133 | .pipe(assets.restore())
134 | .pipe($.useref())
135 | // Minify Any HTML
136 | .pipe($.if('*.html', $.minifyHtml({
137 | quotes: true,
138 | empty: true,
139 | spare: true
140 | })))
141 | // Output Files
142 | .pipe(gulp.dest('dist'))
143 | .pipe($.size({title: 'html'}));
144 | });
145 |
146 | // Vulcanize imports
147 | gulp.task('vulcanize', function () {
148 | var DEST_DIR = 'dist/elements';
149 |
150 | return gulp.src('dist/elements/elements.vulcanized.html')
151 | .pipe($.vulcanize({
152 | dest: DEST_DIR,
153 | strip: true,
154 | inlineCss: true,
155 | inlineScripts: true
156 | }))
157 | .pipe(gulp.dest(DEST_DIR))
158 | .pipe($.size({title: 'vulcanize'}));
159 | });
160 |
161 | // Generate a list of files that should be precached when serving from 'dist'.
162 | // The list will be consumed by the element.
163 | gulp.task('precache', function (callback) {
164 | var dir = 'dist';
165 |
166 | glob('{elements,scripts,styles}/**/*.*', {cwd: dir}, function(error, files) {
167 | if (error) {
168 | callback(error);
169 | } else {
170 | files.push('index.html', './', 'bower_components/webcomponentsjs/webcomponents.min.js');
171 | var filePath = path.join(dir, 'precache.json');
172 | fs.writeFile(filePath, JSON.stringify(files), callback);
173 | }
174 | });
175 | });
176 |
177 | // Clean Output Directory
178 | gulp.task('clean', del.bind(null, ['.tmp', 'dist']));
179 |
180 | // Watch Files For Changes & Reload
181 | gulp.task('serve', ['styles', 'elements', 'images'], function () {
182 | browserSync({
183 | notify: false,
184 | // Run as an https by uncommenting 'https: true'
185 | // Note: this uses an unsigned certificate which on first access
186 | // will present a certificate warning in the browser.
187 | // https: true,
188 | server: {
189 | baseDir: ['.tmp', 'app'],
190 | routes: {
191 | '/bower_components': 'bower_components'
192 | }
193 | }
194 | });
195 |
196 | gulp.watch(['app/**/*.html'], reload);
197 | gulp.watch(['app/styles/**/*.css'], ['styles', reload]);
198 | gulp.watch(['app/elements/**/*.css'], ['elements', reload]);
199 | gulp.watch(['app/{scripts,elements}/**/*.js'], ['jshint']);
200 | gulp.watch(['app/images/**/*'], reload);
201 | });
202 |
203 | // Build and serve the output from the dist build
204 | gulp.task('serve:dist', ['default'], function () {
205 | browserSync({
206 | notify: false,
207 | // Run as an https by uncommenting 'https: true'
208 | // Note: this uses an unsigned certificate which on first access
209 | // will present a certificate warning in the browser.
210 | // https: true,
211 | server: 'dist'
212 | });
213 | });
214 |
215 | // Build Production Files, the Default Task
216 | gulp.task('default', ['clean'], function (cb) {
217 | runSequence(
218 | ['copy', 'styles'],
219 | 'elements',
220 | ['jshint', 'images', 'fonts', 'html'],
221 | 'vulcanize', 'precache',
222 | cb);
223 | });
224 |
225 | // Load tasks for web-component-tester
226 | // Adds tasks for `gulp test:local` and `gulp test:remote`
227 | try { require('web-component-tester').gulp.init(gulp); } catch (err) {}
228 |
229 | // Load custom tasks from the `tasks` directory
230 | try { require('require-dir')('tasks'); } catch (err) {}
231 |
232 | gulp.task('watch', ['test:local'], function() {
233 | gulp.watch(['components/**/*.html', 'test/**/*.html'], ['test:local']);
234 | });
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tc-books",
3 | "version": "0.0.0",
4 | "dependencies": {},
5 | "scripts": {
6 | "deps": "npm install -g gulp && npm install -g bower && npm install && bower install"
7 | },
8 | "devDependencies": {
9 | "apache-server-configs": "^2.7.1",
10 | "browser-sync": "^2.6.4",
11 | "del": "^1.1.1",
12 | "glob": "^5.0.6",
13 | "gulp": "^3.8.5",
14 | "gulp-autoprefixer": "^2.1.0",
15 | "gulp-cache": "^0.2.8",
16 | "gulp-changed": "^1.0.0",
17 | "gulp-cssmin": "^0.1.6",
18 | "gulp-flatten": "0.0.4",
19 | "gulp-if": "^1.2.1",
20 | "gulp-imagemin": "^2.2.1",
21 | "gulp-jshint": "^1.6.3",
22 | "gulp-load-plugins": "^0.10.0",
23 | "gulp-minify-html": "^1.0.2",
24 | "gulp-rename": "^1.2.0",
25 | "gulp-replace": "^0.5.3",
26 | "gulp-size": "^1.0.0",
27 | "gulp-uglify": "^1.2.0",
28 | "gulp-uncss": "^1.0.1",
29 | "gulp-useref": "^1.1.2",
30 | "gulp-vulcanize": "^6.0.0",
31 | "gulp-yuidoc": "^0.1.2",
32 | "gulp-compass": "^2.1.0",
33 | "jshint-stylish": "^1.0.1",
34 | "merge-stream": "^0.1.7",
35 | "opn": "^1.0.0",
36 | "require-dir": "^0.3.0",
37 | "run-sequence": "^1.0.2",
38 | "vulcanize": ">= 1.4.2",
39 | "web-component-tester": "git://github.com/Polymer/web-component-tester.git#master"
40 | },
41 | "engines": {
42 | "node": ">=0.10.0"
43 | }
44 | }
--------------------------------------------------------------------------------
/client/test.html:
--------------------------------------------------------------------------------
1 | This is just a test
--------------------------------------------------------------------------------
/client/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
17 |
18 |
--------------------------------------------------------------------------------
/client/test/tc-book-form.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
649 |
650 |
651 |
--------------------------------------------------------------------------------
/client/test/tc-books.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
197 |
198 |
199 |
--------------------------------------------------------------------------------
/client/wct-complete.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | verbose: true,
3 | plugins: {
4 | local: {
5 | browsers: ['firefox']
6 | }
7 | }
8 | };
--------------------------------------------------------------------------------
/client/wct.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | verbose: false,
3 | plugins: {
4 | local: {
5 | browsers: ['firefox']
6 | }
7 | }
8 | };
--------------------------------------------------------------------------------
/consul_check.ctmpl:
--------------------------------------------------------------------------------
1 | {
2 | "service": {
3 | "name": "books-ms",
4 | "tags": ["service"],
5 | "port": 80,
6 | "address": "{{key "proxy/ip"}}",
7 | "checks": [{
8 | "id": "api",
9 | "name": "HTTP on port 80",
10 | "http": "http://{{key "proxy/ip"}}/api/v1/books",
11 | "interval": "10s",
12 | "timeout": "1s",
13 | "status": "passing"
14 | }]
15 | }
16 | }
--------------------------------------------------------------------------------
/docker-compose-dev-v2.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 |
5 | app:
6 | image: 10.100.198.200:5000/books-ms
7 | ports:
8 | - 8080:8080
9 | links:
10 | - db:db
11 |
12 | db:
13 | image: mongo
14 |
15 | tests:
16 | image: 10.100.198.200:5000/books-ms-tests
17 | volumes:
18 | - ./client/components:/source/client/components
19 | - ./client/test:/source/client/test
20 | - ./src:/source/src
21 | - ./target/scala-2.10:/source/target/scala-2.10
22 | - ./client/wct-complete.conf.js:/source/client/wct.conf.js
23 | - /data/tests/db:/data/db
24 | environment:
25 | - TEST_TYPE=all
26 |
27 | testsLocal:
28 | image: vfarcic/books-ms-tests
29 | volumes:
30 | - ./client/components:/source/client/components
31 | - ./client/test:/source/client/test
32 | - ./src:/source/src
33 | - ./target/scala-2.10:/source/target/scala-2.10
34 | - ./client/wct-complete.conf.js:/source/client/wct.conf.js
35 | - /data/tests/db:/data/db
36 | environment:
37 | - TEST_TYPE=all
38 |
39 | integ:
40 | image: 10.100.198.200:5000/books-ms-tests
41 | volumes:
42 | - ./src:/source/src
43 | environment:
44 | - TEST_TYPE=integ
45 | - DOMAIN=$DOMAIN
46 |
47 | feTests:
48 | image: 10.100.198.200:5000/books-ms-tests
49 | stdin_open: true
50 | tty: true
51 | volumes:
52 | - ./client/components:/source/client/components
53 | - ./client/test:/source/client/test
54 | - ./src:/source/src
55 | - ./target:/source/target
56 | ports:
57 | - 8080:8080
58 | environment:
59 | - TEST_TYPE=watch-front
60 |
61 | feTestsLocal:
62 | image: vfarcic/books-ms-tests
63 | stdin_open: true
64 | tty: true
65 | volumes:
66 | - ./client/components:/source/client/components
67 | - ./client/test:/source/client/test
68 | - ./src:/source/src
69 | - ./target:/source/target
70 | ports:
71 | - 8080:8080
72 | environment:
73 | - TEST_TYPE=watch-front
74 |
--------------------------------------------------------------------------------
/docker-compose-dev.yml:
--------------------------------------------------------------------------------
1 | app:
2 | image: 10.100.198.200:5000/books-ms
3 | ports:
4 | - 8080:8080
5 | links:
6 | - db:db
7 |
8 | db:
9 | image: mongo
10 |
11 | tests:
12 | image: 10.100.198.200:5000/books-ms-tests
13 | volumes:
14 | - ./client/components:/source/client/components
15 | - ./client/test:/source/client/test
16 | - ./src:/source/src
17 | - ./target/scala-2.10:/source/target/scala-2.10
18 | - ./client/wct-complete.conf.js:/source/client/wct.conf.js
19 | environment:
20 | - TEST_TYPE=all
21 |
22 | testsLocal:
23 | image: vfarcic/books-ms-tests
24 | volumes:
25 | - ./client/components:/source/client/components
26 | - ./client/test:/source/client/test
27 | - ./src:/source/src
28 | - ./target/scala-2.10:/source/target/scala-2.10
29 | - ./client/wct-complete.conf.js:/source/client/wct.conf.js
30 | - /data/tests/db:/data/db
31 | environment:
32 | - TEST_TYPE=all
33 |
34 | integ:
35 | image: 10.100.198.200:5000/books-ms-tests
36 | volumes:
37 | - ./src:/source/src
38 | environment:
39 | - JAVA_OPTS=-Xmx512m -Xms256m
40 | - TEST_TYPE=integ
41 | - DOMAIN=$DOMAIN
42 |
43 | feTests:
44 | image: 10.100.198.200:5000/books-ms-tests
45 | stdin_open: true
46 | tty: true
47 | volumes:
48 | - ./client/components:/source/client/components
49 | - ./client/test:/source/client/test
50 | - ./src:/source/src
51 | - ./target:/source/target
52 | ports:
53 | - 8080:8080
54 | environment:
55 | - TEST_TYPE=watch-front
56 |
57 | feTestsLocal:
58 | image: vfarcic/books-ms-tests
59 | stdin_open: true
60 | tty: true
61 | volumes:
62 | - ./client/components:/source/client/components
63 | - ./client/test:/source/client/test
64 | - ./src:/source/src
65 | - ./target:/source/target
66 | ports:
67 | - 8080:8080
68 | environment:
69 | - TEST_TYPE=watch-front
70 |
--------------------------------------------------------------------------------
/docker-compose-jenkins-v2.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 |
5 | app:
6 | image: vfarcic/books-ms
7 | ports:
8 | - 8080
9 | environment:
10 | - SERVICE_NAME=books-ms
11 | - DB_HOST=books-ms-db
12 |
13 | app-blue:
14 | environment:
15 | - SERVICE_NAME=books-ms-blue
16 | extends:
17 | service: app
18 |
19 | app-green:
20 | environment:
21 | - SERVICE_NAME=books-ms-green
22 | extends:
23 | service: app
24 |
25 | db:
26 | container_name: books-ms-db
27 | image: mongo
28 | environment:
29 | - SERVICE_NAME=books-ms-db
30 |
31 | integ:
32 | image: vfarcic/books-ms-tests
33 | volumes:
34 | - ./src:/source/src
35 | environment:
36 | - TEST_TYPE=integ
37 | - DOMAIN=$DOMAIN
--------------------------------------------------------------------------------
/docker-compose-jenkins.yml:
--------------------------------------------------------------------------------
1 | app:
2 | image: vfarcic/books-ms
3 | ports:
4 | - 8080
5 | environment:
6 | - SERVICE_NAME=books-ms
7 | - DB_HOST=books-ms-db
8 |
9 | app-blue:
10 | environment:
11 | - SERVICE_NAME=books-ms-blue
12 | extends:
13 | service: app
14 |
15 | app-green:
16 | environment:
17 | - SERVICE_NAME=books-ms-green
18 | extends:
19 | service: app
20 |
21 | db:
22 | container_name: books-ms-db
23 | image: mongo
24 | environment:
25 | - SERVICE_NAME=books-ms-db
26 |
27 | integ:
28 | image: vfarcic/books-ms-tests
29 | volumes:
30 | - ./src:/source/src
31 | environment:
32 | - TEST_TYPE=integ
33 | - DOMAIN=$DOMAIN
--------------------------------------------------------------------------------
/docker-compose-logging-v2.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 |
5 | app:
6 | image: 10.100.198.200:5000/books-ms
7 | ports:
8 | - 8080
9 | links:
10 | - db:db
11 | environment:
12 | - SERVICE_NAME=books-ms
13 | log_driver: syslog
14 | log_opt:
15 | tag: books-ms
16 |
17 | db:
18 | image: mongo
19 | log_driver: syslog
20 | log_opt:
21 | tag: books-ms
22 |
--------------------------------------------------------------------------------
/docker-compose-logging.yml:
--------------------------------------------------------------------------------
1 | app:
2 | image: vfarcic/books-ms
3 | ports:
4 | - 8080
5 | links:
6 | - db:db
7 | environment:
8 | - SERVICE_NAME=books-ms
9 | log_driver: syslog
10 | log_opt:
11 | tag: books-ms
12 |
13 | db:
14 | image: mongo
15 | log_driver: syslog
16 | log_opt:
17 | tag: books-ms
18 |
--------------------------------------------------------------------------------
/docker-compose-no-links-v2.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 |
5 | app:
6 | image: 10.100.198.200:5000/books-ms
7 | ports:
8 | - 8080
9 |
10 | db:
11 | image: mongo
12 |
--------------------------------------------------------------------------------
/docker-compose-no-links.yml:
--------------------------------------------------------------------------------
1 | app:
2 | image: 10.100.198.200:5000/books-ms
3 | ports:
4 | - 8080
5 |
6 | db:
7 | image: mongo
8 |
--------------------------------------------------------------------------------
/docker-compose-swarm-v2.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 | app:
5 | image: 10.100.198.200:5000/books-ms
6 | ports:
7 | - 8080
8 | environment:
9 | - SERVICE_NAME=books-ms
10 | - DB_HOST=books-ms-db
11 |
12 | app-blue:
13 | environment:
14 | - SERVICE_NAME=books-ms-blue
15 | extends:
16 | service: app
17 | depends_on:
18 | - db
19 |
20 | app-green:
21 | environment:
22 | - SERVICE_NAME=books-ms-green
23 | extends:
24 | service: app
25 | depends_on:
26 | - db
27 |
28 | db:
29 | container_name: books-ms-db
30 | image: mongo
31 | environment:
32 | - SERVICE_NAME=books-ms-db
33 |
--------------------------------------------------------------------------------
/docker-compose-swarm.yml:
--------------------------------------------------------------------------------
1 | app:
2 | image: 10.100.198.200:5000/books-ms
3 | ports:
4 | - 8080
5 | net: books-ms
6 | environment:
7 | - SERVICE_NAME=books-ms
8 | - DB_HOST=books-ms-db
9 |
10 | app-blue:
11 | environment:
12 | - SERVICE_NAME=books-ms-blue
13 | extends:
14 | service: app
15 |
16 | app-green:
17 | environment:
18 | - SERVICE_NAME=books-ms-green
19 | extends:
20 | service: app
21 |
22 | db:
23 | container_name: books-ms-db
24 | image: mongo
25 | net: books-ms
26 | environment:
27 | - SERVICE_NAME=books-ms-db
28 |
--------------------------------------------------------------------------------
/docker-compose-v2.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 |
5 | base:
6 | image: 10.100.198.200:5000/books-ms
7 | ports:
8 | - 8080
9 | environment:
10 | - SERVICE_NAME=books-ms
11 |
12 | app:
13 | extends:
14 | service: base
15 | links:
16 | - db:db
17 |
18 | app-blue:
19 | extends:
20 | service: base
21 | environment:
22 | - SERVICE_NAME=books-ms-blue
23 | links:
24 | - db:db
25 |
26 | app-green:
27 | extends:
28 | service: base
29 | environment:
30 | - SERVICE_NAME=books-ms-green
31 | links:
32 | - db:db
33 |
34 | applocal:
35 | extends:
36 | service: base
37 | links:
38 | - db:db
39 |
40 | db:
41 | image: mongo
42 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | base:
2 | image: 10.100.198.200:5000/books-ms
3 | ports:
4 | - 8080
5 | environment:
6 | - SERVICE_NAME=books-ms
7 |
8 | app:
9 | extends:
10 | service: base
11 | links:
12 | - db:db
13 |
14 | app-blue:
15 | extends:
16 | service: base
17 | environment:
18 | - SERVICE_NAME=books-ms-blue
19 | links:
20 | - db:db
21 |
22 | app-green:
23 | extends:
24 | service: base
25 | environment:
26 | - SERVICE_NAME=books-ms-green
27 | links:
28 | - db:db
29 |
30 | applocal:
31 | extends:
32 | service: base
33 | links:
34 | - db:db
35 |
36 | db:
37 | image: mongo
38 |
--------------------------------------------------------------------------------
/haproxy.ctmpl:
--------------------------------------------------------------------------------
1 |
2 | frontend books-ms-fe
3 | bind *:80
4 | option http-server-close
5 | acl url_books-ms path_beg /api/v1/books
6 | use_backend books-ms-be if url_books-ms
7 |
8 | backend books-ms-be
9 | {{range service "books-ms" "any"}}
10 | server {{.Node}}_{{.Port}} {{.Address}}:{{.Port}} check
11 | {{end}}
12 |
--------------------------------------------------------------------------------
/img/book-form-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vfarcic/books-ms/581fcd369f89e177a7f36664c3ed0bdeee8577e1/img/book-form-demo.png
--------------------------------------------------------------------------------
/img/book-form-sc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vfarcic/books-ms/581fcd369f89e177a7f36664c3ed0bdeee8577e1/img/book-form-sc.png
--------------------------------------------------------------------------------
/img/book-form-styled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vfarcic/books-ms/581fcd369f89e177a7f36664c3ed0bdeee8577e1/img/book-form-styled.png
--------------------------------------------------------------------------------
/img/book-form.ora:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vfarcic/books-ms/581fcd369f89e177a7f36664c3ed0bdeee8577e1/img/book-form.ora
--------------------------------------------------------------------------------
/img/book-form.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vfarcic/books-ms/581fcd369f89e177a7f36664c3ed0bdeee8577e1/img/book-form.png
--------------------------------------------------------------------------------
/img/books-sc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vfarcic/books-ms/581fcd369f89e177a7f36664c3ed0bdeee8577e1/img/books-sc.png
--------------------------------------------------------------------------------
/img/books.ora:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vfarcic/books-ms/581fcd369f89e177a7f36664c3ed0bdeee8577e1/img/books.ora
--------------------------------------------------------------------------------
/img/books.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vfarcic/books-ms/581fcd369f89e177a7f36664c3ed0bdeee8577e1/img/books.png
--------------------------------------------------------------------------------
/img/demo-styled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vfarcic/books-ms/581fcd369f89e177a7f36664c3ed0bdeee8577e1/img/demo-styled.png
--------------------------------------------------------------------------------
/img/fe-monolith.ora:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vfarcic/books-ms/581fcd369f89e177a7f36664c3ed0bdeee8577e1/img/fe-monolith.ora
--------------------------------------------------------------------------------
/img/fe-monolith.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vfarcic/books-ms/581fcd369f89e177a7f36664c3ed0bdeee8577e1/img/fe-monolith.png
--------------------------------------------------------------------------------
/img/first-test-failure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vfarcic/books-ms/581fcd369f89e177a7f36664c3ed0bdeee8577e1/img/first-test-failure.png
--------------------------------------------------------------------------------
/img/first-test-success.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vfarcic/books-ms/581fcd369f89e177a7f36664c3ed0bdeee8577e1/img/first-test-success.png
--------------------------------------------------------------------------------
/img/idea-code-tests.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vfarcic/books-ms/581fcd369f89e177a7f36664c3ed0bdeee8577e1/img/idea-code-tests.png
--------------------------------------------------------------------------------
/img/ms.ora:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vfarcic/books-ms/581fcd369f89e177a7f36664c3ed0bdeee8577e1/img/ms.ora
--------------------------------------------------------------------------------
/img/ms.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vfarcic/books-ms/581fcd369f89e177a7f36664c3ed0bdeee8577e1/img/ms.png
--------------------------------------------------------------------------------
/img/tests-console.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vfarcic/books-ms/581fcd369f89e177a7f36664c3ed0bdeee8577e1/img/tests-console.png
--------------------------------------------------------------------------------
/img/toast.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vfarcic/books-ms/581fcd369f89e177a7f36664c3ed0bdeee8577e1/img/toast.png
--------------------------------------------------------------------------------
/mesos.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "books-ms",
3 | "cpus": 0.2,
4 | "mem": 20.0,
5 | "instances": 1,
6 | "constraints": [["hostname", "UNIQUE", ""]],
7 | "container": {
8 | "type": "DOCKER",
9 | "docker": {
10 | "image": "10.100.198.200:5000/books-ms",
11 | "network": "BRIDGE",
12 | "portMappings": [
13 | { "containerPort": 8080, "hostPort": 0, "servicePort": 0, "protocol": "tcp" }
14 | ]
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/nginx-includes.conf:
--------------------------------------------------------------------------------
1 | location /api/v1/books {
2 | proxy_pass http://books-ms/api/v1/books;
3 | proxy_next_upstream error timeout invalid_header http_500;
4 | }
5 |
--------------------------------------------------------------------------------
/nginx-upstreams-blue.ctmpl:
--------------------------------------------------------------------------------
1 | upstream books-ms {
2 | {{range service "books-ms-blue" "any"}}
3 | server {{.Address}}:{{.Port}};
4 | {{end}}
5 | }
6 |
--------------------------------------------------------------------------------
/nginx-upstreams-green.ctmpl:
--------------------------------------------------------------------------------
1 | upstream books-ms {
2 | {{range service "books-ms-green" "any"}}
3 | server {{.Address}}:{{.Port}};
4 | {{end}}
5 | }
6 |
--------------------------------------------------------------------------------
/nginx-upstreams.ctmpl:
--------------------------------------------------------------------------------
1 | upstream books-ms {
2 | {{range service "books-ms" "any"}}
3 | server {{.Address}}:{{.Port}};
4 | {{end}}
5 | }
6 |
--------------------------------------------------------------------------------
/preload.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | docker pull vfarcic/books-ms-tests
4 |
5 | cd /vagrant
6 |
7 | docker-compose -f docker-compose-dev.yml run testsLocal
--------------------------------------------------------------------------------
/project/assembly.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.12.0")
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=0.13.13
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | logLevel := Level.Warn
--------------------------------------------------------------------------------
/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | java -jar bs.jar
--------------------------------------------------------------------------------
/run_tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | if [ "$TEST_TYPE" = "integ" ]
6 | then
7 | sbt "testOnly *Integ"
8 | elif [ "$TEST_TYPE" = "watch-front" ]
9 | then
10 | mongod &
11 | sbt run &
12 | cd /source/client
13 | Xvfb :1 -screen 0 1024x768x16 &>/dev/null &
14 | echo ">>> NPM is broken (again) so front-end tests are temporarily disabled."
15 | # gulp watch
16 | cat
17 | elif [ "$TEST_TYPE" = "all" ]
18 | then
19 | mongod &
20 | sbt "testOnly *Spec"
21 | mongod --shutdown
22 | cd /source/client
23 | Xvfb :1 -screen 0 1024x768x16 &>/dev/null &
24 | echo ">>> NPM is broken (again) so front-end tests are temporarily disabled."
25 | # gulp test:local
26 | cd /source
27 | sbt assembly
28 | else
29 | mongod &
30 | sbt "testOnly *Spec"
31 | mongod --shutdown
32 | sbt assembly
33 | fi
34 |
--------------------------------------------------------------------------------
/src/main/scala/com/technologyconversations/api/Service.scala:
--------------------------------------------------------------------------------
1 | package com.technologyconversations.api
2 |
3 | import akka.actor.{Props, ActorSystem}
4 | import akka.io.IO
5 | import spray.can.Http
6 |
7 | object Service extends App {
8 |
9 | implicit val system = ActorSystem("routingSystem")
10 | val service = system.actorOf(Props[ServiceActor], "service")
11 | IO(Http) ! Http.Bind(service, "0.0.0.0", port = 8080)
12 |
13 | }
--------------------------------------------------------------------------------
/src/main/scala/com/technologyconversations/api/ServiceActor.scala:
--------------------------------------------------------------------------------
1 | package com.technologyconversations.api
2 |
3 | import java.io.File
4 |
5 | import akka.actor.Actor
6 | import spray.http.HttpHeaders.RawHeader
7 | import spray.json.DefaultJsonProtocol
8 | import spray.routing.HttpService
9 | import spray.httpx.SprayJsonSupport._
10 | import com.mongodb.casbah.Imports._
11 | import com.novus.salat._
12 | import com.novus.salat.global._
13 | import com.mongodb.casbah.MongoClient
14 | import scala.util.Properties._
15 |
16 | case class BookReduced(_id: Int, title: String, author: String)
17 | case class Book(_id: Int, title: String, author: String, description: String) {
18 | require(!title.isEmpty)
19 | require(!author.isEmpty)
20 | }
21 |
22 | class ServiceActor extends Actor with ServiceRoute with StaticRoute {
23 |
24 | val address = envOrElse("DB_PORT_27017_TCP_ADDR", envOrElse("DB_HOST", "localhost"))
25 | val port = envOrElse("DB_PORT_27017_PORT", "27017")
26 | val client = MongoClient(MongoClientURI(s"mongodb://$address:$port/"))
27 | val db = client(envOrElse("DB_DBNAME", "books"))
28 | val collection = db(envOrElse("DB_COLLECTION", "books"))
29 |
30 | def actorRefFactory = context
31 | def receive = runRoute {
32 | respondWithHeaders(RawHeader("Access-Control-Allow-Origin", "*"))
33 | { serviceRoute ~ staticRoute }
34 | }
35 |
36 | }
37 |
38 | trait StaticRoute extends HttpService {
39 |
40 | val staticRoute = pathPrefix("") {
41 | getFromDirectory("client/")
42 | }
43 |
44 | }
45 |
46 | trait ServiceRoute extends HttpService with DefaultJsonProtocol {
47 |
48 | implicit val booksReducedFormat = jsonFormat3(BookReduced)
49 | implicit val booksFormat = jsonFormat4(Book)
50 | val collection: MongoCollection
51 |
52 | val serviceRoute = pathPrefix("api" / "v1" / "books") {
53 | path("_id" / IntNumber) { id =>
54 | get {
55 | complete(
56 | grater[Book].asObject(
57 | collection.findOne(MongoDBObject("_id" -> id)).get
58 | )
59 | )
60 | } ~ delete {
61 | complete(
62 | grater[Book].asObject(
63 | collection.findAndRemove(MongoDBObject("_id" -> id)).get
64 | )
65 | )
66 | }
67 | } ~ pathEnd {
68 | get {
69 | complete(
70 | collection.find().toList.map(grater[BookReduced].asObject(_))
71 | )
72 | } ~ put {
73 | entity(as[Book]) { book =>
74 | collection.update(
75 | MongoDBObject("_id" -> book._id),
76 | grater[Book].asDBObject(book),
77 | upsert = true
78 | )
79 | complete(book)
80 | }
81 | } ~ post {
82 | entity(as[Book]) { book =>
83 | collection.update(
84 | MongoDBObject("_id" -> book._id),
85 | grater[Book].asDBObject(book),
86 | upsert = true
87 | )
88 | complete(book)
89 | }
90 | }
91 | }
92 | }
93 |
94 | }
--------------------------------------------------------------------------------
/src/test/resources/application.conf:
--------------------------------------------------------------------------------
1 | books {
2 | db {
3 | host = localhost
4 | port = 27017
5 | db = test-books
6 | collection = books
7 | }
8 | }
--------------------------------------------------------------------------------
/src/test/scala/com/technologyconversations/api/ServiceInteg.scala:
--------------------------------------------------------------------------------
1 | package com.technologyconversations.api
2 |
3 | import org.specs2.mutable.Specification
4 | import scalaj.http._
5 | import scala.util.Properties._
6 |
7 | class ServiceInteg extends Specification {
8 |
9 | val domain = envOrElse("DOMAIN", "http://localhost:8080")
10 | val uri = s"$domain/api/v1/books"
11 |
12 | s"GET $uri" should {
13 |
14 | "return OK" in {
15 | val response: HttpResponse[String] = Http(uri).asString
16 | response.code must equalTo(200)
17 | }
18 |
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/test/scala/com/technologyconversations/api/ServiceSpec.scala:
--------------------------------------------------------------------------------
1 | package com.technologyconversations.api
2 |
3 | import com.mongodb.casbah.Imports._
4 | import com.mongodb.casbah.MongoClient
5 | import com.mongodb.casbah.commons.MongoDBObject
6 | import com.novus.salat._
7 | import com.novus.salat.global._
8 | import org.specs2.mutable.Specification
9 | import org.specs2.specification.BeforeExample
10 | import spray.http.{ContentType, HttpEntity}
11 | import spray.http.MediaTypes._
12 | import spray.routing.HttpService
13 | import spray.testkit.Specs2RouteTest
14 | import spray.http.StatusCodes._
15 | import spray.json._
16 | import DefaultJsonProtocol._
17 | import spray.httpx.SprayJsonSupport._
18 |
19 | import spray.httpx.SprayJsonSupport._
20 |
21 | class ServiceSpec extends Specification with Specs2RouteTest with HttpService with ServiceRoute with StaticRoute with BeforeExample {
22 |
23 | val client = MongoClient("localhost", 27017)
24 | val db = client("books")
25 | val collection = db("books")
26 | val apiUri = "/api/v1/books"
27 | val staticUri = "/test.html"
28 | val bookId = 1234
29 |
30 | def actorRefFactory = system
31 | def before = db.dropDatabase()
32 |
33 | sequential
34 |
35 | s"GET $staticUri" should {
36 |
37 | "return OK" in {
38 | Get(staticUri) ~> staticRoute ~> check {
39 | response.status must equalTo(OK)
40 | }
41 | }
42 |
43 | "return file content" in {
44 | Get(staticUri) ~> staticRoute ~> check {
45 | val content = responseAs[String]
46 | content must equalTo("This is just a test")
47 | }
48 | }
49 |
50 | }
51 |
52 | s"GET $apiUri" should {
53 |
54 | "return OK" in {
55 | Get(apiUri) ~> serviceRoute ~> check {
56 | response.status must equalTo(OK)
57 | }
58 | }
59 |
60 | "return all books" in {
61 | val expected = insertBooks(3).map { book =>
62 | BookReduced(book._id, book.title, book.author)
63 | }
64 | Get(apiUri) ~> serviceRoute ~> check {
65 | response.entity must not equalTo None
66 | val books = responseAs[List[BookReduced]]
67 | books must haveSize(expected.size)
68 | books must equalTo(expected)
69 | }
70 | }
71 |
72 | }
73 |
74 | s"GET $apiUri/_id/$bookId" should {
75 |
76 | val expected = Book(bookId, "Title", "Author", "Description")
77 |
78 | "return OK" in {
79 | insertBook(expected)
80 | Get(s"$apiUri/_id/$bookId") ~> serviceRoute ~> check {
81 | response.status must equalTo(OK)
82 | }
83 | }
84 |
85 | "return book" in {
86 | insertBook(expected)
87 | Get(s"$apiUri/_id/$bookId") ~> serviceRoute ~> check {
88 | response.entity must not equalTo None
89 | val book = responseAs[Book]
90 | book must equalTo(expected)
91 | }
92 | }
93 |
94 | }
95 |
96 | s"PUT $apiUri" should {
97 |
98 | val expected = Book(bookId, "PUT title", "Put author", "Put description")
99 |
100 | "return OK" in {
101 | Put(apiUri, expected) ~> serviceRoute ~> check {
102 | response.status must equalTo(OK)
103 | }
104 | }
105 |
106 | "return Book" in {
107 | Put(apiUri, expected) ~> serviceRoute ~> check {
108 | response.entity must not equalTo None
109 | val book = responseAs[Book]
110 | book must equalTo(expected)
111 | }
112 | }
113 |
114 | "insert book to the DB" in {
115 | Put(apiUri, expected) ~> serviceRoute ~> check {
116 | response.status must equalTo(OK)
117 | val book = getBook(bookId)
118 | book must equalTo(expected)
119 | }
120 | }
121 |
122 | "update book when it exists in the DB" in {
123 | collection.insert(grater[Book].asDBObject(expected))
124 | Put(apiUri, expected) ~> serviceRoute ~> check {
125 | response.status must equalTo(OK)
126 | val book = getBook(bookId)
127 | book must equalTo(expected)
128 | }
129 | }
130 |
131 | }
132 |
133 | s"DELETE $apiUri/_id/$bookId" should {
134 |
135 | val expected = Book(bookId, "Title", "Author", "Description")
136 |
137 | "return OK" in {
138 | insertBook(expected)
139 | Delete(s"$apiUri/_id/$bookId") ~> serviceRoute ~> check {
140 | response.status must equalTo(OK)
141 | }
142 | }
143 |
144 | "return book" in {
145 | insertBook(expected)
146 | Delete(s"$apiUri/_id/$bookId") ~> serviceRoute ~> check {
147 | response.entity must not equalTo None
148 | val book = responseAs[Book]
149 | book must equalTo(expected)
150 | }
151 | }
152 |
153 | "remove book from the DB" in {
154 | insertBook(expected)
155 | Delete(s"$apiUri/_id/$bookId") ~> serviceRoute ~> check {
156 | response.status must equalTo(OK)
157 | getBooks must haveSize(0)
158 | }
159 | }
160 |
161 | }
162 |
163 | def insertBook(book: Book) {
164 | collection.insert(grater[Book].asDBObject(book))
165 | }
166 |
167 | def insertBooks(quantity: Int): List[Book] = {
168 | val books = List.tabulate(quantity)(id => Book(id, s"Title $id", s"Author $id", s"Description $id"))
169 | for (book <- books) {
170 | collection.insert(grater[Book].asDBObject(book))
171 | }
172 | books
173 | }
174 |
175 | def getBook(id: Int): Book = {
176 | val dbObject = collection.findOne(MongoDBObject("_id" -> id))
177 | grater[Book].asObject(dbObject.get)
178 | }
179 |
180 | def getBooks: List[Book] = {
181 | collection.find().toList.map(grater[Book].asObject(_))
182 | }
183 |
184 | }
185 |
--------------------------------------------------------------------------------