provisioning 0.0.1.alpha.3 → 0.0.1.alpha.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c4c3ddbf8d9e399ad23cec152889080da86482c6
4
- data.tar.gz: 586672476340031fa884f5e9fad0abc008811b79
3
+ metadata.gz: 933806e5f54e7f91d0c47aa40f1ccebebdc2623c
4
+ data.tar.gz: a4178fef2186d84051a44985379023c56626d700
5
5
  SHA512:
6
- metadata.gz: bdc9b54888a68f7e09483aa574eeaa9a8365676cc833ec39ae027cc0d0fcbce477150840d2f4891a3238e2b77c7dcffcf5ac9962a0e012d5e9c840cb02721668
7
- data.tar.gz: 51b45ae173f24de850b15564a3f6ff2319d907acc7384ecc863ff6259c919cafcee4c1aad8b6197be8b1476b5c57886024abe1d2090793460044e42486393221
6
+ metadata.gz: dd72fdf87aa3ae6189da7fd8bb10aa207fd4785a480f54dd64caede766abe0ac342bcccc5bc993e60a28cf0abb2dffbc57bbcc95233b9325467835ee9b2f91f3
7
+ data.tar.gz: 8caf109b025a5b3339b072f87fb90f41b609f87e3e7572ccc94572c066db23c114e5fd65d976ade15221531177fa7ceed211569f11922d7c24dd51396487d252
data/README.md CHANGED
@@ -6,7 +6,7 @@ Open PaaS provisioning.
6
6
  This gem will install a `provision` command for provisioning an open
7
7
  platform-as-a-service (PaaS) from a JSON manifest file.
8
8
 
9
- Currently limited to dokku apps on AWS or DigitalOcean servers.
9
+ Currently limited to dokku or flynn apps on AWS or DigitalOcean servers.
10
10
 
11
11
 
12
12
  Installation
@@ -16,7 +16,7 @@ Installation
16
16
 
17
17
  Alternatively you can build the gem from its repository:
18
18
 
19
- $ git clone git://github.com/vinc/provisioning.git
19
+ $ git clone git://github.com/vinc/provisioning.rb.git
20
20
  $ cd provisioning
21
21
  $ gem build provisioning.gemspec
22
22
  $ gem install provisioning-0.0.1.gem
@@ -25,33 +25,31 @@ Alternatively you can build the gem from its repository:
25
25
  Usage
26
26
  -----
27
27
 
28
- Run the provisioning script:
28
+ Provision a manifest file:
29
29
 
30
- $ provision manifest.json
30
+ $ provision manifest.sample.json
31
+ Reading provisioning manifest file 'manifest.sample.json'
32
+ ==> Provisioning digitalocean compute
31
33
  Uploading SSH key to DigitalOcean
32
-
33
- Creating server 'dokku.server.net'
34
-
35
- Creating domain 'server.net'
36
- Configue 'server.net' with the following DNS servers:
37
- - ns1.digitalocean.com
38
- - ns2.digitalocean.com
39
- - ns3.digitalocean.com
40
-
41
- Creating domain record A 'dokku.server.net' to '192.168.13.37'
42
- Creating domain record CNAME 'example.server.net' to 'dokku.server.net.'
43
- Configue 'example.com' to point to 'dokku.server.net'
44
- Configue 'www.example.com' to point to 'dokku.server.net'
45
-
46
- Installing dokku v0.10.4 on '192.168.13.37'
47
- Run `gem install dokku-cli` to get dokku client on your machine
48
-
49
- Creating dokku app 'example' on '192.168.13.37'
50
-
34
+ Creating server 'dokku1.sfo1.example.net'
35
+ ==> Provisioning digitalocean dns
36
+ Creating zone 'sfo1.example.net'
37
+ Creating domain record A '@' to '104.131.186.241'
38
+ Configue 'sfo1.example.net' with the following DNS servers:
39
+ - ns1.example.net
40
+ - ns2.example.net
41
+ Creating domain record CNAME '*' to 'sfo1.example.net.'
42
+ Configue 'example.com' to point to 'sfo1.example.net'
43
+ Configue 'www.example.com' to point to 'sfo1.example.net'
44
+ ==> Provisioning dokku platform
45
+ Installing dokku v0.10.4 on '104.131.186.241'
46
+ Creating dokku app 'example' on '104.131.186.241'
47
+ Run `gem install dokku-cli` to get dokku client on your computer
51
48
  Adding dokku to git remotes
