panoramix 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,47 @@
1
+ require "panoramix/locale"
2
+
3
+ module Panoramix
4
+ module Plugin
5
+
6
+ class ValidationError < StandardError; end
7
+
8
+ class Base
9
+
10
+ def initialize host
11
+ @host = host
12
+ end
13
+
14
+ # Return current timestamp
15
+ def timestamp
16
+ raise "Not implemented"
17
+ end
18
+
19
+ # When this instance needs to be executed
20
+ def needed? timestamps
21
+ raise "Not implemented"
22
+ end
23
+
24
+ # Has this instance already been created
25
+ def created?
26
+ raise "Not implemented"
27
+ end
28
+
29
+ # Default action for this task
30
+ def run_default
31
+ raise "Not implemented"
32
+ end
33
+
34
+ def shell(cmd, silent=false, env=Hash.new)
35
+ # Get a connection for the current host
36
+ connection = Panoramix.connection(@host)
37
+ # Run the shell command
38
+ connection.shell(cmd, silent, env)
39
+ end
40
+
41
+ def shell_ready?
42
+ Panoramix.connection(@host).ready?
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,83 @@
1
+ require "panoramix/plugin/docker_image_base"
2
+
3
+ module Panoramix
4
+ module Plugin
5
+
6
+ class DockerBuild < DockerImageBase
7
+
8
+ attr_reader :src
9
+
10
+ def initialize(dst, src, host)
11
+ super(dst, host)
12
+ @src = src
13
+ end
14
+
15
+ # When this instance needs to be executed
16
+ def needed? timestamps
17
+ return false if ENV['NO_BUILD'].split(":").any? { |image| image == @dst }
18
+ this_time = timestamp
19
+ timestamps.any? { |t| t > this_time }
20
+ end
21
+
22
+ # Default action for this task
23
+ def run_default
24
+ shell "docker build -t #{dst} #{File.dirname(@src)}"
25
+ end
26
+
27
+ def ps
28
+ super ("Built image")
29
+ end
30
+
31
+ def validate
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+ end
38
+
39
+ def docker_image(args, block=nil)
40
+
41
+ puts "Warning using obsolete docker_image
42
+ \t => #{"docker_image".bold} <image_name> => {dockerfile: <path_dockerfile>}.\n\tchange to:
43
+ \t => #{"docker_build".bold} <image_name> => {dockerfile: <path_dockerfile>}".red
44
+ docker_build(args, block)
45
+ end
46
+
47
+ def docker_build(args, &block)
48
+ # Get task name
49
+ name = args.keys[0]
50
+
51
+ # Get configuration hash
52
+ config = args[name].select { |dep| dep.is_a? Hash }.first
53
+
54
+ # Get dockerfile if given
55
+ dockerfile = config.fetch(:dockerfile, nil)
56
+
57
+ # Raise error if :dockerfile value is nil
58
+ unless dockerfile
59
+ message = I18n.t('errors.docker_build.no_dockerfile_provided')
60
+ raise message
61
+ end
62
+
63
+ # Select provided dependencies
64
+ prerequisites = args[name].select { |d| ! d.is_a? Hash }
65
+
66
+ # Merge prerequisites with the Dockerfile file_task
67
+ prerequisites = prerequisites.push(dockerfile) unless dockerfile.nil?
68
+
69
+ # Set host as a new prerequisite if needed
70
+ host_dep = Panoramix.resolve_host_dependencies
71
+ prerequisites = prerequisites.push(host_dep) unless host_dep.nil?
72
+ prerequisites.flatten!
73
+
74
+ # Get hostname for this scope
75
+ host = Panoramix.current_host_name
76
+
77
+ instance = Panoramix::Plugin::DockerBuild.new(name, dockerfile, host)
78
+
79
+ descriptions = I18n.t('docker_build')
80
+ descriptions = Hash.new if descriptions.class != Hash
81
+
82
+ Panoramix.define_tasks(name,descriptions, instance, prerequisites, block)
83
+ end
@@ -0,0 +1,67 @@
1
+ require "panoramix/plugin/docker_image_base"
2
+
3
+ module Panoramix
4
+ module Plugin
5
+
6
+ class DockerImage < DockerImageBase
7
+
8
+ attr_reader :src
9
+
10
+ def initialize(dst, src, host)
11
+ super(dst, host)
12
+ @src = src
13
+ end
14
+
15
+ # When this instance needs to be executed
16
+ def needed? timestamps
17
+ true
18
+ end
19
+
20
+ # Action clobber for this task
21
+ def clobber
22
+ super
23
+ info = created?
24
+ if info
25
+ id = info.split(" ")[2]
26
+ shell "docker rmi -f #{id}"
27
+ end
28
+ end
29
+
30
+ # Default action for this task
31
+ def run_default
32
+ shell "docker pull #{@src}" unless ENV["NO_PULL"]
33
+
34
+ # Tag image with required tag
35
+ shell "docker tag -f #{@src} #{@dst}"
36
+ end
37
+
38
+ def ps
39
+ super ("Pulled image")
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+ end
46
+
47
+
48
+ def external_docker_image(args, block)
49
+ name = args.keys[0]
50
+ src = args[name][:dockerimage] || args[name][:docker]
51
+
52
+ prerequisites = []
53
+
54
+ # Set host as a new prerequisite if needed
55
+ host_dep = Panoramix.resolve_host_dependencies
56
+ prerequisites = prerequisites.push(host_dep) unless host_dep.nil?
57
+
58
+ # Get hostname for this scope
59
+ host = Panoramix.current_host_name
60
+
61
+ instance = Panoramix::Plugin::DockerImage.new(name, src, host)
62
+
63
+ descriptions = I18n.t('docker_image')
64
+ descriptions = Hash.new if descriptions.class != Hash
65
+
66
+ Panoramix.define_tasks(name, descriptions, instance, prerequisites, block)
67
+ end
@@ -0,0 +1,61 @@
1
+ require "time"
2
+ require "panoramix/plugin/base"
3
+
4
+ module Panoramix
5
+ module Plugin
6
+
7
+ class DockerImageBase < Base
8
+
9
+ attr_reader :dst
10
+
11
+ def initialize(dst, host)
12
+ super host
13
+ @dst = dst
14
+ @tag = "latest"
15
+ end
16
+
17
+ # Return timestamp of the image
18
+ def timestamp
19
+ info = created?
20
+ if info
21
+ # Get image timestamp
22
+ id = info.split(" ")[2]
23
+ time = shell("docker inspect -f {{.Created}} #{id}", true)[:out]
24
+ return Time.parse(time)
25
+ else
26
+ return Time.at 0
27
+ end
28
+ end
29
+
30
+ # Has this image already been created
31
+ def created?
32
+ return @created if @created
33
+ return nil unless shell_ready?
34
+ info = shell("docker images | grep -w '^#{@dst}' | grep -w '#{@tag}'", true)[:out]
35
+ @created = info = info.empty? ? nil: info
36
+ end
37
+
38
+ # Action clobber for this task
39
+ def clobber
40
+ info = created?
41
+ if info
42
+ id = info.split(" ")[2]
43
+ shell "docker rmi -f #{id}"
44
+ end
45
+ end
46
+
47
+ def ps(message)
48
+ puts "#{message}: #{@dst}"
49
+ info = created?
50
+ if info
51
+ puts "Timestamp: #{timestamp}"
52
+ puts info
53
+ else
54
+ puts "Timestamp: Not created"
55
+ end
56
+ puts
57
+ end
58
+
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,178 @@
1
+ require "time"
2
+ require "panoramix/plugin/base"
3
+
4
+ module Panoramix
5
+ module Plugin
6
+
7
+ class DockerUpExceptionError < StandardError; end
8
+
9
+ class DockerUp < Base
10
+
11
+ attr_reader :dst
12
+ attr_reader :src
13
+ attr_reader :env
14
+
15
+ def initialize(dst, src, env, host)
16
+ super (host)
17
+ @dst = dst
18
+ @src = src
19
+ @env = env
20
+ end
21
+
22
+ # Return current timestamp for the container
23
+ def timestamp
24
+ info = created?
25
+ if info
26
+ id = info.split("\n").first
27
+ time = shell("docker inspect -f {{.Created}} #{id}", true, @env)[:out]
28
+ return Time.parse(time)
29
+ else
30
+ Time.at 0
31
+ end
32
+ end
33
+
34
+ # Has this image already been created
35
+ def created?
36
+ return nil unless shell_ready?
37
+ info = shell("docker-compose -p #{@dst} -f #{@src} ps -q", true, @env)[:out]
38
+ @created = info = info.empty? ? nil: info
39
+ end
40
+
41
+ # Action clobber for this task
42
+ def clobber
43
+ shell("docker-compose -p #{@dst} -f #{@src} kill; docker-compose -p #{@dst} -f #{@src} rm -v --force", false, @env) if created?
44
+ end
45
+
46
+ # When this instance needs to be executed
47
+ def needed? timestamps
48
+ this_time = timestamp
49
+ timestamps.any? { |t| t > this_time }
50
+ end
51
+
52
+ # Default action for this task
53
+ def run_default
54
+ @env = parse_env @env.keys
55
+ # Raise an exception if already has been created
56
+ if created?
57
+ message = I18n.t('errors.docker_up.cluster_deployed', {:id => @dst})
58
+ raise(DockerUpExceptionError, message)
59
+ end
60
+ # Run docker-compose up otherwise
61
+ shell("docker-compose -p #{dst} -f #{@src} up -d", false, @env)
62
+ end
63
+
64
+ def stop
65
+ if created?
66
+ shell("docker-compose -p #{dst} -f #{@src} stop", false, @env)
67
+ end
68
+ end
69
+
70
+ def start
71
+ if created?
72
+ shell("docker-compose -p #{dst} -f #{@src} up -d", false, @env)
73
+ end
74
+ end
75
+
76
+ # Print docker-compose project logs
77
+ def logs
78
+ info = created?
79
+ if info
80
+ containers = info.split("\n")
81
+ colors = ['red', 'green','brown','blue','magenta','cyan']
82
+ containers.each do |c|
83
+ out = shell("docker logs #{c} | tail -n 200", true, @env)
84
+ name = shell("docker inspect -f {{.Name}} #{c}", true, @env)[:out]
85
+ name.gsub!("\n", "")
86
+ name.gsub!("/", "")
87
+ name = eval %& "#{name} >".#{colors.first}&
88
+ colors.push colors.shift
89
+ out[:out].split("\n").each{ |out| puts name + out }
90
+ out[:err].split("\n").each{ |out| puts name + out }
91
+ end
92
+ end
93
+ end
94
+
95
+ # Print docker-compose ps
96
+ def ps
97
+ puts "Service: #{@dst}"
98
+ if created?
99
+ puts "Timestamp: #{timestamp}"
100
+ info = shell("docker-compose -p #{@dst} -f #{@src} ps", true, @env)[:out]
101
+ puts info
102
+ else
103
+ puts "Timestamp: Not created"
104
+ end
105
+ puts
106
+ end
107
+
108
+ def validate
109
+ keys_errors = ""
110
+ @env.each do |key, value|
111
+ if value.nil?
112
+ keys_errors << "#{key} " if Panoramix::Link[key].nil?
113
+ end
114
+ end
115
+
116
+ if keys_errors != ""
117
+ message = I18n.t('errors.docker_up.env_var_not_given', {:env => keys_errors})
118
+ raise(ValidationError, message)
119
+ end
120
+ end
121
+
122
+ end
123
+
124
+ end
125
+ end
126
+
127
+
128
+ def parse_env environment
129
+ res=Hash.new
130
+
131
+ #TODO: chech if some env variable is a link
132
+ environment.each do |var|
133
+ res[var]=ENV[var]
134
+ end
135
+
136
+ res
137
+ end
138
+
139
+ def docker_up(args, &block)
140
+ name = args.keys[0]
141
+
142
+ deps = args[name]
143
+
144
+ compose_params = deps.select { |d| d.is_a? Hash }
145
+
146
+ # Get docker-compose file path
147
+ compose = compose_params.first[:compose]
148
+
149
+ # Raise error if :compose value is nil
150
+ unless compose
151
+ message = I18n.t('errors.docker_up.no_compose_provided')
152
+ raise(Panoramix::Plugin::DockerUpExceptionError, message)
153
+ end
154
+
155
+ # Get flags
156
+ flags = {}
157
+
158
+ # Get environment
159
+ environment = parse_env(compose_params.first[:env] || [])
160
+
161
+ # Merge deps with the compose file_task
162
+ prerequisites = deps.select { |d| ! d.is_a? Hash }
163
+ prerequisites = prerequisites.push(compose)
164
+
165
+ # Set host as a new prerequisite if needed
166
+ host_dep = Panoramix.resolve_host_dependencies
167
+ prerequisites = prerequisites.push(host_dep) unless host_dep.nil?
168
+
169
+ # Get hostname for this scope
170
+ host = Panoramix.current_host_name
171
+
172
+ instance = Panoramix::Plugin::DockerUp.new(name, compose, environment, host)
173
+
174
+ descriptions = I18n.t('docker_up')
175
+ descriptions = Hash.new if descriptions.class != Hash
176
+
177
+ Panoramix.define_tasks(name, descriptions, instance, prerequisites, block)
178
+ end
@@ -0,0 +1,109 @@
1
+ require "time"
2
+ require "panoramix/plugin/base"
3
+
4
+ module Panoramix
5
+ module Plugin
6
+ class Git < Base
7
+
8
+ attr_reader :dst
9
+ attr_reader :src
10
+ attr_reader :tag
11
+
12
+ def initialize(dst, src, tag, host)
13
+ super host
14
+ # Get git project name
15
+ repo_name=src.split("/").last.gsub(".git", "")
16
+
17
+ # Base path under which project will be downloaded
18
+ base_path=dst.split(repo_name)[0..-1].join(repo_name)
19
+
20
+ @dst=File.join(base_path, repo_name)
21
+ @src = src
22
+ @tag = tag
23
+ end
24
+
25
+ # Return current timestamp
26
+ def timestamp
27
+ return Time.at 0 unless created?
28
+ git_time = shell("git -C #{@dst} log -1 --format=\"%cd\"", true)[:out]
29
+ #TODO check exit code
30
+ Time.parse(git_time)
31
+ end
32
+
33
+ # When this instance needs to be executed
34
+ def needed? timestamps
35
+ return true if !created?
36
+ # Get remote branches
37
+ remote = shell("git ls-remote #{src}", true)[:out].split("\n")
38
+
39
+ # Is @tag a branch?
40
+ is_branch = remote.select { |r| r.include?("refs/heads/#{tag}") }
41
+
42
+ if is_branch.any?
43
+ # Compare local refs with remote ref
44
+ remote_ref = is_branch.first.gsub /\t/, ' '
45
+ local_refs = shell("git -C #{@dst} show-ref", true)[:out].split("\n")
46
+
47
+ return !local_refs.include?(remote_ref)
48
+ end
49
+
50
+ #TODO: To be implemented for tags and commits
51
+
52
+ return true
53
+ end
54
+
55
+ # Has this instance already been created
56
+ def created?
57
+ File.directory?(@dst)
58
+ end
59
+
60
+ # Action clean fot this task
61
+ def clean
62
+ shell "rm -rf #{@dst}"
63
+ end
64
+
65
+ # Default action for this task
66
+ def run_default
67
+ if created?
68
+ shell "git -C #{@dst} pull"
69
+ # File does not exists, so clone the full repository
70
+ else
71
+ shell "git clone #{@src} #{@dst}"
72
+ end
73
+
74
+ # Switch to tag
75
+ shell "git -C #{@dst} checkout #{@tag}"
76
+ end
77
+
78
+ def validate
79
+ out = shell("git ls-remote #{@src} | grep \'#{@tag}$\' | wc -l", true)
80
+ if (! out[:exit_status].success? || out[:out] =~ /0/)
81
+ message = I18n.t('errors.git.not_found', {:branch => @src, :tag => @tag})
82
+ raise(ValidationError, message)
83
+ end
84
+ end
85
+
86
+ end
87
+ end
88
+ end
89
+
90
+ def git(args, prerequisites, block=nil)
91
+ # Name of the task
92
+ name = args.keys[0]
93
+ # Get :git value from arguments
94
+ src = args.values[0][:git]
95
+ # Get :tag value from arguments
96
+ tag = args.values[0][:tag]
97
+
98
+ # Git tasks always run as local
99
+ host = "local"
100
+
101
+ # Generate a new instance using the provided arguments
102
+ instance = Panoramix::Plugin::Git.new(name, src, tag, host)
103
+
104
+ descriptions = I18n.t('git')
105
+ descriptions = Hash.new if descriptions.class != Hash
106
+
107
+ # Define git instance tasks
108
+ Panoramix.define_tasks(name, descriptions, instance, prerequisites, block)
109
+ end
@@ -0,0 +1,95 @@
1
+ require "panoramix/plugin/base"
2
+
3
+ module Panoramix
4
+ module Plugin
5
+ class Provision < Base
6
+
7
+ attr_reader :host
8
+ attr_accessor :config
9
+
10
+ def initialize(host, config)
11
+ super host
12
+ @host = host
13
+ @config = config
14
+ end
15
+
16
+ # Return current timestamp
17
+ def timestamp
18
+ if created?
19
+ @host.timestamp
20
+ else
21
+ Time.at 0
22
+ end
23
+ end
24
+
25
+ # When this instance needs to be executed
26
+ def needed? timestamps
27
+ !created?
28
+ end
29
+
30
+ # Has this instance already been created
31
+ def created?
32
+ @host.deployed?
33
+ end
34
+
35
+ # Action clobber for this task
36
+ def clobber
37
+ terminate_instance
38
+ end
39
+
40
+ # Run a new instance
41
+ def run_instance args
42
+ @host.run_instance args
43
+ end
44
+
45
+ # Terminate instance
46
+ def terminate_instance args=nil
47
+ unless args
48
+ args=@config
49
+ end
50
+
51
+ @host.terminate_instance args
52
+ end
53
+
54
+ # Default action for this task
55
+ def run_default
56
+ raise "Machine already created" if created?
57
+ run_instance @config
58
+ end
59
+
60
+ # Get aws status
61
+ def ps
62
+ if created?
63
+ puts "Timestamp: #{timestamp}"
64
+ puts @host.status
65
+ else
66
+ puts "Timestamp: Not created"
67
+ end
68
+ puts
69
+ end
70
+
71
+
72
+ end
73
+ end
74
+ end
75
+
76
+
77
+ def provision(&block)
78
+
79
+ raise "Provision defined outside an scope" unless Rake.application.current_scope.any?
80
+
81
+ # Get host for this task
82
+ name = Rake.application.current_scope.first
83
+
84
+ # Get host for this task
85
+ host = Panoramix::Hosts[name][:instance]
86
+
87
+ instance = Panoramix::Plugin::Provision.new(host, host.machine.config)
88
+
89
+ descriptions = I18n.t('provision')
90
+ descriptions = Hash.new if descriptions.class != Hash
91
+
92
+ prerequisites = []
93
+
94
+ Panoramix.define_tasks(:host, descriptions, instance, prerequisites, block)
95
+ end