sunzi-vps 0.1.0
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 +4 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/README.md +24 -0
- data/Rakefile +17 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/sunzi/vps/api.rb +51 -0
- data/lib/sunzi/vps/cli.rb +27 -0
- data/lib/sunzi/vps/compute/base.rb +93 -0
- data/lib/sunzi/vps/compute/digital_ocean.rb +90 -0
- data/lib/sunzi/vps/compute/linode.rb +151 -0
- data/lib/sunzi/vps/dependency.rb +12 -0
- data/lib/sunzi/vps/dns/base.rb +22 -0
- data/lib/sunzi/vps/dns/digital_ocean.rb +35 -0
- data/lib/sunzi/vps/dns/linode.rb +26 -0
- data/lib/sunzi/vps/init.rb +18 -0
- data/lib/sunzi/vps/mapping.rb +14 -0
- data/lib/sunzi/vps.rb +12 -0
- data/sunzi-vps.gemspec +27 -0
- data/templates/digital_ocean.yml +18 -0
- data/templates/linode.yml +34 -0
- metadata +206 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 22de99d93f1dcc0b6189b27a8f76827c611f85b142f75ac8220725a909f1746b
|
4
|
+
data.tar.gz: 4d10928a3be79b0c81e103eff8b3fcd2dc9c88eaec980584d275c91c9d341539
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 655ef8b565505126a3a20438c1f79699d84f50aba835680cf3ea9ca9565935d27a6456260e5ef14c788ec2eee4fac34f8e2fcf5c8864ef449c5bae4cbe1b4a2d
|
7
|
+
data.tar.gz: 236e67762ce6e2aa1dbf82d39f795b5f8d612d35d19b3dd14c0d6b4c43e4fbe05bd25c0d691046cf22620bbdc8d6eaf0b1c6e426be6eca76f806835d99121c82
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# Sunzi VPS
|
2
|
+
|
3
|
+
sunzi-vps is a simple command line utility to create and/or destroy VPS instances interactively.
|
4
|
+
|
5
|
+
It works as a plugin for [sunzi](https://github.com/kenn/sunzi), but can be used as a standalone tool.
|
6
|
+
|
7
|
+

|
8
|
+
|
9
|
+
## Quick start
|
10
|
+
|
11
|
+
Install:
|
12
|
+
|
13
|
+
```bash
|
14
|
+
$ gem install sunzi-vps
|
15
|
+
```
|
16
|
+
|
17
|
+
## Commands
|
18
|
+
|
19
|
+
```bash
|
20
|
+
$ sunzi vps # Show command help
|
21
|
+
$ sunzi vps init # Generate VPS config file
|
22
|
+
$ sunzi vps up # Set up a new instance
|
23
|
+
$ sunzi vps down # Tear down an existing instance
|
24
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
Rake::TestTask.new(:test) do |t|
|
5
|
+
t.libs << 'test'
|
6
|
+
t.libs << 'lib'
|
7
|
+
t.test_files = FileList['test/**/*_test.rb']
|
8
|
+
end
|
9
|
+
|
10
|
+
task :default => :test
|
11
|
+
|
12
|
+
namespace :vcr do
|
13
|
+
task :refresh do
|
14
|
+
FileUtils.rm_f('test/vcr/vps_up.yml')
|
15
|
+
Rake::Task['test'].invoke
|
16
|
+
end
|
17
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'sunzi/vps'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require 'pry'
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'sunzi/vps/mapping'
|
2
|
+
|
3
|
+
require 'sunzi/vps/compute/base'
|
4
|
+
require 'sunzi/vps/compute/linode'
|
5
|
+
require 'sunzi/vps/compute/digital_ocean'
|
6
|
+
|
7
|
+
require 'sunzi/vps/dns/base'
|
8
|
+
require 'sunzi/vps/dns/linode'
|
9
|
+
require 'sunzi/vps/dns/digital_ocean'
|
10
|
+
|
11
|
+
module Sunzi
|
12
|
+
module Vps
|
13
|
+
class Api
|
14
|
+
attr_reader :provider
|
15
|
+
|
16
|
+
def initialize(provider)
|
17
|
+
@provider = provider
|
18
|
+
Sunzi::Dependency.load(mapping[:gem])
|
19
|
+
end
|
20
|
+
|
21
|
+
def client
|
22
|
+
@client ||= begin
|
23
|
+
case provider
|
24
|
+
when 'linode'
|
25
|
+
::Linode.new(api_key: config.api_key)
|
26
|
+
when 'digital_ocean'
|
27
|
+
DropletKit::Client.new(access_token: config.api_key)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def config
|
33
|
+
@config ||= YAML.load(File.read("#{provider}/#{provider}.yml")).to_hashugar
|
34
|
+
end
|
35
|
+
|
36
|
+
def compute
|
37
|
+
@compute ||= Object.const_get("Sunzi::Vps::Compute::#{mapping[:klass]}").new(self)
|
38
|
+
end
|
39
|
+
|
40
|
+
def dns
|
41
|
+
@dns ||= Object.const_get("Sunzi::Vps::DNS::#{mapping[:klass]}").new(self)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def mapping
|
47
|
+
@mapping ||= Mapping.fetch(provider.to_sym)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'sunzi/vps/init'
|
2
|
+
require 'sunzi/vps/api'
|
3
|
+
|
4
|
+
module Sunzi
|
5
|
+
module Vps
|
6
|
+
class Cli < Thor
|
7
|
+
|
8
|
+
desc 'init [provider]', 'Generate VPS config file (provider: linode, digital_ocean)'
|
9
|
+
def init(provider)
|
10
|
+
Sunzi::Vps::Init.new.run(provider)
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'up [provider]', 'Set up a new instance (provider: linode, digital_ocean)'
|
14
|
+
def up(provider)
|
15
|
+
api = Sunzi::Vps::Api.new(provider)
|
16
|
+
api.compute.up
|
17
|
+
end
|
18
|
+
|
19
|
+
desc 'down [provider]', 'Tear down an existing instance (provider: linode, digital_ocean)'
|
20
|
+
def down(provider)
|
21
|
+
api = Sunzi::Vps::Api.new(provider)
|
22
|
+
api.compute.down
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Sunzi
|
2
|
+
module Vps
|
3
|
+
class Compute
|
4
|
+
class Base
|
5
|
+
include Sunzi::Actions::Delegate
|
6
|
+
|
7
|
+
delegate_to_thor :empty_directory, :template, :create_file, :remove_file, :say
|
8
|
+
|
9
|
+
attr_reader :api
|
10
|
+
|
11
|
+
def initialize(api)
|
12
|
+
@api = api
|
13
|
+
api.dns.verify # fail early if domain is not registered
|
14
|
+
end
|
15
|
+
|
16
|
+
def up
|
17
|
+
if config.api_key == 'your_api_key'
|
18
|
+
abort_with "You must have your own settings in #{api.provider}.yml"
|
19
|
+
end
|
20
|
+
|
21
|
+
# Ask environment and hostname
|
22
|
+
@env = ask("environment?", limited_to: config.environments, default: 'production')
|
23
|
+
@host = ask('hostname? (only the first part of subdomain): ')
|
24
|
+
|
25
|
+
abort_with '"label" field in linode.yml is no longer supported. rename it to "name".' if config.label
|
26
|
+
@fqdn = config.fqdn.send(@env).gsub(/%{host}/, @host)
|
27
|
+
@name = config.name.send(@env).gsub(/%{host}/, @host)
|
28
|
+
abort_with "#{@name} already exists!" if instance_config_path.exist?
|
29
|
+
|
30
|
+
@attributes = {}
|
31
|
+
|
32
|
+
# Run Linode / DigitalOcean specific tasks
|
33
|
+
do_up
|
34
|
+
|
35
|
+
# Save instance info
|
36
|
+
create_file instance_config_path, YAML.dump(@instance)
|
37
|
+
|
38
|
+
# Register IP to DNS
|
39
|
+
api.dns.add(@fqdn, @public_ip)
|
40
|
+
end
|
41
|
+
|
42
|
+
def down
|
43
|
+
names = Dir.glob("#{api.provider}/instances/*.yml").map{|i| i.split('/').last.sub('.yml','') }
|
44
|
+
abort_with "No match found with #{api.provider}/instances/*.yml" if names.empty?
|
45
|
+
|
46
|
+
names.each{|i| say i }
|
47
|
+
@name = ask('which instance?', limited_to: names)
|
48
|
+
|
49
|
+
@instance = YAML.load(instance_config_path.read).to_hashugar
|
50
|
+
|
51
|
+
# Are you sure?
|
52
|
+
moveon = ask("Are you sure about deleting #{@instance.fqdn} permanently? (y/n)", limited_to: ['y','n'])
|
53
|
+
exit unless moveon == 'y'
|
54
|
+
|
55
|
+
# Run Linode / DigitalOcean specific tasks
|
56
|
+
do_down
|
57
|
+
|
58
|
+
# Delete DNS record
|
59
|
+
api.dns.delete @instance.send(ip_key)
|
60
|
+
|
61
|
+
# Remove the instance config file
|
62
|
+
remove_file instance_config_path
|
63
|
+
|
64
|
+
say 'Done.'
|
65
|
+
end
|
66
|
+
|
67
|
+
def ask(statement, *args)
|
68
|
+
Sunzi.thor.ask statement.color(:green).bright, *args
|
69
|
+
end
|
70
|
+
|
71
|
+
def proceed?
|
72
|
+
moveon = ask("Are you ready to go ahead and create #{@fqdn}? (y/n)", limited_to: ['y','n'])
|
73
|
+
exit unless moveon == 'y'
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def instance_config_path
|
79
|
+
Pathname.new "#{api.provider}/instances/#{@name}.yml"
|
80
|
+
end
|
81
|
+
|
82
|
+
def config
|
83
|
+
api.config
|
84
|
+
end
|
85
|
+
|
86
|
+
def client
|
87
|
+
@api.client
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Sunzi
|
2
|
+
module Vps
|
3
|
+
class Compute
|
4
|
+
class DigitalOcean < Base
|
5
|
+
|
6
|
+
def do_up
|
7
|
+
choose(:size, client.sizes.all.to_a)
|
8
|
+
choose(:region, client.regions.all.to_a)
|
9
|
+
|
10
|
+
# Choose an image
|
11
|
+
result = client.images.all
|
12
|
+
if config.distributions_filter
|
13
|
+
result = result.select{|i| i.distribution.match Regexp.new(config.distributions_filter, Regexp::IGNORECASE) }
|
14
|
+
end
|
15
|
+
choose(:image, result)
|
16
|
+
|
17
|
+
# Go ahead?
|
18
|
+
proceed?
|
19
|
+
|
20
|
+
ssh_keys = client.ssh_keys.all.map(&:fingerprint)
|
21
|
+
|
22
|
+
# Create
|
23
|
+
say "creating a new droplets..."
|
24
|
+
droplet = client.droplets.create(
|
25
|
+
DropletKit::Droplet.new(
|
26
|
+
name: @name,
|
27
|
+
size: @attributes[:size],
|
28
|
+
image: @attributes[:image],
|
29
|
+
region: @attributes[:region],
|
30
|
+
ssh_keys: ssh_keys
|
31
|
+
)
|
32
|
+
)
|
33
|
+
|
34
|
+
@droplet_id = droplet.id
|
35
|
+
say "Created a new droplet (id: #{@droplet_id}). Booting..."
|
36
|
+
|
37
|
+
# Boot - we need this before getting public IP
|
38
|
+
while droplet.status.downcase != 'active'
|
39
|
+
sleep 3
|
40
|
+
droplet = client.droplets.find(id: @droplet_id)
|
41
|
+
end
|
42
|
+
|
43
|
+
@public_ip = droplet.networks.v4.first.ip_address
|
44
|
+
say "Done. ip address = #{@public_ip}"
|
45
|
+
|
46
|
+
@instance = {
|
47
|
+
droplet_id: @droplet_id,
|
48
|
+
env: @env,
|
49
|
+
host: @host,
|
50
|
+
fqdn: @fqdn,
|
51
|
+
name: @name,
|
52
|
+
ip_address: @public_ip,
|
53
|
+
size: @attributes[:size],
|
54
|
+
region: @attributes[:region],
|
55
|
+
image: @attributes[:image],
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
def choose(key, result)
|
60
|
+
abort "no #{key} found!" if result.first.nil?
|
61
|
+
|
62
|
+
rows = result.map(&:attributes).map do |h|
|
63
|
+
h.values.map do |v|
|
64
|
+
next v unless v.is_a?(Array)
|
65
|
+
if v.size > 5
|
66
|
+
v.first(5).join(', ') + ', ...'
|
67
|
+
else
|
68
|
+
v.join(', ')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
table = Terminal::Table.new headings: result.first.attributes.keys, rows: rows
|
74
|
+
say table
|
75
|
+
|
76
|
+
@attributes[key] = ask("which #{key}?: ", default: result.first.slug, limited_to: result.map(&:slug))
|
77
|
+
end
|
78
|
+
|
79
|
+
def do_down
|
80
|
+
say 'deleting droplet...'
|
81
|
+
client.droplets.delete(id: @instance[:droplet_id])
|
82
|
+
end
|
83
|
+
|
84
|
+
def ip_key
|
85
|
+
:ip_address
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
module Sunzi
|
2
|
+
module Vps
|
3
|
+
class Compute
|
4
|
+
class Linode < Base
|
5
|
+
|
6
|
+
def do_up
|
7
|
+
@sshkey = File.read(File.expand_path(config.root_sshkey_path)).chomp
|
8
|
+
if @sshkey.match(/\n/)
|
9
|
+
abort_with "RootSSHKey #{@sshkey.inspect} must not be multi-line! Check inside \"#{config.root_sshkey_path}\""
|
10
|
+
end
|
11
|
+
|
12
|
+
choose(:plan, client.avail.linodeplans)
|
13
|
+
choose(:datacenter, client.avail.datacenters, :label_method => :location)
|
14
|
+
choose(:distribution, client.avail.distributions, :filter => 'distributions_filter')
|
15
|
+
choose(:kernel, client.avail.kernels, :filter => 'kernels_filter')
|
16
|
+
|
17
|
+
# Choose swap size
|
18
|
+
@swap_size = ask('swap size in MB?', default: 256).to_i
|
19
|
+
|
20
|
+
# Go ahead?
|
21
|
+
proceed?
|
22
|
+
|
23
|
+
# Create
|
24
|
+
say "creating a new linode..."
|
25
|
+
result = client.linode.create(
|
26
|
+
:DatacenterID => @attributes[:datacenterid],
|
27
|
+
:PlanID => @attributes[:planid],
|
28
|
+
:PaymentTerm => config.payment_term)
|
29
|
+
@linodeid = result.linodeid
|
30
|
+
say "created a new instance: linodeid = #{@linodeid}"
|
31
|
+
|
32
|
+
result = client.linode.list.select{|i| i.linodeid == @linodeid }.first
|
33
|
+
@totalhd = result.totalhd
|
34
|
+
|
35
|
+
# Update settings
|
36
|
+
say "Updating settings..."
|
37
|
+
@group = config.group[@env]
|
38
|
+
settings = { :LinodeID => @linodeid, :Label => @name, :lpm_displayGroup => @group }
|
39
|
+
settings.update(config.settings) if config.settings
|
40
|
+
client.linode.update(settings)
|
41
|
+
|
42
|
+
# Create a root disk
|
43
|
+
say "Creating a root disk..."
|
44
|
+
result = client.linode.disk.createfromdistribution(
|
45
|
+
:LinodeID => @linodeid,
|
46
|
+
:DistributionID => @attributes[:distributionid],
|
47
|
+
:Label => "#{@attributes[:distribution_label]} Image",
|
48
|
+
:Size => @totalhd - @swap_size,
|
49
|
+
:rootPass => config.root_pass,
|
50
|
+
:rootSSHKey => @sshkey
|
51
|
+
)
|
52
|
+
@root_diskid = result.diskid
|
53
|
+
|
54
|
+
# Create a swap disk
|
55
|
+
say "Creating a swap disk..."
|
56
|
+
result = client.linode.disk.create(
|
57
|
+
:LinodeID => @linodeid,
|
58
|
+
:Label => "#{@swap_size}MB Swap Image",
|
59
|
+
:Type => 'swap',
|
60
|
+
:Size => @swap_size
|
61
|
+
)
|
62
|
+
@swap_diskid = result.diskid
|
63
|
+
|
64
|
+
# Create a config profiile
|
65
|
+
say "Creating a config profile..."
|
66
|
+
result = client.linode.config.create(
|
67
|
+
:LinodeID => @linodeid,
|
68
|
+
:KernelID => @attributes[:kernelid],
|
69
|
+
:Label => "#{@attributes[:distribution_label]} Profile",
|
70
|
+
:DiskList => [ @root_diskid, @swap_diskid ].join(',')
|
71
|
+
)
|
72
|
+
@config_id = result.configid
|
73
|
+
|
74
|
+
# Add a private IP
|
75
|
+
say "Adding a private IP..."
|
76
|
+
result = client.linode.ip.list(:LinodeID => @linodeid)
|
77
|
+
@public_ip = result.first.ipaddress
|
78
|
+
result = client.linode.ip.addprivate(:LinodeID => @linodeid)
|
79
|
+
result = client.linode.ip.list(:LinodeID => @linodeid).find{|i| i.ispublic == 0 }
|
80
|
+
@private_ip = result.ipaddress
|
81
|
+
|
82
|
+
@instance = {
|
83
|
+
:linode_id => @linodeid,
|
84
|
+
:env => @env,
|
85
|
+
:host => @host,
|
86
|
+
:fqdn => @fqdn,
|
87
|
+
:label => @name,
|
88
|
+
:group => @group,
|
89
|
+
:plan_id => @attributes[:planid],
|
90
|
+
:datacenter_id => @attributes[:datacenterid],
|
91
|
+
:datacenter_location => @attributes[:datacenter_location],
|
92
|
+
:distribution_id => @attributes[:distributionid],
|
93
|
+
:distribution_label => @attributes[:distribution_label],
|
94
|
+
:kernel_id => @attributes[:kernelid],
|
95
|
+
:kernel_label => @attributes[:kernel_label],
|
96
|
+
:swap_size => @swap_size,
|
97
|
+
:totalhd => @totalhd,
|
98
|
+
:root_diskid => @root_diskid,
|
99
|
+
:swap_diskid => @swap_diskid,
|
100
|
+
:config_id => @config_id,
|
101
|
+
:public_ip => @public_ip,
|
102
|
+
:private_ip => @private_ip,
|
103
|
+
}
|
104
|
+
|
105
|
+
# Boot
|
106
|
+
say 'Done. Booting...'
|
107
|
+
client.linode.boot(:LinodeID => @linodeid)
|
108
|
+
end
|
109
|
+
|
110
|
+
def choose(key, result, options = {})
|
111
|
+
label_method = options[:label_method] || :label
|
112
|
+
id = :"#{key}id"
|
113
|
+
label = :"#{key}_#{label_method}"
|
114
|
+
|
115
|
+
# Filters
|
116
|
+
if options[:filter] and config[options[:filter]]
|
117
|
+
result = result.select{|i| i.label.match Regexp.new(config[options[:filter]], Regexp::IGNORECASE) }
|
118
|
+
end
|
119
|
+
|
120
|
+
result.each{|i| say "#{i.send(id)}: #{i.send(label_method)}" }
|
121
|
+
@attributes[id] = ask("which #{key}?", limited_to: result.map(&id).map(&:to_s), default: result.first.send(id).to_s).to_i
|
122
|
+
@attributes[label] = result.find{|i| i.send(id) == @attributes[id] }.send(label_method)
|
123
|
+
end
|
124
|
+
|
125
|
+
def do_down
|
126
|
+
@linode_id_hash = { :LinodeID => @instance[:linode_id] }
|
127
|
+
|
128
|
+
# Shutdown first or disk deletion will fail
|
129
|
+
say 'shutting down...'
|
130
|
+
client.linode.shutdown(@linode_id_hash)
|
131
|
+
# Wait until linode.shutdown has completed
|
132
|
+
wait_for('linode.shutdown')
|
133
|
+
|
134
|
+
# Delete the instance
|
135
|
+
say 'deleting linode...'
|
136
|
+
client.linode.delete(@linode_id_hash.merge(:skipChecks => 1))
|
137
|
+
end
|
138
|
+
|
139
|
+
def ip_key
|
140
|
+
:public_ip
|
141
|
+
end
|
142
|
+
|
143
|
+
def wait_for(action)
|
144
|
+
begin
|
145
|
+
sleep 3
|
146
|
+
end until client.linode.job.list(@linode_id_hash).find{|i| i.action == action }.host_success == 1
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Sunzi
|
2
|
+
module Vps
|
3
|
+
class DNS
|
4
|
+
class Base
|
5
|
+
include Sunzi::Actions::Delegate
|
6
|
+
|
7
|
+
delegate_to_thor :say
|
8
|
+
|
9
|
+
attr_reader :api
|
10
|
+
|
11
|
+
def initialize(api)
|
12
|
+
@api = api
|
13
|
+
@zone = api.config.fqdn.zone
|
14
|
+
end
|
15
|
+
|
16
|
+
def client
|
17
|
+
api.client
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Sunzi
|
2
|
+
module Vps
|
3
|
+
class DNS
|
4
|
+
class DigitalOcean < Base
|
5
|
+
|
6
|
+
def verify
|
7
|
+
@domain = client.domains.find(name: @zone)
|
8
|
+
rescue DropletKit::Error => e
|
9
|
+
abort_with "zone for #{@zone} was not found on DigitalOcean DNS!" if e.message =~ /not_found/
|
10
|
+
raise
|
11
|
+
end
|
12
|
+
|
13
|
+
def add(fqdn, ip)
|
14
|
+
say 'adding the public IP to DigitalOcean DNS...'
|
15
|
+
client.domain_records.create(
|
16
|
+
DropletKit::DomainRecord.new(
|
17
|
+
type: 'A',
|
18
|
+
name: fqdn.sub('.' + @domain.name, ''),
|
19
|
+
data: ip,
|
20
|
+
),
|
21
|
+
for_domain: @domain.name
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete(ip)
|
26
|
+
say 'deleting the public IP from DigitalOcean DNS...'
|
27
|
+
domain_record = client.domain_records.all(for_domain: @domain.name).find{|i| i.type == 'A' && i.data == ip }
|
28
|
+
abort_with "ip address #{ip} was not found on DigitalOcean DNS!" unless domain_record
|
29
|
+
client.domain_records.delete(id: domain_record.id, for_domain: @domain.name)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Sunzi
|
2
|
+
module Vps
|
3
|
+
class DNS
|
4
|
+
class Linode < Base
|
5
|
+
|
6
|
+
def verify
|
7
|
+
@domain = client.domain.list.find{|i| i.domain == @zone }
|
8
|
+
abort_with "zone for #{@zone} was not found on Linode DNS!" unless @domain
|
9
|
+
end
|
10
|
+
|
11
|
+
def add(fqdn, ip)
|
12
|
+
say 'adding the public IP to Linode DNS Manager...'
|
13
|
+
client.domain.resource.create(:DomainID => @domain.domainid, :Type => 'A', :Name => fqdn, :Target => ip)
|
14
|
+
end
|
15
|
+
|
16
|
+
def delete(ip)
|
17
|
+
say 'deleting the public IP from Linode DNS Manager...'
|
18
|
+
resource = client.domain.resource.list(:DomainID => @domain.domainid).find{|i| i.target == ip }
|
19
|
+
abort_with "ip address #{ip} was not found on Linode DNS!" unless resource
|
20
|
+
client.domain.resource.delete(:DomainID => @domain.domainid, :ResourceID => resource.resourceid)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Sunzi
|
2
|
+
module Vps
|
3
|
+
class Init
|
4
|
+
include Sunzi::Actions::Delegate
|
5
|
+
|
6
|
+
delegate_to_thor :empty_directory, :template
|
7
|
+
|
8
|
+
def run(provider)
|
9
|
+
config_path = "#{provider}/#{provider}.yml"
|
10
|
+
return if File.exist? config_path
|
11
|
+
|
12
|
+
empty_directory "#{provider}/instances"
|
13
|
+
template "templates/#{provider}.yml", config_path, context: binding
|
14
|
+
exit_with "Now go ahead and edit #{provider}.yml"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/sunzi/vps.rb
ADDED
data/sunzi-vps.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = 'sunzi-vps'
|
5
|
+
spec.version = '0.1.0' # retrieve this value by: Gem.loaded_specs['sunzi'].version.to_s
|
6
|
+
spec.authors = ['Kenn Ejima']
|
7
|
+
spec.email = ['kenn.ejima@gmail.com']
|
8
|
+
spec.summary = %q{Sunzi VPS}
|
9
|
+
spec.description = %q{Simple CLI to create and/or destroy VPS instances}
|
10
|
+
spec.homepage = 'https://github.com/kenn/sunzi-vps'
|
11
|
+
spec.license = 'MIT'
|
12
|
+
spec.files = `git ls-files -z`.split("\x0").reject {|f| f.match(%r{^(test|spec|features)/}) }
|
13
|
+
spec.bindir = 'exe'
|
14
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
15
|
+
spec.require_paths = ['lib']
|
16
|
+
|
17
|
+
spec.add_dependency 'sunzi', '~> 2.0'
|
18
|
+
spec.add_dependency 'hashugar', '~> 1.0'
|
19
|
+
spec.add_dependency 'terminal-table', '~> 1.8'
|
20
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
21
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
22
|
+
spec.add_development_dependency 'minitest', '~> 5.0'
|
23
|
+
spec.add_development_dependency 'vcr', '~> 4.0'
|
24
|
+
spec.add_development_dependency 'webmock', '~> 3.2'
|
25
|
+
spec.add_development_dependency 'linode', '~> 0.7'
|
26
|
+
spec.add_development_dependency 'droplet_kit', '~> 2.2'
|
27
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
---
|
2
|
+
api_key: your_api_key
|
3
|
+
client_id: your_client_id
|
4
|
+
|
5
|
+
# add / remove environments
|
6
|
+
environments:
|
7
|
+
- production
|
8
|
+
- staging
|
9
|
+
fqdn:
|
10
|
+
zone: example.com
|
11
|
+
production: '%{host}.example.com'
|
12
|
+
staging: '%{host}.staging.example.com'
|
13
|
+
name:
|
14
|
+
production: 'example-%{host}'
|
15
|
+
staging: 'example-staging-%{host}'
|
16
|
+
|
17
|
+
# filter out large lists by keyword
|
18
|
+
distributions_filter: debian
|
@@ -0,0 +1,34 @@
|
|
1
|
+
---
|
2
|
+
api_key: your_api_key
|
3
|
+
root_pass: your_root_password
|
4
|
+
root_sshkey_path: ~/.ssh/id_rsa.pub
|
5
|
+
|
6
|
+
# payment_term must be 1, 12 or 24
|
7
|
+
payment_term: 1
|
8
|
+
|
9
|
+
# add / remove environments
|
10
|
+
environments:
|
11
|
+
- production
|
12
|
+
- staging
|
13
|
+
fqdn:
|
14
|
+
zone: example.com
|
15
|
+
production: '%{host}.example.com'
|
16
|
+
staging: '%{host}.staging.example.com'
|
17
|
+
name:
|
18
|
+
production: 'example-%{host}'
|
19
|
+
staging: 'example-staging-%{host}'
|
20
|
+
group:
|
21
|
+
production: example
|
22
|
+
staging: example-staging
|
23
|
+
|
24
|
+
# filter out large lists by keyword
|
25
|
+
distributions_filter: debian
|
26
|
+
kernels_filter: latest
|
27
|
+
|
28
|
+
# other parameters for settings.
|
29
|
+
# settings:
|
30
|
+
# alert_cpu_threshold: 90
|
31
|
+
# alert_diskio_threshold: 1000
|
32
|
+
# alert_bwin_threshold: 5
|
33
|
+
# alert_bwout_threshold: 5
|
34
|
+
# alert_bwquota_threshold: 80
|
metadata
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sunzi-vps
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kenn Ejima
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-01-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sunzi
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: hashugar
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: terminal-table
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.8'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.8'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.16'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.16'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: minitest
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '5.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '5.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: vcr
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '4.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '4.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: webmock
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '3.2'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '3.2'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: linode
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0.7'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0.7'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: droplet_kit
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '2.2'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '2.2'
|
153
|
+
description: Simple CLI to create and/or destroy VPS instances
|
154
|
+
email:
|
155
|
+
- kenn.ejima@gmail.com
|
156
|
+
executables: []
|
157
|
+
extensions: []
|
158
|
+
extra_rdoc_files: []
|
159
|
+
files:
|
160
|
+
- ".gitignore"
|
161
|
+
- ".travis.yml"
|
162
|
+
- Gemfile
|
163
|
+
- README.md
|
164
|
+
- Rakefile
|
165
|
+
- bin/console
|
166
|
+
- bin/setup
|
167
|
+
- lib/sunzi/vps.rb
|
168
|
+
- lib/sunzi/vps/api.rb
|
169
|
+
- lib/sunzi/vps/cli.rb
|
170
|
+
- lib/sunzi/vps/compute/base.rb
|
171
|
+
- lib/sunzi/vps/compute/digital_ocean.rb
|
172
|
+
- lib/sunzi/vps/compute/linode.rb
|
173
|
+
- lib/sunzi/vps/dependency.rb
|
174
|
+
- lib/sunzi/vps/dns/base.rb
|
175
|
+
- lib/sunzi/vps/dns/digital_ocean.rb
|
176
|
+
- lib/sunzi/vps/dns/linode.rb
|
177
|
+
- lib/sunzi/vps/init.rb
|
178
|
+
- lib/sunzi/vps/mapping.rb
|
179
|
+
- sunzi-vps.gemspec
|
180
|
+
- templates/digital_ocean.yml
|
181
|
+
- templates/linode.yml
|
182
|
+
homepage: https://github.com/kenn/sunzi-vps
|
183
|
+
licenses:
|
184
|
+
- MIT
|
185
|
+
metadata: {}
|
186
|
+
post_install_message:
|
187
|
+
rdoc_options: []
|
188
|
+
require_paths:
|
189
|
+
- lib
|
190
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
196
|
+
requirements:
|
197
|
+
- - ">="
|
198
|
+
- !ruby/object:Gem::Version
|
199
|
+
version: '0'
|
200
|
+
requirements: []
|
201
|
+
rubyforge_project:
|
202
|
+
rubygems_version: 2.7.3
|
203
|
+
signing_key:
|
204
|
+
specification_version: 4
|
205
|
+
summary: Sunzi VPS
|
206
|
+
test_files: []
|