panoramix 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,13 @@
1
+ require "panoramix/instances/aws"
2
+
3
+ module Panoramix
4
+
5
+ module AWS
6
+ module_function
7
+
8
+ def config(config)
9
+ AwsInstance.new(config.getHash)
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,49 @@
1
+ require 'open3'
2
+
3
+ module Panoramix
4
+ class BaseConnection
5
+
6
+ def shell(cmd, silent, env)
7
+
8
+ env.select{|k,v| v.nil?}.each{ |k, v| env[k]=Panoramix.ip_host(Panoramix::Link[k]) }
9
+ puts "Running #{cmd}".magenta if ENV["VERBOSE"]
10
+
11
+ @err = ""
12
+ @out = ""
13
+ Open3.popen3(env, cmd) do |stdin, stdout, stderr, wait_thr|
14
+ stdin.close
15
+ stdout.each_line do |line|
16
+ puts line unless silent
17
+ @out << "#{line}"
18
+ end
19
+ stderr.each_line do |line|
20
+ puts line.red unless silent
21
+ @err << "#{line}"
22
+ end
23
+ @exit_status = wait_thr.value
24
+ end
25
+
26
+ if ENV["VERBOSE"] && !@out.empty?
27
+ puts "\tout => \n\t\t#{@out.gsub("\n","\n\t\t")}".green
28
+ end
29
+ if ENV["VERBOSE"] && !@err.empty?
30
+ if @exit_status.success?
31
+ puts "\terr => \n\t\t#{@err.gsub("\n","\n\t\t")}".brown
32
+ else
33
+ puts "\terr => \n\t\t#{@err.gsub("\n","\n\t\t")}".red
34
+ end
35
+ end
36
+ puts "\texit_status => #{@exit_status}\n".blue if ENV["VERBOSE"]
37
+
38
+ return {
39
+ :out => @out,
40
+ :err => @err,
41
+ :exit_status => @exit_status
42
+ }
43
+ end
44
+
45
+ def close_connection
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,85 @@
1
+ require 'time'
2
+ require 'net/ssh/gateway'
3
+ require 'panoramix/connections/base_connection'
4
+
5
+
6
+ module Panoramix
7
+ class DockerConnection < BaseConnection
8
+
9
+ def initialize(host, user)
10
+ @host = host
11
+ @user = user
12
+ @connected = false
13
+ end
14
+
15
+ def connected?
16
+ @connected
17
+ end
18
+
19
+ def connect
20
+ return if @connected
21
+ return unless @host && @user
22
+ ready?
23
+ end
24
+
25
+ # Returns true if connection is enabled
26
+ def ready?
27
+ return false unless @host && @user
28
+ ! open_tunnel.nil?
29
+ end
30
+
31
+ def shell(cmd, silent=false, env=Hash.new)
32
+ raise "No host unless" unless @host && @user
33
+ #open_tunnel unless @gateway
34
+ # Run passing tunneled port
35
+ env['DOCKER_HOST'] = "tcp://127.0.0.1:#{@port}"
36
+ super(cmd, silent, env)
37
+ end
38
+
39
+ def close_connection
40
+ if @connected
41
+ @gateway.close(@port)
42
+ @connected = false
43
+ @gateway = nil
44
+ @port = nil
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def open_tunnel
51
+ unless @gateway
52
+ # Open a new ssh tunnel
53
+ print "Connecting remote host..."
54
+ # Wait until docker starts
55
+ Timeout::timeout(600) do
56
+ begin
57
+ @gateway = Net::SSH::Gateway.new(@host, @user)
58
+ 3.times do
59
+ begin
60
+ @port = @gateway.open('127.0.0.1', 666, rand(63000)+2000)
61
+ break
62
+ rescue
63
+ puts "Port already in use"
64
+ end
65
+ end
66
+ #Check this connection
67
+ Timeout::timeout(10) do
68
+ res = shell("docker ps", true)
69
+ raise "Servide docker not available" unless res[:exit_status].success?
70
+ end
71
+ rescue Exception => err
72
+ sleep(10)
73
+ print "."
74
+ @gateway.close(@port) if @gateway
75
+ retry
76
+ end
77
+ end
78
+ @connected = true
79
+ puts " done."
80
+ end
81
+ @gateway
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,15 @@
1
+ require 'panoramix/connections/base_connection'
2
+
3
+ module Panoramix
4
+
5
+ class LocalConnection < BaseConnection
6
+ def shell(cmd, silent, env )
7
+ res = super(cmd, silent, env)
8
+ end
9
+
10
+ def ready?
11
+ return true
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,28 @@
1
+ require 'panoramix/connections/local_connection'
2
+ require 'panoramix/connections/docker_connection'
3
+
4
+ module Panoramix
5
+
6
+ class Connections
7
+ class << self
8
+ attr_reader :connections
9
+
10
+ def initialize
11
+ @connections=Hash.new
12
+ @connections["local"] = LocalConnection.new
13
+ end
14
+
15
+ def register(host, config)
16
+ @connections[host] = DockerConnection.new(config[:host], config[:user])
17
+ end
18
+
19
+ def connect key
20
+ @connections[key].connect unless @connections[key].connected?
21
+ end
22
+
23
+ def [](key)
24
+ @connections[key]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,11 @@
1
+ module Rake
2
+ module DSL
3
+
4
+ def load file
5
+ task=Rake::Task.tasks.any? { |task| task.name == file }
6
+ Rake::Task[file].invoke if task
7
+ super file
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,39 @@
1
+ require "panoramix/plugin/wget"
2
+ require "panoramix/plugin/s3"
3
+ require "panoramix/plugin/git"
4
+ require "panoramix/plugin/docker_image"
5
+ require "panoramix/mpi"
6
+
7
+ def external(dep, &block)
8
+ # Provided value can be either array or hash, so we need to make sure the final value is an array
9
+ value = [dep.values[0]].flatten
10
+
11
+ # Select the hash with params
12
+ params = value.select { |d| d.is_a? Hash }.first
13
+
14
+ # Handle with MPI
15
+ handled_dep = Panoramix::MPI.handle_external(dep.keys[0], params)
16
+
17
+ raise "Only a single external depdency supported (received: #{dep.keys})" if handled_dep.length != 1
18
+
19
+ # Select provided dependencies
20
+ prerequisites = value.select { |d| ! d.is_a? Hash }
21
+
22
+ case handled_dep.values[0].keys[0]
23
+ when :s3
24
+ s3(handled_dep, prerequisites, block)
25
+ when :wget
26
+ wget(handled_dep, prerequisites, block)
27
+ when :git
28
+ git(handled_dep, prerequisites, block)
29
+ when :dockerimage
30
+ external_docker_image(handled_dep, block)
31
+ when :docker
32
+ puts "Warning using obsolete external dependency key
33
+ \t => external <image_name> {#{"docker".bold}: <docker_image_name> }.\n\tchange to:
34
+ \t => external <image_name> {#{"dockerimage".bold}: <docker_image_name> }.".red
35
+ external_docker_image(handled_dep, block)
36
+ else
37
+ raise "Unknown external protocol '#{dep.values[0].keys[0]}'"
38
+ end
39
+ end
@@ -0,0 +1,58 @@
1
+ require "panoramix/instances/local"
2
+
3
+ module Panoramix
4
+ class Hosts
5
+ class << self
6
+
7
+ attr_reader :hosts
8
+
9
+ def initialize
10
+ @hosts=Hash.new
11
+ register("local", LocalInstance.new)
12
+ end
13
+
14
+ def register(name, instance)
15
+ if instance.class.ancestors.include? BaseInstance
16
+ instance.host(name)
17
+ @hosts[name] = {instance: instance, tasks: {}}
18
+ define_tasks(name)
19
+ else
20
+ puts "Warning using obsolete instance on host key
21
+ \t => host <host_name>, #{"<environment_class>".bold} \n\tchange to:
22
+ \t => host <host_name>, #{"Panoramix::<Cloud>.config(<environment_class>)".bold}
23
+ \t where Cloud must be AWS or Metal".red
24
+ exit(1)
25
+ end
26
+ end
27
+
28
+ def define_tasks(name)
29
+ Panoramix::Tasks::Actions.each do |act|
30
+ block_act = custom_host_action(act.action_name, act.order_class, name)
31
+ descriptions =(name != 'local') ? I18n.t('host') : Hash.new
32
+ Panoramix.define_task("#{name}:#{act.action_name}", [], descriptions[act.action_name.to_sym], block_act)
33
+ end
34
+ end
35
+
36
+ def custom_host_action(action, class_order, host_name)
37
+ Proc.new do |t|
38
+ tasks = Panoramix::Hosts[host_name][:tasks]
39
+ class_order.each do |p|
40
+ tasks[p].each do |task_name|
41
+ host_name2 =(host_name != 'local') ? "#{host_name}:" : ""
42
+ Rake::Task["#{host_name2}#{task_name}:#{action}"].invoke
43
+ end if tasks[p]
44
+ end
45
+ end
46
+ end
47
+
48
+ def [](key)
49
+ @hosts[key]
50
+ end
51
+
52
+ def to_s
53
+ @hosts.pretty_inspect
54
+ end
55
+
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,74 @@
1
+ require "raws"
2
+ require "panoramix/instances/base"
3
+
4
+ module Panoramix
5
+
6
+ class AwsInstance < BaseInstance
7
+
8
+ attr_reader :machine
9
+
10
+ def initialize(config)
11
+ @tunnel=nil
12
+ @machine = Raws::EC2::Machine.new(config[:name], config[:region])
13
+ @machine.config = config
14
+ @record = Raws::Route53::Record.new(config[:subdomain_id], config[:region])
15
+ end
16
+
17
+ def host(host)
18
+ @id=host
19
+ @machine.config[:name] = host
20
+ @record.name = host
21
+ end
22
+
23
+ def status
24
+ puts @machine.status
25
+ end
26
+
27
+ def deployed?
28
+ @machine.deployed?
29
+ end
30
+
31
+ def timestamp
32
+ @machine.timestamp
33
+ end
34
+
35
+ def run_instance args = {}
36
+ @machine.config.merge!(args)
37
+ if (@machine.config[:create_dns_record])
38
+ @record.ips.push(@machine.public_ip) #TODO: Wrong if private dns in aws => change for ip
39
+ @record.save
40
+ end
41
+ @machine.save
42
+ clear_known_hosts_by_hostid
43
+ end
44
+
45
+ def terminate_instance args=nil
46
+ @machine.terminate_instance
47
+ @record.delete if (@machine.config[:create_dns_record])
48
+ end
49
+
50
+ def self.deployable?
51
+ true
52
+ end
53
+
54
+ def connection_params
55
+ {:user => "ubuntu", :host => ip}
56
+ end
57
+
58
+ def ip
59
+ if @machine.config[:use_public_ip]
60
+ @ip = @machine.public_ip unless @ip
61
+ else
62
+ @ip = @machine.private_ip unless @ip
63
+ end
64
+ @ip
65
+ end
66
+ private
67
+
68
+ def clear_known_hosts_by_hostid
69
+ result = `ssh-keygen -R #{ip} 2>&1`
70
+ raie ("Failed to update known_host file. \n#{result}") if !result.include? "updated"
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,13 @@
1
+ module Panoramix
2
+ class BaseInstance
3
+
4
+ def host(host)
5
+ @host = host
6
+ end
7
+
8
+ def ip
9
+ @host
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ require "panoramix/instances/base"
2
+
3
+ module Panoramix
4
+
5
+ class LocalInstance < BaseInstance
6
+ def deployed?
7
+ true
8
+ end
9
+
10
+ def status
11
+ ""
12
+ end
13
+
14
+ def ip
15
+ "127.0.0.1"
16
+ end
17
+
18
+ def self.deployable?
19
+ false
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,26 @@
1
+ require "panoramix/instances/base"
2
+
3
+ module Panoramix
4
+
5
+ class MetalInstance < BaseInstance
6
+ def initialize(user)
7
+ @tunnel = nil
8
+ @user = user
9
+ end
10
+
11
+ def connection_params
12
+ {:user => @user, :host => @host}
13
+ end
14
+
15
+ def deployed?
16
+ `ping -c 1 #{@host}`
17
+ $?.exitstatus == 0
18
+ end
19
+
20
+ def self.deployable?
21
+ false
22
+ end
23
+
24
+ end
25
+
26
+ end
@@ -0,0 +1,4 @@
1
+ require "i18n"
2
+
3
+ I18n.load_path << File.expand_path(File.join(File.dirname(__FILE__),"../../locales/en.yml"))
4
+ I18n.enforce_available_locales = true
@@ -0,0 +1,13 @@
1
+ require "panoramix/instances/metal"
2
+
3
+ module Panoramix
4
+
5
+ module Metal
6
+ module_function
7
+
8
+ def config(user)
9
+ MetalInstance.new(user)
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,97 @@
1
+ require "json"
2
+ require 'pry'
3
+
4
+ module Panoramix
5
+
6
+ class MPI
7
+
8
+ class << self
9
+
10
+ def initialize
11
+ @mpi_path = "MPI.rb"
12
+ @mpi_file = {}
13
+ @project_mpi = {}
14
+ @final_mpi = {}
15
+
16
+ # Read MPI.rb if exists
17
+ if File.exists?(@mpi_path)
18
+ load @mpi_path
19
+ @mpi_file = Panoramix::MPI::Deps
20
+ end
21
+ end
22
+
23
+ def handle_external(key, value)
24
+ # Get current scope
25
+ scope = Panoramix::current_host_name
26
+
27
+ # Save dependency into @project_mpi
28
+ @project_mpi[scope] = {} if @project_mpi[scope].nil?
29
+ @project_mpi[scope].merge!({key => value})
30
+
31
+ @final_mpi[scope] = {} if @final_mpi[scope].nil?
32
+ @final_mpi[scope].merge!({key => value})
33
+
34
+ # Check whether dependencies can be overriden by an MPI file
35
+ if (@mpi_file && @mpi_file[scope] && @mpi_file[scope][key])
36
+
37
+ # Generate the new value from MPI
38
+ mpi_value = @mpi_file[scope][key]
39
+
40
+ # Save overriden value to @final_mpi
41
+ @final_mpi[scope] = {} if @final_mpi[scope].nil?
42
+ @final_mpi[scope].merge!({key => mpi_value})
43
+
44
+ return {key => mpi_value}
45
+ end
46
+
47
+ {key => value}
48
+ end
49
+
50
+ def print_final_mpi
51
+ puts @final_mpi.pretty_inspect
52
+ end
53
+
54
+ def print_mpi
55
+ puts @project_mpi.pretty_inspect
56
+ end
57
+
58
+ def diff_sh
59
+ system("bash -c \"diff <(echo '#{@final_mpi.pretty_inspect}') <(echo '#{@project_mpi.pretty_inspect}')| colordiff\"")
60
+ end
61
+
62
+ def generate
63
+ # Check whether the file already exists
64
+ if File.exists? @mpi_path
65
+ puts "Error: mpi.rb already exists in this project. Please, remove it before in order to create a newest version.".red
66
+ exit 0
67
+ end
68
+
69
+ # Get directory containing MPI.rb
70
+ dir = Rake.application.original_dir
71
+
72
+ # Replace each key path with the variable current_dir
73
+ new_file = {}
74
+ @project_mpi.each do |host ,deps|
75
+ new_hash = {}
76
+ deps.each do |k, v|
77
+ key = k.gsub(dir, '#{current_dir}')
78
+ new_hash[key] = v
79
+ end
80
+ new_file[host] = new_hash
81
+ end
82
+
83
+ header =
84
+ %&current_dir = File.expand_path(File.dirname(__FILE__))
85
+
86
+ Panoramix::MPI::Deps =&
87
+
88
+ File.open(@mpi_path,"w") do |f|
89
+ f.write(header)
90
+ f.write(new_file.pretty_inspect.gsub('\#','#'))
91
+ end
92
+ end
93
+
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,109 @@
1
+ require "time"
2
+
3
+ module Panoramix
4
+ module_function
5
+
6
+ def global_timestamps(prerequisites, scope)
7
+ timestamps = prerequisites.map do |p|
8
+ any_task = Rake::Task.tasks.any? { |t| t.name == p }
9
+ if( any_task && (Rake::Task[p].class == Rake::FileTask))
10
+ Rake::Task[p].timestamp
11
+ else
12
+ Panoramix::TIMESTAMP["#{scope}:#{p}"]
13
+ end
14
+ end
15
+
16
+ # Remove nil values (fe: Directory tasks)
17
+ timestamps.compact
18
+ end
19
+
20
+ def run_task(task, instance, block=nil)
21
+ if instance.needed? global_timestamps(task.prerequisites, task.scope.first)
22
+ begin
23
+ block.call(instance) if block
24
+ instance.run_default unless block
25
+ rescue Panoramix::Plugin::DockerUpExceptionError => e
26
+ puts "Error: #{e.message}".red
27
+ puts "Exiting program...".red
28
+ exit(0)
29
+ end
30
+ else
31
+ puts "task #{task} not needed"
32
+ end
33
+ end
34
+
35
+ def run_custom_task(instance, name)
36
+ method = instance.method(name.to_sym)
37
+ begin
38
+ method.call
39
+ rescue Panoramix::Plugin::ValidationError => e
40
+ puts "Validation error: #{e.message}".red
41
+ end
42
+ end
43
+
44
+ def define_action_task(name, desctiption, instance, operation)
45
+ if instance.respond_to? operation
46
+ Rake.application.last_description = desctiption
47
+ Rake::Task.define_task "#{name}:#{operation.to_s}" do |t|
48
+ run_custom_task(instance, operation)
49
+ end
50
+ end
51
+ end
52
+
53
+ def define_task(name, prerequisites, description, block=nil)
54
+ Rake.application.last_description = description
55
+ Rake::Task.define_task({name => prerequisites}, &block)
56
+ end
57
+
58
+ def define_tasks(name, descriptions, instance, prerequisites, block=nil)
59
+ fill_current_host_tasks(name, instance.class)
60
+ action = Proc.new do |t|
61
+ run_task(t, instance, block) if block
62
+ run_task(t, instance) unless block
63
+ Panoramix::TIMESTAMP["#{t.scope.first}:#{name}"] = instance.timestamp
64
+ end
65
+
66
+ define_task(name, prerequisites, descriptions[:main], action)
67
+ Panoramix::Tasks::Actions.each do |act|
68
+ define_action_task(name, descriptions[act.action_name.to_sym], instance, act.action_name)
69
+ end
70
+ end
71
+
72
+ def resolve_host_dependencies
73
+ current_host.class.deployable? ? :host : nil
74
+ end
75
+
76
+ def connection host_name
77
+ unless Panoramix::Connections[host_name]
78
+ host = Panoramix::Hosts[host_name][:instance]
79
+ Panoramix::Connections.register(host_name, host.connection_params)
80
+ Panoramix::Connections.connect host_name
81
+ end
82
+ Panoramix::Connections[host_name]
83
+ end
84
+
85
+ def current_host
86
+ Panoramix::Hosts[current_host_name][:instance]
87
+ end
88
+
89
+ def fill_current_host_tasks(name, type)
90
+ unless Panoramix::Hosts[current_host_name][:tasks][type]
91
+ Panoramix::Hosts[current_host_name][:tasks][type] = []
92
+ end
93
+ Panoramix::Hosts[current_host_name][:tasks][type].push(name)
94
+ end
95
+
96
+ def current_host_name
97
+ Rake.application.current_scope.first || "local"
98
+ end
99
+
100
+ def validation_error(instance, error)
101
+ puts "#{instance.class.name.split("::").last} validation error \n\t#{error.gsub("\n", "\n\t").red}"
102
+ end
103
+
104
+ def ip_host host_name
105
+ host = Panoramix::Hosts[host_name]
106
+ return !host.nil? ? host[:instance].ip : nil
107
+ end
108
+
109
+ end