krates-plugin-digitalocean 0.3.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,34 @@
1
+ require 'kontena/plugin/digital_ocean/prompts'
2
+
3
+ module Kontena::Plugin::DigitalOcean::Nodes
4
+ class RestartCommand < 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
+
12
+ def execute
13
+ suppress_warnings # until DO merges resource_kit pr #32
14
+ require 'kontena/machine/digital_ocean'
15
+ require_api_url
16
+ require_current_grid
17
+ do_token = ask_do_token
18
+
19
+ node_name = ask_node(require_token)
20
+ client = DropletKit::Client.new(access_token: do_token)
21
+ droplet = client.droplets.all.find{|d| d.name == node_name}
22
+ if droplet
23
+ spinner "Restarting DigitalOcean droplet #{pastel.cyan(name)} " do
24
+ client.droplet_actions.reboot(droplet_id: droplet.id)
25
+ sleep 1 until client.droplets.find(id: droplet.id).status == 'active'
26
+ end
27
+ else
28
+ exit_with_error "Cannot find droplet #{pastel.cyan(name)} in DigitalOcean"
29
+ end
30
+ ensure
31
+ resume_warnings
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,36 @@
1
+ require 'kontena/plugin/digital_ocean/prompts'
2
+
3
+ module Kontena::Plugin::DigitalOcean::Nodes
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]", "Node 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
+ require_api_url
17
+ require_current_grid
18
+ token = require_token
19
+ node_name = ask_node(token)
20
+ do_token = ask_do_token
21
+ confirm_command(node_name) unless forced?
22
+
23
+ grid = client(require_token).get("grids/#{current_grid}")
24
+ destroyer = destroyer(client(token), do_token)
25
+ destroyer.run!(grid, node_name)
26
+ ensure
27
+ resume_warnings
28
+ end
29
+
30
+ # @param [Kontena::Client] client
31
+ # @param [String] token
32
+ def destroyer(client, token)
33
+ Kontena::Machine::DigitalOcean::NodeDestroyer.new(client, token)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,112 @@
1
+ module Kontena::Plugin::DigitalOcean::Prompts
2
+
3
+ # Until DO merges https://github.com/digitalocean/resource_kit/pull/32
4
+ def suppress_warnings
5
+ @original_verbosity = $VERBOSE
6
+ $VERBOSE = nil
7
+ end
8
+
9
+ def resume_warnings
10
+ $VERBOSE = @original_verbosity
11
+ end
12
+
13
+ def ask_ssh_key(do_token)
14
+ manager = Kontena::Machine::DigitalOcean::SshKeyManager.new(do_token)
15
+
16
+ if self.ssh_key
17
+ public_key = File.read(self.ssh_key).strip
18
+ else
19
+ keys = manager.list
20
+ key = :new
21
+
22
+ default_path = File.join(Dir.home, '.ssh', 'id_rsa.pub')
23
+ default = File.exist?(default_path) ? File.read(default_path).strip : nil
24
+
25
+ unless keys.empty?
26
+ key = prompt.select("Choose SSH key:") do |menu|
27
+ i = 1
28
+ keys.each do |item|
29
+ menu.choice "#{item.name} (#{item.fingerprint})" , item
30
+ menu.default i if item.public_key == default
31
+ i += 1
32
+ end
33
+ menu.choice "Create new SSH key", :new
34
+ end
35
+ end
36
+
37
+ if key == :new
38
+
39
+ public_key = prompt.ask('SSH public key: (enter an ssh key in OpenSSH format "ssh-xxx xxxxx key_name")', default: default) do |q|
40
+ q.validate /^ssh-rsa \S+ \S+$/
41
+ end
42
+ else
43
+ return key.id
44
+ end
45
+ end
46
+ manager.find_or_create_by_public_key(public_key).id
47
+ end
48
+
49
+ def ask_do_token
50
+ if self.token.nil?
51
+ prompt.mask('DigitalOcean API token:')
52
+ else
53
+ self.token
54
+ end
55
+ end
56
+
57
+ def ask_droplet_region(do_token)
58
+ if self.region.nil?
59
+ prompt.select("Choose a datacenter region:") do |menu|
60
+ do_client = DropletKit::Client.new(access_token: do_token)
61
+ do_client.regions.all.sort_by{|r| r.slug }.each{ |region|
62
+ menu.choice region.name, region.slug
63
+ }
64
+ end
65
+ else
66
+ self.region
67
+ end
68
+ end
69
+
70
+ def ask_droplet_size(do_token, do_region)
71
+ if self.size.nil?
72
+ prompt.select("Choose droplet size:") do |menu|
73
+ do_client = DropletKit::Client.new(access_token: do_token)
74
+ do_client.sizes.all.to_a.select{ |s| s.memory > 1000 }.sort_by{|s| s.memory }.each{ |size|
75
+ #p size
76
+ if size.regions.include?(do_region)
77
+ memory = size.memory.to_i / 1024
78
+ menu.choice "#{size.slug}: #{memory}GB/#{size.vcpus}CPU/#{size.disk}GB ($#{size.price_monthly.to_i}/mo)", size.slug
79
+ end
80
+ }
81
+ end
82
+ else
83
+ self.size
84
+ end
85
+ end
86
+
87
+ def ask_node(token)
88
+ if self.name.nil?
89
+ nodes = client(token).get("grids/#{current_grid}/nodes")
90
+ nodes = nodes['nodes'].select{ |n|
91
+ n['labels'] && n['labels'].include?('provider=digitalocean'.freeze)
92
+ }
93
+ raise "Did not find any nodes with label provider=digitalocean" if nodes.size == 0
94
+ prompt.select("Select node:") do |menu|
95
+ nodes.sort_by{|n| n['node_number'] }.reverse.each do |node|
96
+ initial = node['initial_member'] ? '(initial) ' : ''
97
+ menu.choice "#{node['name']} #{initial}", node['name']
98
+ end
99
+ end
100
+ else
101
+ self.name
102
+ end
103
+ end
104
+
105
+ def ask_channel
106
+ prompt.select('Select Container Linux channel:') do |menu|
107
+ menu.choice 'Stable', 'stable'
108
+ menu.choice 'Beta', 'beta'
109
+ menu.choice 'Alpha', 'alpha'
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,7 @@
1
+ module Kontena
2
+ module Plugin
3
+ module DigitalOcean
4
+ VERSION = "0.3.8"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ class Kontena::Plugin::DigitalOceanCommand < Kontena::Command
2
+ subcommand 'master', 'DigitalOcean master related commands', load_subcommand('kontena/plugin/digital_ocean/master_command')
3
+ subcommand 'node', 'DigitalOcean node related commands', load_subcommand('kontena/plugin/digital_ocean/node_command')
4
+ end
@@ -0,0 +1,5 @@
1
+ require 'kontena_cli'
2
+ require 'kontena/plugin/digital_ocean'
3
+ require 'kontena/cli/subcommand_loader'
4
+
5
+ Kontena::MainCommand.register("digitalocean", "DigitalOcean specific commands", Kontena::Cli::SubcommandLoader.new('kontena/plugin/digital_ocean_command'))
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: krates-plugin-digitalocean
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.8
5
+ platform: ruby
6
+ authors:
7
+ - Pavel Tsurbeleu
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-08-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: kontena-cli
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.5.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 1.5.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: droplet_kit
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '4.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '4.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pastel
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 0.7.2
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 0.7.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.11'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.11'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '10.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '10.0'
97
+ description: Krates DigitalOcean plugin
98
+ email:
99
+ - staticpagesio@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".gitmodules"
106
+ - ".rspec"
107
+ - Gemfile
108
+ - LICENSE.txt
109
+ - README.md
110
+ - krates-plugin-digitalocean.gemspec
111
+ - lib/kontena/machine/digital_ocean.rb
112
+ - lib/kontena/machine/digital_ocean/cloudinit.yml
113
+ - lib/kontena/machine/digital_ocean/cloudinit_master.yml
114
+ - lib/kontena/machine/digital_ocean/master_destroyer.rb
115
+ - lib/kontena/machine/digital_ocean/master_provisioner.rb
116
+ - lib/kontena/machine/digital_ocean/node_destroyer.rb
117
+ - lib/kontena/machine/digital_ocean/node_provisioner.rb
118
+ - lib/kontena/machine/digital_ocean/ssh_key_manager.rb
119
+ - lib/kontena/plugin/digital_ocean.rb
120
+ - lib/kontena/plugin/digital_ocean/master/create_command.rb
121
+ - lib/kontena/plugin/digital_ocean/master/terminate_command.rb
122
+ - lib/kontena/plugin/digital_ocean/master_command.rb
123
+ - lib/kontena/plugin/digital_ocean/node_command.rb
124
+ - lib/kontena/plugin/digital_ocean/nodes/create_command.rb
125
+ - lib/kontena/plugin/digital_ocean/nodes/restart_command.rb
126
+ - lib/kontena/plugin/digital_ocean/nodes/terminate_command.rb
127
+ - lib/kontena/plugin/digital_ocean/prompts.rb
128
+ - lib/kontena/plugin/digital_ocean_command.rb
129
+ - lib/kontena_cli_plugin.rb
130
+ homepage: https://krates.appsters.io
131
+ licenses:
132
+ - MIT
133
+ metadata: {}
134
+ post_install_message:
135
+ rdoc_options: []
136
+ require_paths:
137
+ - lib
138
+ required_ruby_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ requirements: []
149
+ rubygems_version: 3.0.4
150
+ signing_key:
151
+ specification_version: 4
152
+ summary: Krates DigitalOcean plugin
153
+ test_files: []