provisioning 0.0.1.alpha.2 → 0.0.1.alpha.3

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: cf03c84e2e8d34e1c1dfce24fc082dcd4398280b
4
- data.tar.gz: '09f4199f5ed0a8ff641198ff0f415334044dc0ed'
3
+ metadata.gz: c4c3ddbf8d9e399ad23cec152889080da86482c6
4
+ data.tar.gz: 586672476340031fa884f5e9fad0abc008811b79
5
5
  SHA512:
6
- metadata.gz: b53ad19a248e97f54ffbe212aab0576db04c53914744f2837a50e194299f7d5ed7990632424ff15ec3bdcd2d2ff68ec61271c1f79c892bd61b01f623c4db7248
7
- data.tar.gz: 1450bb3d987f0e10badf8cfdaf515398c889bb73547107716d4f15f8692887f3ff91eba26be724f584019d8c162c46d423e0fb377eb6ca73bd7bb18d1ae33626
6
+ metadata.gz: bdc9b54888a68f7e09483aa574eeaa9a8365676cc833ec39ae027cc0d0fcbce477150840d2f4891a3238e2b77c7dcffcf5ac9962a0e012d5e9c840cb02721668
7
+ data.tar.gz: 51b45ae173f24de850b15564a3f6ff2319d907acc7384ecc863ff6259c919cafcee4c1aad8b6197be8b1476b5c57886024abe1d2090793460044e42486393221
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Vincent Ollivier
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,86 @@
1
+ Provisioning.rb
2
+ ===============
3
+
4
+ Open PaaS provisioning.
5
+
6
+ This gem will install a `provision` command for provisioning an open
7
+ platform-as-a-service (PaaS) from a JSON manifest file.
8
+
9
+ Currently limited to dokku apps on AWS or DigitalOcean servers.
10
+
11
+
12
+ Installation
13
+ ------------
14
+
15
+ $ gem install provisioning
16
+
17
+ Alternatively you can build the gem from its repository:
18
+
19
+ $ git clone git://github.com/vinc/provisioning.git
20
+ $ cd provisioning
21
+ $ gem build provisioning.gemspec
22
+ $ gem install provisioning-0.0.1.gem
23
+
24
+
25
+ Usage
26
+ -----
27
+
28
+ Run the provisioning script:
29
+
30
+ $ provision manifest.json
31
+ 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
+
51
+ Adding dokku to git remotes
52
+ Run `git push dokku master` to deploy your code
53
+
54
+ Provisioning manifest json file:
55
+
56
+ ```json
57
+ {
58
+ "manifest": {
59
+ "app": {
60
+ "name": "example",
61
+ "domains": ["example.com", "www.example.com"],
62
+ "services": ["postgres", "redis"]
63
+ },
64
+ "platform": {
65
+ "provider": "dokku",
66
+ "domain": "sfo1.example.net",
67
+ "version": "v0.10.4"
68
+ },
69
+ "compute": {
70
+ "provider": "digitalocean",
71
+ "region": "sfo1",
72
+ "image": "ubuntu-16-04-x64",
73
+ "size": "1gb"
74
+ },
75
+ "dns": {
76
+ "provider": "digitalocean"
77
+ }
78
+ }
79
+ }
80
+ ```
81
+
82
+
83
+ License
84
+ -------
85
+
86
+ Copyright (C) 2017 Vincent Ollivier. Released under MIT.
@@ -2,4 +2,4 @@
2
2
 
3
3
  require "provisioning"
4
4
 
5
- Provisioning::CLI.start(ARGV, ENV)
5
+ Provisioning::CLI.new(ARGV, ENV).run
@@ -0,0 +1,13 @@
1
+ class Object
2
+ def blank?
3
+ respond_to?(:empty?) ? !!empty? : !self
4
+ end
5
+
6
+ def present?
7
+ !blank?
8
+ end
9
+
10
+ def presence
11
+ self if present?
12
+ end
13
+ end
@@ -1,7 +1,12 @@
1
+ require "object"
1
2
  require "provisioning/cli"
