evrone-ci-worker 0.2.0.pre0

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