TProv or the Tomcat Provisioner is a Sinatra app that demonstrates how to build a simple PAAS with Docker. It allows you to provision Tomcat applications in Docker containers.
65 |
66 |
68 |
69 | <%= javascript_script_tag "/js/jquery.js" %>
70 | <%= javascript_script_tag "/js/bootstrap.min.js" %>
71 |
72 |
73 |
--------------------------------------------------------------------------------
/code/6/tomcat/tprov/lib/version.rb:
--------------------------------------------------------------------------------
1 | module TProv
2 | VERSION = "0.0.6"
3 | end
4 |
--------------------------------------------------------------------------------
/code/6/tomcat/tprov/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | $LOAD_PATH.unshift(File.dirname(__FILE__))
2 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3 |
4 | require 'tprov'
5 | require 'rspec'
6 | require 'rack/test'
7 |
8 | set :environment, :test
9 | set :run, false
10 | set :raise_errors, true
11 | set :logging, false
12 |
13 | RSpec.configure do |config|
14 | config.include Rack::Test::Methods
15 | config.mock_framework = :mocha
16 | end
17 |
18 | ENV['RACK_ENV'] = "test"
19 |
20 | def app
21 | @app ||= TProv::Application
22 | end
23 |
24 |
--------------------------------------------------------------------------------
/code/6/tomcat/tprov/spec/tprov_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe TProv::Application do
4 |
5 | describe "GET '/'" do
6 | it "should return the index page." do
7 | get '/'
8 | last_response.should be_ok
9 | end
10 | end
11 |
12 | describe "GET '/tomcat/list'" do
13 | it "should return the Tomcat list instances page." do
14 | get '/tomcat/list'
15 | last_response.should be_ok
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/code/6/tomcat/tprov/tprov.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | $:.push File.expand_path("../lib", __FILE__)
3 | require "version"
4 |
5 | Gem::Specification.new do |s|
6 | s.name = "tprov"
7 | s.version = TProv::VERSION
8 | s.authors = ["James Turnbull"]
9 | s.email = ["james@lovedthanlost.net"]
10 | s.homepage = "http://www.dockerbook.com"
11 | s.summary = %q{A simple app to provision Tomcat apps with Docker}
12 | s.description = %q{A simple app to provision Tomcat apps with Docker written for The Docker Book - http://www.dockerbook.com}
13 | s.license = "MIT"
14 |
15 | s.files = `git ls-files`.split("\n")
16 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18 | s.require_paths = ["lib"]
19 |
20 | s.add_dependency "sinatra"
21 | s.add_dependency "haml"
22 | s.add_dependency "sass"
23 | s.add_dependency "json"
24 | s.add_dependency "sinatra-flash"
25 | s.add_dependency "sinatra-static-assets"
26 | s.add_dependency "emk-sinatra-url-for"
27 | s.add_dependency "sinatra-redirect-with-flash"
28 | end
29 |
--------------------------------------------------------------------------------
/code/7/composeapp/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:2.7
2 | LABEL maintainer="james@example.com"
3 | ENV REFRESHED_AT 2016-08-01
4 |
5 | ADD . /composeapp
6 |
7 | WORKDIR /composeapp
8 |
9 | RUN pip install -r requirements.txt
10 |
--------------------------------------------------------------------------------
/code/7/composeapp/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction, and
10 | distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright
13 | owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all other entities
16 | that control, are controlled by, or are under common control with that entity.
17 | For the purposes of this definition, "control" means (i) the power, direct or
18 | indirect, to cause the direction or management of such entity, whether by
19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
20 | outstanding shares, or (iii) beneficial ownership of such entity.
21 |
22 | "You" (or "Your") shall mean an individual or Legal Entity exercising
23 | permissions granted by this License.
24 |
25 | "Source" form shall mean the preferred form for making modifications, including
26 | but not limited to software source code, documentation source, and configuration
27 | files.
28 |
29 | "Object" form shall mean any form resulting from mechanical transformation or
30 | translation of a Source form, including but not limited to compiled object code,
31 | generated documentation, and conversions to other media types.
32 |
33 | "Work" shall mean the work of authorship, whether in Source or Object form, made
34 | available under the License, as indicated by a copyright notice that is included
35 | in or attached to the work (an example is provided in the Appendix below).
36 |
37 | "Derivative Works" shall mean any work, whether in Source or Object form, that
38 | is based on (or derived from) the Work and for which the editorial revisions,
39 | annotations, elaborations, or other modifications represent, as a whole, an
40 | original work of authorship. For the purposes of this License, Derivative Works
41 | shall not include works that remain separable from, or merely link (or bind by
42 | name) to the interfaces of, the Work and Derivative Works thereof.
43 |
44 | "Contribution" shall mean any work of authorship, including the original version
45 | of the Work and any modifications or additions to that Work or Derivative Works
46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work
47 | by the copyright owner or by an individual or Legal Entity authorized to submit
48 | on behalf of the copyright owner. For the purposes of this definition,
49 | "submitted" means any form of electronic, verbal, or written communication sent
50 | to the Licensor or its representatives, including but not limited to
51 | communication on electronic mailing lists, source code control systems, and
52 | issue tracking systems that are managed by, or on behalf of, the Licensor for
53 | the purpose of discussing and improving the Work, but excluding communication
54 | that is conspicuously marked or otherwise designated in writing by the copyright
55 | owner as "Not a Contribution."
56 |
57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf
58 | of whom a Contribution has been received by Licensor and subsequently
59 | incorporated within the Work.
60 |
61 | 2. Grant of Copyright License.
62 |
63 | Subject to the terms and conditions of this License, each Contributor hereby
64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
65 | irrevocable copyright license to reproduce, prepare Derivative Works of,
66 | publicly display, publicly perform, sublicense, and distribute the Work and such
67 | Derivative Works in Source or Object form.
68 |
69 | 3. Grant of Patent License.
70 |
71 | Subject to the terms and conditions of this License, each Contributor hereby
72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
73 | irrevocable (except as stated in this section) patent license to make, have
74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where
75 | such license applies only to those patent claims licensable by such Contributor
76 | that are necessarily infringed by their Contribution(s) alone or by combination
77 | of their Contribution(s) with the Work to which such Contribution(s) was
78 | submitted. If You institute patent litigation against any entity (including a
79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a
80 | Contribution incorporated within the Work constitutes direct or contributory
81 | patent infringement, then any patent licenses granted to You under this License
82 | for that Work shall terminate as of the date such litigation is filed.
83 |
84 | 4. Redistribution.
85 |
86 | You may reproduce and distribute copies of the Work or Derivative Works thereof
87 | in any medium, with or without modifications, and in Source or Object form,
88 | provided that You meet the following conditions:
89 |
90 | You must give any other recipients of the Work or Derivative Works a copy of
91 | this License; and
92 | You must cause any modified files to carry prominent notices stating that You
93 | changed the files; and
94 | You must retain, in the Source form of any Derivative Works that You distribute,
95 | all copyright, patent, trademark, and attribution notices from the Source form
96 | of the Work, excluding those notices that do not pertain to any part of the
97 | Derivative Works; and
98 | If the Work includes a "NOTICE" text file as part of its distribution, then any
99 | Derivative Works that You distribute must include a readable copy of the
100 | attribution notices contained within such NOTICE file, excluding those notices
101 | that do not pertain to any part of the Derivative Works, in at least one of the
102 | following places: within a NOTICE text file distributed as part of the
103 | Derivative Works; within the Source form or documentation, if provided along
104 | with the Derivative Works; or, within a display generated by the Derivative
105 | Works, if and wherever such third-party notices normally appear. The contents of
106 | the NOTICE file are for informational purposes only and do not modify the
107 | License. You may add Your own attribution notices within Derivative Works that
108 | You distribute, alongside or as an addendum to the NOTICE text from the Work,
109 | provided that such additional attribution notices cannot be construed as
110 | modifying the License.
111 | You may add Your own copyright statement to Your modifications and may provide
112 | additional or different license terms and conditions for use, reproduction, or
113 | distribution of Your modifications, or for any such Derivative Works as a whole,
114 | provided Your use, reproduction, and distribution of the Work otherwise complies
115 | with the conditions stated in this License.
116 |
117 | 5. Submission of Contributions.
118 |
119 | Unless You explicitly state otherwise, any Contribution intentionally submitted
120 | for inclusion in the Work by You to the Licensor shall be under the terms and
121 | conditions of this License, without any additional terms or conditions.
122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of
123 | any separate license agreement you may have executed with Licensor regarding
124 | such Contributions.
125 |
126 | 6. Trademarks.
127 |
128 | This License does not grant permission to use the trade names, trademarks,
129 | service marks, or product names of the Licensor, except as required for
130 | reasonable and customary use in describing the origin of the Work and
131 | reproducing the content of the NOTICE file.
132 |
133 | 7. Disclaimer of Warranty.
134 |
135 | Unless required by applicable law or agreed to in writing, Licensor provides the
136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
138 | including, without limitation, any warranties or conditions of TITLE,
139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
140 | solely responsible for determining the appropriateness of using or
141 | redistributing the Work and assume any risks associated with Your exercise of
142 | permissions under this License.
143 |
144 | 8. Limitation of Liability.
145 |
146 | In no event and under no legal theory, whether in tort (including negligence),
147 | contract, or otherwise, unless required by applicable law (such as deliberate
148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be
149 | liable to You for damages, including any direct, indirect, special, incidental,
150 | or consequential damages of any character arising as a result of this License or
151 | out of the use or inability to use the Work (including but not limited to
152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or
153 | any and all other commercial damages or losses), even if such Contributor has
154 | been advised of the possibility of such damages.
155 |
156 | 9. Accepting Warranty or Additional Liability.
157 |
158 | While redistributing the Work or Derivative Works thereof, You may choose to
159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or
160 | other liability obligations and/or rights consistent with this License. However,
161 | in accepting such obligations, You may act only on Your own behalf and on Your
162 | sole responsibility, not on behalf of any other Contributor, and only if You
163 | agree to indemnify, defend, and hold each Contributor harmless for any liability
164 | incurred by, or claims asserted against, such Contributor by reason of your
165 | accepting any such warranty or additional liability.
166 |
167 | END OF TERMS AND CONDITIONS
168 |
169 | APPENDIX: How to apply the Apache License to your work
170 |
171 | To apply the Apache License to your work, attach the following boilerplate
172 | notice, with the fields enclosed by brackets "[]" replaced with your own
173 | identifying information. (Don't include the brackets!) The text should be
174 | enclosed in the appropriate comment syntax for the file format. We also
175 | recommend that a file or class name and description of purpose be included on
176 | the same "printed page" as the copyright notice for easier identification within
177 | third-party archives.
178 |
179 | Copyright [yyyy] [name of copyright owner]
180 |
181 | Licensed under the Apache License, Version 2.0 (the "License");
182 | you may not use this file except in compliance with the License.
183 | You may obtain a copy of the License at
184 |
185 | http://www.apache.org/licenses/LICENSE-2.0
186 |
187 | Unless required by applicable law or agreed to in writing, software
188 | distributed under the License is distributed on an "AS IS" BASIS,
189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190 | See the License for the specific language governing permissions and
191 | limitations under the License.
192 |
--------------------------------------------------------------------------------
/code/7/composeapp/README.md:
--------------------------------------------------------------------------------
1 | figapp
2 | ======
3 |
4 | Sample Python application for testing Fig in The Docker Book.
5 |
--------------------------------------------------------------------------------
/code/7/composeapp/app.py:
--------------------------------------------------------------------------------
1 | from flask import Flask
2 | from redis import Redis
3 | import os
4 |
5 | app = Flask(__name__)
6 | redis = Redis(host="redis", port=6379)
7 |
8 | @app.route('/')
9 | def hello():
10 | redis.incr('hits')
11 | return 'Hello Docker Book reader! I have been seen {0} times'.format(redis.get('hits'))
12 |
13 | if __name__ == "__main__":
14 | app.run(host="0.0.0.0", debug=True)
15 |
--------------------------------------------------------------------------------
/code/7/composeapp/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | web:
4 | image: jamtur01/composeapp
5 | command: python app.py
6 | ports:
7 | - "5000:5000"
8 | volumes:
9 | - .:/composeapp
10 | redis:
11 | image: redis
12 |
--------------------------------------------------------------------------------
/code/7/composeapp/requirements.txt:
--------------------------------------------------------------------------------
1 | flask
2 | redis
3 |
--------------------------------------------------------------------------------
/code/7/consul/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:18.04
2 | LABEL maintainer="james@example.com"
3 | ENV REFRESHED_AT 2014-08-01
4 |
5 | RUN apt-get -qq update
6 | RUN apt-get -qq install curl unzip
7 |
8 | ADD https://releases.hashicorp.com/consul/0.3.1/consul_0.3.1_linux_amd64.zip /tmp/consul.zip
9 | RUN cd /usr/sbin && unzip /tmp/consul.zip && chmod +x /usr/sbin/consul && rm /tmp/consul.zip
10 | ADD consul.json /config/
11 |
12 | EXPOSE 8300 8301 8301/udp 8302 8302/udp 8400 8500 53/udp
13 |
14 | VOLUME ["/data"]
15 |
16 | ENTRYPOINT [ "/usr/sbin/consul", "agent", "-config-dir=/config" ]
17 | CMD []
18 |
--------------------------------------------------------------------------------
/code/7/consul/consul.json:
--------------------------------------------------------------------------------
1 | {
2 | "data_dir": "/data",
3 | "ui_dir": "/webui",
4 | "client_addr": "0.0.0.0",
5 | "ports": {
6 | "dns": 53
7 | },
8 | "recursor": "8.8.8.8"
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/code/7/consul/consul/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:18.04
2 | LABEL maintainer="james@example.com"
3 | ENV REFRESHED_AT 2016-06-01
4 |
5 | RUN apt-get -qq update
6 | RUN apt-get -qq install curl unzip
7 |
8 | ADD https://releases.hashicorp.com/consul/0.6.4/consul_0.6.4_linux_amd64.zip /tmp/consul.zip
9 | RUN cd /usr/sbin && unzip /tmp/consul.zip && chmod +x /usr/sbin/consul && rm /tmp/consul.zip
10 |
11 | RUN mkdir -p /webui/
12 | ADD https://releases.hashicorp.com/consul/0.6.4/consul_0.6.4_web_ui.zip /webui/webui.zip
13 | RUN cd /webui && unzip webui.zip && rm webui.zip
14 |
15 | ADD consul.json /config/
16 |
17 | EXPOSE 53/udp 8300 8301 8301/udp 8302 8302/udp 8400 8500
18 |
19 | VOLUME ["/data"]
20 |
21 | ENTRYPOINT [ "/usr/sbin/consul", "agent", "-config-dir=/config" ]
22 | CMD []
23 |
--------------------------------------------------------------------------------
/code/7/consul/consul/consul.json:
--------------------------------------------------------------------------------
1 | {
2 | "data_dir": "/data",
3 | "ui_dir": "/webui",
4 | "client_addr": "0.0.0.0",
5 | "ports": {
6 | "dns": 53
7 | },
8 | "recursor": "8.8.8.8"
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/code/7/consul/distributed_app/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:18.04
2 | LABEL maintainer="james@example.com"
3 | ENV REFRESHED_AT 2016-06-01
4 |
5 | RUN apt-get -qq update
6 | RUN apt-get -qq install ruby-dev git libcurl4-openssl-dev curl build-essential python
7 | RUN gem install --no-ri --no-rdoc uwsgi sinatra
8 |
9 | RUN mkdir -p /opt/distributed_app
10 | WORKDIR /opt/distributed_app
11 | RUN uwsgi --build-plugin https://github.com/unbit/uwsgi-consul
12 |
13 | ADD uwsgi-consul.ini /opt/distributed_app/
14 | ADD config.ru /opt/distributed_app/
15 |
16 | ENTRYPOINT [ "uwsgi", "--ini", "uwsgi-consul.ini", "--ini", "uwsgi-consul.ini:server1", "--ini", "uwsgi-consul.ini:server2" ]
17 | CMD []
18 |
19 |
--------------------------------------------------------------------------------
/code/7/consul/distributed_app/config.ru:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'sinatra'
3 |
4 | get '/' do
5 | "Hello World!"
6 | end
7 |
8 | run Sinatra::Application
9 |
--------------------------------------------------------------------------------
/code/7/consul/distributed_app/uwsgi-consul.ini:
--------------------------------------------------------------------------------
1 | [uwsgi]
2 | plugins = consul
3 | socket = 127.0.0.1:9999
4 | master = true
5 | enable-threads = true
6 |
7 | [server1]
8 | consul-register = url=http://%h.node.consul:8500,name=distributed_app,id=server1,port=2001
9 | mule = config.ru
10 |
11 | [server2]
12 | consul-register = url=http://%h.node.consul:8500,name=distributed_app,id=server2,port=2002
13 | mule = config.ru
14 |
--------------------------------------------------------------------------------
/code/7/consul/distributed_client/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:18.04
2 | LABEL maintainer="james@example.com"
3 | ENV REFRESHED_AT 2016-06-01
4 |
5 | RUN apt-get -qq update
6 | RUN apt-get -qq install ruby ruby-dev build-essential
7 | RUN gem install --no-ri --no-rdoc json
8 |
9 | RUN mkdir -p /opt/distributed_client
10 | ADD client.rb /opt/distributed_client/
11 |
12 | WORKDIR /opt/distributed_client
13 |
14 | ENTRYPOINT [ "ruby", "/opt/distributed_client/client.rb" ]
15 | CMD []
16 |
17 |
--------------------------------------------------------------------------------
/code/7/consul/distributed_client/client.rb:
--------------------------------------------------------------------------------
1 | require "rubygems"
2 | require "json"
3 | require "net/http"
4 | require "uri"
5 | require "resolv"
6 |
7 | empty = "There are no distributed applications registered in Consul"
8 |
9 | uri = URI.parse("http://consul.service.consul:8500/v1/catalog/service/distributed_app")
10 |
11 | http = Net::HTTP.new(uri.host, uri.port)
12 | request = Net::HTTP::Get.new(uri.request_uri)
13 |
14 | response = http.request(request)
15 |
16 | while true
17 | if response.body == "{}"
18 | puts empty
19 | sleep(1)
20 | elsif
21 | result = JSON.parse(response.body)
22 | result.each do |service|
23 | puts "Application #{service['ServiceName']} with element #{service["ServiceID"]} on port #{service["ServicePort"]} found on node #{service["Node"]} (#{service["Address"]})."
24 | dns = Resolv::DNS.new.getresources("distributed_app.service.consul", Resolv::DNS::Resource::IN::A)
25 | puts "We can also resolve DNS - #{service['ServiceName']} resolves to #{dns.collect { |d| d.address }.join(" and ")}."
26 | sleep(1)
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/code/7/tprov_api/.gitignore:
--------------------------------------------------------------------------------
1 | pkg/*
2 | *.gem
3 | .bundle
4 | db/statum.db
5 | config/config.yml
6 |
--------------------------------------------------------------------------------
/code/7/tprov_api/Gemfile:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 | gem "sinatra"
3 | gem "sinatra-static-assets"
4 | gem "emk-sinatra-url-for"
5 | gem "sinatra-flash"
6 | gem "sinatra-redirect-with-flash"
7 | gem "docker-api", :require => "docker"
8 | gem "redis"
9 |
--------------------------------------------------------------------------------
/code/7/tprov_api/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: http://rubygems.org/
3 | specs:
4 | docker-api (1.34.2)
5 | excon (>= 0.47.0)
6 | multi_json
7 | emk-sinatra-url-for (0.2.1)
8 | sinatra (>= 0.9.1.1)
9 | excon (0.71.0)
10 | multi_json (1.13.1)
11 | mustermann (1.0.3)
12 | rack (2.2.3)
13 | rack-protection (2.0.5)
14 | rack
15 | redis (4.1.1)
16 | sinatra (2.0.5)
17 | mustermann (~> 1.0)
18 | rack (~> 2.0)
19 | rack-protection (= 2.0.5)
20 | tilt (~> 2.0)
21 | sinatra-flash (0.3.0)
22 | sinatra (>= 1.0.0)
23 | sinatra-redirect-with-flash (0.2.1)
24 | sinatra (>= 1.0.0)
25 | sinatra-static-assets (1.0.4)
26 | sinatra (>= 1.1.0)
27 | tilt (2.0.9)
28 |
29 | PLATFORMS
30 | ruby
31 |
32 | DEPENDENCIES
33 | docker-api
34 | emk-sinatra-url-for
35 | redis
36 | sinatra
37 | sinatra-flash
38 | sinatra-redirect-with-flash
39 | sinatra-static-assets
40 |
41 | BUNDLED WITH
42 | 1.17.2
43 |
--------------------------------------------------------------------------------
/code/7/tprov_api/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 James Turnbull, james@lovedthanlost.net
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/code/7/tprov_api/README.md:
--------------------------------------------------------------------------------
1 | # TProv
2 |
3 | **TProv** or the Tomcat Provisioner is a
4 | [Sinatra](http://www.sinatrarb.com) app that demonstrates how to build
5 | a simple PAAS with Docker. It allows you to provision Tomcat
6 | applications running in Docker containers.
7 |
8 | ## Running standalone
9 |
10 | Simply run the ``bundle`` and then ``rackup`` commands.
11 |
12 | ## Bundling as a gem
13 |
14 | gem build tprov.gemspec
15 | sudo gem install tprov-0.0.1.gem
16 |
--------------------------------------------------------------------------------
/code/7/tprov_api/Rakefile:
--------------------------------------------------------------------------------
1 | $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "lib")))
2 |
3 | require 'rubygems'
4 | require 'rspec/core/rake_task'
5 | require 'bundler/gem_tasks'
6 | require 'rubygems'
7 | require 'sinatra'
8 | require 'tprov'
9 |
10 | task :default => :help
11 |
12 | desc "Run specs"
13 | task :spec do
14 | RSpec::Core::RakeTask.new(:spec) do |t|
15 | t.pattern = './spec/**/*_spec.rb'
16 | end
17 | end
18 |
19 | desc "Run IRB console with app environment"
20 | task :console do
21 | puts "Loading development console..."
22 | system("irb -r ./lib/wprov.rb")
23 | end
24 |
25 | desc "Show help menu"
26 | task :help do
27 | puts "Available rake tasks: "
28 | puts "rake console - Run a IRB console with all enviroment loaded"
29 | puts "rake spec - Run specs and calculate coverage"
30 | end
31 |
--------------------------------------------------------------------------------
/code/7/tprov_api/bin/tprov:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
4 |
5 | require 'rubygems'
6 | require 'optparse'
7 | require 'tprov'
8 | require 'version'
9 |
10 | options = {}
11 |
12 | optparse = OptionParser.new do |opts|
13 | opts.banner = "Usage: TProv [options] ..."
14 |
15 | opts.separator ''
16 | opts.separator "Configuration options:"
17 |
18 | opts.on_tail( "-v", "--version", "Show version") do
19 | puts "WProv version #{TProv::VERSION}"
20 | exit
21 | end
22 |
23 | opts.separator ""
24 | opts.separator "Common options:"
25 |
26 | opts.on_tail("-h", "--help", "Display this screen" ) do
27 | puts opts
28 | exit
29 | end
30 | end
31 |
32 | begin
33 | optparse.parse!
34 | TProv::Application.run!
35 | rescue OptionParser::InvalidArgument, OptionParser::InvalidOption, OptionParser::MissingArgument
36 | puts $!.to_s
37 | puts optparse
38 | exit
39 | end
40 |
--------------------------------------------------------------------------------
/code/7/tprov_api/config.ru:
--------------------------------------------------------------------------------
1 | $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "lib")))
2 |
3 | require 'rubygems'
4 | require 'tprov'
5 |
6 | run TProv::Application
7 |
--------------------------------------------------------------------------------
/code/7/tprov_api/lib/tprov.rb:
--------------------------------------------------------------------------------
1 | require "version"
2 | require "tprov/app"
3 |
--------------------------------------------------------------------------------
/code/7/tprov_api/lib/tprov/app.rb:
--------------------------------------------------------------------------------
1 | $: << File.dirname(__FILE__)
2 |
3 | require 'sinatra/base'
4 | require 'sinatra/url_for'
5 | require 'sinatra/static_assets'
6 | require 'sinatra/flash'
7 | require 'sinatra/redirect_with_flash'
8 | require 'json'
9 | require 'uri'
10 | require 'docker'
11 | require 'pp'
12 |
13 | module TProv
14 | class Application < Sinatra::Base
15 |
16 | register Sinatra::StaticAssets
17 | register Sinatra::Flash
18 | helpers Sinatra::RedirectWithFlash
19 |
20 | set :public_folder, File.join(File.dirname(__FILE__), 'public')
21 | set :views, File.join(File.dirname(__FILE__), 'views')
22 | set :bind, '0.0.0.0'
23 |
24 | Docker.url = ENV['DOCKER_URL'] || 'https://localhost:2375'
25 | Docker.options = {
26 | :ssl_verify_peer => false
27 | }
28 |
29 | enable :sessions, :logging, :dump_errors, :raise_errors, :show_exceptions
30 |
31 | before do
32 | @app_name = "TProv"
33 | end
34 |
35 | get '/' do
36 | erb :index
37 | end
38 |
39 | post '/tomcat/create' do
40 | if params[:name].empty? or params[:url].empty?
41 | redirect '/', :error => "You must specify a name and a URL."
42 | end
43 | success, output = get_war(params[:name], params[:url])
44 | unless success
45 | redirect '/', :error => "Tomcat application #{params[:name]} failed to be fetched because #{output}."
46 | end
47 | success, output = create_instance(params[:name])
48 | unless success
49 | redirect '/', :error => "Tomcat application #{params[:name]} failed to be created because #{output}."
50 | end
51 | redirect '/', :success => "Tomcat Application #{params[:name]} created!"
52 | end
53 |
54 | post '/tomcat/delete' do
55 | success, output = delete_instance(params[:id])
56 | unless success
57 | redirect '/', :error => "Tomcat application #{params[:name]} failed to be deleted because #{output}."
58 | end
59 | redirect '/', :success => "Instance #{params[:id]} deleted!"
60 | end
61 |
62 | get '/tomcat/list' do
63 | @instances = list_instances
64 | erb :instance_list
65 | end
66 |
67 | helpers do
68 | def get_war(name, url)
69 | container = Docker::Container.create('Cmd' => url, 'Image' => 'jamtur01/fetcher', 'name' => name)
70 | container.start
71 | container.id
72 | end
73 |
74 | def create_instance(name)
75 | container = Docker::Container.create('Image' => 'jamtur01/tomcat8')
76 | container.start('PublishAllPorts' => true, 'VolumesFrom' => name)
77 | container.id
78 | end
79 |
80 | def delete_instance(cid)
81 | container = Docker::Container.get(cid)
82 | container.kill
83 | end
84 |
85 | def list_instances
86 | @list = {}
87 | instance_ids = Docker::Container.all
88 | instance_ids.each { |id|
89 | container = Docker::Container.get(id.id)
90 | config = container.json
91 | if config['NetworkSettings']['Ports']['8080/tcp']
92 | @list[id] = { 'hostname' => config['Config']['Hostname'], 'ip' => config['NetworkSettings']['IPAddress'], 'port' => config['NetworkSettings']['Ports']['8080/tcp'][0]['HostPort'] }
93 | end
94 | }
95 | @list
96 | end
97 | end
98 |
99 | end
100 | end
101 |
--------------------------------------------------------------------------------
/code/7/tprov_api/lib/tprov/public/css/bootstrap-responsive.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Responsive v2.0.4
3 | *
4 | * Copyright 2012 Twitter, Inc
5 | * Licensed under the Apache License v2.0
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Designed and built with all the love in the world @twitter by @mdo and @fat.
9 | */
10 |
11 | .clearfix {
12 | *zoom: 1;
13 | }
14 |
15 | .clearfix:before,
16 | .clearfix:after {
17 | display: table;
18 | content: "";
19 | }
20 |
21 | .clearfix:after {
22 | clear: both;
23 | }
24 |
25 | .hide-text {
26 | font: 0/0 a;
27 | color: transparent;
28 | text-shadow: none;
29 | background-color: transparent;
30 | border: 0;
31 | }
32 |
33 | .input-block-level {
34 | display: block;
35 | width: 100%;
36 | min-height: 28px;
37 | -webkit-box-sizing: border-box;
38 | -moz-box-sizing: border-box;
39 | -ms-box-sizing: border-box;
40 | box-sizing: border-box;
41 | }
42 |
43 | .hidden {
44 | display: none;
45 | visibility: hidden;
46 | }
47 |
48 | .visible-phone {
49 | display: none !important;
50 | }
51 |
52 | .visible-tablet {
53 | display: none !important;
54 | }
55 |
56 | .hidden-desktop {
57 | display: none !important;
58 | }
59 |
60 | @media (max-width: 767px) {
61 | .visible-phone {
62 | display: inherit !important;
63 | }
64 | .hidden-phone {
65 | display: none !important;
66 | }
67 | .hidden-desktop {
68 | display: inherit !important;
69 | }
70 | .visible-desktop {
71 | display: none !important;
72 | }
73 | }
74 |
75 | @media (min-width: 768px) and (max-width: 979px) {
76 | .visible-tablet {
77 | display: inherit !important;
78 | }
79 | .hidden-tablet {
80 | display: none !important;
81 | }
82 | .hidden-desktop {
83 | display: inherit !important;
84 | }
85 | .visible-desktop {
86 | display: none !important ;
87 | }
88 | }
89 |
90 | @media (max-width: 480px) {
91 | .nav-collapse {
92 | -webkit-transform: translate3d(0, 0, 0);
93 | }
94 | .page-header h1 small {
95 | display: block;
96 | line-height: 18px;
97 | }
98 | input[type="checkbox"],
99 | input[type="radio"] {
100 | border: 1px solid #ccc;
101 | }
102 | .form-horizontal .control-group > label {
103 | float: none;
104 | width: auto;
105 | padding-top: 0;
106 | text-align: left;
107 | }
108 | .form-horizontal .controls {
109 | margin-left: 0;
110 | }
111 | .form-horizontal .control-list {
112 | padding-top: 0;
113 | }
114 | .form-horizontal .form-actions {
115 | padding-right: 10px;
116 | padding-left: 10px;
117 | }
118 | .modal {
119 | position: absolute;
120 | top: 10px;
121 | right: 10px;
122 | left: 10px;
123 | width: auto;
124 | margin: 0;
125 | }
126 | .modal.fade.in {
127 | top: auto;
128 | }
129 | .modal-header .close {
130 | padding: 10px;
131 | margin: -10px;
132 | }
133 | .carousel-caption {
134 | position: static;
135 | }
136 | }
137 |
138 | @media (max-width: 767px) {
139 | body {
140 | padding-right: 20px;
141 | padding-left: 20px;
142 | }
143 | .navbar-fixed-top,
144 | .navbar-fixed-bottom {
145 | margin-right: -20px;
146 | margin-left: -20px;
147 | }
148 | .container-fluid {
149 | padding: 0;
150 | }
151 | .dl-horizontal dt {
152 | float: none;
153 | width: auto;
154 | clear: none;
155 | text-align: left;
156 | }
157 | .dl-horizontal dd {
158 | margin-left: 0;
159 | }
160 | .container {
161 | width: auto;
162 | }
163 | .row-fluid {
164 | width: 100%;
165 | }
166 | .row,
167 | .thumbnails {
168 | margin-left: 0;
169 | }
170 | [class*="span"],
171 | .row-fluid [class*="span"] {
172 | display: block;
173 | float: none;
174 | width: auto;
175 | margin-left: 0;
176 | }
177 | .input-large,
178 | .input-xlarge,
179 | .input-xxlarge,
180 | input[class*="span"],
181 | select[class*="span"],
182 | textarea[class*="span"],
183 | .uneditable-input {
184 | display: block;
185 | width: 100%;
186 | min-height: 28px;
187 | -webkit-box-sizing: border-box;
188 | -moz-box-sizing: border-box;
189 | -ms-box-sizing: border-box;
190 | box-sizing: border-box;
191 | }
192 | .input-prepend input,
193 | .input-append input,
194 | .input-prepend input[class*="span"],
195 | .input-append input[class*="span"] {
196 | display: inline-block;
197 | width: auto;
198 | }
199 | }
200 |
201 | @media (min-width: 768px) and (max-width: 979px) {
202 | .row {
203 | margin-left: -20px;
204 | *zoom: 1;
205 | }
206 | .row:before,
207 | .row:after {
208 | display: table;
209 | content: "";
210 | }
211 | .row:after {
212 | clear: both;
213 | }
214 | [class*="span"] {
215 | float: left;
216 | margin-left: 20px;
217 | }
218 | .container,
219 | .navbar-fixed-top .container,
220 | .navbar-fixed-bottom .container {
221 | width: 724px;
222 | }
223 | .span12 {
224 | width: 724px;
225 | }
226 | .span11 {
227 | width: 662px;
228 | }
229 | .span10 {
230 | width: 600px;
231 | }
232 | .span9 {
233 | width: 538px;
234 | }
235 | .span8 {
236 | width: 476px;
237 | }
238 | .span7 {
239 | width: 414px;
240 | }
241 | .span6 {
242 | width: 352px;
243 | }
244 | .span5 {
245 | width: 290px;
246 | }
247 | .span4 {
248 | width: 228px;
249 | }
250 | .span3 {
251 | width: 166px;
252 | }
253 | .span2 {
254 | width: 104px;
255 | }
256 | .span1 {
257 | width: 42px;
258 | }
259 | .offset12 {
260 | margin-left: 764px;
261 | }
262 | .offset11 {
263 | margin-left: 702px;
264 | }
265 | .offset10 {
266 | margin-left: 640px;
267 | }
268 | .offset9 {
269 | margin-left: 578px;
270 | }
271 | .offset8 {
272 | margin-left: 516px;
273 | }
274 | .offset7 {
275 | margin-left: 454px;
276 | }
277 | .offset6 {
278 | margin-left: 392px;
279 | }
280 | .offset5 {
281 | margin-left: 330px;
282 | }
283 | .offset4 {
284 | margin-left: 268px;
285 | }
286 | .offset3 {
287 | margin-left: 206px;
288 | }
289 | .offset2 {
290 | margin-left: 144px;
291 | }
292 | .offset1 {
293 | margin-left: 82px;
294 | }
295 | .row-fluid {
296 | width: 100%;
297 | *zoom: 1;
298 | }
299 | .row-fluid:before,
300 | .row-fluid:after {
301 | display: table;
302 | content: "";
303 | }
304 | .row-fluid:after {
305 | clear: both;
306 | }
307 | .row-fluid [class*="span"] {
308 | display: block;
309 | float: left;
310 | width: 100%;
311 | min-height: 28px;
312 | margin-left: 2.762430939%;
313 | *margin-left: 2.709239449638298%;
314 | -webkit-box-sizing: border-box;
315 | -moz-box-sizing: border-box;
316 | -ms-box-sizing: border-box;
317 | box-sizing: border-box;
318 | }
319 | .row-fluid [class*="span"]:first-child {
320 | margin-left: 0;
321 | }
322 | .row-fluid .span12 {
323 | width: 99.999999993%;
324 | *width: 99.9468085036383%;
325 | }
326 | .row-fluid .span11 {
327 | width: 91.436464082%;
328 | *width: 91.38327259263829%;
329 | }
330 | .row-fluid .span10 {
331 | width: 82.87292817100001%;
332 | *width: 82.8197366816383%;
333 | }
334 | .row-fluid .span9 {
335 | width: 74.30939226%;
336 | *width: 74.25620077063829%;
337 | }
338 | .row-fluid .span8 {
339 | width: 65.74585634900001%;
340 | *width: 65.6926648596383%;
341 | }
342 | .row-fluid .span7 {
343 | width: 57.182320438000005%;
344 | *width: 57.129128948638304%;
345 | }
346 | .row-fluid .span6 {
347 | width: 48.618784527%;
348 | *width: 48.5655930376383%;
349 | }
350 | .row-fluid .span5 {
351 | width: 40.055248616%;
352 | *width: 40.0020571266383%;
353 | }
354 | .row-fluid .span4 {
355 | width: 31.491712705%;
356 | *width: 31.4385212156383%;
357 | }
358 | .row-fluid .span3 {
359 | width: 22.928176794%;
360 | *width: 22.874985304638297%;
361 | }
362 | .row-fluid .span2 {
363 | width: 14.364640883%;
364 | *width: 14.311449393638298%;
365 | }
366 | .row-fluid .span1 {
367 | width: 5.801104972%;
368 | *width: 5.747913482638298%;
369 | }
370 | input,
371 | textarea,
372 | .uneditable-input {
373 | margin-left: 0;
374 | }
375 | input.span12,
376 | textarea.span12,
377 | .uneditable-input.span12 {
378 | width: 714px;
379 | }
380 | input.span11,
381 | textarea.span11,
382 | .uneditable-input.span11 {
383 | width: 652px;
384 | }
385 | input.span10,
386 | textarea.span10,
387 | .uneditable-input.span10 {
388 | width: 590px;
389 | }
390 | input.span9,
391 | textarea.span9,
392 | .uneditable-input.span9 {
393 | width: 528px;
394 | }
395 | input.span8,
396 | textarea.span8,
397 | .uneditable-input.span8 {
398 | width: 466px;
399 | }
400 | input.span7,
401 | textarea.span7,
402 | .uneditable-input.span7 {
403 | width: 404px;
404 | }
405 | input.span6,
406 | textarea.span6,
407 | .uneditable-input.span6 {
408 | width: 342px;
409 | }
410 | input.span5,
411 | textarea.span5,
412 | .uneditable-input.span5 {
413 | width: 280px;
414 | }
415 | input.span4,
416 | textarea.span4,
417 | .uneditable-input.span4 {
418 | width: 218px;
419 | }
420 | input.span3,
421 | textarea.span3,
422 | .uneditable-input.span3 {
423 | width: 156px;
424 | }
425 | input.span2,
426 | textarea.span2,
427 | .uneditable-input.span2 {
428 | width: 94px;
429 | }
430 | input.span1,
431 | textarea.span1,
432 | .uneditable-input.span1 {
433 | width: 32px;
434 | }
435 | }
436 |
437 | @media (min-width: 1200px) {
438 | .row {
439 | margin-left: -30px;
440 | *zoom: 1;
441 | }
442 | .row:before,
443 | .row:after {
444 | display: table;
445 | content: "";
446 | }
447 | .row:after {
448 | clear: both;
449 | }
450 | [class*="span"] {
451 | float: left;
452 | margin-left: 30px;
453 | }
454 | .container,
455 | .navbar-fixed-top .container,
456 | .navbar-fixed-bottom .container {
457 | width: 1170px;
458 | }
459 | .span12 {
460 | width: 1170px;
461 | }
462 | .span11 {
463 | width: 1070px;
464 | }
465 | .span10 {
466 | width: 970px;
467 | }
468 | .span9 {
469 | width: 870px;
470 | }
471 | .span8 {
472 | width: 770px;
473 | }
474 | .span7 {
475 | width: 670px;
476 | }
477 | .span6 {
478 | width: 570px;
479 | }
480 | .span5 {
481 | width: 470px;
482 | }
483 | .span4 {
484 | width: 370px;
485 | }
486 | .span3 {
487 | width: 270px;
488 | }
489 | .span2 {
490 | width: 170px;
491 | }
492 | .span1 {
493 | width: 70px;
494 | }
495 | .offset12 {
496 | margin-left: 1230px;
497 | }
498 | .offset11 {
499 | margin-left: 1130px;
500 | }
501 | .offset10 {
502 | margin-left: 1030px;
503 | }
504 | .offset9 {
505 | margin-left: 930px;
506 | }
507 | .offset8 {
508 | margin-left: 830px;
509 | }
510 | .offset7 {
511 | margin-left: 730px;
512 | }
513 | .offset6 {
514 | margin-left: 630px;
515 | }
516 | .offset5 {
517 | margin-left: 530px;
518 | }
519 | .offset4 {
520 | margin-left: 430px;
521 | }
522 | .offset3 {
523 | margin-left: 330px;
524 | }
525 | .offset2 {
526 | margin-left: 230px;
527 | }
528 | .offset1 {
529 | margin-left: 130px;
530 | }
531 | .row-fluid {
532 | width: 100%;
533 | *zoom: 1;
534 | }
535 | .row-fluid:before,
536 | .row-fluid:after {
537 | display: table;
538 | content: "";
539 | }
540 | .row-fluid:after {
541 | clear: both;
542 | }
543 | .row-fluid [class*="span"] {
544 | display: block;
545 | float: left;
546 | width: 100%;
547 | min-height: 28px;
548 | margin-left: 2.564102564%;
549 | *margin-left: 2.510911074638298%;
550 | -webkit-box-sizing: border-box;
551 | -moz-box-sizing: border-box;
552 | -ms-box-sizing: border-box;
553 | box-sizing: border-box;
554 | }
555 | .row-fluid [class*="span"]:first-child {
556 | margin-left: 0;
557 | }
558 | .row-fluid .span12 {
559 | width: 100%;
560 | *width: 99.94680851063829%;
561 | }
562 | .row-fluid .span11 {
563 | width: 91.45299145300001%;
564 | *width: 91.3997999636383%;
565 | }
566 | .row-fluid .span10 {
567 | width: 82.905982906%;
568 | *width: 82.8527914166383%;
569 | }
570 | .row-fluid .span9 {
571 | width: 74.358974359%;
572 | *width: 74.30578286963829%;
573 | }
574 | .row-fluid .span8 {
575 | width: 65.81196581200001%;
576 | *width: 65.7587743226383%;
577 | }
578 | .row-fluid .span7 {
579 | width: 57.264957265%;
580 | *width: 57.2117657756383%;
581 | }
582 | .row-fluid .span6 {
583 | width: 48.717948718%;
584 | *width: 48.6647572286383%;
585 | }
586 | .row-fluid .span5 {
587 | width: 40.170940171000005%;
588 | *width: 40.117748681638304%;
589 | }
590 | .row-fluid .span4 {
591 | width: 31.623931624%;
592 | *width: 31.5707401346383%;
593 | }
594 | .row-fluid .span3 {
595 | width: 23.076923077%;
596 | *width: 23.0237315876383%;
597 | }
598 | .row-fluid .span2 {
599 | width: 14.529914530000001%;
600 | *width: 14.4767230406383%;
601 | }
602 | .row-fluid .span1 {
603 | width: 5.982905983%;
604 | *width: 5.929714493638298%;
605 | }
606 | input,
607 | textarea,
608 | .uneditable-input {
609 | margin-left: 0;
610 | }
611 | input.span12,
612 | textarea.span12,
613 | .uneditable-input.span12 {
614 | width: 1160px;
615 | }
616 | input.span11,
617 | textarea.span11,
618 | .uneditable-input.span11 {
619 | width: 1060px;
620 | }
621 | input.span10,
622 | textarea.span10,
623 | .uneditable-input.span10 {
624 | width: 960px;
625 | }
626 | input.span9,
627 | textarea.span9,
628 | .uneditable-input.span9 {
629 | width: 860px;
630 | }
631 | input.span8,
632 | textarea.span8,
633 | .uneditable-input.span8 {
634 | width: 760px;
635 | }
636 | input.span7,
637 | textarea.span7,
638 | .uneditable-input.span7 {
639 | width: 660px;
640 | }
641 | input.span6,
642 | textarea.span6,
643 | .uneditable-input.span6 {
644 | width: 560px;
645 | }
646 | input.span5,
647 | textarea.span5,
648 | .uneditable-input.span5 {
649 | width: 460px;
650 | }
651 | input.span4,
652 | textarea.span4,
653 | .uneditable-input.span4 {
654 | width: 360px;
655 | }
656 | input.span3,
657 | textarea.span3,
658 | .uneditable-input.span3 {
659 | width: 260px;
660 | }
661 | input.span2,
662 | textarea.span2,
663 | .uneditable-input.span2 {
664 | width: 160px;
665 | }
666 | input.span1,
667 | textarea.span1,
668 | .uneditable-input.span1 {
669 | width: 60px;
670 | }
671 | .thumbnails {
672 | margin-left: -30px;
673 | }
674 | .thumbnails > li {
675 | margin-left: 30px;
676 | }
677 | .row-fluid .thumbnails {
678 | margin-left: 0;
679 | }
680 | }
681 |
682 | @media (max-width: 979px) {
683 | body {
684 | padding-top: 0;
685 | }
686 | .navbar-fixed-top,
687 | .navbar-fixed-bottom {
688 | position: static;
689 | }
690 | .navbar-fixed-top {
691 | margin-bottom: 18px;
692 | }
693 | .navbar-fixed-bottom {
694 | margin-top: 18px;
695 | }
696 | .navbar-fixed-top .navbar-inner,
697 | .navbar-fixed-bottom .navbar-inner {
698 | padding: 5px;
699 | }
700 | .navbar .container {
701 | width: auto;
702 | padding: 0;
703 | }
704 | .navbar .brand {
705 | padding-right: 10px;
706 | padding-left: 10px;
707 | margin: 0 0 0 -5px;
708 | }
709 | .nav-collapse {
710 | clear: both;
711 | }
712 | .nav-collapse .nav {
713 | float: none;
714 | margin: 0 0 9px;
715 | }
716 | .nav-collapse .nav > li {
717 | float: none;
718 | }
719 | .nav-collapse .nav > li > a {
720 | margin-bottom: 2px;
721 | }
722 | .nav-collapse .nav > .divider-vertical {
723 | display: none;
724 | }
725 | .nav-collapse .nav .nav-header {
726 | color: #999999;
727 | text-shadow: none;
728 | }
729 | .nav-collapse .nav > li > a,
730 | .nav-collapse .dropdown-menu a {
731 | padding: 6px 15px;
732 | font-weight: bold;
733 | color: #999999;
734 | -webkit-border-radius: 3px;
735 | -moz-border-radius: 3px;
736 | border-radius: 3px;
737 | }
738 | .nav-collapse .btn {
739 | padding: 4px 10px 4px;
740 | font-weight: normal;
741 | -webkit-border-radius: 4px;
742 | -moz-border-radius: 4px;
743 | border-radius: 4px;
744 | }
745 | .nav-collapse .dropdown-menu li + li a {
746 | margin-bottom: 2px;
747 | }
748 | .nav-collapse .nav > li > a:hover,
749 | .nav-collapse .dropdown-menu a:hover {
750 | background-color: #222222;
751 | }
752 | .nav-collapse.in .btn-group {
753 | padding: 0;
754 | margin-top: 5px;
755 | }
756 | .nav-collapse .dropdown-menu {
757 | position: static;
758 | top: auto;
759 | left: auto;
760 | display: block;
761 | float: none;
762 | max-width: none;
763 | padding: 0;
764 | margin: 0 15px;
765 | background-color: transparent;
766 | border: none;
767 | -webkit-border-radius: 0;
768 | -moz-border-radius: 0;
769 | border-radius: 0;
770 | -webkit-box-shadow: none;
771 | -moz-box-shadow: none;
772 | box-shadow: none;
773 | }
774 | .nav-collapse .dropdown-menu:before,
775 | .nav-collapse .dropdown-menu:after {
776 | display: none;
777 | }
778 | .nav-collapse .dropdown-menu .divider {
779 | display: none;
780 | }
781 | .nav-collapse .navbar-form,
782 | .nav-collapse .navbar-search {
783 | float: none;
784 | padding: 9px 15px;
785 | margin: 9px 0;
786 | border-top: 1px solid #222222;
787 | border-bottom: 1px solid #222222;
788 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
789 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
790 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
791 | }
792 | .navbar .nav-collapse .nav.pull-right {
793 | float: none;
794 | margin-left: 0;
795 | }
796 | .nav-collapse,
797 | .nav-collapse.collapse {
798 | height: 0;
799 | overflow: hidden;
800 | }
801 | .navbar .btn-navbar {
802 | display: block;
803 | }
804 | .navbar-static .navbar-inner {
805 | padding-right: 10px;
806 | padding-left: 10px;
807 | }
808 | }
809 |
810 | @media (min-width: 980px) {
811 | .nav-collapse.collapse {
812 | height: auto !important;
813 | overflow: visible !important;
814 | }
815 | }
816 |
--------------------------------------------------------------------------------
/code/7/tprov_api/lib/tprov/public/css/bootstrap-responsive.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Responsive v2.0.4
3 | *
4 | * Copyright 2012 Twitter, Inc
5 | * Licensed under the Apache License v2.0
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Designed and built with all the love in the world @twitter by @mdo and @fat.
9 | */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}@media(max-width:767px){.visible-phone{display:inherit!important}.hidden-phone{display:none!important}.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}}@media(min-width:768px) and (max-width:979px){.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:18px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-group>label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.modal{position:absolute;top:10px;right:10px;left:10px;width:auto;margin:0}.modal.fade.in{top:auto}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:auto;margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;content:""}.row:after{clear:both}[class*="span"]{float:left;margin-left:20px}.container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:28px;margin-left:2.762430939%;*margin-left:2.709239449638298%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:99.999999993%;*width:99.9468085036383%}.row-fluid .span11{width:91.436464082%;*width:91.38327259263829%}.row-fluid .span10{width:82.87292817100001%;*width:82.8197366816383%}.row-fluid .span9{width:74.30939226%;*width:74.25620077063829%}.row-fluid .span8{width:65.74585634900001%;*width:65.6926648596383%}.row-fluid .span7{width:57.182320438000005%;*width:57.129128948638304%}.row-fluid .span6{width:48.618784527%;*width:48.5655930376383%}.row-fluid .span5{width:40.055248616%;*width:40.0020571266383%}.row-fluid .span4{width:31.491712705%;*width:31.4385212156383%}.row-fluid .span3{width:22.928176794%;*width:22.874985304638297%}.row-fluid .span2{width:14.364640883%;*width:14.311449393638298%}.row-fluid .span1{width:5.801104972%;*width:5.747913482638298%}input,textarea,.uneditable-input{margin-left:0}input.span12,textarea.span12,.uneditable-input.span12{width:714px}input.span11,textarea.span11,.uneditable-input.span11{width:652px}input.span10,textarea.span10,.uneditable-input.span10{width:590px}input.span9,textarea.span9,.uneditable-input.span9{width:528px}input.span8,textarea.span8,.uneditable-input.span8{width:466px}input.span7,textarea.span7,.uneditable-input.span7{width:404px}input.span6,textarea.span6,.uneditable-input.span6{width:342px}input.span5,textarea.span5,.uneditable-input.span5{width:280px}input.span4,textarea.span4,.uneditable-input.span4{width:218px}input.span3,textarea.span3,.uneditable-input.span3{width:156px}input.span2,textarea.span2,.uneditable-input.span2{width:94px}input.span1,textarea.span1,.uneditable-input.span1{width:32px}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;content:""}.row:after{clear:both}[class*="span"]{float:left;margin-left:30px}.container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:28px;margin-left:2.564102564%;*margin-left:2.510911074638298%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145300001%;*width:91.3997999636383%}.row-fluid .span10{width:82.905982906%;*width:82.8527914166383%}.row-fluid .span9{width:74.358974359%;*width:74.30578286963829%}.row-fluid .span8{width:65.81196581200001%;*width:65.7587743226383%}.row-fluid .span7{width:57.264957265%;*width:57.2117657756383%}.row-fluid .span6{width:48.717948718%;*width:48.6647572286383%}.row-fluid .span5{width:40.170940171000005%;*width:40.117748681638304%}.row-fluid .span4{width:31.623931624%;*width:31.5707401346383%}.row-fluid .span3{width:23.076923077%;*width:23.0237315876383%}.row-fluid .span2{width:14.529914530000001%;*width:14.4767230406383%}.row-fluid .span1{width:5.982905983%;*width:5.929714493638298%}input,textarea,.uneditable-input{margin-left:0}input.span12,textarea.span12,.uneditable-input.span12{width:1160px}input.span11,textarea.span11,.uneditable-input.span11{width:1060px}input.span10,textarea.span10,.uneditable-input.span10{width:960px}input.span9,textarea.span9,.uneditable-input.span9{width:860px}input.span8,textarea.span8,.uneditable-input.span8{width:760px}input.span7,textarea.span7,.uneditable-input.span7{width:660px}input.span6,textarea.span6,.uneditable-input.span6{width:560px}input.span5,textarea.span5,.uneditable-input.span5{width:460px}input.span4,textarea.span4,.uneditable-input.span4{width:360px}input.span3,textarea.span3,.uneditable-input.span3{width:260px}input.span2,textarea.span2,.uneditable-input.span2{width:160px}input.span1,textarea.span1,.uneditable-input.span1{width:60px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:18px}.navbar-fixed-bottom{margin-top:18px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 9px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#999;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:6px 15px;font-weight:bold;color:#999;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#222}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:block;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:9px 15px;margin:9px 0;border-top:1px solid #222;border-bottom:1px solid #222;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}}
10 |
--------------------------------------------------------------------------------
/code/7/tprov_api/lib/tprov/public/css/flash.css:
--------------------------------------------------------------------------------
1 | .flash {
2 | padding: 8px 35px 8px 14px;
3 | margin-bottom: 18px;
4 | color: #c09853;
5 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
6 | background-color: #fcf8e3;
7 | border: 1px solid #fbeed5;
8 | -webkit-border-radius: 4px;
9 | -moz-border-radius: 4px;
10 | border-radius: 4px;
11 | }
12 |
13 | .success {
14 | color: #468847;
15 | background-color: #dff0d8;
16 | border-color: #d6e9c6;
17 | }
18 |
19 | .error {
20 | color: #b94a48;
21 | background-color: #f2dede;
22 | border-color: #eed3d7;
23 | }
24 |
--------------------------------------------------------------------------------
/code/7/tprov_api/lib/tprov/public/img/glyphicons-halflings-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/turnbullpress/dockerbook-code/c57fe820c13f47dca64fa16c09f4e47a2198d9ff/code/7/tprov_api/lib/tprov/public/img/glyphicons-halflings-white.png
--------------------------------------------------------------------------------
/code/7/tprov_api/lib/tprov/public/img/glyphicons-halflings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/turnbullpress/dockerbook-code/c57fe820c13f47dca64fa16c09f4e47a2198d9ff/code/7/tprov_api/lib/tprov/public/img/glyphicons-halflings.png
--------------------------------------------------------------------------------
/code/7/tprov_api/lib/tprov/views/index.erb:
--------------------------------------------------------------------------------
1 |
2 |
Welcome to TProv
3 |
4 |
5 |
6 |
TProv or the Tomcat Provisioner is a Sinatra app that demonstrates how to build a simple PAAS with Docker. It allows you to provision Tomcat applications in Docker containers.
TProv API or the Tomcat Provisioner API-version is a Sinatra app that demonstrates how to build a simple PAAS with Docker. It allows you to provision Tomcat applications in Docker containers.
TProv or the Tomcat Provisioner is a Sinatra app that demonstrates how to build a simple PAAS with Docker. It allows you to provision Tomcat applications in Docker containers.