3
+ require "provisioning/compute/aws"
4
+ require "provisioning/compute/digitalocean"
2
5
  require "provisioning/console"
3
- require "provisioning/digitalocean"
4
- require "provisioning/dokku"
6
+ require "provisioning/dns/digitalocean"
7
+ require "provisioning/platform/dokku"
8
+ require "provisioning/public_key"
9
+ require "provisioning/version"
5
10
 
6
11
  module Provisioning
7
12
  end
@@ -2,106 +2,156 @@ require "git"
2
2
  require "json"
3
3
  require "net/ssh"
4
4
  require "rainbow"
5
+ require "trollop"
5
6
 
6
7
  require "provisioning"
7
8
 
8
9
  module Provisioning
9
- module CLI
10
- def self.get_ssh_key(fingerprint)
11
- Console.info("Getting SSH key from authentication agent")
12
- agent = Net::SSH::Authentication::Agent.connect
13
- agent.identities.find do |identity|
14
- identity.fingerprint == fingerprint
15
- end || Console.error("could not get key from the authentication agent, run `ssh-add`")
16
- end
10
+ class CLI
11
+ def initialize(args, env)
12
+ @opts = parse_opts(args)
13
+ @env = env
14
+
15
+ if @opts[:silent]
16
+ Console.silent_mode!
17
+ elsif @opts[:verbose]
18
+ Console.debug_mode!
19
+ end
17
20
 
18
- def self.start(args, env)
19
- json_file = args.shift || "manifest.json"
21
+ @manifest = self.class.read_manifest_file(args.shift || "manifest.json")
20
22
 
21
- begin
22
- json = JSON.parse(File.open(json_file).read)
23
- rescue Errno::ENOENT, JSON::ParserError
24
- Console.error("could not read provisioning manifest file '#{json_file}'")
25
- end
23
+ set_instance_variable_from_manifest(%w[app name])
24
+ set_instance_variable_from_manifest(%w[platform domain])
25
+ set_instance_variable_from_manifest(%w[platform provider])
26
+ set_instance_variable_from_manifest(%w[compute provider])
27
+ set_instance_variable_from_manifest(%w[dns provider])
28
+
29
+ @server_address = nil
30
+ @server_hostname = [@platform_provider, @platform_domain].join(".")
31
+
32
+ @ssh_key = PublicKey.new(@env["SSH_PUBLIC_KEY"])
33
+ end
26
34
 
27
- manifest = json["manifest"]
35
+ def parse_opts(args)
36
+ Trollop::options(args) do
37
+ version "Provisioning v#{Provisioning::VERSION}"
38
+ opt :silent, "Use silent mode"
39
+ opt :verbose, "Use verbose mode"
40
+ opt :mock, "Use mock mode"
41
+ opt :help, "Show this message"
42
+ opt :version, "Print version and exit", short: "V"
43
+ end
44
+ end
28
45
 
29
- ssh_key_fingerprint = manifest["ssh"]["key"]["fingerprint"]
46
+ def run
47
+ provision_compute
48
+ provision_dns
49
+ provision_platform
50
+ add_git_remote
51
+ end
30
52
 
31
- ssh_key = get_ssh_key(ssh_key_fingerprint)
32
- puts
53
+ def provision_compute
54
+ klass = Compute.const_get(@compute_provider.capitalize)
55
+ compute = klass.new(@manifest["compute"], @opts, @env)
33
56
 
34
- app_name = manifest["app"]["name"]
35
- domain = manifest["domain"]
36
- platform = manifest["providers"]["platform"]
37
- server_hostname = [platform, domain].join(".")
38
- server_address = nil
57
+ compute.upload_ssh_key(@ssh_key)
39
58
 
40
- if manifest["providers"]["hosting"] == "digitalocean"
41
- digitalocean = DigitalOcean.new(manifest["digitalocean"])
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
65
+ end
42
66
 
43
- digitalocean.upload_ssh_key(ssh_key)
44
- puts
67
+ def provision_dns
68
+ klass = DNS.const_get(@dns_provider.capitalize)
69
+ dns = klass.new(@manifest["dns"], @opts, @env)
45
70
 
