asteroid 0.0.1

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.
@@ -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