49
+ Uploading SSH key to DigitalOcean
52
50
  Run `git push dokku master` to deploy your code
53
51
 
54
- Provisioning manifest json file:
52
+ Sample manifest file (in JSON format)
55
53
 
56
54
  ```json
57
55
  {
@@ -63,8 +61,8 @@ Provisioning manifest json file:
63
61
  },
64
62
  "platform": {
65
63
  "provider": "dokku",
66
- "domain": "sfo1.example.net",
67
- "version": "v0.10.4"
64
+ "version": "v0.10.4",
65
+ "domain": "sfo1.example.net"
68
66
  },
69
67
  "compute": {
70
68
  "provider": "digitalocean",
@@ -1,10 +1,16 @@
1
1
  require "object"
2
+ require "provisioning/core"
2
3
  require "provisioning/cli"
4
+ require "provisioning/compute/base"
3
5
  require "provisioning/compute/aws"
4
6
  require "provisioning/compute/digitalocean"
5
7
  require "provisioning/console"
8
+ require "provisioning/dns/base"
9
+ require "provisioning/dns/aws"
6
10
  require "provisioning/dns/digitalocean"
11
+ require "provisioning/platform/base"
7
12
  require "provisioning/platform/dokku"
13
+ require "provisioning/platform/flynn"
8
14
  require "provisioning/public_key"
9
15
  require "provisioning/version"
10
16
 
@@ -26,10 +26,11 @@ module Provisioning
26
26
  set_instance_variable_from_manifest(%w[compute provider])
27
27
  set_instance_variable_from_manifest(%w[dns provider])
28
28
 
29
- @server_address = nil
30
- @server_hostname = [@platform_provider, @platform_domain].join(".")
29
+ @servers = []
31
30
 
32
- @ssh_key = PublicKey.new(@env["SSH_PUBLIC_KEY"])
31
+ key_path = File.join(@env["HOME"], ".ssh/id_rsa.pub")
32
+ key_body = @env["SSH_PUBLIC_KEY"] || File.open(key_path).read
33
+ @ssh_key = PublicKey.new(key_body)
33
34
  end
34
35
 
35
36
  def parse_opts(args)
@@ -48,58 +49,84 @@ module Provisioning
48
49
  provision_dns
49
50
  provision_platform
50
51
  add_git_remote
52
+ # TODO: return false when an error occur
51
53
  end
52
54
 
53
55
  def provision_compute
54
- klass = Compute.const_get(@compute_provider.capitalize)
55
- compute = klass.new(@manifest["compute"], @opts, @env)
56
+ compute = provider("compute")
56
57
 
57
58
  compute.upload_ssh_key(@ssh_key)
58
59
 
59
- server = compute.find_or_create_server(
60
- name: @server_hostname,
61
- ssh_key: @ssh_key
62
- )
63
- server.wait_for { ready? }
64
- @server_address = server.public_ip_address
60
+ n = (@manifest["compute"]["count"] || 1).to_i
61
+ @servers = n.times.map do |i|
62
+ compute.find_or_create_server(
63
+ name: "#{@platform_provider}#{i + 1}.#{@platform_domain}",
64
+ ssh_key: @ssh_key
65
+ )
66
+ end
67
+
68
+ @servers.each do |server|
69
+ server.wait_for { ready? }
70
+ end
71
+ sleep 5
65
72
  end
66
73
 
67
74
  def provision_dns
68
- klass = DNS.const_get(@dns_provider.capitalize)
69
- dns = klass.new(@manifest["dns"], @opts, @env)
75
+ dns = provider("dns")
70
76
 
71
- dns.create_domain(@platform_domain, @server_address)
77
+ cluster = @servers.map(&:public_ip_address)
78
+
79
+ if @dns_provider == "digitalocean"
80
+ dns.create_zone(@platform_domain, cluster.shift)
81
+ else
82
+ dns.create_zone(@platform_domain)
83
+ end
72
84
  Console.success("Configue '#{@platform_domain}' with the following DNS servers:")
73
- dns.get_domain_name_servers(@platform_domain).each do |hostname|
85
+ dns.get_name_servers(@platform_domain).each do |hostname|
74
86
  Console.success(" - #{hostname}")
75
87
  end
76
88
 
77
- dns.create_domain_record(
78
- domain: @platform_domain,
79
- type: "A",
80
- name: @platform_provider,
81
- data: @server_address
82
- )
89
+ if cluster.count > 0
90
+ dns.create_record(@platform_domain,
91
+ type: "A",
92
+ name: [@platform_domain, ""].join("."),
93
+ value: cluster
94
+ )
95
+ end
96
+
97
+ @servers.each_with_index do |server, i|
98
+ dns.create_record(@platform_domain,
99
+ type: "A",
100
+ name: "#{@platform_provider}#{i + 1}.#{@platform_domain}",
101
+ value: server.public_ip_address
102
+ )
103
+ end
83
104
 
84
- dns.create_domain_record(
85
- domain: @platform_domain,
105
+ # Wilcard subdomains
106
+ dns.create_record(@platform_domain,
86
107
  type: "CNAME",
87
- name: @app_name,
88
- data: @server_hostname + "."
108
+ name: ["*", @platform_domain, ""].join("."),
109
+ value: [@platform_domain, ""].join(".")
89
110
  )
90
111
 
91
112
  @manifest["app"]["domains"].each do |app_domain|
92
- Console.success("Configue '#{app_domain}' to point to '#{@server_hostname}'")
113
+ Console.success("Configue '#{app_domain}' to point to '#{@platform_domain}'")
93
114
  end
94
115
  end
95
116
 
96
117
  def provision_platform
97
- klass = Platform.const_get(@platform_provider.capitalize)
98
- platform = klass.new(@manifest["platform"], @opts, @env)
118
+ platform = provider("platform")
119
+
120
+ @servers.each do |server|
121
+ platform.setup(
122
+ address: server.public_ip_address,
123
+ user: @compute_provider == "aws" ? "ubuntu" : "root"
124
+ )
125
+ end
99
126
 
100
- platform.setup(address: @server_address, domain: @platform_domain)
101
127
  platform.create_app(@manifest["app"])
102
128
 
129
+ # TODO: add `platform.get_post_install_instructions`
103
130
  case @platform_provider
104
131
  when "dokku"
105
132
  Console.success("Run `gem install dokku-cli` to get dokku client on your computer")
@@ -119,11 +146,14 @@ module Provisioning
119
146
  else
120
147
  case @platform_provider
121
148
  when "dokku"
122
- url = "#{@platform_provider}@#{@server_hostname}:#{@app_name}"
149
+ url = "#{@platform_provider}@#{@platform_domain}:#{@app_name}"
150
+ git.add_remote(@platform_provider, url)
151
+ when "flynn"
152
+ url = "https://git.#{@platform_domain}/#{@app_name}.git"
123
153
  git.add_remote(@platform_provider, url)
124
154
  end
125
155
  end
126
- Console.success("Run `git push dokku master` to deploy your code")
156
+ Console.success("Run `git push #{@platform_provider} master` to deploy your code")
127
157
  end
