panoramix 0.5.0

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,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