evrone-ci-worker 0.2.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +3 -0
  4. data/Gemfile +6 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +29 -0
  7. data/Rakefile +24 -0
  8. data/bin/evrone-ci-worker +22 -0
  9. data/docker/Dockerfile +61 -0
  10. data/docker/sv-enable +26 -0
  11. data/docker/sv-gen +50 -0
  12. data/evrone-ci-worker.gemspec +32 -0
  13. data/lib/evrone/ci/worker/configuration.rb +58 -0
  14. data/lib/evrone/ci/worker/consumers/job_logs_consumer.rb +15 -0
  15. data/lib/evrone/ci/worker/consumers/job_status_consumer.rb +16 -0
  16. data/lib/evrone/ci/worker/consumers/jobs_consumer.rb +27 -0
  17. data/lib/evrone/ci/worker/docker.rb +35 -0
  18. data/lib/evrone/ci/worker/ext/string.rb +10 -0
  19. data/lib/evrone/ci/worker/helper/config.rb +14 -0
  20. data/lib/evrone/ci/worker/helper/logger.rb +14 -0
  21. data/lib/evrone/ci/worker/initializers/amqp.rb +53 -0
  22. data/lib/evrone/ci/worker/job.rb +42 -0
  23. data/lib/evrone/ci/worker/local.rb +37 -0
  24. data/lib/evrone/ci/worker/middlewares/docker_before_script.rb +38 -0
  25. data/lib/evrone/ci/worker/middlewares/docker_fetch_repo.rb +76 -0
  26. data/lib/evrone/ci/worker/middlewares/docker_script.rb +39 -0
  27. data/lib/evrone/ci/worker/middlewares/docker_start_container.rb +81 -0
  28. data/lib/evrone/ci/worker/middlewares/local_before_script.rb +29 -0
  29. data/lib/evrone/ci/worker/middlewares/local_create_dirs.rb +46 -0
  30. data/lib/evrone/ci/worker/middlewares/local_fetch_repo.rb +47 -0
  31. data/lib/evrone/ci/worker/middlewares/local_script.rb +29 -0
  32. data/lib/evrone/ci/worker/middlewares/log_job.rb +25 -0
  33. data/lib/evrone/ci/worker/middlewares/update_job_status.rb +64 -0
  34. data/lib/evrone/ci/worker/runner/docker.rb +0 -0
  35. data/lib/evrone/ci/worker/runner/local.rb +52 -0
  36. data/lib/evrone/ci/worker/version.rb +7 -0
  37. data/lib/evrone/ci/worker.rb +84 -0
  38. data/spec/lib/worker/configuration_spec.rb +40 -0
  39. data/spec/lib/worker/docker_spec.rb +29 -0
  40. data/spec/lib/worker/job_spec.rb +54 -0
  41. data/spec/lib/worker/local_spec.rb +29 -0
  42. data/spec/lib/worker/middlewares/docker_before_script_spec.rb +30 -0
  43. data/spec/lib/worker/middlewares/docker_fetch_repo_spec.rb +26 -0
  44. data/spec/lib/worker/middlewares/docker_script_spec.rb +30 -0
  45. data/spec/lib/worker/middlewares/docker_start_container_spec.rb +21 -0
  46. data/spec/lib/worker/middlewares/local_before_script_spec.rb +35 -0
  47. data/spec/lib/worker/middlewares/local_create_dirs_spec.rb +42 -0
  48. data/spec/lib/worker/middlewares/local_fetch_repo_spec.rb +56 -0
  49. data/spec/lib/worker/middlewares/local_script_spec.rb +35 -0
  50. data/spec/lib/worker/middlewares/log_job_spec.rb +15 -0
  51. data/spec/lib/worker/middlewares/update_job_status_spec.rb +70 -0
  52. data/spec/lib/worker_spec.rb +39 -0
  53. data/spec/spec_helper.rb +26 -0
  54. data/spec/support/create.rb +33 -0
  55. data/spec/support/shared_examples/update_job_status_message_spec.rb +7 -0
  56. metadata +257 -0