128
158
  end
129
159
 
@@ -131,19 +161,16 @@ module Provisioning
131
161
  Console.info("Reading provisioning manifest file '#{filename}'")
132
162
  begin
133
163
  json = JSON.parse(File.open(filename).read)
134
- rescue Errno::ENOENT, JSON::ParserError
164
+ rescue Errno::ENOENT
135
165
  Console.error("Could not read provisioning manifest file '#{filename}'")
166
+ rescue JSON::ParserError
167
+ Console.error("Could not parse provisioning manifest file '#{filename}'")
136
168
  end
137
169
  json["manifest"]
138
170
  end
139
171
 
140
172
  private
141
173
 
142
- def set_instance_variable_from_manifest(keys)
143
- name = "@" + keys.join("_")
144
- instance_variable_set(name, dig_manifest(keys))
145
- end
146
-
147
174
  def dig_manifest(keys)
148
175
  path = []
149
176
  hash = @manifest
@@ -153,5 +180,22 @@ module Provisioning
153
180
  end
154
181
  hash
155
182
  end
183
+
184
+ def set_instance_variable_from_manifest(keys)
185
+ name = "@" + keys.join("_")
186
+ instance_variable_set(name, dig_manifest(keys))
187
+ end
188
+
189
+ def provider(type)
190
+ provider = instance_variable_get("@#{type}_provider")
191
+
192
+ Console.info("==> Provisioning #{provider} #{type}")
193
+
194
+ klass = Provisioning.
195
+ const_get(type.capitalize).
196
+ const_get(provider.capitalize)
197
+
198
+ klass.new(@manifest[type], @opts, @env)
199
+ end
156
200
  end
