├── .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 | 69 | 70 | 157 | -------------------------------------------------------------------------------- /client/components/tc-books/tc-books.html: -------------------------------------------------------------------------------- 1 | 2 | 12 | 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 | 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 | 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 | --------------------------------------------------------------------------------