asteroid 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,113 @@
1
+ require 'net/ssh'
2
+
3
+ require_relative './instance/vars'
4
+ require_relative './instance/scp'
5
+ require_relative './instance/ssh'
6
+ require_relative './instance/command'
7
+
8
+
9
+ module Asteroid
10
+
11
+ class MissingAttributeError < StandardError
12
+ def initialize(name)
13
+ @attribute_name = name
14
+ end
15
+
16
+ def attribute
17
+ @attribute_name
18
+ end
19
+ end
20
+
21
+ class Instance
22
+
23
+ attr_accessor :logger
24
+
25
+ attr_reader :server,
26
+ :provider,
27
+ :type
28
+
29
+
30
+ def initialize(options = {})
31
+ @provider = options.delete :provider
32
+ @server = options.delete :server
33
+ @type = options.delete :type
34
+
35
+ @attributes = options
36
+
37
+ # Validate Attributes
38
+ require_attribute :name
39
+ require_attribute :id
40
+
41
+ provider.required_instance_attributes.each do |att|
42
+ require_attribute att
43
+ end
44
+
45
+ # The type is added on the name
46
+ @attributes[:type] ||= if @attributes[:name]
47
+ @attributes[:name].match(/[a-z]+/)[0].to_sym
48
+ end
49
+ end
50
+
51
+ def self.all
52
+ instances = Provider.all.instances
53
+ instances.map do |info|
54
+ new info
55
+ end
56
+ end
57
+
58
+ def self.first(type)
59
+ all_with_type(type).first
60
+ end
61
+
62
+ def self.all_with_type(type)
63
+ all.select{|i| i.type == type}
64
+ end
65
+
66
+ def password(username = nil)
67
+ username ||= self["login.username"]
68
+ password_filename = File.join(Asteroid::Config.password_dir, "/instance_#{@id}-user_#{username}")
69
+ if File.exists? password_filename
70
+ File.read(password_filename)
71
+ else
72
+ password = SecureRandom.hex(64)
73
+ File.open(password_filename, 'w'){|f| f.write password}
74
+ password
75
+ end
76
+ end
77
+
78
+ def server_defaults
79
+ server.instance_config
80
+ end
81
+
82
+ def server
83
+ @server ||= Server.named self.type.to_s
84
+ end
85
+
86
+ def self.find(id)
87
+ all.select{|i| i.id == id}.first
88
+ end
89
+
90
+ def env
91
+ {
92
+ "INSTANCE_PRIVATE_KEY" => self.server.ssh_key_filename,
93
+ "INSTANCE_SSH_PORT" => self["ssh.port"].to_i,
94
+ "INSTANCE_SSH_LOGIN" => self["login.username"],
95
+ "INSTANCE_IP_ADDRESS" => self.ip_address,
96
+ "INSTANCE_ID" => self.id,
97
+ }
98
+ end
99
+
100
+ def destroy
101
+ @provider.destroy_instance self
102
+ end
103
+
104
+ private
105
+
106
+ def require_attribute(name)
107
+ if @attributes[name].nil?
108
+ raise MissingAttributeError.new(name)
109
+ end
110
+ end
111
+
112
+ end
113
+ end
File without changes
@@ -0,0 +1,146 @@
1
+
2
+ module Asteroid
3
+ class Instance
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
32
+ end
33
+
34
+ def template_data
35
+ {
36
+ instance: self, server: server
37
+ }
38
+ end
39
+
40
+ def command_command(cmd, args, env)
41
+ env ||= template_data
42
+ command = server.commands[cmd]
43
+
44
+ env = if command["args"]
45
+ command["args"].inject({}) do |r, k|
46
+ r[k] = args.shift
47
+ r
48
+ end
49
+ end
50
+
51
+ env = template_data.merge({
52
+ args: env
53
+ })
54
+
55
+ command["steps"].map do |step|
56
+ eval_command step, env
57
+ end.last
58
+ end
59
+
60
+ def command_run_yml_script(yml_script, env = nil)
61
+ env ||= template_data
62
+ filename = File.join(Asteroid::Config.script_dir, '/', yml_script)
63
+ script = File.read(filename)
64
+ yml = Template.new(:erb).render(script, env)
65
+ data = YAML::load yml
66
+ unless data["steps"]
67
+ raise "No steps in #{filenbame}"
68
+ else
69
+ data["steps"].each do |step|
70
+ eval_command step, env
71
+ end
72
+ end
73
+ end
74
+
75
+ def aster_environment
76
+ @aster_environment ||= Aster::Environment.new.tap do |e|
77
+ e.define_function :run, [] do |arguments|
78
+ binding.pry
79
+ end
80
+
81
+ e.define_function :exec, [] do |arguments|
82
+ binding.pry
83
+ end
84
+ end
85
+ end
86
+
87
+ def command_run_aster_script(aster_script, env = nil)
88
+ filename = File.join(Asteroid::Config.script_dir, '/', aster_script)
89
+ script = File.read(filename)
90
+ aster_environment.eval script
91
+ end
92
+
93
+ def command_run(script_name, env = nil)
94
+ env ||= template_data
95
+ script = Script.named(script_name)
96
+
97
+ if script.nil?
98
+ raise "No script named #{script_name}"
99
+ end
100
+
101
+ # Return early if we're running a script
102
+ if script.yml?
103
+ # TODO: send the fucking script obj
104
+ command_run_yml_script(script_name)
105
+ return
106
+ elsif script.aster?
107
+ command_run_aster_script(script_name)
108
+ return
109
+ end
110
+
111
+
112
+ if script.template?
113
+ script.set_data env
114
+ end
115
+
116
+ rendered_script = script.render
117
+ puts rendered_script
118
+ puts self.ssh_exec rendered_script
119
+ end
120
+
121
+ def command_upload(from, to, env = nil)
122
+ env ||= template_data
123
+ from = ConfigFile.new(from)
124
+
125
+ if from.template?
126
+ from.set_data env
127
+ end
128
+
129
+ puts from.filename
130
+ self.scp.upload! from.filename, to
131
+ end
132
+
133
+ def command_exec(command, env = nil)
134
+ self.ssh_exec command
135
+ end
136
+
137
+ def command_config(set_or_get, var, val)
138
+ if set_or_get.to_s == "set"
139
+ self.config_set(var, val)
140
+ elsif set_or_get == "get"
141
+ self.config_get(var)
142
+ end
143
+ end
144
+
145
+ end
146
+ end
@@ -0,0 +1,17 @@
1
+ require 'net/scp'
2
+
3
+ module Asteroid
4
+ class Instance
5
+ def scp
6
+ if @scp && (@scp.session.transport.port.to_s != self["ssh.port"] || (@scp.session.transport.options[:user] != self["login.username"]))
7
+ @scp = nil
8
+ end
9
+ @scp ||= Net::SCP.start(
10
+ ip_address,
11
+ self["login.username"],
12
+ port: self["ssh.port"].to_i,
13
+ :keys => [server.ssh_key_filename]
14
+ )
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,29 @@
1
+ require 'net/ssh'
2
+
3
+ module Asteroid
4
+ class Instance
5
+
6
+ def ssh_exec(command)
7
+ ssh.exec!(command) do |channel, stream, data|
8
+ if stream == :stdout
9
+ puts data
10
+ else
11
+ puts data
12
+ end
13
+ end
14
+ end
15
+
16
+ def ssh
17
+ if @ssh && (@ssh.transport.port.to_s != self["ssh.port"] || (@ssh.transport.options[:user] != self["login.username"]))
18
+ @ssh = nil
19
+ end
20
+ @ssh ||= Net::SSH.start(
21
+ ip_address,
22
+ self["login.username"],
23
+ port: self["ssh.port"].to_i,
24
+ :keys => [server.ssh_key_filename]
25
+ )
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,45 @@
1
+
2
+
3
+ module Asteroid
4
+
5
+ class Instance
6
+
7
+ def [](k)
8
+ val = config_get k
9
+ val.nil? ? server_defaults[k] : val
10
+ end
11
+
12
+ def []=(k, v)
13
+ config_set k, v
14
+ end
15
+
16
+ def config_get(k)
17
+ data = load_config_data
18
+ data && data[k]
19
+ end
20
+
21
+ def config_set(k, v)
22
+ data = load_config_data
23
+ data[k] = v
24
+ save_config_data data
25
+ v
26
+ end
27
+
28
+ def config_filename
29
+ File.join(Asteroid::Config.instance_config_dir, "/instance_#{@id}.yml")
30
+ end
31
+
32
+ def load_config_data
33
+ begin
34
+ YAML::load_file config_filename
35
+ rescue
36
+ {}
37
+ end
38
+ end
39
+
40
+ def save_config_data(data)
41
+ File.open(config_filename, 'w') {|f| f.write data.to_yaml }
42
+ end
43
+ end
44
+
45
+ end
@@ -0,0 +1,36 @@
1
+
2
+ require_relative './provider/abstract'
3
+ require_relative './provider/digital_ocean'
4
+ require_relative './provider/virtual_box'
5
+ require_relative './provider/mock'
6
+
7
+ module Asteroid
8
+
9
+ class ProviderProxy
10
+
11
+ def method_missing(m, *args, &block)
12
+ Config.providers.map do |p|
13
+ p.send(m, *args, &block)
14
+ end.flatten
15
+ end
16
+
17
+ end
18
+
19
+ module Provider
20
+
21
+ def self.all
22
+ ProviderProxy.new
23
+ end
24
+
25
+ end
26
+
27
+ class Config
28
+ class << self
29
+ attr_accessor :providers
30
+ def providers
31
+ @providers ||= []
32
+ end
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1,37 @@
1
+ # :name=>"web-1c5c2972",
2
+ # :image_id=>3101918,
3
+ # :size_id=>66,
4
+ # :region_id=>4,
5
+ # :backups_active=>false,
6
+ # :ip_address=>"107.170.109.79",
7
+ # :private_ip_address=>"10.128.197.52",
8
+ # :locked=>false,
9
+ # :status=>"active",
10
+ # :created_at=>"2014-04-12T20:44:53Z"}
11
+
12
+ module Asteroid
13
+ module Provider
14
+ class Abstract
15
+
16
+ def required_instance_attributes
17
+ []
18
+ end
19
+
20
+ def initialize(config = {})
21
+ @config = config
22
+ end
23
+
24
+ def self.type
25
+ self.to_s.split('::').last.underscore.to_sym
26
+ end
27
+
28
+ def instances
29
+
30
+ end
31
+
32
+ def destroy_instance(instance)
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,66 @@
1
+ module Asteroid
2
+ module Provider
3
+ class DigitalOcean < Abstract
4
+
5
+ def initialize(config = {})
6
+ @config = config
7
+ Digitalocean.client_id = config[:client_id]
8
+ Digitalocean.api_key = config[:api_key]
9
+ end
10
+
11
+ def sizes
12
+ Digitalocean::Size.all.sizes.map do |d|
13
+ d.marshal_dump.merge(
14
+ provider: self
15
+ )
16
+ end
17
+ end
18
+
19
+ def instances
20
+ response = Digitalocean::Droplet.all
21
+ response.droplets.map do |d|
22
+ d.marshal_dump.merge(
23
+ provider: self
24
+ )
25
+ end
26
+ end
27
+
28
+ def create_instance(server)
29
+ 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],
35
+ private_networking: true
36
+ )
37
+
38
+ if instance.status == "OK"
39
+ Instance.new instance.droplet.merge(provider: self)
40
+ else
41
+ nil
42
+ end
43
+ end
44
+
45
+ def destroy_instance(instance)
46
+ Digitalocean::Droplet.destroy instance.id
47
+ end
48
+
49
+ def ssh_keys
50
+ response = Digitalocean::SshKey.all
51
+ response.ssh_keys.map do |d|
52
+ d.marshal_dump.merge(
53
+ provider: self
54
+ )
55
+ end
56
+ end
57
+
58
+ def create_ssh_key(config)
59
+ end
60
+
61
+ def reboot_instance(instance)
62
+ end
63
+
64
+ end
65
+ end
66
+ end