157
201
  end
@@ -4,18 +4,16 @@ require "provisioning"
4
4
 
5
5
  module Provisioning
6
6
  module Compute
7
- class Aws
8
- KEY_NAME = "provisioning key".freeze
9
-
7
+ class Aws < Base
10
8
  def initialize(config, opts, env)
11
9
  @config = config
12
10
  @opts = opts
13
11
  Fog.mock! if @opts[:mock]
14
12
  @client = Fog::Compute.new(
15
13
  provider: "aws",
16
- region: @config["region"],
17
- aws_access_key_id: env["AWS_ACCESS_KEY_ID"],
18
- aws_secret_access_key: env["AWS_SECRET_ACCESS_KEY"]
14
+ region: fetch("region", config: @config),
15
+ aws_access_key_id: fetch("AWS_ACCESS_KEY_ID", env: env),
16
+ aws_secret_access_key: fetch("AWS_SECRET_ACCESS_KEY", env: env)
19
17
  )
20
18
  end
21
19
 
@@ -38,9 +36,10 @@ module Provisioning
38
36
  end
39
37
  end
40
38
 
39
+ # TODO: set instance name instead of tagging it
41
40
  @client.servers.create(
42
- image_id: @config["image_id"],
43
- flavor_id: @config["flavor_id"],
41
+ image_id: fetch("image_id", config: @config),
42
+ flavor_id: fetch("flavor_id", config: @config),
44
43
  tags: { name: name },
45
44
  key_name: KEY_NAME
46
45
  )
@@ -0,0 +1,23 @@
1
+ require "provisioning"
2
+
3
+ module Provisioning
4
+ module Compute
5
+ class Base
6
+ include Provisioning::Core
7
+
8
+ KEY_NAME = "provisioning key".freeze
9
+
10
+ def initialize(config, opts, env)
11
+ raise NotImplementedError
12
+ end
13
+
14
+ def upload_ssh_key(ssh_key)
15
+ raise NotImplementedError
16
+ end
17
+
18
+ def find_or_create_server(name:, ssh_key:)
19
+ raise NotImplementedError
20
+ end
21
+ end
22
+ end
23
+ end
@@ -4,16 +4,14 @@ require "provisioning"
4
4
 
5
5
  module Provisioning
6
6
  module Compute
7
- class Digitalocean
8
- KEY_NAME = "provisioning key".freeze
9
-
7
+ class Digitalocean < Base
10
8
  def initialize(config, opts, env)
11
9
  @config = config
12
10
  @opts = opts
13
11
  Fog.mock! if @opts[:mock]
14
12
  @client = Fog::Compute.new(
15
13
  provider: :digitalocean,
16
- digitalocean_token: env["DIGITALOCEAN_TOKEN"]
14
+ digitalocean_token: fetch("DIGITALOCEAN_TOKEN", env: env)
17
15
  )
18
16
  end
19
17
 
@@ -41,9 +39,9 @@ module Provisioning
41
39
 
42
40
  @client.servers.create(
43
41
  name: name,
44
- region: @config["region"],
45
- image: @config["image"],
46
- size: @config["size"],
42
+ region: fetch("region", config: @config),
43
+ image: fetch("image", config: @config),
44
+ size: fetch("size", config: @config),
47
45
  ssh_keys: [ssh_key.fingerprint]
48
46
  )