46
- droplet = digitalocean.create_droplet(
47
- name: server_hostname,
48
- ssh_key_fingerprint: ssh_key_fingerprint
49
- )
50
- server_address = droplet.networks.v4.first.ip_address
51
- puts
71
+ dns.create_domain(@platform_domain, @server_address)
72
+ Console.success("Configue '#{@platform_domain}' with the following DNS servers:")
73
+ dns.get_domain_name_servers(@platform_domain).each do |hostname|
74
+ Console.success(" - #{hostname}")
52
75
  end
53
76
 
54
- if manifest["providers"]["dns"] == "digitalocean"
55
- digitalocean = DigitalOcean.new(manifest["digitalocean"])
56
-
57
- digitalocean.create_domain(domain, server_address)
58
- Console.success("Configue '#{domain}' with the following DNS servers:")
59
- digitalocean.get_domain_name_servers(domain).each do |server|
60
- Console.success(" - #{server}")
61
- end
62
- puts
63
-
64
- digitalocean.create_domain_record(
65
- domain: domain,
66
- type: "A",
67
- name: platform,
68
- data: server_address
69
- )
70
- digitalocean.create_domain_record(
71
- domain: domain,
72
- type: "CNAME",
73
- name: app_name,
74
- data: "#{server_hostname}."
75
- )
76
- manifest["app"]["domains"].each do |app_domain|
77
- Console.success("Configue '#{app_domain}' to point to '#{server_hostname}'")
78
- end
79
- puts
77
+ dns.create_domain_record(
78
+ domain: @platform_domain,
79
+ type: "A",
80
+ name: @platform_provider,
81
+ data: @server_address
82
+ )
83
+
84
+ dns.create_domain_record(
85
+ domain: @platform_domain,
86
+ type: "CNAME",
87
+ name: @app_name,
88
+ data: @server_hostname + "."
89
+ )
90
+
91
+ @manifest["app"]["domains"].each do |app_domain|
92
+ Console.success("Configue '#{app_domain}' to point to '#{@server_hostname}'")
80
93
  end
94
+ end
81
95
 
82
- if manifest["providers"]["platform"] == "dokku"
83
- dokku = Dokku.new(manifest["dokku"])
84
- dokku.setup(address: server_address, domain: domain)
85
- Console.success("Run `gem install dokku-cli` to get dokku client on your machine")
86
- puts
96
+ def provision_platform
97
+ klass = Platform.const_get(@platform_provider.capitalize)
98
+ platform = klass.new(@manifest["platform"], @opts, @env)
87
99
 
88
- dokku.create_app(manifest["app"])
89
- puts
100
+ platform.setup(address: @server_address, domain: @platform_domain)
101
+ platform.create_app(@manifest["app"])
90
102
 
91
- Console.info("Adding dokku to git remotes")
92
- begin
93
- git = Git.open(".")
94
- rescue ArgumentError
95
- Console.warning("not a git repository, skipping")
103
+ case @platform_provider
104
+ when "dokku"
105
+ Console.success("Run `gem install dokku-cli` to get dokku client on your computer")
106
+ end
107
+ end
108
+
109
+ def add_git_remote
110
+ Console.info("Adding #{@platform_provider} to git remotes")
111
+ return if @opts[:mock]
112
+ begin
113
+ git = Git.open(".")
114
+ rescue ArgumentError
115
+ Console.warning("Not a git repository, skipping")
116
+ else
117
+ if git.remotes.map(&:name).include?(@platform_provider)
118
+ Console.warning("Remote already exists, skipping")
96
119
  else
97
- if git.remotes.map(&:name).include?("dokku")
98
- Console.warning("remote already exists, skipping")
99
- else
100
- git.add_remote("dokku", "dokku@#{server_hostname}:#{app_name}")
120
+ case @platform_provider
121
+ when "dokku"
122
+ url = "#{@platform_provider}@#{@server_hostname}:#{@app_name}"
123
+ git.add_remote(@platform_provider, url)
101
124
  end
