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

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: 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