49
47
  end
@@ -0,0 +1,15 @@
1
+ require "provisioning"
2
+
3
+ module Provisioning
4
+ module Core
5
+ def fetch(key, default = nil, env: nil, config: nil)
6
+ if env
7
+ env[key] || default || Console.error("Could not find '#{key}' in environment")
8
+ elsif config
9
+ config[key] || default || Console.error("Could not find '#{key}' in manifest")
10
+ else
11
+ # TODO: env or config required
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,54 @@
1
+ require "fog/aws"
2
+
3
+ require "provisioning"
4
+
5
+ module Provisioning
6
+ module Dns
7
+ class Aws < Base
8
+ def initialize(config, opts, env)
9
+ @config = config
10
+ @opts = opts
11
+ Fog.mock! if @opts[:mock]
12
+ @client = Fog::DNS.new(
13
+ provider: "aws",
14
+ aws_access_key_id: fetch("AWS_ACCESS_KEY_ID", env: env),
15
+ aws_secret_access_key: fetch("AWS_SECRET_ACCESS_KEY", env: env)
16
+ )
17
+ end
18
+
19
+ def create_zone(domain)
20
+ Console.info("Creating zone '#{domain}'")
21
+
22
+ zone = get_zone(domain)
23
+ if zone
24
+ Console.warning("Domain already exists, skipping")
25
+ else
26
+ zone = @client.zones.create(domain: domain)
27
+ end
28
+ end
29
+
30
+ def create_record(domain, type:, name:, value:)
31
+ value = value.is_a?(Array) ? value : [value]
32
+ value_to_s = value.join(", ")
33
+ Console.info("Creating domain record #{type} '#{name}' to '#{value_to_s}'")
34
+
35
+ zone = get_zone(domain)
36
+ if zone.records.get(name, type).try(:value) == value
37
+ Console.warning("Record already exists, skipping")
38
+ else
39
+ zone.records.create(type: type, name: name, value: value)
40
+ end
41
+ end
42
+
43
+ def get_name_servers(domain)
44
+ get_zone(domain).reload.nameservers || []
45
+ end
46
+
47
+ private
48
+
49
+ def get_zone(domain)
50
+ @client.zones.all.find { |z| z.domain == domain + "." }
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,25 @@
1
+ require "provisioning"
2
+
3
+ module Provisioning
4
+ module Dns
5
+ class Base
6
+ include Provisioning::Core
7
+
8
+ def initialize(config, opts, env)
9
+ raise NotImplementedError
10
+ end
11
+
12
+ def create_zone(domain)
13
+ raise NotImplementedError
14
+ end
15
+
16
+ def create_record(domain, type:, name:, data:)
17
+ raise NotImplementedError
18
+ end
19
+
20
+ def get_name_servers(domain)
21
+ raise NotImplementedError
22
+ end
23
+ end
24
+ end
25
+ end
@@ -3,24 +3,25 @@ require "droplet_kit"
3
3
  require "provisioning"
4
4
 
5
5
  module Provisioning
6
- module DNS
7
- class Digitalocean
6
+ module Dns
7
+ class Digitalocean < Base
8
8
  def initialize(config, opts, env)
9
9
  @config = config
10
10
  @opts = opts
11
11
  @client = DropletKit::Client.new(
12
- access_token: env["DIGITALOCEAN_TOKEN"]
12
+ access_token: fetch("DIGITALOCEAN_TOKEN", env: env)
13
13
  ) unless @opts[:mock]
14
14
  end
15
15
 
16
- def create_domain(name, address)
17
- Console.info("Creating domain '#{name}'")
16
+ def create_zone(name, address)
17
+ Console.info("Creating zone '#{name}'")
18
18
 
19
19
  disable_verbose
20
20
  return if @opts[:mock]
21
21
  if @client.domains.all.map(&:name).include?(name)
22
22
  Console.warning("Domain already exists, skipping")
23
23
  else
