staticpages-plugin-digitalocean 0.3.5
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 +7 -0
- data/.gitignore +9 -0
- data/.gitmodules +0 -0
- data/.rspec +2 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +214 -0
- data/README.md +18 -0
- data/lib/kontena/machine/digital_ocean/cloudinit.yml +88 -0
- data/lib/kontena/machine/digital_ocean/cloudinit_master.yml +126 -0
- data/lib/kontena/machine/digital_ocean/master_destroyer.rb +30 -0
- data/lib/kontena/machine/digital_ocean/master_provisioner.rb +99 -0
- data/lib/kontena/machine/digital_ocean/node_destroyer.rb +39 -0
- data/lib/kontena/machine/digital_ocean/node_provisioner.rb +90 -0
- data/lib/kontena/machine/digital_ocean/ssh_key_manager.rb +31 -0
- data/lib/kontena/machine/digital_ocean.rb +8 -0
- data/lib/kontena/plugin/digital_ocean/master/create_command.rb +53 -0
- data/lib/kontena/plugin/digital_ocean/master/terminate_command.rb +29 -0
- data/lib/kontena/plugin/digital_ocean/master_command.rb +4 -0
- data/lib/kontena/plugin/digital_ocean/node_command.rb +5 -0
- data/lib/kontena/plugin/digital_ocean/nodes/create_command.rb +68 -0
- data/lib/kontena/plugin/digital_ocean/nodes/restart_command.rb +34 -0
- data/lib/kontena/plugin/digital_ocean/nodes/terminate_command.rb +36 -0
- data/lib/kontena/plugin/digital_ocean/prompts.rb +112 -0
- data/lib/kontena/plugin/digital_ocean.rb +7 -0
- data/lib/kontena/plugin/digital_ocean_command.rb +4 -0
- data/lib/kontena_cli_plugin.rb +5 -0
- data/staticpages-plugin-digitalocean.gemspec +29 -0
- metadata +153 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
module Kontena
|
2
|
+
module Machine
|
3
|
+
module DigitalOcean
|
4
|
+
class MasterDestroyer
|
5
|
+
include Kontena::Cli::ShellSpinner
|
6
|
+
|
7
|
+
attr_reader :client, :api_client
|
8
|
+
|
9
|
+
# @param [String] token Digital Ocean token
|
10
|
+
def initialize(token)
|
11
|
+
@client = DropletKit::Client.new(access_token: token)
|
12
|
+
end
|
13
|
+
|
14
|
+
def run!(name)
|
15
|
+
droplet = client.droplets.all.find{|d| d.name == name}
|
16
|
+
if droplet
|
17
|
+
spinner "Terminating DigitalOcean droplet #{name.colorize(:cyan)} " do
|
18
|
+
result = client.droplets.delete(id: droplet.id)
|
19
|
+
if result.is_a?(String)
|
20
|
+
abort "Cannot delete droplet #{name.colorize(:cyan)} in DigitalOcean"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
else
|
24
|
+
abort "Cannot find droplet #{name.colorize(:cyan)} in DigitalOcean"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'erb'
|
3
|
+
require 'open3'
|
4
|
+
|
5
|
+
module Kontena
|
6
|
+
module Machine
|
7
|
+
module DigitalOcean
|
8
|
+
class MasterProvisioner
|
9
|
+
include RandomName
|
10
|
+
include Machine::CertHelper
|
11
|
+
include Kontena::Cli::ShellSpinner
|
12
|
+
|
13
|
+
attr_reader :client, :http_client
|
14
|
+
|
15
|
+
# @param [String] token Digital Ocean token
|
16
|
+
def initialize(token)
|
17
|
+
@client = DropletKit::Client.new(access_token: token)
|
18
|
+
end
|
19
|
+
|
20
|
+
def run!(opts)
|
21
|
+
if opts[:ssl_cert]
|
22
|
+
abort('Invalid ssl cert') unless File.exists?(File.expand_path(opts[:ssl_cert]))
|
23
|
+
ssl_cert = File.read(File.expand_path(opts[:ssl_cert]))
|
24
|
+
else
|
25
|
+
spinner "Generating self-signed SSL certificate" do
|
26
|
+
ssl_cert = generate_self_signed_cert
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
name = opts[:name]
|
31
|
+
userdata_vars = opts.merge(
|
32
|
+
ssl_cert: ssl_cert,
|
33
|
+
server_name: name
|
34
|
+
)
|
35
|
+
|
36
|
+
droplet = DropletKit::Droplet.new(
|
37
|
+
name: name,
|
38
|
+
region: opts[:region],
|
39
|
+
image: 'coreos-stable',
|
40
|
+
size: opts[:size],
|
41
|
+
private_networking: true,
|
42
|
+
user_data: user_data(userdata_vars),
|
43
|
+
ssh_keys: [opts[:ssh_key_id]],
|
44
|
+
tags: ['master']
|
45
|
+
)
|
46
|
+
|
47
|
+
spinner "Creating DigitalOcean droplet #{droplet.name.colorize(:cyan)} " do
|
48
|
+
droplet = client.droplets.create(droplet)
|
49
|
+
until droplet.status == 'active'
|
50
|
+
droplet = client.droplets.find(id: droplet.id)
|
51
|
+
sleep 1
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
master_url = "https://#{droplet.public_ip}"
|
56
|
+
Excon.defaults[:ssl_verify_peer] = false
|
57
|
+
@http_client = Excon.new("#{master_url}", {
|
58
|
+
:connect_timeout => 10,
|
59
|
+
:ssl_verify_peer => false
|
60
|
+
})
|
61
|
+
|
62
|
+
spinner "Waiting for #{droplet.name.colorize(:cyan)} to start" do
|
63
|
+
sleep 0.5 until master_running?
|
64
|
+
end
|
65
|
+
|
66
|
+
puts
|
67
|
+
puts "Kontena Master is now running at #{master_url}".colorize(:green)
|
68
|
+
puts
|
69
|
+
|
70
|
+
data = {
|
71
|
+
name: name.sub('kontena-master-', ''),
|
72
|
+
public_ip: droplet.public_ip,
|
73
|
+
code: opts[:initial_admin_code]
|
74
|
+
}
|
75
|
+
if respond_to?(:certificate_public_key) && !opts[:ssl_cert]
|
76
|
+
data[:ssl_certificate] = certificate_public_key(ssl_cert)
|
77
|
+
end
|
78
|
+
|
79
|
+
data
|
80
|
+
end
|
81
|
+
|
82
|
+
def user_data(vars)
|
83
|
+
cloudinit_template = File.join(__dir__ , '/cloudinit_master.yml')
|
84
|
+
erb(File.read(cloudinit_template), vars)
|
85
|
+
end
|
86
|
+
|
87
|
+
def master_running?
|
88
|
+
http_client.get(path: '/').status == 200
|
89
|
+
rescue
|
90
|
+
false
|
91
|
+
end
|
92
|
+
|
93
|
+
def erb(template, vars)
|
94
|
+
ERB.new(template, nil, '%<>-').result(OpenStruct.new(vars).instance_eval { binding })
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Kontena
|
2
|
+
module Machine
|
3
|
+
module DigitalOcean
|
4
|
+
class NodeDestroyer
|
5
|
+
include RandomName
|
6
|
+
include Kontena::Cli::ShellSpinner
|
7
|
+
|
8
|
+
attr_reader :client, :api_client
|
9
|
+
|
10
|
+
# @param [Kontena::Client] api_client Kontena api client
|
11
|
+
# @param [String] token Digital Ocean token
|
12
|
+
def initialize(api_client, token)
|
13
|
+
@api_client = api_client
|
14
|
+
@client = DropletKit::Client.new(access_token: token)
|
15
|
+
end
|
16
|
+
|
17
|
+
def run!(grid, name)
|
18
|
+
droplet = client.droplets.all.find{|d| d.name == name}
|
19
|
+
if droplet
|
20
|
+
spinner "Terminating DigitalOcean droplet #{name.colorize(:cyan)} " do
|
21
|
+
result = client.droplets.delete(id: droplet.id)
|
22
|
+
if result.is_a?(String)
|
23
|
+
abort "Cannot delete droplet #{name.colorize(:cyan)} in DigitalOcean"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
else
|
27
|
+
abort "Cannot find droplet #{name.colorize(:cyan)} in DigitalOcean"
|
28
|
+
end
|
29
|
+
node = api_client.get("grids/#{grid['id']}/nodes")['nodes'].find{|n| n['name'] == name}
|
30
|
+
if node
|
31
|
+
spinner "Removing node #{name.colorize(:cyan)} from grid #{grid['name'].colorize(:cyan)} " do
|
32
|
+
api_client.delete("nodes/#{grid['id']}/#{name}")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'erb'
|
3
|
+
require 'open3'
|
4
|
+
|
5
|
+
module Kontena
|
6
|
+
module Machine
|
7
|
+
module DigitalOcean
|
8
|
+
class NodeProvisioner
|
9
|
+
include RandomName
|
10
|
+
include Kontena::Cli::ShellSpinner
|
11
|
+
|
12
|
+
attr_reader :client, :api_client
|
13
|
+
|
14
|
+
# @param [Kontena::Client] api_client Kontena api client
|
15
|
+
# @param [String] token Digital Ocean token
|
16
|
+
def initialize(api_client, token)
|
17
|
+
@api_client = api_client
|
18
|
+
@client = DropletKit::Client.new(access_token: token)
|
19
|
+
end
|
20
|
+
|
21
|
+
def run!(opts)
|
22
|
+
userdata_vars = {
|
23
|
+
version: opts[:version],
|
24
|
+
master_uri: opts[:master_uri],
|
25
|
+
grid_token: opts[:grid_token],
|
26
|
+
}
|
27
|
+
image = "coreos-#{opts[:channel]}"
|
28
|
+
droplets = []
|
29
|
+
opts[:count].to_i.times do
|
30
|
+
droplet = DropletKit::Droplet.new(
|
31
|
+
name: opts[:name] || generate_name,
|
32
|
+
region: opts[:region],
|
33
|
+
image: image,
|
34
|
+
size: opts[:size],
|
35
|
+
private_networking: true,
|
36
|
+
user_data: user_data(userdata_vars),
|
37
|
+
ssh_keys: [opts[:ssh_key_id]],
|
38
|
+
tags: [opts[:grid]]
|
39
|
+
)
|
40
|
+
created = client.droplets.create(droplet)
|
41
|
+
spinner "Creating DigitalOcean droplet #{droplet.name.colorize(:cyan)} " do
|
42
|
+
sleep 1 until client.droplets.find(id: created.id).status == 'active'
|
43
|
+
end
|
44
|
+
droplets << droplet
|
45
|
+
end
|
46
|
+
droplets.each do |droplet|
|
47
|
+
node = nil
|
48
|
+
spinner "Waiting for node #{droplet.name.colorize(:cyan)} join to grid #{opts[:grid].colorize(:cyan)} " do
|
49
|
+
sleep 1 until node = droplet_exists_in_grid?(opts[:grid], droplet)
|
50
|
+
end
|
51
|
+
set_labels(
|
52
|
+
node,
|
53
|
+
[
|
54
|
+
"region=#{opts[:region]}",
|
55
|
+
"az=#{opts[:region]}",
|
56
|
+
"provider=digitalocean"
|
57
|
+
]
|
58
|
+
)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def user_data(vars)
|
63
|
+
cloudinit_template = File.join(__dir__ , '/cloudinit.yml')
|
64
|
+
erb(File.read(cloudinit_template), vars)
|
65
|
+
end
|
66
|
+
|
67
|
+
def generate_name
|
68
|
+
"#{super}-#{rand(1..99)}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def ssh_key(public_key)
|
72
|
+
ssh_key = client.ssh_keys.all.find{|key| key.public_key == public_key}
|
73
|
+
end
|
74
|
+
|
75
|
+
def droplet_exists_in_grid?(grid, droplet)
|
76
|
+
api_client.get("grids/#{grid}/nodes")['nodes'].find{|n| n['name'] == droplet.name}
|
77
|
+
end
|
78
|
+
|
79
|
+
def erb(template, vars)
|
80
|
+
ERB.new(template).result(OpenStruct.new(vars).instance_eval { binding })
|
81
|
+
end
|
82
|
+
|
83
|
+
def set_labels(node, labels)
|
84
|
+
data = {labels: labels}
|
85
|
+
api_client.put("nodes/#{node['id']}", data, {}, {'Kontena-Grid-Token' => node['grid']['token']})
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Kontena
|
2
|
+
module Machine
|
3
|
+
module DigitalOcean
|
4
|
+
class SshKeyManager
|
5
|
+
|
6
|
+
attr_reader :client
|
7
|
+
|
8
|
+
# @param [String] token Digital Ocean API token
|
9
|
+
def initialize(token)
|
10
|
+
@client = DropletKit::Client.new(access_token: token)
|
11
|
+
end
|
12
|
+
|
13
|
+
def find_by_public_key(public_key)
|
14
|
+
list.find { |key| key.public_key == public_key }
|
15
|
+
end
|
16
|
+
|
17
|
+
def list
|
18
|
+
client.ssh_keys.all.to_a
|
19
|
+
end
|
20
|
+
|
21
|
+
def create(public_key)
|
22
|
+
client.ssh_keys.create(DropletKit::SSHKey.new(public_key: public_key, name: public_key.split(/\s+/).last))
|
23
|
+
end
|
24
|
+
|
25
|
+
def find_or_create_by_public_key(public_key)
|
26
|
+
find_by_public_key(public_key) || create(public_key)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
require 'droplet_kit'
|
2
|
+
require 'kontena/machine/random_name'
|
3
|
+
require 'kontena/machine/cert_helper'
|
4
|
+
require_relative 'digital_ocean/node_provisioner'
|
5
|
+
require_relative 'digital_ocean/node_destroyer'
|
6
|
+
require_relative 'digital_ocean/master_provisioner'
|
7
|
+
require_relative 'digital_ocean/master_destroyer'
|
8
|
+
require_relative 'digital_ocean/ssh_key_manager'
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'kontena/plugin/digital_ocean/prompts'
|
2
|
+
|
3
|
+
module Kontena::Plugin::DigitalOcean::Master
|
4
|
+
class CreateCommand < Kontena::Command
|
5
|
+
include Kontena::Cli::Common
|
6
|
+
include Kontena::Plugin::DigitalOcean::Prompts
|
7
|
+
|
8
|
+
option "--name", "[NAME]", "Set master name"
|
9
|
+
option "--token", "TOKEN", "DigitalOcean API token", environment_variable: "DO_TOKEN"
|
10
|
+
option "--region", "REGION", "Region"
|
11
|
+
option "--size", "SIZE", "Droplet size"
|
12
|
+
option "--ssh-key", "SSH_KEY", "Path to ssh public key"
|
13
|
+
option "--ssl-cert", "SSL CERT", "SSL certificate file"
|
14
|
+
option "--vault-secret", "VAULT_SECRET", "Secret key for Vault (optional)"
|
15
|
+
option "--vault-iv", "VAULT_IV", "Initialization vector for Vault (optional)"
|
16
|
+
option "--mongodb-uri", "URI", "External MongoDB uri (optional)"
|
17
|
+
option "--version", "VERSION", "Define installed Kontena version", default: 'latest'
|
18
|
+
|
19
|
+
def execute
|
20
|
+
suppress_warnings # until DO merges resource_kit pr #32
|
21
|
+
do_token = ask_do_token
|
22
|
+
|
23
|
+
require 'securerandom'
|
24
|
+
require 'kontena/machine/digital_ocean'
|
25
|
+
|
26
|
+
do_token = ask_do_token
|
27
|
+
do_region = ask_droplet_region(do_token)
|
28
|
+
do_size = ask_droplet_size(do_token, do_region)
|
29
|
+
do_ssh_key_id = ask_ssh_key(do_token)
|
30
|
+
|
31
|
+
provisioner = provisioner(do_token)
|
32
|
+
provisioner.run!(
|
33
|
+
name: name,
|
34
|
+
ssh_key_id: do_ssh_key_id,
|
35
|
+
ssl_cert: ssl_cert,
|
36
|
+
size: do_size,
|
37
|
+
region: do_region,
|
38
|
+
version: version,
|
39
|
+
vault_secret: vault_secret || SecureRandom.hex(24),
|
40
|
+
vault_iv: vault_iv || SecureRandom.hex(24),
|
41
|
+
initial_admin_code: SecureRandom.hex(16),
|
42
|
+
mongodb_uri: mongodb_uri
|
43
|
+
)
|
44
|
+
ensure
|
45
|
+
resume_warnings
|
46
|
+
end
|
47
|
+
|
48
|
+
# @param [String] token
|
49
|
+
def provisioner(token)
|
50
|
+
Kontena::Machine::DigitalOcean::MasterProvisioner.new(token)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'kontena/plugin/digital_ocean/prompts'
|
2
|
+
|
3
|
+
module Kontena::Plugin::DigitalOcean::Master
|
4
|
+
class TerminateCommand < Kontena::Command
|
5
|
+
include Kontena::Cli::Common
|
6
|
+
include Kontena::Cli::GridOptions
|
7
|
+
include Kontena::Plugin::DigitalOcean::Prompts
|
8
|
+
|
9
|
+
parameter "NAME", "Master name"
|
10
|
+
option "--token", "TOKEN", "DigitalOcean API token", environment_variable: "DO_TOKEN"
|
11
|
+
option "--force", :flag, "Force remove", default: false, attribute_name: :forced
|
12
|
+
|
13
|
+
def execute
|
14
|
+
suppress_warnings # until DO merges resource_kit pr #32
|
15
|
+
require 'kontena/machine/digital_ocean'
|
16
|
+
do_token = ask_do_token
|
17
|
+
confirm_command(name) unless forced?
|
18
|
+
destroyer = destroyer(do_token)
|
19
|
+
destroyer.run!(name)
|
20
|
+
ensure
|
21
|
+
resume_warnings
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param [String] token
|
25
|
+
def destroyer(token)
|
26
|
+
Kontena::Machine::DigitalOcean::MasterDestroyer.new(token)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,4 @@
|
|
1
|
+
class Kontena::Plugin::DigitalOcean::MasterCommand < Kontena::Command
|
2
|
+
subcommand "create", "Create a new master to DigitalOcean", load_subcommand('kontena/plugin/digital_ocean/master/create_command')
|
3
|
+
subcommand "terminate", "Terminate DigitalOcean master", load_subcommand('kontena/plugin/digital_ocean/master/terminate_command')
|
4
|
+
end
|
@@ -0,0 +1,5 @@
|
|
1
|
+
class Kontena::Plugin::DigitalOcean::NodeCommand < Kontena::Command
|
2
|
+
subcommand "create", "Create a new node to DigitalOcean", load_subcommand('kontena/plugin/digital_ocean/nodes/create_command')
|
3
|
+
subcommand "restart", "Restart DigitalOcean node", load_subcommand('kontena/plugin/digital_ocean/nodes/restart_command')
|
4
|
+
subcommand "terminate", "Terminate DigitalOcean node", load_subcommand('kontena/plugin/digital_ocean/nodes/terminate_command')
|
5
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'kontena/plugin/digital_ocean/prompts'
|
2
|
+
|
3
|
+
module Kontena::Plugin::DigitalOcean::Nodes
|
4
|
+
class CreateCommand < Kontena::Command
|
5
|
+
include Kontena::Cli::Common
|
6
|
+
include Kontena::Cli::GridOptions
|
7
|
+
include Kontena::Plugin::DigitalOcean::Prompts
|
8
|
+
|
9
|
+
parameter "[NAME]", "Node name"
|
10
|
+
option "--token", "TOKEN", "DigitalOcean API token", environment_variable: 'DO_TOKEN'
|
11
|
+
option "--region", "REGION", "Region"
|
12
|
+
option "--ssh-key", "SSH_KEY", "Path to ssh public key"
|
13
|
+
option "--size", "SIZE", "Droplet size"
|
14
|
+
option "--count", "COUNT", "How many droplets to create"
|
15
|
+
option "--version", "VERSION", "Define installed Kontena version", default: 'latest'
|
16
|
+
option "--channel", "CHANNEL", "Define CoreOS image channel"
|
17
|
+
|
18
|
+
def execute
|
19
|
+
suppress_warnings # until DO merges resource_kit pr #32
|
20
|
+
require 'kontena/machine/digital_ocean'
|
21
|
+
require_api_url
|
22
|
+
require_current_grid
|
23
|
+
|
24
|
+
do_token = ask_do_token
|
25
|
+
do_region = ask_droplet_region(do_token)
|
26
|
+
coreos_channel = self.channel || ask_channel
|
27
|
+
do_size = ask_droplet_size(do_token, do_region)
|
28
|
+
do_count = ask_droplet_count
|
29
|
+
do_ssh_key_id = ask_ssh_key(do_token)
|
30
|
+
|
31
|
+
grid = fetch_grid
|
32
|
+
provisioner = provisioner(client(require_token), do_token)
|
33
|
+
provisioner.run!(
|
34
|
+
master_uri: api_url,
|
35
|
+
grid_token: grid['token'],
|
36
|
+
grid: current_grid,
|
37
|
+
ssh_key_id: do_ssh_key_id,
|
38
|
+
name: name,
|
39
|
+
size: do_size,
|
40
|
+
count: do_count,
|
41
|
+
region: do_region,
|
42
|
+
version: version,
|
43
|
+
channel: coreos_channel
|
44
|
+
)
|
45
|
+
ensure
|
46
|
+
resume_warnings
|
47
|
+
end
|
48
|
+
|
49
|
+
def ask_droplet_count
|
50
|
+
if self.count.nil?
|
51
|
+
prompt.ask('How many droplets?:', default: 1)
|
52
|
+
else
|
53
|
+
self.count
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# @param [Kontena::Client] client
|
58
|
+
# @param [String] token
|
59
|
+
def provisioner(client, token)
|
60
|
+
Kontena::Machine::DigitalOcean::NodeProvisioner.new(client, token)
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [Hash]
|
64
|
+
def fetch_grid
|
65
|
+
client(require_token).get("grids/#{current_grid}")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|