staticpages-plugin-digitalocean 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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