102
- Console.success("Run `git push dokku master` to deploy your code")
103
125
  end
126
+ Console.success("Run `git push dokku master` to deploy your code")
127
+ end
128
+ end
129
+
130
+ def self.read_manifest_file(filename)
131
+ Console.info("Reading provisioning manifest file '#{filename}'")
132
+ begin
133
+ json = JSON.parse(File.open(filename).read)
134
+ rescue Errno::ENOENT, JSON::ParserError
135
+ Console.error("Could not read provisioning manifest file '#{filename}'")
136
+ end
137
+ json["manifest"]
138
+ end
139
+
140
+ private
141
+
142
+ def set_instance_variable_from_manifest(keys)
143
+ name = "@" + keys.join("_")
144
+ instance_variable_set(name, dig_manifest(keys))
145
+ end
146
+
147
+ def dig_manifest(keys)
148
+ path = []
149
+ hash = @manifest
150
+ keys.each do |key|
151
+ path << key
152
+ hash = hash[key] || Console.error("Could not find #{path.join(".")} in manifest")
104
153
  end
154
+ hash
105
155
  end
106
156
  end
107
157
  end
@@ -0,0 +1,50 @@
1
+ require "fog/aws"
2
+
3
+ require "provisioning"
4
+
5
+ module Provisioning
6
+ module Compute
7
+ class Aws
8
+ KEY_NAME = "provisioning key".freeze
9
+
10
+ def initialize(config, opts, env)
11
+ @config = config
12
+ @opts = opts
13
+ Fog.mock! if @opts[:mock]
14
+ @client = Fog::Compute.new(
15
+ 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"]
19
+ )
20
+ end
21
+
22
+ def upload_ssh_key(ssh_key)
23
+ Console.info("Uploading SSH key to Amazon Web Services")
24
+ if @client.key_pairs.get(KEY_NAME).nil?
25
+ @client.import_key_pair(KEY_NAME, ssh_key.to_s)
26
+ else
27
+ Console.warning("SSH key already uploaded, skipping")
28
+ end
29
+ end
30
+
31
+ def find_or_create_server(name:, ssh_key:)
32
+ Console.info("Creating server '#{name}'")
33
+
34
+ @client.servers.all.each do |server|
35
+ if server.ready? && server.tags["name"] == name
36
+ Console.warning("Server already exists, skipping")
37
+ return server
38
+ end
39
+ end
40
+
41
+ @client.servers.create(
42
+ image_id: @config["image_id"],
43
+ flavor_id: @config["flavor_id"],
44
+ tags: { name: name },
45
+ key_name: KEY_NAME
46
+ )
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,52 @@
1
+ require "fog/digitalocean"
2
+
3
+ require "provisioning"
4
+
5
+ module Provisioning
6
+ module Compute
7
+ class Digitalocean
8
+ KEY_NAME = "provisioning key".freeze
9
+
10
+ def initialize(config, opts, env)
11
+ @config = config
12
+ @opts = opts
13
+ Fog.mock! if @opts[:mock]
14
+ @client = Fog::Compute.new(
15
+ provider: :digitalocean,
16
+ digitalocean_token: env["DIGITALOCEAN_TOKEN"]
17
+ )
18
+ end
19
+
20
+ def upload_ssh_key(ssh_key)
21
+ Console.info("Uploading SSH key to DigitalOcean")
22
+ if @client.ssh_keys.all.map(&:fingerprint).include?(ssh_key.fingerprint)
23
+ Console.warning("SSH key already uploaded, skipping")
24
+ else
25
+ @client.ssh_keys.create(
26
+ name: KEY_NAME,
27
+ public_key: ssh_key.to_s
28
+ )
29
+ end
30
+ end
31
+
32
+ def find_or_create_server(name:, ssh_key:)
33
+ Console.info("Creating server '#{name}'")
34
+
35
+ @client.servers.all.each do |server|
36
+ if server.name == name
37
+ Console.warning("Server already exists, skipping")
38
+ return server
39
+ end
40
+ end
41
+
42
+ @client.servers.create(
43
+ name: name,
44
+ region: @config["region"],
45
+ image: @config["image"],
46
+ size: @config["size"],
47
+ ssh_keys: [ssh_key.fingerprint]
48
+ )
49
+ end
50
+ end
51
+ end
52
+ end
@@ -2,20 +2,34 @@ require "rainbow"
2
2
 
