vps 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +20 -0
- data/README.md +107 -0
- data/Rakefile +10 -0
- data/VERSION +1 -0
- data/bin/vps +10 -0
- data/config/services.yml +63 -0
- data/lib/vps.rb +88 -0
- data/lib/vps/cli.rb +62 -0
- data/lib/vps/cli/domain.rb +59 -0
- data/lib/vps/cli/playbook.rb +110 -0
- data/lib/vps/cli/playbook/state.rb +170 -0
- data/lib/vps/cli/playbook/tasks.rb +262 -0
- data/lib/vps/cli/service.rb +96 -0
- data/lib/vps/cli/upstream.rb +95 -0
- data/lib/vps/core_ext/ostruct.rb +17 -0
- data/lib/vps/core_ext/string.rb +33 -0
- data/lib/vps/version.rb +7 -0
- data/playbooks/deploy.yml +43 -0
- data/playbooks/deploy/docker.yml +96 -0
- data/playbooks/init.yml +12 -0
- data/playbooks/init/ubuntu-18.04.yml +110 -0
- data/playbooks/install.yml +18 -0
- data/playbooks/install/docker/ubuntu-18.04.yml +35 -0
- data/script/console +7 -0
- data/templates/docker/data/nginx/app.conf.erb +69 -0
- data/templates/docker/docker-compose.yml.erb +58 -0
- data/templates/docker/upstream/Dockerfile.phoenix.erb +13 -0
- data/templates/docker/upstream/Dockerfile.plug.erb +13 -0
- data/templates/docker/upstream/Dockerfile.rack.erb +16 -0
- data/templates/docker/upstream/Dockerfile.rails.erb +18 -0
- data/templates/docker/upstream/init-letsencrypt.sh.erb +76 -0
- data/test/test_helper.rb +12 -0
- data/test/test_helper/coverage.rb +8 -0
- data/test/unit/test_version.rb +15 -0
- data/vps.gemspec +29 -0
- metadata +214 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 19a1e8448e7c6d7549da881d3c4410dffd9eee99
|
4
|
+
data.tar.gz: 8ed547a6a851bfc83a7be9df20d8781d8bf39d6b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 589a2204c45d5eb76a578258d6ac56b053f7efd99fec980ed81b8b9f58e2d86d25918ddd0eff90cb91be63ba1d5f0bae4b26e9bedb7636d264a1e6c377693391
|
7
|
+
data.tar.gz: 477b312e229c9cef09c4f75a9e7e8f0aff05be8d9882940b9c03721049563addc569ace2c57739cb73c4a32390c8fdd78f8743af161037183be871745652d791
|
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
## VPS CHANGELOG
|
2
|
+
|
3
|
+
### Version 0.2.0 (December 31, 2019)
|
4
|
+
|
5
|
+
* Provide ability to add both custom HTTP and HTTPS Nginx configs
|
6
|
+
|
7
|
+
### Version 0.1.2 (September 29, 2019)
|
8
|
+
|
9
|
+
* Run postload tasks at the end of deployment
|
10
|
+
* Fix generating Rails and Rack Dockerfile (installing the correct Bundler version)
|
11
|
+
* Include configured :services in the generated docker-compose.yml file
|
12
|
+
|
13
|
+
### Version 0.1.1 (August 12, 2019)
|
14
|
+
|
15
|
+
* Initial release
|
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2019 Paul Engel
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
# VPS
|
2
|
+
|
3
|
+
Zero-config deployments of Plug, Phoenix, Rack and Rails apps on a clean Ubuntu server using Docker and Let's Encrypt
|
4
|
+
|
5
|
+
**NOTE: This is an experimental project**
|
6
|
+
|
7
|
+
## Introduction
|
8
|
+
|
9
|
+
Despite of having my fair share of experience in software development, hosting web applications has never really been my strongest skill set. So as a real programmer, I want to use a command line interface for easy deployments of my web applications (using HTTPS or not).
|
10
|
+
|
11
|
+
Important for me is not want to be locked onto a specific hosting provider and also, I want the server to be dispensable because "I know that my CLI has got my back".
|
12
|
+
|
13
|
+
So enter the Ruby gem `VPS` which makes it able to deploy my web application on a totally clean Ubuntu server by roughly just executing five simple commands :muscle:
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Run the following command to install `VPS`:
|
18
|
+
|
19
|
+
$ gem install "vps"
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
VPS is a command-line-interface, you can print help instructions:
|
24
|
+
|
25
|
+
$ vps help
|
26
|
+
Commands:
|
27
|
+
vps -v, [--version] # Show VPS version number
|
28
|
+
vps deploy HOST [TOOL] # Deploy web application to the server
|
29
|
+
vps domain # Manage upstream domains
|
30
|
+
vps domain add HOST:UPSTREAM DOMAIN [EMAIL] # Add domain to host upstream (email recommended for https)
|
31
|
+
vps domain help [COMMAND] # Describe subcommands or one specific subcommand
|
32
|
+
vps domain list HOST[:UPSTREAM] # List domains of host
|
33
|
+
vps domain remove HOST[:UPSTREAM] [DOMAIN] # Remove domain from host upstream
|
34
|
+
vps edit [HOST] # Edit the VPS configuration(s)
|
35
|
+
vps help [COMMAND] # Describe available commands or one specific command
|
36
|
+
vps init HOST # Execute an initial server setup
|
37
|
+
vps install HOST [TOOL] # Install software on the server
|
38
|
+
vps service # Manage host services
|
39
|
+
vps service add HOST [SERVICE] # Add service to host configuration
|
40
|
+
vps service help [COMMAND] # Describe subcommands or one specific subcommand
|
41
|
+
vps service list HOST # List services of host configuration
|
42
|
+
vps service remove HOST [SERVICE] # Remove service from host configuration
|
43
|
+
vps upstream # Manage host upstreams
|
44
|
+
vps upstream add HOST[:UPSTREAM] PATH # Add upstream to host configuration
|
45
|
+
vps upstream help [COMMAND] # Describe subcommands or one specific subcommand
|
46
|
+
vps upstream list HOST # List upstreams of host configuration
|
47
|
+
vps upstream remove HOST[:UPSTREAM] # Remove upstream from host configuration
|
48
|
+
|
49
|
+
### Deploying a Plug / Phoenix / Rack / Rails application to a totally clean installed Ubuntu server
|
50
|
+
|
51
|
+
Let's say the SSH host is called `silver_surfer` and that the application is located at `~/Sources/spider_web`.
|
52
|
+
|
53
|
+
Just execute the following commands:
|
54
|
+
|
55
|
+
$ vps init silver_surfer
|
56
|
+
$ vps install silver_surfer docker
|
57
|
+
$ vps upstream add silver_surfer ~/Sources/spider_web
|
58
|
+
$ vps domain add silver_surfer:spider_web http://spider.web
|
59
|
+
$ vps deploy silver_surfer
|
60
|
+
|
61
|
+
Et voilà. Your awesome website is online, powered by Docker and Nginx! :D
|
62
|
+
|
63
|
+
### Want to use a HTTPS domain?
|
64
|
+
|
65
|
+
No problem, just specify so and make sure you also pass a valid email address (which is recommended). During deployment `certbot` will be added to the docker compose config and the SSL certificates will be created using [`init-letsencrypt.sh`](https://github.com/archan937/vps/blob/master/templates/docker/init-letsencrypt.sh.erb).
|
66
|
+
|
67
|
+
$ vps init silver_surfer
|
68
|
+
$ vps install silver_surfer docker
|
69
|
+
$ vps upstream add silver_surfer ~/Sources/spider_web
|
70
|
+
$ vps domain add silver_surfer:spider_web https://spider.web your.valid@email-address.com
|
71
|
+
$ vps deploy silver_surfer
|
72
|
+
|
73
|
+
Cool, huh? :D
|
74
|
+
|
75
|
+
### Want to add a (commonly used) service?
|
76
|
+
|
77
|
+
Easy. Just run `vps service add <yourhost>` (e.g. `vps service add silver_surfer`) and follow the instructions.
|
78
|
+
|
79
|
+
## Credits
|
80
|
+
|
81
|
+
Thanks Philipp Medien (@pentacent_hq) for writing about using Nginx and Let's Encrypt with Docker:
|
82
|
+
|
83
|
+
https://medium.com/@pentacent / [the-blog-post](https://medium.com/@pentacent/nginx-and-lets-encrypt-with-docker-in-less-than-5-minutes-b4b8a60d3a71)
|
84
|
+
|
85
|
+
Thanks Dmitry Fedosov (@dimafeng) for writing about scripting Docker based deployments:
|
86
|
+
|
87
|
+
http://dimafeng.com / [the-blog-post](http://dimafeng.com/2015/10/17/docker-distribution)
|
88
|
+
|
89
|
+
## TODO
|
90
|
+
|
91
|
+
* Add documentation about adding docker-compose and Nginx related configs plus adding pre- and postload tasks
|
92
|
+
|
93
|
+
## Contact me
|
94
|
+
|
95
|
+
For support, remarks and requests, please mail me at [pm_engel@icloud.com](mailto:pm_engel@icloud.com).
|
96
|
+
|
97
|
+
## License
|
98
|
+
|
99
|
+
Copyright (c) 2019 Paul Engel, released under the MIT license
|
100
|
+
|
101
|
+
http://github.com/archan937 – http://twitter.com/archan937 – pm_engel@icloud.com
|
102
|
+
|
103
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
104
|
+
|
105
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
106
|
+
|
107
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.2
|
data/bin/vps
ADDED
data/config/services.yml
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
---
|
2
|
+
memcached:
|
3
|
+
restart: on-failure
|
4
|
+
mysql:
|
5
|
+
restart: on-failure
|
6
|
+
ports:
|
7
|
+
- 3306:3306
|
8
|
+
environment:
|
9
|
+
- MYSQL_DATABASE
|
10
|
+
- MYSQL_USER
|
11
|
+
- MYSQL_PASSWORD
|
12
|
+
- MYSQL_ROOT_PASSWORD
|
13
|
+
volumes:
|
14
|
+
- mysql:/var/lib/mysql
|
15
|
+
mariadb:
|
16
|
+
restart: on-failure
|
17
|
+
ports:
|
18
|
+
- 3306:3306
|
19
|
+
environment:
|
20
|
+
- MYSQL_DATABASE
|
21
|
+
- MYSQL_USER
|
22
|
+
- MYSQL_PASSWORD
|
23
|
+
- MYSQL_ROOT_PASSWORD
|
24
|
+
volumes:
|
25
|
+
- mariadb:/var/lib/mysql
|
26
|
+
postgres:
|
27
|
+
restart: on-failure
|
28
|
+
ports:
|
29
|
+
- 5432:5432
|
30
|
+
environment:
|
31
|
+
- POSTGRES_DB
|
32
|
+
- POSTGRES_USER
|
33
|
+
- POSTGRES_PASSWORD
|
34
|
+
volumes:
|
35
|
+
- postgres:/var/lib/postgres
|
36
|
+
mongo:
|
37
|
+
restart: on-failure
|
38
|
+
environment:
|
39
|
+
- MONGO_INITDB_DATABASE
|
40
|
+
- MONGO_INITDB_ROOT_USERNAME
|
41
|
+
- MONGO_INITDB_ROOT_PASSWORD
|
42
|
+
volumes:
|
43
|
+
- mongo-db:/data/db
|
44
|
+
- mongo-config:/data/configdb
|
45
|
+
redis:
|
46
|
+
restart: on-failure
|
47
|
+
volumes:
|
48
|
+
- redis:/data
|
49
|
+
rabbitmq:
|
50
|
+
restart: on-failure
|
51
|
+
environment:
|
52
|
+
- RABBITMQ_ERLANG_COOKIE
|
53
|
+
- RABBITMQ_DEFAULT_USER
|
54
|
+
- RABBITMQ_DEFAULT_PASS
|
55
|
+
volumes:
|
56
|
+
- rabbitmq-etc:/etc/rabbitmq
|
57
|
+
- rabbitmq-lib:/var/lib/rabbitmq
|
58
|
+
- rabbitmq-logs:/var/log/rabbitmq
|
59
|
+
clickhouse:
|
60
|
+
image: yandex/clickhouse-server
|
61
|
+
restart: on-failure
|
62
|
+
volumes:
|
63
|
+
- clickhouse:/var/lib/clickhouse
|
data/lib/vps.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
require "vps/version"
|
2
|
+
|
3
|
+
module VPS
|
4
|
+
extend self
|
5
|
+
|
6
|
+
ROOT = File.expand_path("#{__FILE__}/../..")
|
7
|
+
PLAYBOOKS = "#{ROOT}/playbooks"
|
8
|
+
TEMPLATES = "#{ROOT}/templates"
|
9
|
+
|
10
|
+
def config_path(host, path = "config.yml")
|
11
|
+
File.expand_path("~/.vps/#{host}/#{path}")
|
12
|
+
end
|
13
|
+
|
14
|
+
def read_template(path)
|
15
|
+
File.read("#{TEMPLATES}/#{path}")
|
16
|
+
end
|
17
|
+
|
18
|
+
def read_config(host, key = nil)
|
19
|
+
config =
|
20
|
+
if File.exists?(path = config_path(host))
|
21
|
+
YAML.load_file(path)
|
22
|
+
elsif key.nil?
|
23
|
+
{
|
24
|
+
:user => nil,
|
25
|
+
:tool => nil,
|
26
|
+
:release_path => nil,
|
27
|
+
:services => nil,
|
28
|
+
:upstreams => nil,
|
29
|
+
:volumes => nil,
|
30
|
+
:preload => nil,
|
31
|
+
:postload => nil
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
if config
|
36
|
+
config = with_indifferent_access(config)
|
37
|
+
if key
|
38
|
+
with_indifferent_access(config[key])
|
39
|
+
else
|
40
|
+
config[:services] ||= {}
|
41
|
+
config[:upstreams] ||= []
|
42
|
+
config[:volumes] ||= []
|
43
|
+
config
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def write_config(host, changes)
|
49
|
+
config = read_config(host) || {}
|
50
|
+
changed = false
|
51
|
+
|
52
|
+
%w(services upstreams volumes).each do |key|
|
53
|
+
value = changes[key]
|
54
|
+
changes[key] = nil if value && value.empty?
|
55
|
+
end
|
56
|
+
|
57
|
+
changes.each do |key, value|
|
58
|
+
if !config.include?(key) || (config[key] != value)
|
59
|
+
config[key] = value
|
60
|
+
changed = true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
if changed
|
65
|
+
path = config_path(host)
|
66
|
+
config = JSON.parse(config.to_json)
|
67
|
+
FileUtils.mkdir_p(File.dirname(path))
|
68
|
+
File.write(path, config.to_yaml)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def with_indifferent_access(object)
|
75
|
+
case object
|
76
|
+
when Hash
|
77
|
+
object.inject({}.with_indifferent_access) do |hash, (key, value)|
|
78
|
+
hash[key] = with_indifferent_access(value)
|
79
|
+
hash
|
80
|
+
end
|
81
|
+
when Array
|
82
|
+
object.collect{|item| with_indifferent_access(item)}
|
83
|
+
else
|
84
|
+
object
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
data/lib/vps/cli.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "json"
|
3
|
+
require "yaml"
|
4
|
+
require "ostruct"
|
5
|
+
require "uri"
|
6
|
+
require "thor"
|
7
|
+
require "erubis"
|
8
|
+
require "inquirer"
|
9
|
+
require "net/http"
|
10
|
+
require "net/ssh"
|
11
|
+
|
12
|
+
require "active_support/dependencies/autoload"
|
13
|
+
require "active_support/core_ext/string/inflections"
|
14
|
+
require "active_support/core_ext/hash"
|
15
|
+
require "active_support/number_helper"
|
16
|
+
|
17
|
+
require "vps"
|
18
|
+
require "vps/core_ext/string"
|
19
|
+
require "vps/core_ext/ostruct"
|
20
|
+
require "vps/cli/service"
|
21
|
+
require "vps/cli/upstream"
|
22
|
+
require "vps/cli/domain"
|
23
|
+
require "vps/cli/playbook"
|
24
|
+
|
25
|
+
module VPS
|
26
|
+
class CLI < Thor
|
27
|
+
|
28
|
+
class Error < StandardError; end
|
29
|
+
|
30
|
+
Playbook.all.each do |playbook|
|
31
|
+
desc playbook.usage, playbook.description
|
32
|
+
method_options playbook.options if playbook.options
|
33
|
+
define_method playbook.command do |*args|
|
34
|
+
start = Time.now
|
35
|
+
playbook.run!(args, options)
|
36
|
+
puts "\nDone. ".cyan + "#{(Time.now - start).round(3)}s".gray
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "edit [HOST]", "Edit the VPS configuration(s)"
|
41
|
+
def edit(host = nil)
|
42
|
+
`#{ENV["EDITOR"]} #{VPS.config_path(host, "")}`
|
43
|
+
end
|
44
|
+
|
45
|
+
register(Upstream, "upstream", "upstream", "Manage host upstreams")
|
46
|
+
register(Service, "service", "service", "Manage host services")
|
47
|
+
register(Domain, "domain", "domain", "Manage upstream domains")
|
48
|
+
|
49
|
+
desc "-v, [--version]", "Show VPS version number"
|
50
|
+
map %w(-v --version) => :version
|
51
|
+
def version
|
52
|
+
puts "vps #{VPS::VERSION}"
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def method_missing(method, *_args)
|
58
|
+
raise Error, "Unrecognized command \"#{method}\". Please consult `vps help`."
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module VPS
|
2
|
+
class CLI < Thor
|
3
|
+
class Domain < Thor
|
4
|
+
|
5
|
+
desc "add HOST:UPSTREAM DOMAIN [EMAIL]", "Add domain to host upstream (email recommended for https)"
|
6
|
+
def add(host_and_upstream, domain, email = nil)
|
7
|
+
return unless domain.match(/^https?:\/\/([a-z0-9\-]{2,}\.)+[a-z]{2,}$/)
|
8
|
+
|
9
|
+
host, name = host_and_upstream.split(":")
|
10
|
+
config = VPS.read_config(host)
|
11
|
+
|
12
|
+
if (upstream = config[:upstreams].detect{|upstream| upstream[:name] == name})
|
13
|
+
upstream[:email] = email if email
|
14
|
+
upstream[:domains].push(domain).uniq!
|
15
|
+
VPS.write_config(host, config)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "remove HOST[:UPSTREAM] [DOMAIN]", "Remove domain from host upstream"
|
20
|
+
def remove(host_and_upstream, domain = nil)
|
21
|
+
host, name = host_and_upstream.split(":")
|
22
|
+
config = VPS.read_config(host)
|
23
|
+
|
24
|
+
unless name
|
25
|
+
list = config[:upstreams].collect{|upstream| upstream[:name]}.sort
|
26
|
+
name = list[Ask.list("From which upstream do you want to remove a domain?", list)]
|
27
|
+
end
|
28
|
+
|
29
|
+
if (upstream = config[:upstreams].detect{|upstream| upstream[:name] == name})
|
30
|
+
unless domain
|
31
|
+
list = upstream[:domains]
|
32
|
+
domain = list[Ask.list("Which domain do you want to remove?", list)]
|
33
|
+
end
|
34
|
+
|
35
|
+
if upstream[:domains].reject!{|x| x == domain}
|
36
|
+
VPS.write_config(host, config)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
desc "list HOST[:UPSTREAM]", "List domains of host"
|
42
|
+
def list(host_and_optional_upstream)
|
43
|
+
host, name = host_and_optional_upstream.split(":")
|
44
|
+
config = VPS.read_config(host)
|
45
|
+
|
46
|
+
domains = config[:upstreams].collect do |upstream|
|
47
|
+
if name.nil? || upstream[:name] == name
|
48
|
+
upstream[:domains]
|
49
|
+
.collect{|domain| " ~> #{domain}"}
|
50
|
+
.unshift("* #{upstream[:name]}:")
|
51
|
+
end
|
52
|
+
end.flatten.compact
|
53
|
+
|
54
|
+
puts domains
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|