24
+ Console.info("Creating domain record A '@' to '#{address}'")
24
25
  domain = DropletKit::Domain.new(name: name, ip_address: address)
25
26
  @client.domains.create(domain)
26
27
  end
@@ -28,22 +29,30 @@ module Provisioning
28
29
  restore_verbose
29
30
  end
30
31
 
31
- def create_domain_record(domain:, type:, name:, data:)
32
- Console.info("Creating domain record #{type} '#{name}.#{domain}' to '#{data}'")
32
+ def create_record(domain, type:, name:, value:)
33
+ name = name.gsub("#{domain}.", "@").gsub(".@", "")
34
+ value = value.is_a?(Array) ? value : [value]
35
+ value_to_s = value.join(", ")
36
+ Console.info("Creating domain record #{type} '#{name}' to '#{value_to_s}'")
33
37
 
34
38
  disable_verbose
35
39
  return if @opts[:mock]
36
- if @client.domain_records.all(for_domain: domain).map(&:name).include?(name)
37
- Console.warning("Record already exists, skipping")
38
- else
39
- record = DropletKit::DomainRecord.new(type: type, name: name, data: data)
40
- @client.domain_records.create(record, for_domain: domain)
40
+
41
+ records = @client.domain_records.all(for_domain: domain)
42
+ value.each do |data|
43
+ data = data.gsub("#{domain}.", "@")
44
+ if records.any? { |r| r.name == name && r.data == data }
45
+ Console.warning("Record already exists, skipping")
46
+ else
47
+ record = DropletKit::DomainRecord.new(type: type, name: name, data: data)
48
+ @client.domain_records.create(record, for_domain: domain)
49
+ end
41
50
  end
42
51
  ensure
43
52
  restore_verbose
44
53
  end
45
54
 
46
- def get_domain_name_servers(domain)
55
+ def get_name_servers(domain)
47
56
  disable_verbose
48
57
  return %w[ns1.example.net ns2.example.net] if @opts[:mock]
49
58
  @client.domain_records.all(for_domain: domain).
@@ -0,0 +1,31 @@
1
+ require "provisioning"
2
+
3
+ module Provisioning
4
+ module Platform
5
+ class Base
6
+ include Provisioning::Core
7
+
8
+ def initialize(config, opts, env)
9
+ @opts = opts
10
+ @config = config
11
+ @servers = []
12
+ end
13
+
14
+ def setup(address:, user: "root")
15
+ @servers << [address, user]
16
+ raise NotImplementedError
17
+ end
18
+
19
+ def create_app(config)
20
+ raise NotImplementedError
21
+ end
22
+
23
+ protected
24
+
25
+ def ssh_exec(ssh, cmd, user: "root")
26
+ cmd = "sudo bash -c '#{cmd}'" if user != "root"
27
+ ssh.exec!(cmd)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -4,58 +4,60 @@ require "provisioning"
4
4
 
5
5
  module Provisioning
6
6
  module Platform
7
- class Dokku
8
- def initialize(config, opts, env)
9
- @opts = opts
10
- @config = config
11
- @servers = []
12
- end
13
-
14
- def setup(address:, domain:)
15
- @servers << address
16
- version = @config["version"]
7
+ class Dokku < Base
8
+ def setup(address:, user: "root")
9
+ @servers << [address, user]
10
+ version = fetch("version", config: @config)
17
11
  Console.info("Installing dokku #{version} on '#{address}'")
18
12
  return if @opts[:mock]
19
- Net::SSH.start(address, "root") do |ssh|
20
- if ssh.exec!("which dokku").present?
13
+ Net::SSH.start(address, user) do |ssh|
14
+ if ssh_exec(ssh, "which dokku", user: user).present?
21
15
  Console.warning("Dokku already installed, skipping")
22
16
  else