3
3
  module Provisioning
4
4
  module Console
5
+ @verbosity = 3
6
+
7
+ def self.debug_mode!
8
+ @verbosity = 4
9
+ end
10
+
11
+ def self.silent_mode!
12
+ @verbosity = 0
13
+ end
14
+
15
+ def self.debug(text)
16
+ puts text if @verbosity > 3
17
+ end
18
+
5
19
  def self.info(text)
6
- puts text
20
+ puts text if @verbosity > 2
7
21
  end
8
22
 
9
23
  def self.success(text)
10
- puts Rainbow("#{text}").green
24
+ puts Rainbow("#{text}").green if @verbosity > 1
11
25
  end
12
26
 
13
27
  def self.warning(text)
14
- puts Rainbow("Warning: #{text}").yellow
28
+ puts Rainbow("Warning: #{text}").yellow if @verbosity > 1
15
29
  end
16
30
 
17
31
  def self.error(text)
18
- puts Rainbow("Error: #{text}").red
32
+ puts Rainbow("Error: #{text}").red if @verbosity > 0
19
33
  exit 1
20
34
  end
21
35
  end
@@ -0,0 +1,68 @@
1
+ require "droplet_kit"
2
+
3
+ require "provisioning"
4
+
5
+ module Provisioning
6
+ module DNS
7
+ class Digitalocean
8
+ def initialize(config, opts, env)
9
+ @config = config
10
+ @opts = opts
11
+ @client = DropletKit::Client.new(
12
+ access_token: env["DIGITALOCEAN_TOKEN"]
13
+ ) unless @opts[:mock]
14
+ end
15
+
16
+ def create_domain(name, address)
17
+ Console.info("Creating domain '#{name}'")
18
+
19
+ disable_verbose
20
+ return if @opts[:mock]
21
+ if @client.domains.all.map(&:name).include?(name)
22
+ Console.warning("Domain already exists, skipping")
23
+ else
24
+ domain = DropletKit::Domain.new(name: name, ip_address: address)
25
+ @client.domains.create(domain)
26
+ end
27
+ ensure
28
+ restore_verbose
29
+ end
30
+
31
+ def create_domain_record(domain:, type:, name:, data:)
32
+ Console.info("Creating domain record #{type} '#{name}.#{domain}' to '#{data}'")
33
+
34
+ disable_verbose
35
+ 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)
41
+ end
42
+ ensure
43
+ restore_verbose
44
+ end
45
+
46
+ def get_domain_name_servers(domain)
47
+ disable_verbose
48
+ return %w[ns1.example.net ns2.example.net] if @opts[:mock]
49
+ @client.domain_records.all(for_domain: domain).
50
+ select { |r| r.type == "NS" }.
51
+ map(&:data)
52
+ ensure
53
+ restore_verbose
54
+ end
55
+
56
+ private
57
+
58
+ def disable_verbose
59
+ $OLD_VERBOSE = $VERBOSE
60
+ $VERBOSE = nil
61
+ end
62
+
63
+ def restore_verbose
64
+ $VERBOSE = $OLD_VERBOSE
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,65 @@
1
+ require "net/ssh"
2
+
3
+ require "provisioning"
4
+
5
+ module Provisioning
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"]
17
+ Console.info("Installing dokku #{version} on '#{address}'")
18
+ return if @opts[:mock]
19
+ Net::SSH.start(address, "root") do |ssh|
20
+ if ssh.exec!("which dokku").present?
21
+ Console.warning("Dokku already installed, skipping")
22
+ else
23
+ [
24
+ "wget https://raw.githubusercontent.com/dokku/dokku/#{version}/bootstrap.sh",
25
+ "DOKKU_TAG=#{version} bash bootstrap.sh",
26
+ "service dokku-installer stop",
27
+ "systemctl disable dokku-installer",
28
+ "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)) }
32
+ end
33
+ end
34
+ end
35
+
36
+ def create_app(config)
37
+ name = config["name"]
38
+ @servers.each do |address|
39
+ 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)
43
+ if existing_apps.include?(name)
44
+ Console.warning("App already exists, skipping")
45
+ else
46
+ Console.debug(ssh.exec!("dokku apps:create #{name}"))
47
+
48
+ config["services"].each do |service|
49
+ #TODO check if service exists
50
+ [
51
+ "dokku plugin:install https://github.com/dokku/dokku-#{service}.git #{service}",
52
+ "dokku #{service}:create #{name}-#{service}",
53
+ "dokku #{service}:link #{name}-#{service} #{name}"
54
+ ].each { |command| Console.debug(ssh.exec!(command)) }
55
+ end
56
+
57
+ domains = config["domains"].join(" ")
58
+ Console.debug(ssh.exec!("dokku domains:add #{name} #{domains}"))
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,21 @@
1
+ module Provisioning
2
+ class PublicKey
3
+ def initialize(key)
4
+ unless key.start_with?("ssh-rsa ")
5
+ raise ArgumentError.new("Wrong SSH RSA public key format")
6
+ end
7
+ @key = key
8
+ end
9
+
10
+ def fingerprint
11
+ bin = Base64.decode64(@key.split[1])
12
+ md5 = OpenSSL::Digest::MD5.new(bin)
13
+ md5.to_s.scan(/../).join(":")
14
+ end
15
+
16
+ def to_s
17
+ @key
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,3 @@
1
+ module Provisioning
2
+ VERSION = "0.0.1.alpha.3".freeze
3
+ end
metadata CHANGED
@@ -1,75 +1,95 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: provisioning
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1.alpha.2
4
+ version: 0.0.1.alpha.3
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-25 00:00:00.000000000 Z
11
+ date: 2017-09-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: git
14
+ name: droplet_kit
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.3'
19
+ version: '2.1'
20
20
  - - ">="
