asteroid 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -0
  3. data/Gemfile +1 -0
  4. data/README.md +45 -4
  5. data/Rakefile +1 -0
  6. data/asteroid.gemspec +0 -1
  7. data/bin/asteroid +4 -2
  8. data/lib/asteroid.rb +11 -10
  9. data/lib/asteroid/application.rb +60 -7
  10. data/lib/asteroid/config.rb +28 -14
  11. data/lib/asteroid/file_reference.rb +102 -0
  12. data/lib/asteroid/generator.rb +0 -4
  13. data/lib/asteroid/instance.rb +29 -9
  14. data/lib/asteroid/instance/command.rb +71 -36
  15. data/lib/asteroid/instance/scp.rb +26 -0
  16. data/lib/asteroid/instance/ssh.rb +2 -1
  17. data/lib/asteroid/instance/vars.rb +5 -2
  18. data/lib/asteroid/key_reference.rb +45 -0
  19. data/lib/asteroid/provider/abstract.rb +16 -1
  20. data/lib/asteroid/provider/digital_ocean.rb +35 -6
  21. data/lib/asteroid/provider/mock.rb +36 -14
  22. data/lib/asteroid/script.rb +3 -57
  23. data/lib/asteroid/server.rb +44 -25
  24. data/lib/asteroid/template.rb +19 -7
  25. data/lib/asteroid/template/liquid.rb +0 -0
  26. data/lib/asteroid/version.rb +1 -1
  27. data/sample/.gitignore +2 -0
  28. data/sample/README.md +1 -0
  29. data/sample/Rakefile +0 -0
  30. data/sample/TODO +9 -0
  31. data/sample/asteroid/files/nginx/available_site.conf.erb +0 -0
  32. data/sample/asteroid/files/nginx/nginx.conf +1 -0
  33. data/sample/asteroid/scripts/ubuntu.aster +4 -0
  34. data/sample/asteroid/servers/default.yml +3 -0
  35. data/sample/asteroid/servers/web.yml +5 -0
  36. data/sample/config/asteroid.rb +9 -0
  37. data/test/helper.rb +18 -2
  38. data/test/unit/test_application.rb +17 -0
  39. data/test/unit/test_file_reference.rb +18 -0
  40. data/test/unit/test_instance.rb +20 -4
  41. data/test/unit/test_script_reference.rb +9 -0
  42. data/test/unit/test_server.rb +46 -2
  43. data/test/unit/test_template.rb +16 -0
  44. metadata +24 -17
  45. data/lib/asteroid/config_file.rb +0 -46
@@ -2,33 +2,39 @@
2
2
  module Asteroid
3
3
  class Instance
4
4
 
5
- def eval_command(cmd, env = nil)
6
- env ||= template_data
7
- cmd = Template.new(:erb).render(cmd, env)
8
- cmd, *rest = cmd.split(" ")
9
- case cmd.to_sym
10
- when :run
11
- self.ssh # reconnect
12
- command_run(rest.first, env)
13
- when :upload
14
- from, to, _ = rest
15
- command_upload(from, to, env)
16
- when :"upload-private-key"
17
- name, _ = rest
18
- command_upload_private_key(name, env)
19
- when :exec
20
- script = rest.join(" ")
21
- command_exec(script, env)
22
- when :config
23
- set_or_get, var, val, _ = rest
24
- command_config(set_or_get, var, val)
25
- else
26
- if server.commands[cmd]
27
- command_command(cmd, rest, env)
28
- else
29
- binding.pry
30
- end
31
- end
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(script, env)
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
- e.define_function :run, [] do |arguments|
78
- binding.pry
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
- e.define_function :exec, [] do |arguments|
82
- binding.pry
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 = ConfigFile.new(from)
157
+ from = FileReference.new(from)
124
158
 
125
159
  if from.template?
126
- from.set_data env
160
+ from.data = env
127
161
  end
128
162
 
129
- puts from.filename
130
- self.scp.upload! from.filename, to
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.ssh_key_filename]
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.join(Asteroid::Config.instance_config_dir, "/instance_#{@id}.yml")
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.instance_name,
31
- size_id: server.attributes[:size_id],
32
- image_id: server.attributes[:image_id],
33
- region_id: server.attributes[:region_id],
34
- ssh_key_ids: server.attributes[: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] ||= File.join(File.dirname(__FILE__), '../../../world.yml')
12
-
13
- begin
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
- @config[:instances] || []
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
@@ -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
- def yml?
27
- file_type == "yml"
28
- end
29
-
30
- def aster?
31
- file_type == "aster"
32
- end
3
+ class ScriptReference < FileReference
33
4
 
34
- def template?
35
- @filename.split('.').last == "erb"
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