17
+ # TODO: configure hostname
18
+ domain = fetch("domain", config: @config)
23
19
  [
24
20
  "wget https://raw.githubusercontent.com/dokku/dokku/#{version}/bootstrap.sh",
25
21
  "DOKKU_TAG=#{version} bash bootstrap.sh",
26
22
  "service dokku-installer stop",
27
23
  "systemctl disable dokku-installer",
28
24
  "cat .ssh/authorized_keys | sshcommand acl-add dokku admin",
29
- "echo -n #{domain} > /home/dokku/VHOST",
30
- "echo -n #{domain} > /home/dokku/HOSTNAME"
31
- ].each { |command| Console.debug(ssh.exec!(command)) }
25
+ "echo #{domain} > /home/dokku/VHOST",
26
+ "echo #{domain} > /home/dokku/HOSTNAME"
27
+ ].each { |cmd| Console.debug(ssh_exec(ssh, cmd, user: user)) }
32
28
  end
33
29
  end
34
30
  end
35
31
 
36
32
  def create_app(config)
37
- name = config["name"]
38
- @servers.each do |address|
33
+ name = fetch("name", config: config)
34
+ @servers.each do |address, user|
39
35
  Console.info("Creating dokku app '#{name}' on '#{address}'")
40
- return if @opts[:mock]
41
- Net::SSH.start(address, "root") do |ssh|
42
- existing_apps = ssh.exec!("dokku apps").to_s.lines.map(&:chomp)
36
+ next if @opts[:mock]
37
+ Net::SSH.start(address, user) do |ssh|
38
+ existing_apps = ssh_exec(ssh, "dokku apps", user: user).to_s.lines.map(&:chomp)
43
39
  if existing_apps.include?(name)
44
40
  Console.warning("App already exists, skipping")
45
41
  else
46
- Console.debug(ssh.exec!("dokku apps:create #{name}"))
42
+ Console.debug(ssh_exec(ssh, "dokku apps:create #{name}", user: user))
47
43
 
48
- config["services"].each do |service|
49
- #TODO check if service exists
44
+ fetch("services", [], config: config).each do |service|
45
+ if @servers.count > 1
46
+ Console.warning("Service '#{service}' for dokku wont work in a cluster configuration")
47
+ end
48
+ # TODO: check if service exists
50
49
  [
51
50
  "dokku plugin:install https://github.com/dokku/dokku-#{service}.git #{service}",
52
51
  "dokku #{service}:create #{name}-#{service}",
53
52
  "dokku #{service}:link #{name}-#{service} #{name}"
54
- ].each { |command| Console.debug(ssh.exec!(command)) }
53
+ ].each { |cmd| Console.debug(ssh_exec(ssh, cmd, user: user)) }
55
54
  end
56
55
 
57
- domains = config["domains"].join(" ")
58
- Console.debug(ssh.exec!("dokku domains:add #{name} #{domains}"))
56
+ fetch("domains", [], config: config).each do |domain|
57
+ # This will replace default subdomain 'foo.example.com' in /dokku/foo/VHOST
58
+ # because the app has not been deployed once yet.
59
+ Console.debug(ssh_exec(ssh, "dokku domains:add #{name} #{domain}", user: user))
60
+ end
59
61
  end
60
62
  end
61
63
  end