21
21
  - !ruby/object:Gem::Version
22
- version: 1.3.0
22
+ version: 2.1.0
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - "~>"
28
28
  - !ruby/object:Gem::Version
29
- version: '1.3'
29
+ version: '2.1'
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: 1.3.0
32
+ version: 2.1.0
33
33
  - !ruby/object:Gem::Dependency
34
- name: rainbow
34
+ name: fog-aws
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '2.2'
39
+ version: '1.4'
40
40
  - - ">="
41
41
  - !ruby/object:Gem::Version
42
- version: 2.2.0
42
+ version: 1.4.0
43
43
  type: :runtime
44
44
  prerelease: false
45
45
  version_requirements: !ruby/object:Gem::Requirement
46
46
  requirements:
47
47
  - - "~>"
48
48
  - !ruby/object:Gem::Version
49
- version: '2.2'
49
+ version: '1.4'
50
50
  - - ">="
51
51
  - !ruby/object:Gem::Version
52
- version: 2.2.0
52
+ version: 1.4.0
53
53
  - !ruby/object:Gem::Dependency
54
- name: droplet_kit
54
+ name: fog-digitalocean
55
55
  requirement: !ruby/object:Gem::Requirement
56
56
  requirements:
57
57
  - - "~>"
58
58
  - !ruby/object:Gem::Version
59
- version: '2.1'
59
+ version: '0.3'
60
60
  - - ">="
61
61
  - !ruby/object:Gem::Version
62
- version: 2.1.0
62
+ version: 0.3.0
63
63
  type: :runtime
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
67
  - - "~>"
68
68
  - !ruby/object:Gem::Version
69
- version: '2.1'
69
+ version: '0.3'
70
70
  - - ">="
71
71
  - !ruby/object:Gem::Version
72
- version: 2.1.0
72
+ version: 0.3.0
73
+ - !ruby/object:Gem::Dependency
74
+ name: git
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: '1.3'
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 1.3.0
83
+ type: :runtime
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.3'
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: 1.3.0
73
93
  - !ruby/object:Gem::Dependency
