asteroid 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -0
- data/Gemfile +1 -0
- data/README.md +45 -4
- data/Rakefile +1 -0
- data/asteroid.gemspec +0 -1
- data/bin/asteroid +4 -2
- data/lib/asteroid.rb +11 -10
- data/lib/asteroid/application.rb +60 -7
- data/lib/asteroid/config.rb +28 -14
- data/lib/asteroid/file_reference.rb +102 -0
- data/lib/asteroid/generator.rb +0 -4
- data/lib/asteroid/instance.rb +29 -9
- data/lib/asteroid/instance/command.rb +71 -36
- data/lib/asteroid/instance/scp.rb +26 -0
- data/lib/asteroid/instance/ssh.rb +2 -1
- data/lib/asteroid/instance/vars.rb +5 -2
- data/lib/asteroid/key_reference.rb +45 -0
- data/lib/asteroid/provider/abstract.rb +16 -1
- data/lib/asteroid/provider/digital_ocean.rb +35 -6
- data/lib/asteroid/provider/mock.rb +36 -14
- data/lib/asteroid/script.rb +3 -57
- data/lib/asteroid/server.rb +44 -25
- data/lib/asteroid/template.rb +19 -7
- data/lib/asteroid/template/liquid.rb +0 -0
- data/lib/asteroid/version.rb +1 -1
- data/sample/.gitignore +2 -0
- data/sample/README.md +1 -0
- data/sample/Rakefile +0 -0
- data/sample/TODO +9 -0
- data/sample/asteroid/files/nginx/available_site.conf.erb +0 -0
- data/sample/asteroid/files/nginx/nginx.conf +1 -0
- data/sample/asteroid/scripts/ubuntu.aster +4 -0
- data/sample/asteroid/servers/default.yml +3 -0
- data/sample/asteroid/servers/web.yml +5 -0
- data/sample/config/asteroid.rb +9 -0
- data/test/helper.rb +18 -2
- data/test/unit/test_application.rb +17 -0
- data/test/unit/test_file_reference.rb +18 -0
- data/test/unit/test_instance.rb +20 -4
- data/test/unit/test_script_reference.rb +9 -0
- data/test/unit/test_server.rb +46 -2
- data/test/unit/test_template.rb +16 -0
- metadata +24 -17
- data/lib/asteroid/config_file.rb +0 -46
@@ -2,33 +2,39 @@
|
|
2
2
|
module Asteroid
|
3
3
|
class Instance
|
4
4
|
|
5
|
-
def
|
6
|
-
env
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
5
|
+
def render_erb(string, env)
|
6
|
+
Template.new(:erb, string).render(env)
|
7
|
+
end
|
8
|
+
|
9
|
+
def eval_command(cmd)
|
10
|
+
|
11
|
+
|
12
|
+
return aster_environment.eval(cmd)
|
13
|
+
|
14
|
+
# env ||= template_data
|
15
|
+
# cmd = Template.new(:erb, cmd).render(env)
|
16
|
+
# cmd, *rest = cmd.split(" ")
|
17
|
+
# case cmd.to_sym
|
18
|
+
# when :run
|
19
|
+
# command_run(rest.first, env)
|
20
|
+
# when :upload
|
21
|
+
# from, to, _ = rest
|
22
|
+
# command_upload(from, to, env)
|
23
|
+
# when :"upload-private-key"
|
24
|
+
# name, _ = rest
|
25
|
+
# command_upload_private_key(name, env)
|
26
|
+
# when :exec
|
27
|
+
# script = rest.join(" ")
|
28
|
+
# command_exec(script, env)
|
29
|
+
# when :config
|
30
|
+
# set_or_get, var, val, _ = rest
|
31
|
+
# command_config(set_or_get, var, val)
|
32
|
+
# else
|
33
|
+
# if server.commands[cmd]
|
34
|
+
# command_command(cmd, rest, env)
|
35
|
+
# else
|
36
|
+
# end
|
37
|
+
# end
|
32
38
|
end
|
33
39
|
|
34
40
|
def template_data
|
@@ -38,6 +44,7 @@ module Asteroid
|
|
38
44
|
end
|
39
45
|
|
40
46
|
def command_command(cmd, args, env)
|
47
|
+
raise "Not here"
|
41
48
|
env ||= template_data
|
42
49
|
command = server.commands[cmd]
|
43
50
|
|
@@ -61,7 +68,7 @@ module Asteroid
|
|
61
68
|
env ||= template_data
|
62
69
|
filename = File.join(Asteroid::Config.script_dir, '/', yml_script)
|
63
70
|
script = File.read(filename)
|
64
|
-
yml = Template.new(:erb).render(
|
71
|
+
yml = Template.new(:erb, script).render(env)
|
65
72
|
data = YAML::load yml
|
66
73
|
unless data["steps"]
|
67
74
|
raise "No steps in #{filenbame}"
|
@@ -74,12 +81,36 @@ module Asteroid
|
|
74
81
|
|
75
82
|
def aster_environment
|
76
83
|
@aster_environment ||= Aster::Environment.new.tap do |e|
|
77
|
-
|
78
|
-
|
84
|
+
|
85
|
+
self.server.commands.each_pair do |name, data|
|
86
|
+
data = case data
|
87
|
+
when Array
|
88
|
+
{args: [], steps: data}
|
89
|
+
when Object
|
90
|
+
data
|
91
|
+
else
|
92
|
+
raise "Commands should be Arrays or arguments or Objects"
|
93
|
+
end
|
94
|
+
|
95
|
+
commands = Aster::Parser.new.send :parse_lines, data[:steps]
|
96
|
+
e.define_function name, data[:args], commands
|
79
97
|
end
|
80
98
|
|
81
|
-
|
82
|
-
|
99
|
+
|
100
|
+
e.define_function :run, [:"..."] do |arguments|
|
101
|
+
command_run(arguments.join(' '))
|
102
|
+
end
|
103
|
+
|
104
|
+
e.define_function :upload, [:"..."] do |(from, to, _)|
|
105
|
+
if command_upload from, to
|
106
|
+
"yes"
|
107
|
+
else
|
108
|
+
"no"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
e.define_function :exec, [:"..."] do |arguments|
|
113
|
+
command_exec(arguments.join(' '))
|
83
114
|
end
|
84
115
|
end
|
85
116
|
end
|
@@ -94,6 +125,9 @@ module Asteroid
|
|
94
125
|
env ||= template_data
|
95
126
|
script = Script.named(script_name)
|
96
127
|
|
128
|
+
# touch ssh
|
129
|
+
self.ssh
|
130
|
+
|
97
131
|
if script.nil?
|
98
132
|
raise "No script named #{script_name}"
|
99
133
|
end
|
@@ -120,14 +154,15 @@ module Asteroid
|
|
120
154
|
|
121
155
|
def command_upload(from, to, env = nil)
|
122
156
|
env ||= template_data
|
123
|
-
from =
|
157
|
+
from = FileReference.new(from)
|
124
158
|
|
125
159
|
if from.template?
|
126
|
-
from.
|
160
|
+
from.data = env
|
127
161
|
end
|
128
162
|
|
129
|
-
puts from.
|
130
|
-
|
163
|
+
puts from.rendered_filename
|
164
|
+
|
165
|
+
self.scp_upload from.rendered_filename, to
|
131
166
|
end
|
132
167
|
|
133
168
|
def command_exec(command, env = nil)
|
@@ -1,8 +1,34 @@
|
|
1
1
|
require 'net/scp'
|
2
2
|
|
3
3
|
module Asteroid
|
4
|
+
|
5
|
+
class MockSCP
|
6
|
+
|
7
|
+
def initialize(fs)
|
8
|
+
@fs = fs || {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def upload!(from, to)
|
12
|
+
@fs[to] = File.read(from)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
4
17
|
class Instance
|
18
|
+
|
19
|
+
def scp_upload(from, to)
|
20
|
+
scp.upload! from, to
|
21
|
+
end
|
22
|
+
|
23
|
+
def mock_file_system
|
24
|
+
@mock_file_system ||= {}
|
25
|
+
end
|
26
|
+
|
5
27
|
def scp
|
28
|
+
if server.provider.type == :mock
|
29
|
+
return @mock_scp ||= MockSCP.new(mock_file_system)
|
30
|
+
end
|
31
|
+
|
6
32
|
if @scp && (@scp.session.transport.port.to_s != self["ssh.port"] || (@scp.session.transport.options[:user] != self["login.username"]))
|
7
33
|
@scp = nil
|
8
34
|
end
|
@@ -17,11 +17,12 @@ module Asteroid
|
|
17
17
|
if @ssh && (@ssh.transport.port.to_s != self["ssh.port"] || (@ssh.transport.options[:user] != self["login.username"]))
|
18
18
|
@ssh = nil
|
19
19
|
end
|
20
|
+
|
20
21
|
@ssh ||= Net::SSH.start(
|
21
22
|
ip_address,
|
22
23
|
self["login.username"],
|
23
24
|
port: self["ssh.port"].to_i,
|
24
|
-
:keys => [server.
|
25
|
+
:keys => [server.ssh_key.private.filename]
|
25
26
|
)
|
26
27
|
end
|
27
28
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
require 'fileutils'
|
2
2
|
|
3
3
|
module Asteroid
|
4
4
|
|
@@ -26,7 +26,10 @@ module Asteroid
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def config_filename
|
29
|
-
File.
|
29
|
+
unless File.directory?(Asteroid::Config.secret_instance_dir)
|
30
|
+
FileUtils.mkdir Asteroid::Config.secret_instance_dir
|
31
|
+
end
|
32
|
+
File.join(Asteroid::Config.secret_instance_dir, "/instance_#{@id}.yml")
|
30
33
|
end
|
31
34
|
|
32
35
|
def load_config_data
|
@@ -0,0 +1,45 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Asteroid
|
4
|
+
class KeyReference
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def search_paths
|
8
|
+
@search_paths ||= []
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(name)
|
13
|
+
@name = name
|
14
|
+
|
15
|
+
@public = nil
|
16
|
+
@private = nil
|
17
|
+
|
18
|
+
self.class.search_paths.each do |path|
|
19
|
+
if @public.nil?
|
20
|
+
key = File.join(path, "#{@name}.pub")
|
21
|
+
if File.exists? key
|
22
|
+
@public = key
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
if @private.nil?
|
27
|
+
key = File.join(path, "#{@name}")
|
28
|
+
if File.exists? key
|
29
|
+
@private = key
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def public
|
36
|
+
@public && FileReference.new(@public)
|
37
|
+
end
|
38
|
+
|
39
|
+
def private
|
40
|
+
@private && FileReference.new(@private)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
@@ -21,17 +21,32 @@ module Asteroid
|
|
21
21
|
@config = config
|
22
22
|
end
|
23
23
|
|
24
|
+
def type
|
25
|
+
self.class.type
|
26
|
+
end
|
27
|
+
|
24
28
|
def self.type
|
25
29
|
self.to_s.split('::').last.underscore.to_sym
|
26
30
|
end
|
27
31
|
|
28
32
|
def instances
|
29
|
-
|
33
|
+
[]
|
30
34
|
end
|
31
35
|
|
32
36
|
def destroy_instance(instance)
|
33
37
|
end
|
34
38
|
|
39
|
+
protected
|
40
|
+
|
41
|
+
def require_attribute(o, att, message)
|
42
|
+
if o[att].nil?
|
43
|
+
raise [
|
44
|
+
"Can't create instance on #{instance_name} because #{message}:",
|
45
|
+
"Add Add #{att} to your server config .yml\n #{o.inspect}"
|
46
|
+
].join(' ')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
35
50
|
end
|
36
51
|
end
|
37
52
|
end
|
@@ -26,17 +26,36 @@ module Asteroid
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def create_instance(server)
|
29
|
+
|
30
|
+
instance_options = server.instance_options
|
31
|
+
|
32
|
+
require_attribute instance_options,
|
33
|
+
:size_id,
|
34
|
+
"needs size of droplet"
|
35
|
+
|
36
|
+
require_attribute instance_options,
|
37
|
+
:image_id,
|
38
|
+
"needs starting image for droplet"
|
39
|
+
|
40
|
+
require_attribute instance_options,
|
41
|
+
:region_id,
|
42
|
+
"needs region for droplet"
|
43
|
+
|
44
|
+
require_attribute instance_options,
|
45
|
+
:ssh_key_ids,
|
46
|
+
"needs SSH key registered for droplet"
|
47
|
+
|
29
48
|
instance = Digitalocean::Droplet.create(
|
30
|
-
name: server.
|
31
|
-
size_id:
|
32
|
-
image_id:
|
33
|
-
region_id:
|
34
|
-
ssh_key_ids:
|
49
|
+
name: server.generate_instance_name,
|
50
|
+
size_id: instance_options[:size_id],
|
51
|
+
image_id: instance_options[:image_id],
|
52
|
+
region_id: instance_options[:region_id],
|
53
|
+
ssh_key_ids: instance_options[:ssh_key_ids],
|
35
54
|
private_networking: true
|
36
55
|
)
|
37
56
|
|
38
57
|
if instance.status == "OK"
|
39
|
-
Instance.new instance.droplet.merge(provider: self)
|
58
|
+
Instance.new instance.droplet.marshal_dump.merge(provider: self)
|
40
59
|
else
|
41
60
|
nil
|
42
61
|
end
|
@@ -61,6 +80,16 @@ module Asteroid
|
|
61
80
|
def reboot_instance(instance)
|
62
81
|
end
|
63
82
|
|
83
|
+
private
|
84
|
+
|
85
|
+
def provider_name
|
86
|
+
"DigitalOcean"
|
87
|
+
end
|
88
|
+
|
89
|
+
def instance_name
|
90
|
+
"Droplet"
|
91
|
+
end
|
92
|
+
|
64
93
|
end
|
65
94
|
end
|
66
95
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
|
1
3
|
module Asteroid
|
2
4
|
module Provider
|
3
5
|
class Mock < Abstract
|
@@ -6,20 +8,23 @@ module Asteroid
|
|
6
8
|
(0..3).to_a.map{|a| rand(255)}.join(".")
|
7
9
|
end
|
8
10
|
|
11
|
+
def self.clear!
|
12
|
+
FileUtils.rm filename if File.exists? filename
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.filename
|
16
|
+
File.join(File.dirname(__FILE__), '../../../world.yml')
|
17
|
+
end
|
18
|
+
|
9
19
|
def initialize(config = {})
|
10
|
-
@config = config
|
11
|
-
@config[:filename] ||=
|
12
|
-
|
13
|
-
|
14
|
-
@world = YAML::load_file @config[:filename]
|
15
|
-
rescue
|
16
|
-
puts "No such file #{@config[:filename]}"
|
17
|
-
@world = {}
|
18
|
-
end
|
20
|
+
@config = config
|
21
|
+
@config[:filename] ||= self.class.filename
|
22
|
+
|
23
|
+
load_world
|
19
24
|
end
|
20
25
|
|
21
26
|
def instances
|
22
|
-
@
|
27
|
+
@world[:instances] ||= []
|
23
28
|
end
|
24
29
|
|
25
30
|
def create_instance(server)
|
@@ -27,23 +32,40 @@ module Asteroid
|
|
27
32
|
name: server.generate_instance_name,
|
28
33
|
id: SecureRandom.hex(5),
|
29
34
|
ip_address: self.class.ip_address,
|
35
|
+
type: server.type
|
36
|
+
}
|
37
|
+
|
38
|
+
self.instances << instance
|
39
|
+
save_world
|
40
|
+
|
41
|
+
Instance.new instance.merge({
|
30
42
|
provider: self,
|
31
|
-
type: server.type,
|
32
43
|
server: server
|
33
|
-
}
|
34
|
-
Instance.new instance
|
44
|
+
})
|
35
45
|
end
|
36
46
|
|
37
47
|
def destroy_instance(instance)
|
38
|
-
|
48
|
+
self.instances.delete_if do |i|
|
49
|
+
i[:id] == i.id
|
50
|
+
end
|
51
|
+
save_world
|
39
52
|
end
|
40
53
|
|
41
54
|
private
|
42
55
|
|
56
|
+
def load_world
|
57
|
+
begin
|
58
|
+
@world = YAML::load_file @config[:filename]
|
59
|
+
rescue
|
60
|
+
@world = {}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
43
64
|
def save_world
|
44
65
|
File.open(@config[:filename], 'w') do |f|
|
45
66
|
f.write @world.to_yaml
|
46
67
|
end
|
68
|
+
load_world
|
47
69
|
end
|
48
70
|
|
49
71
|
end
|
data/lib/asteroid/script.rb
CHANGED
@@ -1,64 +1,10 @@
|
|
1
|
-
require 'erb'
|
2
|
-
|
3
1
|
module Asteroid
|
4
|
-
class Script
|
5
|
-
|
6
|
-
def initialize(filename)
|
7
|
-
@filename = filename
|
8
|
-
end
|
9
|
-
|
10
|
-
def set_data(data)
|
11
|
-
@data = data
|
12
|
-
end
|
13
|
-
|
14
|
-
def data
|
15
|
-
@data ||= {}
|
16
|
-
end
|
17
|
-
|
18
|
-
def name
|
19
|
-
File.basename @filename
|
20
|
-
end
|
21
|
-
|
22
|
-
def file_type
|
23
|
-
@file_type ||= @filename.split('.').last
|
24
|
-
end
|
25
2
|
|
26
|
-
|
27
|
-
file_type == "yml"
|
28
|
-
end
|
29
|
-
|
30
|
-
def aster?
|
31
|
-
file_type == "aster"
|
32
|
-
end
|
3
|
+
class ScriptReference < FileReference
|
33
4
|
|
34
|
-
def
|
35
|
-
|
5
|
+
def executable?
|
6
|
+
[:yml, :aster].include?(type)
|
36
7
|
end
|
37
8
|
|
38
|
-
def render(locals = {})
|
39
|
-
script = File.read @filename
|
40
|
-
if template?
|
41
|
-
Template.new(:erb).render(
|
42
|
-
script,
|
43
|
-
self.data.merge(locals)
|
44
|
-
)
|
45
|
-
else
|
46
|
-
script
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def self.named(name)
|
51
|
-
all.select{|s| s.name == name}.first
|
52
|
-
end
|
53
|
-
|
54
|
-
def self.all
|
55
|
-
files = Dir[Asteroid::Config.script_dir + "/*"]
|
56
|
-
files.map do |filename|
|
57
|
-
new(filename)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
|
62
|
-
|
63
9
|
end
|
64
10
|
end
|