├── .bettercodehub.yml ├── .dockerignore ├── .gitignore ├── .travis.yml ├── Dockerfile ├── Jenkinsfile ├── LICENSE ├── README.adoc ├── Vagrantfile ├── build.gradle ├── build.seu ├── contributing.json ├── docker-compose.yml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── native ├── jacob-1.14.3-x64.dll └── jacob-1.14.3-x86.dll ├── package.json ├── scripts ├── git.gradle └── jenkins.gradle └── src ├── ansible ├── group_vars │ └── all.yml ├── inventory │ └── development ├── jenkinsci.yml └── templates │ └── pipeline-job.xml ├── cloud ├── dcos │ ├── dcos-as-code.json │ └── dcos-vagrant.sh ├── k8s │ ├── k8s-as-code.yml │ └── k8s-vagrant.sh ├── openshift │ ├── base-image-stream.yaml │ ├── build-config-pipeline.yaml │ ├── build-config.yaml │ └── image-stream.yaml └── swarm │ ├── Vagrantfile │ ├── config.rb │ └── user-data ├── docs ├── architecture │ ├── 01_introduction_and_goals.adoc │ ├── 02_architecture_constraints.adoc │ ├── 03_system_scope_and_context.adoc │ ├── 04_solution_strategy.adoc │ ├── 05_building_block_view.adoc │ ├── 06_runtime_view.adoc │ ├── 07_deployment_view.adoc │ ├── 08_concepts.adoc │ ├── 09_design_decisions.adoc │ ├── 10_quality_scenarios.adoc │ ├── 11_technical_risks.adoc │ ├── 12_glossary.adoc │ ├── about-arc42.adoc │ ├── appendix-contributing.adoc │ ├── appendix-examples.adoc │ ├── appendix-references.adoc │ ├── config.adoc │ ├── everything-as-code.adoc │ └── images │ │ └── qaware-logo.png └── presentation │ ├── Everything-as-code.md │ └── Images │ ├── myself.png │ └── workbench.png ├── gatling ├── resources │ ├── conf │ │ └── gatling.conf │ └── data │ │ └── books.csv └── scala │ └── everything │ └── as │ └── code │ └── BooksPerformanceTest.scala ├── main ├── java │ └── everything │ │ └── as │ │ └── code │ │ └── BookApplication.java ├── kotlin │ └── everything │ │ └── as │ │ └── code │ │ ├── Book.kt │ │ ├── BookResource.kt │ │ ├── Bookshelf.kt │ │ ├── BookstoreAPI.kt │ │ ├── CORSFilter.kt │ │ ├── EverythingAsCode.kt │ │ └── LoggerProducer.kt ├── resources │ └── META-INF │ │ └── beans.xml └── webapp │ ├── BookApplication.js │ ├── WEB-INF │ ├── glassfish-web.xml │ └── web.xml │ ├── book.png │ ├── index.html │ └── style.css ├── test ├── groovy │ └── everything │ │ └── as │ │ └── code │ │ ├── BookResourceSpec.groovy │ │ ├── BooksArchitectureSpec.groovy │ │ ├── BooksIntegrationSpec.groovy │ │ ├── BookshelfSpec.groovy │ │ ├── BookstoreAPISpec.groovy │ │ ├── IndexPage.groovy │ │ └── IndexPageSpec.groovy └── resources │ └── GebConfig.groovy └── vagrant ├── .ansible.cfg ├── cache └── apt-archives │ └── .donotdelete └── vagrant.yml /.bettercodehub.yml: -------------------------------------------------------------------------------- 1 | component_depth: 6 2 | languages: 3 | - groovy 4 | - java 5 | - scala 6 | - kotlin 7 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | .idea/ 3 | .vagrant/ 4 | 5 | gradle/ 6 | native/ 7 | scripts/ 8 | src/ 9 | 10 | gradlew 11 | gradlew.bat 12 | gradle.properties 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | 4 | *.log 5 | .gradle/ 6 | build/ 7 | out/ 8 | 9 | gradle/wrapper/dists/ 10 | gradle/caches/ 11 | gradle/native/ 12 | 13 | # Ignore Gradle GUI config 14 | gradle-app.setting 15 | 16 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 17 | !gradle-wrapper.jar 18 | 19 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 20 | hs_err_pid* 21 | 22 | # Structurizr definition files 23 | structurizr*.json 24 | 25 | # Ignore SEU as Code files 26 | seuac.*.db 27 | 28 | # Ignore temporary generated files abd thumbnails metadata generated by OSX. 29 | *~ 30 | .DS_Store 31 | .gradletasknamecache 32 | 33 | .vagrant 34 | src/vagrant/cache/apt-archives/*.deb 35 | src/vagrant/cache/apt-archives/lock 36 | 37 | src/cloud/dcos/dcos-vagrant-*/ 38 | src/cloud/k8s/kubernetes.tar.gz 39 | src/vagrant/cache/apt-archives/partial/ 40 | structure101-settings.javax.hsp 41 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | env: 7 | - TERM=dumb 8 | 9 | before_cache: 10 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 11 | cache: 12 | directories: 13 | - $HOME/.gradle/caches/ 14 | - $HOME/.gradle/wrapper/ 15 | 16 | before_install: 17 | - chmod +x gradlew 18 | 19 | script: ./gradlew build asciidoctor -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM qaware-oss-docker-registry.bintray.io/base/alpine-k8s-openjdk8:8u121 2 | MAINTAINER M.-Leander Reimer 3 | 4 | RUN mkdir -p /app 5 | ADD build/distributions/everything-as-code-1.2.3.tar /app 6 | 7 | WORKDIR /app/everything-as-code-1.2.3 8 | RUN chmod 755 bin/everything-as-code 9 | 10 | EXPOSE 18080 11 | 12 | CMD ./bin/everything-as-code 13 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | 3 | def appName = "everything-as-code" 4 | def project = "" 5 | 6 | node { 7 | stage('Pipeline-as-code') { 8 | echo "The build pipeline for Everything-as-code." 9 | echo "Build ${env.BUILD_TAG}" 10 | } 11 | 12 | stage("Initialize") { 13 | project = env.PROJECT_NAME 14 | } 15 | 16 | stage('Checkout SCM') { 17 | checkout scm 18 | } 19 | 20 | stage('Build/Analyse/Test') { 21 | sh './gradlew clean build' 22 | archiveUnitTestResults() 23 | archiveDistributions() 24 | stash name: "tar", includes: "build/libs/everything-as-code-1.2.3.tar" 25 | } 26 | 27 | stage('Generate Documentation') { 28 | sh './gradlew asciidoctor' 29 | } 30 | 31 | // add additional stages to build docker image 32 | // run integration and performance tests 33 | // maybe add a stage to deploy the image to 34 | // the infrastructure 35 | 36 | /* 37 | stage("Build Docker Image") { 38 | unstash name: "tar" 39 | sh "oc start-build everything-as-code-image --from-file=build/libs/everything-as-code-1.2.3.tar -n ${project}" 40 | openshiftVerifyBuild bldCfg: "everything-as-code-image", namespace: project, waitTime: '20', waitUnit: 'min' 41 | } 42 | 43 | stage("Deploy to OpenShift") { 44 | openshiftDeploy deploymentConfig: appName, namespace: project 45 | } 46 | */ 47 | 48 | } 49 | 50 | def archiveUnitTestResults() { 51 | step([$class: "JUnitResultArchiver", testResults: "build/**/TEST-*.xml"]) 52 | } 53 | 54 | def archiveDistributions() { 55 | step([$class: 'ArtifactArchiver', artifacts: 'build/distributions/*.zip', fingerprint: true]) 56 | } 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 M.-Leander Reimer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | image:https://travis-ci.org/lreimer/everything-as-code.svg?branch=master["Build Status", link="https://travis-ci.org/lreimer/everything-as-code"] 2 | image:https://bettercodehub.com/edge/badge/lreimer/everything-as-code?branch=master["BCH compliance", link="https://bettercodehub.com/"] 3 | image:https://img.shields.io/badge/license-MIT%20License-blue.svg["MIT License", link=https://github.com/lreimer/everything-as-code/blob/master/LICENSE"] 4 | 5 | = Everything-as-code. A polyglot experiment. 6 | 7 | As modern, agile developers we love to master several different languages all at once to be 100% productive. 8 | We define our development environments using Gradle. We implement our software in Java, Kotlin or another JVM 9 | based language. We use Groovy or Scala to test our code at different layers. We construct the build pipelines 10 | for our software using a Groovy DSL or JSON. We use YAML and Python to describe the infrastructure and the 11 | deployment for our applications. We document our architectures using AsciiDoc and JRuby. Welcome to Babel! 12 | Making the right choice in this multitude of available languages is not easy. This polyglot example project is 13 | a highly opinionated journey into the modern era of software industrialization. 14 | 15 | == SEU-as-code 16 | 17 | This project uses SEU-as-code to build the software development environment for this project using Gradle. The definition 18 | is contained in the `build.seu` build file. To bootstrap the SEU, you need to issue the following command: 19 | ```bash 20 | $ ./gradlew -b build.seu bootstrapSeu 21 | ``` 22 | 23 | == Build-as-code 24 | 25 | There are quite some build tools around, every one with its own strengths. Usually I would have used Maven 26 | to build this kind of project. But Gradle beats Maven by far. It is way more flexible since you can use it 27 | in a declarative as well as imperative way. It is super fast, since it only executes the really required tasks 28 | in case of changes. It is highly customizable, via scripts and plugins. And it is short. 29 | 30 | ```bash 31 | $ ./gradlew clean build 32 | ``` 33 | 34 | == Main-as-code 35 | 36 | What is your favourite primary language? Java is not a bad choice at all. But Kotlin is a serious contender. 37 | Highly expressive, easy to learn for a Java developer. Stable. 38 | 39 | == Test-as-code 40 | 41 | This project uses Groovy and Spock for Unit and Integration testing, and Scala with Gatling for Performance testing. 42 | Spock is JUnit, Hamcrest and Mockito on steroids. Plus a super slick Groovy based DSL. Writing tests like this is fun. 43 | Also the integration with other well-known frameworks such as RestAssured is very easy and expressive with Spock. 44 | 45 | We use Scala and Gatling as a framework to implement the load tests for this project. Gatling has an 46 | expressive DSL, any kind of load test can be expressed easily, not matter if it is a REST-stype project 47 | or a JSF-based UI project. 48 | 49 | In order to run the integration and performance tests you currently have to start the application: 50 | ```bash 51 | $ ./gradlew run 52 | $ ./gradlew gatlingRun 53 | ``` 54 | 55 | == Pipeline-as-code 56 | 57 | We use the latest Jenkins CI server together with the new Pipeline plugin to programmatically define 58 | the build pipeline for the project. First, we start a Jenkins instance a Docker container: 59 | ```bash 60 | $ docker run --name jenkinsci -p 8080:8080 -p 50000:50000 -v ~/Jenkins:/var/jenkins_home jenkinsci/jenkins 61 | ``` 62 | 63 | Next, we add a `Jenkinsfile` to our source repository. This file defines the build pipeline with the 64 | individual steps and stages. 65 | 66 | == Infrastructure-as-code 67 | 68 | First, we are going to package our micro service application in a Docker container. You can do this on the command 69 | line or using a suitable Gradle plugin. 70 | ```bash 71 | $ ./gradlew buildDockerImage 72 | $ ./gradlew tagDockerImage pushDockerImage 73 | ``` 74 | 75 | For easy local deployment of the Docker image you can either start a container manually or use Docker Compose 76 | to manage the lifecycle. 77 | ```bash 78 | $ docker-compose -d up 79 | $ docker-compose down 80 | ``` 81 | 82 | Sometimes it is useful to have a separate local VM you can use to spin up a Docker Swarm cluster with CoreOS, or 83 | maybe only have a box that you can use as an Ansible control node. We use Vagrant and Ruby for this kind of job. 84 | ```bash 85 | $ vagrant up 86 | ``` 87 | 88 | In the Vagrant box we now have an Ansible installation for easy code based infrastructure provisioning. 89 | You can log into the box using SSH and then execute the Ansible playbooks. 90 | ```bash 91 | $ vagrant ssh 92 | $ cd provisioning 93 | $ ansible-playbook -i development jenkinsci.yml 94 | ``` 95 | 96 | To deploy our microservice to DC/OS and Marathon you need a cluster. You can create a cluster 97 | locally using Vagrant or using a cloud provider. Maybe use something like Terraform to create it. 98 | Then you can create an app in Marathon using: 99 | ```bash 100 | $ dcos marathon app add src/cloud/dcos-as-code.json 101 | ``` 102 | 103 | To deploy our microservice to Kubernetes you need a K8S cluster. You can create a cluster 104 | locally using Vagrant or by using a cloud provider. Maybe use something like Terraform to create it. 105 | Then you can create a deployment using: 106 | ```bash 107 | $ kubectl create -f src/cloud/k8s-as-code.yml 108 | ``` 109 | 110 | == Documentation-as-code 111 | 112 | We want our technical documentation close to the actual source code. We do not want to use a specific editor 113 | such as Word. The format needs to be lightweight and developer friendly. We want to version control and diff 114 | our documentation. Good choices are Markdown and Asciidoc. 115 | 116 | With AsciidoctorJ we can easily run Asciidoctor on the JVM using JRuby. There also is an excellent Gradle plugin 117 | available, as well as IDEs for a little more comfort such as IntelliJ or Atom. As an example we used the arc42 118 | documentation template. To generate the document in HTML5 and PDF run: 119 | ```bash 120 | $ ./gradlew -t ascii 121 | ``` 122 | 123 | == Presentation-as-code 124 | 125 | The slides for the initial JavaOne 2016 presentation are written in Markdown. There are several rendering alternatives available. 126 | Or if you are a JavaScript fan you can use Reveal.js as a framework to program your slides. 127 | 128 | ```markdown 129 | --- 130 | ## [fit] These slides are written in Markdown. 131 | 132 | - This is for real programmers! :smiley: 133 | - Several open source projects available 134 | 135 | --- 136 | ``` 137 | 138 | == References 139 | 140 | - http://seu-as-code.io[Lightweight Developer Provisioning with Gradle] 141 | - https://gradle.org[Gradle Build Tool] 142 | - https://kotlinlang.org/docs/reference/[Kotlin Language Reference] 143 | - http://www.groovy-lang.org/documentation.html[Groovy Language Documentation] 144 | - http://spockframework.org/spock/docs/[Spcok Framework Reference Documentation] 145 | - http://gatling.io/docs/2.2.2/[Gatling Documentation] 146 | - http://scala-lang.org/documentation/[Scala Language Documentation] 147 | - https://jenkins.io/doc/pipeline/[Getting started with Jenkins Pipeline] 148 | - https://www.vagrantup.com/docs/[Vagrant Documentation] 149 | - http://kubernetes.io/docs/[Kubernetes Documentation] 150 | - https://mesosphere.github.io/marathon/docs/[DC/OS Marathon Documentation] 151 | - https://docs.ansible.com/ansible/intro.html[Ansible Documentation] 152 | - https://github.com/asciidoctor/asciidoctorj[AsciidoctorJ] 153 | - https://arc42.github.io[Arc42 Architecture Documentation] 154 | 155 | 156 | == Maintainer 157 | 158 | M.-Leander Reimer (@lreimer), 159 | 160 | == License 161 | 162 | The software and documentation is provided under the MIT open source license, 163 | read the `LICENSE` file for details. 164 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | require 'yaml' 4 | 5 | VAGRANTFILE_API_VERSION = "2" 6 | 7 | $setup = < 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main/webapp/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | 5 | body { 6 | height: 100%; 7 | margin: 0; 8 | background: linear-gradient(#ff9224, #ff6f1f) no-repeat fixed; 9 | font-family: Arial, Helvetica, sans-serif; 10 | } 11 | 12 | header{ 13 | text-align: center; 14 | } 15 | 16 | header h1{ 17 | color: #ffffff; 18 | width: 300px; 19 | height: 110px; 20 | display: inline-block; 21 | background: url("book.png") no-repeat; 22 | background-size: 150px; 23 | } 24 | 25 | header h1:hover{ 26 | /*Animation. Because I am funny*/ 27 | animation-duration: 5s; 28 | animation-iteration-count: infinite; 29 | animation-name: shaking-logo; 30 | } 31 | 32 | .book-list { 33 | display: flex; 34 | flex-wrap: wrap; 35 | justify-content: center; 36 | } 37 | 38 | .book { 39 | color: #444; 40 | width: 20%; 41 | margin: 10px; 42 | padding: 20px; 43 | background-color: white; 44 | border-radius: 3px; 45 | box-shadow: 0 1px 6px rgba(0, 0, 0, .12), 0 1px 6px rgba(0, 0, 0, .12); 46 | transition: box-shadow .28s cubic-bezier(.4, 0, .2, 1); 47 | cursor: pointer; 48 | } 49 | 50 | .book:hover { 51 | box-shadow: 0 6px 10px rgba(0, 0, 0, .23), 0 10px 30px rgba(0, 0, 0, .19); 52 | } 53 | 54 | .book-title, 55 | .book-author, 56 | .book-isbn{ 57 | word-wrap:break-word; 58 | } 59 | 60 | .book-title{ 61 | border-bottom: 1px #e8e8e8 solid; 62 | margin-top:0; 63 | } 64 | 65 | .book-search{ 66 | text-align: center; 67 | margin-bottom: 10px; 68 | } 69 | 70 | .book-search .search-field{ 71 | height: 20px; 72 | border: none; 73 | margin: 1px; 74 | padding: 5px; 75 | -webkit-appearance: none; 76 | } 77 | 78 | .book-search .search-field:focus, 79 | .book-search .search-button:focus{ 80 | outline: none; 81 | } 82 | 83 | .book-search .search-button{ 84 | height: 20px; 85 | border: none; 86 | margin: 1px; 87 | color: #ff6f1f; 88 | background-color: white; 89 | } 90 | 91 | .book-search .search-button:hover{ 92 | color: white; 93 | background-color: #444; 94 | cursor: pointer; 95 | } 96 | 97 | @keyframes shaking-logo { 98 | 2% { 99 | transform: translate(0, 1.5px) rotate(1.5deg); } 100 | 4% { 101 | transform: translate(0, -1.5px) rotate(-0.5deg); } 102 | 6% { 103 | transform: translate(0, 1.5px) rotate(-1.5deg); } 104 | 8% { 105 | transform: translate(0, -1.5px) rotate(-1.5deg); } 106 | 10% { 107 | transform: translate(0, 2.5px) rotate(1.5deg); } 108 | 12% { 109 | transform: translate(0, -0.5px) rotate(1.5deg); } 110 | 14% { 111 | transform: translate(0, -1.5px) rotate(1.5deg); } 112 | 16% { 113 | transform: translate(0, -0.5px) rotate(-1.5deg); } 114 | 18% { 115 | transform: translate(0, 0.5px) rotate(-1.5deg); } 116 | 20% { 117 | transform: translate(0, -1.5px) rotate(2.5deg); } 118 | 22% { 119 | transform: translate(0, 0.5px) rotate(-1.5deg); } 120 | 24% { 121 | transform: translate(0, 1.5px) rotate(1.5deg); } 122 | 26% { 123 | transform: translate(0, 0.5px) rotate(0.5deg); } 124 | 28% { 125 | transform: translate(0, 0.5px) rotate(1.5deg); } 126 | 30% { 127 | transform: translate(0, -0.5px) rotate(2.5deg); } 128 | 32% { 129 | transform: translate(0, 1.5px) rotate(-0.5deg); } 130 | 34% { 131 | transform: translate(0, 1.5px) rotate(-0.5deg); } 132 | 36% { 133 | transform: translate(0, -1.5px) rotate(2.5deg); } 134 | 38% { 135 | transform: translate(0, 1.5px) rotate(-1.5deg); } 136 | 40% { 137 | transform: translate(0, -0.5px) rotate(2.5deg); } 138 | 42% { 139 | transform: translate(0, 2.5px) rotate(-1.5deg); } 140 | 44% { 141 | transform: translate(0, 1.5px) rotate(0.5deg); } 142 | 46% { 143 | transform: translate(0, -1.5px) rotate(2.5deg); } 144 | 48% { 145 | transform: translate(0, -0.5px) rotate(0.5deg); } 146 | 50% { 147 | transform: translate(0, 0.5px) rotate(0.5deg); } 148 | 52% { 149 | transform: translate(0, 2.5px) rotate(2.5deg); } 150 | 54% { 151 | transform: translate(0, -1.5px) rotate(1.5deg); } 152 | 56% { 153 | transform: translate(0, 2.5px) rotate(2.5deg); } 154 | 58% { 155 | transform: translate(0, 0.5px) rotate(2.5deg); } 156 | 60% { 157 | transform: translate(0, 2.5px) rotate(2.5deg); } 158 | 62% { 159 | transform: translate(0, -0.5px) rotate(2.5deg); } 160 | 64% { 161 | transform: translate(0, -0.5px) rotate(1.5deg); } 162 | 66% { 163 | transform: translate(0, 1.5px) rotate(-0.5deg); } 164 | 68% { 165 | transform: translate(0, -1.5px) rotate(-0.5deg); } 166 | 70% { 167 | transform: translate(0, 1.5px) rotate(0.5deg); } 168 | 72% { 169 | transform: translate(0, 2.5px) rotate(1.5deg); } 170 | 74% { 171 | transform: translate(0, -0.5px) rotate(0.5deg); } 172 | 76% { 173 | transform: translate(0, -0.5px) rotate(2.5deg); } 174 | 78% { 175 | transform: translate(0, -0.5px) rotate(1.5deg); } 176 | 80% { 177 | transform: translate(0, 1.5px) rotate(1.5deg); } 178 | 82% { 179 | transform: translate(0, -0.5px) rotate(0.5deg); } 180 | 84% { 181 | transform: translate(0, 1.5px) rotate(2.5deg); } 182 | 86% { 183 | transform: translate(0, -1.5px) rotate(-1.5deg); } 184 | 88% { 185 | transform: translate(0, -0.5px) rotate(2.5deg); } 186 | 90% { 187 | transform: translate(0, 2.5px) rotate(-0.5deg); } 188 | 92% { 189 | transform: translate(0, 0.5px) rotate(-0.5deg); } 190 | 94% { 191 | transform: translate(0, 2.5px) rotate(0.5deg); } 192 | 96% { 193 | transform: translate(0, -0.5px) rotate(1.5deg); } 194 | 98% { 195 | transform: translate(0, -1.5px) rotate(-0.5deg); } 196 | 0%, 100% { 197 | transform: translate(0, 0) rotate(0); } } 198 | -------------------------------------------------------------------------------- /src/test/groovy/everything/as/code/BookResourceSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2016 M.-Leander Reimer 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package everything.as.code 25 | 26 | import spock.lang.Specification 27 | 28 | import java.util.logging.Logger 29 | 30 | /** 31 | * An interaction spec for our book REST resource. 32 | */ 33 | class BookResourceSpec extends Specification { 34 | 35 | Bookshelf bookshelf 36 | BookResource resource 37 | 38 | void setup() { 39 | bookshelf = Mock(Bookshelf) 40 | resource = new BookResource(bookshelf, Stub(Logger)) 41 | } 42 | 43 | def "Get list of all books"() { 44 | given: 'a fresh resource and initialized mock' 45 | bookshelf.all() >> [new Book('Title', 'ISBN', 'Unknown')] 46 | 47 | when: 'we GET the list of all books' 48 | def books = resource.books(null) 49 | 50 | then: 'the test book should be returned' 51 | books.size() == 1 52 | books[0].title == 'Title' 53 | books[0].isbn == 'ISBN' 54 | books[0].author == 'Unknown' 55 | } 56 | 57 | def "Get a book by its title"() { 58 | given: 59 | bookshelf.byTitle("Title") >> [new Book('Title', '4711', 'Unknown')] 60 | 61 | expect: 62 | resource.books("Title").size() == 1 63 | } 64 | 65 | def "Get a book by its ISBN"() { 66 | given: 67 | def book = new Book('Title', '4711', 'Unknown') 68 | 69 | when: 70 | def response = resource.byIsbn("4711") 71 | 72 | then: 73 | 1 * bookshelf.byIsbn("4711") >> book 74 | response.status == 200 75 | response.entity == book 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/groovy/everything/as/code/BooksArchitectureSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2016 M.-Leander Reimer 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package everything.as.code 25 | 26 | import com.structurizr.Workspace 27 | import com.structurizr.api.StructurizrClient 28 | import com.structurizr.model.Tags 29 | import com.structurizr.view.PaperSize 30 | import com.structurizr.view.Shape 31 | import com.structurizr.view.Styles 32 | import spock.lang.Specification 33 | 34 | /** 35 | * Describe the Architecture-as-code using Structurizr. 36 | */ 37 | class BooksArchitectureSpec extends Specification { 38 | 39 | def "Define the architecture using Structurizr"() { 40 | expect: 'the architecture definition' 41 | def workspace = new Workspace("Everything-as-code", "This is a model of the system context of Everything-as-code.") 42 | def model = workspace.model 43 | 44 | // create a model and the software system we want to describe 45 | def bookApp = model.addSoftwareSystem("Book Application", "The best source to get info on books.") 46 | 47 | // create the various types of people (roles) that use the software system 48 | def anonymousUser = model.addPerson("Anonymous User", "Anybody on the web.") 49 | anonymousUser.uses(bookApp, "Searches for books and views details.") 50 | 51 | def browser = bookApp.addContainer("Web Browser", 52 | "Allows users to view information about books", 53 | "Edge, Chrome, Firefox") 54 | anonymousUser.uses(browser, "Views information from and makes requests to") 55 | 56 | def webApp = bookApp.addContainer("Web Application", 57 | "Hosts the browser-based web application and services", 58 | "Payara Fish") 59 | browser.uses(webApp, "uses [JSON/HTTPS]") 60 | 61 | // now create the system context view based upon the model 62 | def viewSet = workspace.views 63 | def contextView = viewSet.createSystemContextView(bookApp, "context", "The system context diagram for the book service.") 64 | contextView.setPaperSize(PaperSize.Slide_4_3) 65 | contextView.addAllSoftwareSystems() 66 | contextView.addAllPeople() 67 | 68 | def containerView = viewSet.createContainerView(bookApp, "container", "The container view diagram for the book service.") 69 | containerView.setPaperSize(PaperSize.Slide_4_3) 70 | containerView.addAllContainers() 71 | containerView.addAllPeople() 72 | 73 | // tag and style some elements 74 | bookApp.addTags("Everything-as-code") 75 | 76 | Styles styles = viewSet.configuration.styles 77 | styles.addElementStyle(Tags.ELEMENT).width(600).height(450).color("#FFFFFF").fontSize(40).shape(Shape.RoundedBox) 78 | styles.addElementStyle("Everything-as-code").background("#041F37") 79 | styles.addElementStyle(Tags.SOFTWARE_SYSTEM).background("#2A4E6E") 80 | styles.addElementStyle(Tags.PERSON).width(575).background("#728DA5").shape(Shape.Person) 81 | styles.addRelationshipStyle(Tags.RELATIONSHIP).thickness(5).fontSize(40).width(500) 82 | 83 | // TODO: add Structurizr API credentials here 84 | def client = new StructurizrClient("", "") 85 | client.putWorkspace(22571, workspace) 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /src/test/groovy/everything/as/code/BooksIntegrationSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2016 M.-Leander Reimer 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package everything.as.code 25 | 26 | import io.restassured.specification.RequestSpecification 27 | import spock.lang.Shared 28 | import spock.lang.Specification 29 | import spock.lang.Unroll 30 | 31 | import static io.restassured.RestAssured.* 32 | import static org.hamcrest.Matchers.equalTo 33 | 34 | /** 35 | * An integration test using RestAssured to check proper REST API behaviour 36 | * over the wire. 37 | */ 38 | class BooksIntegrationSpec extends Specification { 39 | 40 | @Shared 41 | RequestSpecification request 42 | 43 | void setupSpec() { 44 | request = given().config(config()).baseUri('http://localhost').port(18080) 45 | } 46 | 47 | def "Get all books is OK."() { 48 | when: 49 | def response = request.get('/api/books') 50 | 51 | then: 52 | with(response) { 53 | statusCode == 200 54 | expect().body("length()", equalTo(2)) 55 | } 56 | } 57 | 58 | @Unroll 59 | def "Get book by title is HTTP #status"() { 60 | when: 61 | def response = request.param('title', title).get('/api/books') 62 | 63 | then: 64 | with(response) { 65 | statusCode == status 66 | expect().body("title", equalTo(title)) 67 | expect().body("isbn", equalTo(isbn)) 68 | } 69 | 70 | where: 71 | title || status | isbn 72 | "The Hitchhiker's Guide to the Galaxy" || 200 | "0345391802" 73 | "Life, the Universe and Everything" || 200 | "0345391829" 74 | } 75 | 76 | @Unroll 77 | def "Get book by ISBN #isbn is HTTP #status"() { 78 | when: 79 | def response = request.get("/api/books/$isbn") 80 | 81 | then: 82 | with(response) { 83 | statusCode == status 84 | expect().body("isbn", equalTo(isbn)) 85 | } 86 | 87 | where: 88 | isbn || status 89 | "0345391802" || 200 90 | "0345391829" || 200 91 | "1234567890" || 404 92 | } 93 | } -------------------------------------------------------------------------------- /src/test/groovy/everything/as/code/BookshelfSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2016 M.-Leander Reimer 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package everything.as.code 25 | 26 | import spock.lang.Specification 27 | import spock.lang.Subject 28 | import spock.lang.Unroll 29 | 30 | /** 31 | * Basic Spock specification for our bookshelf. 32 | */ 33 | class BookshelfSpec extends Specification { 34 | 35 | @Subject 36 | def bookshelf = new Bookshelf() 37 | 38 | def "Check all books are there"() { 39 | expect: 'the correct number of books' 40 | bookshelf.all().size() == 5 41 | } 42 | 43 | @Unroll 44 | def "Find book #title by ISBN #isbn"() { 45 | when: 'we search a book by ISBN' 46 | def book = bookshelf.byIsbn(isbn) 47 | 48 | then: 'the title and author are correct' 49 | book?.title == title 50 | book?.author == author 51 | 52 | where: 53 | isbn || title | author 54 | "0345391802" || "The Hitchhiker's Guide to the Galaxy" | "Douglas Adams" 55 | "0345391829" || "Life, the Universe and Everything" | "Douglas Adams" 56 | } 57 | 58 | def "Search a known book by it's title"() { 59 | expect: 'we search a book by a known title' 60 | bookshelf.byTitle("Life, the Universe and Everything").size() == 1 61 | } 62 | 63 | def "Search an unknown book by it's title"() { 64 | expect: 'we search a book by an unknown title' 65 | bookshelf.byTitle("Everything-as-code").size() == 0 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/groovy/everything/as/code/BookstoreAPISpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2016 M.-Leander Reimer 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package everything.as.code 25 | 26 | import spock.lang.Specification 27 | 28 | /** 29 | * Basic test spec for our REST API class. 30 | */ 31 | class BookstoreAPISpec extends Specification { 32 | def "Check correct number of classes"() { 33 | setup: 34 | def api = new BookstoreAPI() 35 | 36 | expect: 37 | api.classes.size() == 2 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/groovy/everything/as/code/IndexPage.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2016 M.-Leander Reimer 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package everything.as.code 25 | 26 | import geb.Page 27 | 28 | /** 29 | * The Geb page object for the QAware index page. 30 | */ 31 | class IndexPage extends Page { 32 | static url = "http://localhost:18080" 33 | static at = { browser.title.contains("Everything-as-code") } 34 | static content = { 35 | headline { $("h1", 0) } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/groovy/everything/as/code/IndexPageSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2016 M.-Leander Reimer 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package everything.as.code 25 | 26 | import geb.spock.GebReportingSpec 27 | import spock.lang.Stepwise 28 | 29 | import static org.hamcrest.CoreMatchers.containsString 30 | import static org.junit.Assert.assertThat 31 | 32 | /** 33 | * A simple Geb test using Spock to check the index page. 34 | */ 35 | @Stepwise 36 | class IndexPageSpec extends GebReportingSpec { 37 | def "Launch browser and navigate to index page"() { 38 | when: 'we navigate to the index page' 39 | go("http://localhost:18080") 40 | 41 | then: 'the index page is displayed' 42 | waitFor { at IndexPage } 43 | 44 | and: 'the headline is correct' 45 | assertThat headline.text(), containsString("Everything-as-code") 46 | } 47 | } -------------------------------------------------------------------------------- /src/test/resources/GebConfig.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2016 M.-Leander Reimer 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | import org.openqa.selenium.htmlunit.HtmlUnitDriver 25 | 26 | driver = { 27 | def htmlUnitDriver = new HtmlUnitDriver() 28 | htmlUnitDriver.javascriptEnabled = false 29 | htmlUnitDriver 30 | } 31 | 32 | reportsDir = "build/reports/geb" 33 | 34 | waiting { 35 | timeout = 30 36 | retryInterval = 1 37 | } 38 | 39 | environments { 40 | // activate using -Dgeb.env=HtmlUnit 41 | HtmlUnit { 42 | driver = { 43 | def htmlUnitDriver = new HtmlUnitDriver() 44 | htmlUnitDriver.javascriptEnabled = false 45 | htmlUnitDriver 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/vagrant/.ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | log_path=~/ansible.log 3 | host_key_checking = False 4 | ask_pass=True 5 | remote_tmp = /tmp/.ansible/tmp 6 | forks=20 7 | pipelining=True -------------------------------------------------------------------------------- /src/vagrant/cache/apt-archives/.donotdelete: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lreimer/everything-as-code/ce688be59ac332ca2b58243fff067df23ba810f8/src/vagrant/cache/apt-archives/.donotdelete -------------------------------------------------------------------------------- /src/vagrant/vagrant.yml: -------------------------------------------------------------------------------- 1 | vm: 2 | name: everything-as-code-1.2.3 --------------------------------------------------------------------------------