@@ -0,0 +1,76 @@
1
+ require 'docker'
2
+ require 'evrone/common/spawn'
3
+ require 'net/scp'
4
+
5
+ module Evrone
6
+ module CI
7
+ module Worker
8
+
9
+ DockerFetchRepo = Struct.new(:app) do
10
+
11
+ include Helper::Logger
12
+ include Helper::Config
13
+
14
+ def call(env)
15
+ if env.ssh && env.docker_repo_dir
16
+ code = -1
17
+
18
+ code = prepare_ssh_files(env)
19
+ code = spawn_script(env) if code == 0
20
+
21
+ if code == 0
22
+ app.call env
23
+ else
24
+ -1
25
+ end
26
+ else
27
+ app.call env
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def prepare_ssh_files(env)
34
+ dir = config.docker.remote_dir
35
+ scp = ::Net::SCP.new(env.ssh.connection)
36
+
37
+ code = env.ssh.spawn "mkdir -p #{dir}/.ssh && chmod 0700 #{dir}/.ssh", &env.job.method(:add_to_output)
38
+
39
+ if code == 0
40
+ config = "Host *\n"
41
+ config << " StrictHostKeyChecking no\n"
42
+ config << " UserKnownHostsFile /dev/null\n"
43
+
44
+ scp.upload! StringIO.new(config), "#{dir}/.ssh/config"
45
+ scp.upload! StringIO.new(env.job.message.deploy_key.to_s), "#{dir}/.ssh/id_rsa"
46
+
47
+ code = env.ssh.spawn "chmod 0600 #{dir}/.ssh/id_rsa", &env.job.method(:add_to_output)
48
+ end
49
+ code
50
+ end
51
+
52
+ def spawn_script(env)
53
+ scm = create_scm(env)
54
+
55
+ env.job.add_command_to_output("git clone #{scm.src} #{scm.path}")
56
+ env.job.add_command_to_output("git checkout -q #{scm.sha}")
57
+
58
+ script = "sh -c '#{scm.make_fetch_command}'"
59
+ env.ssh.spawn script, &env.job.method(:add_to_output)
60
+ end
61
+
62
+ def create_scm(env)
63
+ SCM::Git.new(
64
+ env.job.message.src,
65
+ env.job.message.sha,
66
+ env.docker_repo_dir,
67
+ &env.job.method(:add_to_output)
68
+ )
69
+ end
70
+
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+
@@ -0,0 +1,39 @@
1
+ require 'net/scp'
2
+
3
+ module Evrone
4
+ module CI
5
+ module Worker
6
+
7
+ DockerScript = Struct.new(:app) do
8
+
9
+ include Helper::Logger
10
+
11
+ def call(env)
12
+ if env.ssh && env.docker_repo_dir
13
+ code = run_script(env)
14
+ if code == 0
15
+ app.call env
16
+ else
17
+ code
18
+ end
19
+ else
20
+ app.call env
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def run_script(env)
27
+ scp = ::Net::SCP.new(env.ssh.connection)
28
+ script = [env.docker_repo_dir, ".ci_script.sh"].join("/")
29
+ puts env.job.message.script.inspect
30
+ scp.upload! StringIO.new(env.job.message.script), script
31
+ env.ssh.spawn "env - bash #{script}", chdir: env.docker_repo_dir, &env.job.method(:add_to_output)
32
+ end
33
+
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+
@@ -0,0 +1,81 @@
1
+ require 'docker'
2
+ require 'evrone/common/spawn'
3
+ require 'socket'
4
+
5
+ module Evrone
6
+ module CI
7
+ module Worker
8
+
9
+ DockerStartContainer = Struct.new(:app) do
10
+
11
+ include Helper::Config
12
+ include Helper::Logger
13
+ include Evrone::Common::Spawn
14
+
15
+ def call(env)
16
+ spawn_container(env) do
17
+ open_ssh_session(env) do
18
+ app.call env
19
+ end
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def open_ssh_session(env)
26
+ host = env.container_addr
27
+ user = config.docker.ssh.user
28
+ pass = config.docker.ssh.password
29
+
30
+ ssh_options = {
31
+ password: pass,
32
+ port: config.docker.ssh.port || 22,
33
+ paranoid: false,
34
+ forward_agent: false
35
+ }
36
+ env.job.add_to_output "open ssh session to #{user}@#{host}:#{ssh_options[:port]}"
37
+ open_ssh(host, "ci", ssh_options) do |ssh|
38
+ begin
39
+ env.ssh = ssh
40
+ logger.tagged "SSH" do
41
+ yield
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ def spawn_container(env)
48
+ env.container = ::Docker::Container.create create_options
49
+ env.container.start
50
+
51
+ env.job.add_to_output "using #{Socket.gethostname}/#{env.container.id}"
52
+
53
+ env.container_addr = config.docker.ssh.host
54
+ env.container_addr ||= env.container.json['NetworkSettings']['IPAddress']
55
+ env.docker_repo_dir = [config.docker.remote_dir, env.job.message.name].join('/')
56
+
57
+ logger.tagged "DOCKER #{env.container.id}" do
58
+ begin
59
+ logger.info "start container"
60
+ sleep 3
61
+ yield
62
+ ensure
63
+ env.container.stop
64
+ logger.info "stop container"
65
+ end
66
+ end
67
+ end
68
+
69
+ def create_options
70
+ {
71
+ 'Cmd' => config.docker.init,
72
+ 'Image' => config.docker.image,
73
+ }.merge(config.docker.create_options || {})
74
+ end
75
+
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+
@@ -0,0 +1,29 @@
1
+ require 'evrone/ci/common'
2
+
3
+ module Evrone
4
+ module CI
5
+ module Worker
6
+
7
+ LocalBeforeScript = Struct.new(:app) do
8
+
9
+ include Common::Helper::Shell
10
+
11
+ def call(env)
12
+ script = env.work_dir.join(".ci_before_script.sh")
13
+ write_file script, env.job.message.before_script, 0700
14
+
15
+ code = bash file: script, chdir: env.work_dir, &env.job.method(:add_to_output)
16
+
17
+ if code == 0
18
+ app.call env
19
+ else
20
+ -1
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+
@@ -0,0 +1,46 @@
1
+ require 'evrone/ci/common'
2
+
3
+ module Evrone
4
+ module CI
5
+ module Worker
6
+
7
+ LocalCreateDirs = Struct.new(:app) do
8
+
9
+ include Common::Helper::Shell
10
+ include Worker::Helper::Config
11
+
12
+ def call(env)
13
+ tmp_dir! env
14
+ work_dir! env
15
+ repo_dir! env
16
+
17
+ app.call env
18
+ end
19
+
20
+ private
21
+
22
+ def tmp_dir!(env)
23
+ env.tmp_dir = env.path_prefix.join(config.tmp_dir_name)
24
+ .join(env.job.message.name)
25
+ recreate env.tmp_dir
26
+ end
27
+
28
+ def work_dir!(env)
29
+ env.work_dir = env.path_prefix.join(config.work_dir_name)
30
+ .join(env.job.message.name)
31
+
32
+ recreate env.work_dir
33
+ end
34
+
35
+ def repo_dir!(env)
36
+ env.repo_dir = env.path_prefix.join(config.repo_dir_name)
37
+ .join(env.job.message.name)
38
+
39
+ mkdir env.repo_dir
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,47 @@
1
+ require 'evrone/ci/common'
2
+
3
+ module Evrone
4
+ module CI
5
+ module Worker
6
+
7
+ LocalFetchRepo = Struct.new(:app) do
8
+
9
+ include Common::Helper::Shell
10
+
11
+ def call(env)
12
+ scm = create_scm(env)
13
+
14
+ if env.repo_dir
15
+ if scm.fetch == 0 && export(scm, env) == 0
16
+ app.call env
17
+ else
18
+ -1
19
+ end
20
+ else
21
+ app.call env
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def export(scm, env)
28
+ cmd = scm.class.make_export_command env.repo_dir, env.work_dir
29
+ env.job.add_command_to_output cmd
30
+ bash cmd, &env.job.method(:add_to_output)
31
+ end
32
+
33
+ def create_scm(env)
34
+ SCM::Git.new(
35
+ env.job.message.src,
36
+ env.job.message.sha,
37
+ env.repo_dir,
38
+ deploy_key: env.job.message.deploy_key,
39
+ &env.job.method(:add_to_output)
40
+ )
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,29 @@
1
+ require 'evrone/ci/common'
2
+
3
+ module Evrone
4
+ module CI
5
+ module Worker
6
+
7
+ LocalScript = Struct.new(:app) do
8
+
9
+ include Common::Helper::Shell
10
+
11
+ def call(env)
12
+ script = env.work_dir.join(".ci_script.sh")
13
+ write_file script, env.job.message.script, 0700
14
+
15
+ code = bash file: script, chdir: env.work_dir, &env.job.method(:add_to_output)
16
+
17
+ if code == 0
18
+ app.call env
19
+ else
20
+ code
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+
@@ -0,0 +1,25 @@
1
+ module Evrone
2
+ module CI
3
+ module Worker
4
+
5
+ LogJob = Struct.new(:app) do
6
+
7
+ include Helper::Logger
8
+
9
+ def call(env)
10
+ matrix_key = env.job.message.matrix_keys.join(" ")
11
+
12
+ logger.tagged("JOB #{env.job.message.id}.#{env.job.message.job_id} #{matrix_key}") do
13
+ logger.info "starting job"
14
+ rs = app.call env
15
+ logger.info "done job"
16
+ rs
17
+ end
18
+ end
19
+
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+
@@ -0,0 +1,64 @@
1
+ require 'evrone/ci/message'
2
+
3
+ module Evrone
4
+ module CI
5
+ module Worker
6
+
7
+ UpdateJobStatus = Struct.new(:app) do
8
+
9
+ include Helper::Logger
10
+
11
+ STARTED = 2
12
+ FINISHED = 3
13
+ BROKEN = 4
14
+ FAILED = 5
15
+
16
+ def call(env)
17
+
18
+ update_status env.job, STARTED
19
+ rs = -1
20
+ begin
21
+ rs = app.call env
22
+ rescue Exception => e
23
+ logger.error("ERROR: #{e.inspect}\n BACKTRACE:\n#{e.backtrace.map{|i| " #{i}" }.join("\n")}")
24
+ end
25
+
26
+ case
27
+ when rs == 0
28
+ update_status env.job, FINISHED
29
+ when rs > 0
30
+ update_status env.job, BROKEN
31
+ when rs < 0
32
+ update_status env.job, FAILED
33
+ end
34
+
35
+ rs
36
+ end
37
+
38
+ private
39
+
40
+ def update_status(job, status)
41
+ publish_status create_message(job, status)
42
+ end
43
+
44
+ def create_message(job, status)
45
+ tm = Time.now
46
+ Message::JobStatus.new(
47
+ build_id: job.message.id,
48
+ job_id: job.message.job_id,
49
+ status: status,
50
+ tm: tm.to_i,
51
+ tm_usec: tm.usec,
52
+ matrix: job.message.matrix_keys
53
+ )
54
+ end
55
+
56
+ def publish_status(message)
57
+ logger.info "delivered job status #{message.inspect}"
58
+ JobStatusConsumer.publish message
59
+ end
60
+
61
+ end
62
+ end
63
+ end
64
+ end
File without changes
@@ -0,0 +1,52 @@
1
+ module Evrone
2
+ module CI
3
+ module Worker
4
+ class Runner
5
+ class Local
6
+
7
+ include Worker::Helper::Logger
8
+ include Worker::Helper::Config
9
+ include Common::Helper::Shell
10
+
11
+ attr_reader :build, :path, :build_script
12
+
13
+ def initialize(build, path)
14
+ @build = build
15
+ @path = path
16
+ @build_script = path.tmp_dir.join(".build.sh").expand_path
17
+ end
18
+
19
+ def perform
20
+ make_build_script
21
+ run_build_script
22
+ end
23
+
24
+ private
25
+
26
+ def run_build_script
27
+ bash file: build_script, chdir: path.work_dir, &method(:add_output)
28
+ end
29
+
30
+ def make_build_script
31
+ content = build.travis.to_queue.to_shell_script script_replaces do |env|
32
+ prepare_env env
33
+ end
34
+ write_file build_script, content, 0755
35
+ add_output_command build_script
36
+ end
37
+
38
+ def script_replaces
39
+ { "SHARED_PATH" => path.shared_dir }
40
+ end
41
+
42
+ def prepare_env(env)
43
+ env['init'].unshift "echo 'copy repo from #{path.repo_dir} to #{path.work_dir}'"
44
+ env['init'].unshift build.csm.make_export_command path.repo_dir, path.work_dir
45
+ env
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+
@@ -0,0 +1,7 @@
1
+ module Evrone
2
+ module CI
3
+ module Worker
4
+ VERSION = "0.2.0.pre0"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,84 @@
1
+ require 'rubygems'
2
+ require 'pathname'
3
+ require 'bundler'
4
+ Bundler.require :default
5
+
6
+ require File.expand_path("../worker/ext/string", __FILE__)
7
+ require File.expand_path("../worker/version", __FILE__)
8
+
9
+ module Evrone
10
+ module CI
11
+ module Worker
12
+
13
+ autoload :JobsConsumer, File.expand_path("../worker/consumers/jobs_consumer", __FILE__)
14
+ autoload :JobLogsConsumer, File.expand_path("../worker/consumers/job_logs_consumer", __FILE__)
15
+ autoload :JobStatusConsumer, File.expand_path("../worker/consumers/job_status_consumer", __FILE__)
16
+ autoload :Configuration, File.expand_path("../worker/configuration", __FILE__)
17
+ autoload :Job, File.expand_path("../worker/job", __FILE__)
18
+ autoload :Local, File.expand_path("../worker/local", __FILE__)
19
+ autoload :Docker, File.expand_path("../worker/docker", __FILE__)
20
+
21
+ autoload :LogJob, File.expand_path("../worker/middlewares/log_job", __FILE__)
22
+ autoload :UpdateJobStatus, File.expand_path("../worker/middlewares/update_job_status", __FILE__)
23
+
24
+ autoload :LocalCreateDirs, File.expand_path("../worker/middlewares/local_create_dirs", __FILE__)
25
+ autoload :LocalFetchRepo, File.expand_path("../worker/middlewares/local_fetch_repo", __FILE__)
26
+ autoload :LocalBeforeScript, File.expand_path("../worker/middlewares/local_before_script", __FILE__)
27
+ autoload :LocalScript, File.expand_path("../worker/middlewares/local_script", __FILE__)
28
+
29
+ autoload :DockerStartContainer, File.expand_path("../worker/middlewares/docker_start_container", __FILE__)
30
+ autoload :DockerFetchRepo, File.expand_path("../worker/middlewares/docker_fetch_repo", __FILE__)
31
+ autoload :DockerBeforeScript, File.expand_path("../worker/middlewares/docker_before_script", __FILE__)
32
+ autoload :DockerScript, File.expand_path("../worker/middlewares/docker_script", __FILE__)
33
+
34
+ module Helper
35
+ autoload :Logger, File.expand_path("../worker/helper/logger", __FILE__)
36
+ autoload :Config, File.expand_path("../worker/helper/config", __FILE__)
37
+ end
38
+
39
+ extend self
40
+
41
+ @@root = Pathname.new File.expand_path('../../../..', __FILE__)
42
+ @@config = Configuration.new
43
+
44
+ def logger
45
+ if ENV['CI_WORKER_SILENT']
46
+ config.null_logger
47
+ else
48
+ config.logger
49
+ end
50
+ end
51
+
52
+ def configure
53
+ yield config
54
+ config
55
+ end
56
+
57
+ def config
58
+ @@config
59
+ end
60
+
61
+ def root
62
+ @@root
63
+ end
64
+
65
+ def perform(job, path_prefix)
66
+ run_class.new(job, path_prefix).perform
67
+ end
68
+
69
+ def run_class
70
+ self.const_get(config.run.to_s.camelize)
71
+ end
72
+
73
+ def reset_config!
74
+ @@config = Configuration.new
75
+ end
76
+
77
+ end
78
+ end
79
+ end
80
+
81
+ Evrone::CI::Worker.root.join("lib/evrone/ci/worker/initializers").children.each do |e|
82
+ require e
83
+ end
84
+
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe Evrone::CI::Worker::Configuration do
4
+ let(:config) { Evrone::CI::Worker.config }
5
+ subject { config }
6
+
7
+ its(:run) { should eq :docker }
8
+ its(:docker) { should be }
9
+ its(:work_dir_name) { should eq 'work' }
10
+ its(:repo_dir_name) { should eq 'repo' }
11
+ its(:timeout) { should eq 1800 }
12
+ its(:amqp_url) { should be_nil }
13
+
14
+ context "docker" do
15
+ subject { config.docker }
16
+
17
+ its(:ssh) { should be }
18
+ its(:remote_dir) { should eq '/home/ci' }
19
+ its(:init) { should be }
20
+ its(:image) { should eq 'ci' }
21
+ end
22
+
23
+ context "docker.ssh" do
24
+ subject { config.docker.ssh }
25
+
26
+ its(:user) { should eq 'ci' }
27
+ its(:password) { should eq 'ci' }
28
+ end
29
+
30
+ context ".configure" do
31
+ subject {
32
+ Evrone::CI::Worker.configure do |c|
33
+ c.run = "local"
34
+ c.docker.image = 'image'
35
+ end
36
+ }
37
+ its(:run) { should eq :local }
38
+ its("docker.image") { should eq 'image' }
39
+ end
40
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+ require 'pathname'
3
+ require 'fileutils'
4
+
5
+ describe Evrone::CI::Worker::Docker do
6
+ let(:options) { { } }
7
+ let(:job) { create :job, options }
8
+ let(:path) { '/tmp/.ci' }
9
+ let(:local) { described_class.new job, path }
10
+ subject { local }
11
+
12
+ after { FileUtils.rm_rf path }
13
+
14
+ context "perform", run_docker: true do
15
+ subject { local.perform }
16
+ it { should eq 0 }
17
+
18
+ context "when fail before_script" do
19
+ let(:options) { { before_script: "/bin/false" } }
20
+ it { should eq(-1) }
21
+ end
22
+
23
+ context "when fail script" do
24
+ let(:options) { { script: "/bin/false" } }
25
+ it { should eq(1) }
26
+ end
27
+ end
28
+
29
+ end