74
94
  name: net-ssh
75
95
  requirement: !ruby/object:Gem::Requirement
@@ -90,19 +110,66 @@ dependencies:
90
110
  - - ">="
91
111
  - !ruby/object:Gem::Version
92
112
  version: 4.1.0
93
- description: PaaS Provisioning
113
+ - !ruby/object:Gem::Dependency
114
+ name: rainbow
115
+ requirement: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - "~>"
118
+ - !ruby/object:Gem::Version
119
+ version: '2.2'
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: 2.2.0
123
+ type: :runtime
124
+ prerelease: false
125
+ version_requirements: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: '2.2'
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: 2.2.0
133
+ - !ruby/object:Gem::Dependency
134
+ name: trollop
135
+ requirement: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - "~>"
138
+ - !ruby/object:Gem::Version
139
+ version: '2.1'
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: 2.1.0
143
+ type: :runtime
144
+ prerelease: false
145
+ version_requirements: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - "~>"
148
+ - !ruby/object:Gem::Version
149
+ version: '2.1'
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: 2.1.0
153
+ description: Open PaaS provisioning on cloud providers from JSON manifest file
94
154
  email: v@vinc.cc
95
155
  executables:
96
156
  - provision
97
157
  extensions: []
98
158
  extra_rdoc_files: []
99
159
  files:
160
+ - LICENSE
161
+ - README.md
100
162
  - bin/provision
163
+ - lib/object.rb
101
164
  - lib/provisioning.rb
102
165
  - lib/provisioning/cli.rb
166
+ - lib/provisioning/compute/aws.rb
167
+ - lib/provisioning/compute/digitalocean.rb
103
168
  - lib/provisioning/console.rb
104
- - lib/provisioning/digitalocean.rb
105
- - lib/provisioning/dokku.rb
169
+ - lib/provisioning/dns/digitalocean.rb
170
+ - lib/provisioning/platform/dokku.rb
171
+ - lib/provisioning/public_key.rb
172
+ - lib/provisioning/version.rb
106
173
  homepage: https://github.com/vinc/provisioning.rb
107
174
  licenses:
108
175
  - MIT
@@ -126,5 +193,5 @@ rubyforge_project:
126
193
  rubygems_version: 2.6.8
127
194
  signing_key:
128
195
  specification_version: 4
129
- summary: PaaS Provisioning
196
+ summary: Open PaaS provisioning
130
197
  test_files: []