@@ -0,0 +1,74 @@
1
+ require "net/ssh"
2
+
3
+ require "provisioning"
4
+
5
+ module Provisioning
6
+ module Platform
7
+ class Flynn < Base
8
+ def setup(address:, user: "root")
9
+ Console.info("Installing flynn on '#{address}'")
10
+ @servers << [address, user]
11
+ return if @opts[:mock]
12
+ Net::SSH.start(address, user) do |ssh|
13
+ if ssh_exec(ssh, "which flynn", user: user).present?
14
+ Console.warning("Flynn already installed, skipping")
15
+ else
16
+ # Install flynn
17
+ ssh_exec(ssh, "bash < <(curl -fsSL https://dl.flynn.io/install-flynn)", user: user)
18
+
19
+ # Init cluster
20
+ if @servers.count == 1
21
+ @token = ssh_exec(ssh, "flynn-host init --init-discovery", user: user)
22
+ else
23
+ ssh_exec(ssh, "flynn-host init --discovery #{@token}", user: user)
24
+ end
25
+
26
+ # Start flynn
27
+ ssh_exec(ssh, "systemctl start flynn-host", user: user)
28
+ end
29
+ end
30
+ end
31
+
32
+ def create_app(config)
33
+ name = fetch("name", config: config)
34
+ (address, user) = @servers.first
35
+
36
+ Console.info("Creating flynn app '#{name}' on '#{address}'")
37
+ return if @opts[:mock]
38
+ Net::SSH.start(address, user) do |ssh|
39
+ out = ssh_exec(ssh, "flynn apps | awk '{ print $2 }'", user: user)
40
+ existing_apps = out.lines.map(&:chomp)
41
+ if existing_apps.include?(name)
42
+ Console.warning("App already exists, skipping")
43
+ else
44
+ cmds = []
45
+
46
+ # Bootstrap flynn
47
+ platform_domain = fetch("domain", config: @config)
48
+ bootstrap = "CLUSTER_DOMAIN=#{platform_domain} flynn-host bootstrap"
49
+ bootstrap += " --min-hosts 3 --discovery #{@token}" if @token && @servers.count >= 3
50
+ cmds << bootstrap
51
+
52
+ # Configure flynn command
53
+ out = ssh_exec(ssh, "flynn-host cli-add-command", user: user)
54
+ cmds << out.lines.map(&:chomp).find do |line|
55
+ line.start_with?("flynn cluster add")
56
+ end
57
+
58
+ cmds << "flynn create #{name}"
59
+
60
+ fetch("services", [], config: config).each do |service|
61
+ cmds << "flynn -a #{name} resource add #{service}"
62
+ end
63
+
64
+ fetch("domains", [], config: config).each do |domain|
65
+ cmds << "flynn -a #{name} route add http #{domain}"
66
+ end
67
+
68
+ cmds.each { |cmd| Console.debug(ssh_exec(ssh, cmd, user: user)) }
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -1,3 +1,3 @@
1
1
  module Provisioning
2
- VERSION = "0.0.1.alpha.3".freeze
2
+ VERSION = "0.0.1.alpha.4".freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: provisioning
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1.alpha.3
4
+ version: 0.0.1.alpha.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vincent Ollivier
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-28 00:00:00.000000000 Z
11
+ date: 2017-11-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: droplet_kit
@@ -150,6 +150,26 @@ dependencies:
150
150
  - - ">="
151
151
  - !ruby/object:Gem::Version
152
152
  version: 2.1.0
153
+ - !ruby/object:Gem::Dependency
154
+ name: rspec
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '3.6'
160
+ - - ">="
161
+ - !ruby/object:Gem::Version
162
+ version: 3.6.0
163
+ type: :development
164
+ prerelease: false
165
+ version_requirements: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - "~>"
168
+ - !ruby/object:Gem::Version
169
+ version: '3.6'
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: 3.6.0
153
173
  description: Open PaaS provisioning on cloud providers from JSON manifest file
154
174
  email: v@vinc.cc
155
175
  executables:
@@ -164,10 +184,16 @@ files:
164
184
  - lib/provisioning.rb
165
185
  - lib/provisioning/cli.rb
166
186
  - lib/provisioning/compute/aws.rb
187
+ - lib/provisioning/compute/base.rb
167
188
  - lib/provisioning/compute/digitalocean.rb
168
189
  - lib/provisioning/console.rb
190
+ - lib/provisioning/core.rb
191
+ - lib/provisioning/dns/aws.rb
192
+ - lib/provisioning/dns/base.rb
169
193
  - lib/provisioning/dns/digitalocean.rb
194
+ - lib/provisioning/platform/base.rb
170
195
  - lib/provisioning/platform/dokku.rb
196
+ - lib/provisioning/platform/flynn.rb
171
197
  - lib/provisioning/public_key.rb
172
198
  - lib/provisioning/version.rb
173
199
  homepage: https://github.com/vinc/provisioning.rb
@@ -190,7 +216,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
190
216
  version: 1.3.1
191
217
  requirements: []
192
218
  rubyforge_project:
193
- rubygems_version: 2.6.8
219
+ rubygems_version: 2.6.11
194
220
  signing_key:
195
221
  specification_version: 4
196
222
  summary: Open PaaS provisioning