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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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