@@ -1,100 +0,0 @@
1
- require "droplet_kit"
2
-
3
- require "provisioning"
4
-
5
- module Provisioning
6
- class DigitalOcean
7
- def initialize(config)
8
- @config = config
9
- @client = DropletKit::Client.new(access_token: @config["token"])
10
- end
11
-
12
- def upload_ssh_key(ssh_key)
13
- Console.info("Uploading SSH key to DigitalOcean")
14
-
15
- disable_verbose
16
- if @client.ssh_keys.all.map(&:fingerprint).include?(ssh_key.fingerprint)
17
- Console.warning("SSH key already uploaded to DigitalOcean, skipping")
18
- else
19
- key = DropletKit::SSHKey.new(
20
- name: "provisioning key",
21
- public_key: ssh_key.to_s
22
- )
23
- @client.ssh_keys.create(key)
24
- end
25
- restore_verbose
26
- end
27
-
28
- def create_droplet(name:, ssh_key_fingerprint:)
29
- Console.info("Creating droplet '#{name}'")
30
-
31
- disable_verbose
32
- @client.droplets.all.each do |droplet|
33
- if droplet.name == name
34
- Console.warning("droplet already exists, skipping")
35
- return droplet
36
- end
37
- end
38
-
39
- droplet = DropletKit::Droplet.new(
40
- name: name,
41
- region: @config["droplet"]["region"],
42
- image: @config["droplet"]["image"],
43
- size: @config["droplet"]["size"],
44
- ssh_keys: [ssh_key_fingerprint]
45
- )
46
- @client.droplets.create(droplet)
47
- droplet
48
- ensure
49
- restore_verbose
50
- end
51
-
52
- def create_domain(name, address)
53
- Console.info("Creating domain '#{name}'")
54
-
55
- disable_verbose
56
- if @client.domains.all.map(&:name).include?(name)
57
- Console.warning("domain already exists, skipping")
58
- else
59
- domain = DropletKit::Domain.new(name: name, ip_address: address)
60
- @client.domains.create(domain)
61
- end
62
- ensure
63
- restore_verbose
64
- end
65
-
66
- def create_domain_record(domain:, type:, name:, data:)
67
- Console.info("Creating domain record #{type} '#{name}.#{domain}' to '#{data}'")
68
-
69
- disable_verbose
70
- if @client.domain_records.all(for_domain: domain).map(&:name).include?(name)
71
- Console.warning("record already exists, skipping")
72
- else
73
- record = DropletKit::DomainRecord.new(type: type, name: name, data: data)
74
- @client.domain_records.create(record, for_domain: domain)
75
- end
76
- ensure
77
- restore_verbose
78
- end
79
-
80
- def get_domain_name_servers(domain)
81
- disable_verbose
82
- @client.domain_records.all(for_domain: domain).
83
- select { |r| r.type == "NS" }.
84
- map(&:data)
85
- ensure
86
- restore_verbose
87
- end
88
-
89
- private
90
-
91
- def disable_verbose
92
- $OLD_VERBOSE = $VERBOSE
93
- $VERBOSE = nil
94
- end
95
-
96
- def restore_verbose
97
- $VERBOSE = $OLD_VERBOSE
98
- end
99
- end
100
- end
@@ -1,58 +0,0 @@
1
- require "net/ssh"
2
-
3
- require "provisioning"
4
-
5
- module Provisioning
6
- class Dokku
7
- def initialize(config)
8
- @config = config
9
- @servers = []
10
- end
11
-
12
- def setup(address:, domain:)
13
- @servers << address
14
- version = @config["version"]
15
- Console.info("Installing dokku #{version} on '#{address}'")
16
- Net::SSH.start(address, "root") do |ssh|
17
- if ssh.exec!("which dokku").present?
18
- Console.warning("dokku already installed, skipping")
19
- else
20
- puts ssh.exec!("wget https://raw.githubusercontent.com/dokku/dokku/#{version}/bootstrap.sh")
21
- puts ssh.exec!("DOKKU_TAG=#{version} bash bootstrap.sh")
22
- puts ssh.exec!("service dokku-installer stop")
23
- puts ssh.exec!("systemctl disable dokku-installer")
24
- puts ssh.exec!("cat .ssh/authorized_keys | sshcommand acl-add dokku admin")
25
- puts ssh.exec!("echo -n #{domain} > /home/dokku/VHOST")
26
- puts ssh.exec!("echo -n #{domain} > /home/dokku/HOSTNAME")
27
- end
28
- end
29
- end
30
-
31
- def create_app(config)
32
- name = config["name"]
33
- @servers.each do |address|
34
- Console.info("Creating dokku app '#{name}' on '#{address}'")
35
- Net::SSH.start(address, "root") do |ssh|
36
- existing_apps = ssh.exec!("dokku apps").to_s.lines.map(&:chomp)
37
- if existing_apps.include?(name)
38
- Console.warning("app already exists, skipping")
39
- else
40
- puts ssh.exec!("dokku apps:create #{name}")
41
-
42
- config["services"].each do |service|
43
- case service
44
- when "mongo", "postgres", "redis"
45
- puts ssh.exec!("dokku plugin:install https://github.com/dokku/dokku-#{service}.git #{service}")
46
- puts ssh.exec!("dokku #{service}:create #{name}-#{service}")
47
- puts ssh.exec!("dokku #{service}:link #{name}-#{service} #{name}")
48
- end
49
- end
50
-
51
- domains = config["domains"].join(" ")
52
- puts ssh.exec!("dokku domains:add #{name} #{domains}")
53
- end
54
- end
55
- end
56
- end
57
- end
58
- end