vx-common 0.2.0.pre28

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,81 @@
1
+ # from activesupport
2
+
3
+ require 'logger'
4
+
5
+ module Vx
6
+ module Common
7
+ module TaggedLogging
8
+
9
+ class Formatter < ::Logger::Formatter
10
+ Format = "[%s] %1s : %s\n"
11
+
12
+ def call(severity, time, progname, msg)
13
+ Format % [format_datetime(time),
14
+ severity[0...1],
15
+ msg2str("#{tags_text}#{msg}")]
16
+ end
17
+
18
+ def thread_id
19
+ Thread.current.object_id
20
+ end
21
+
22
+ def tagged(*tags)
23
+ new_tags = push_tags(*tags)
24
+ yield self
25
+ ensure
26
+ pop_tags(new_tags.size)
27
+ end
28
+
29
+ def push_tags(*tags)
30
+ tags.flatten.reject{|i| i.to_s.strip.empty? }.tap do |new_tags|
31
+ current_tags.concat new_tags
32
+ end
33
+ end
34
+
35
+ def pop_tags(size = 1)
36
+ current_tags.pop size
37
+ end
38
+
39
+ def clear_tags!
40
+ current_tags.clear
41
+ end
42
+
43
+ def current_tags
44
+ Thread.current[:activesupport_tagged_logging_tags] ||= []
45
+ end
46
+
47
+ private
48
+
49
+ def tags_text
50
+ tags = current_tags
51
+ if tags.any?
52
+ tags.collect { |tag| "[#{tag}] " }.join
53
+ end
54
+ end
55
+
56
+ end
57
+
58
+ def self.new(logger)
59
+ # Ensure we set a default formatter so we aren't extending nil!
60
+ logger.formatter = Formatter.new
61
+ logger.extend(self)
62
+ end
63
+
64
+ %w{ push_tags pop_tags clear_tags! }.each do |m|
65
+ define_method m do
66
+ formatter.send(m)
67
+ end
68
+ end
69
+
70
+ def tagged(*tags)
71
+ formatter.tagged(*tags) { yield self }
72
+ end
73
+
74
+ def flush
75
+ clear_tags!
76
+ super if defined?(super)
77
+ end
78
+
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,5 @@
1
+ module Vx
2
+ module Common
3
+ VERSION = "0.2.0.pre28"
4
+ end
5
+ end
data/lib/vx/common.rb ADDED
@@ -0,0 +1,20 @@
1
+ require File.expand_path("../common/version", __FILE__)
2
+
3
+ module Vx
4
+ module Common
5
+ module Helper
6
+ autoload :Shell, File.expand_path("../common/helper/shell", __FILE__)
7
+ autoload :Middlewares, File.expand_path("../common/helper/middlewares", __FILE__)
8
+ autoload :UploadShCommand, File.expand_path("../common/helper/upload_sh_command", __FILE__)
9
+ end
10
+
11
+ autoload :OutputBuffer, File.expand_path("../common/output_buffer", __FILE__)
12
+ autoload :EnvFile, File.expand_path("../common/env_file", __FILE__)
13
+ autoload :ErrorNotifier, File.expand_path("../common/error_notifier", __FILE__)
14
+ end
15
+
16
+ module SCM
17
+ autoload :Git, File.expand_path("../scm/git", __FILE__)
18
+ end
19
+
20
+ end
@@ -0,0 +1,58 @@
1
+ module Vx
2
+ module SCM
3
+
4
+ class Git
5
+
6
+ class GitSSH
7
+
8
+ include Common::Helper::Shell
9
+
10
+ attr_reader :deploy_key
11
+
12
+ def initialize(deploy_key)
13
+ @deploy_key = deploy_key
14
+ end
15
+
16
+ def open
17
+ begin
18
+ yield create
19
+ ensure
20
+ destroy
21
+ end
22
+ end
23
+
24
+ def create
25
+ key_location
26
+ location
27
+ end
28
+
29
+ def destroy
30
+ key_location.unlink if key_location
31
+ location.unlink
32
+ @location = nil
33
+ @key_location = nil
34
+ end
35
+
36
+ def location
37
+ @location ||= write_tmp_file 'git', self.class.template(key_location && key_location.path), 0700
38
+ end
39
+
40
+ def key_location
41
+ if deploy_key
42
+ @key_location ||= write_tmp_file 'key', deploy_key, 0600
43
+ end
44
+ end
45
+
46
+ class << self
47
+ def template(key_location)
48
+ key = key_location ? "-i #{key_location}" : ""
49
+ out = ['#!/bin/sh']
50
+ out << "exec /usr/bin/ssh -A -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null #{key} $@"
51
+ out.join "\n"
52
+ end
53
+ end
54
+
55
+ end
56
+ end
57
+ end
58
+ end
data/lib/vx/scm/git.rb ADDED
@@ -0,0 +1,105 @@
1
+ require 'ostruct'
2
+ require File.expand_path("../git/git_ssh", __FILE__)
3
+
4
+ module Vx
5
+ module SCM
6
+
7
+ class Git
8
+
9
+ include Common::Helper::Shell
10
+
11
+ COMMIT_RE = /^(.*) -:- (.*) \((.*)\) -:- (.*)$/
12
+
13
+ attr_reader :src, :sha, :path, :logger, :git_ssh, :branch
14
+
15
+ def initialize(src, sha, path, options = {}, &block)
16
+ @src = src
17
+ @sha = sha
18
+ @path = path
19
+ @branch = options[:branch]
20
+ @git_ssh = GitSSH.new options[:deploy_key]
21
+ @logger = block
22
+ end
23
+
24
+ def open
25
+ git_ssh.open do
26
+ yield if block_given?
27
+ end
28
+ end
29
+
30
+ def fetch
31
+ open do
32
+ run_git make_fetch_command
33
+ end
34
+ end
35
+
36
+ def self.make_export_command(from, to)
37
+ %{ (cd '#{from}' && git checkout-index -a -f --prefix='#{to}/') }.strip
38
+ end
39
+
40
+ def make_fetch_command(options = {})
41
+ depth = options.key?(:depth) ? options[:depth] : 50
42
+ clone_branch = " --branch=#{branch}" if branch
43
+ clone_cmd = "git clone -q --depth=#{depth}#{clone_branch} #{src} #{path}"
44
+ checkout_cmd = "git checkout -qf #{sha}"
45
+ fetch_cmd = "git fetch -q origin"
46
+ cmd = %{
47
+ if [ -d #{path}/.git ] ; then
48
+ echo "$ #{fetch_cmd}" &&
49
+ cd #{path} &&
50
+ #{fetch_cmd} || exit $? ;
51
+ else
52
+ echo "$ #{clone_cmd}" &&
53
+ #{clone_cmd} || exit $? ;
54
+ fi ;
55
+ echo "$ #{checkout_cmd}" &&
56
+ cd #{path} &&
57
+ #{checkout_cmd}
58
+ }.gsub("\n", ' ').gsub(/\ +/, ' ').strip
59
+ cmd
60
+ end
61
+
62
+ def commit_info
63
+ rs = {}
64
+ if str = commit_info_string
65
+ if m = str.match(COMMIT_RE)
66
+ rs.merge!(
67
+ sha: m[1],
68
+ author: m[2],
69
+ email: m[3],
70
+ message: m[4]
71
+ )
72
+ end
73
+ end
74
+ OpenStruct.new rs
75
+ end
76
+
77
+ private
78
+
79
+ def commit_info_string
80
+ output = ""
81
+ code = spawn commit_info_cmd, chdir: path do |io|
82
+ output << io
83
+ end
84
+ if code == 0
85
+ output.strip
86
+ else
87
+ nil
88
+ end
89
+ end
90
+
91
+ def commit_info_cmd
92
+ %{git log -1 --pretty=format:'%H -:- %cn (%ce) -:- %s'}
93
+ end
94
+
95
+ def run_git(cmd, options = {})
96
+ env = {
97
+ 'GIT_SSH' => git_ssh.location.path
98
+ }
99
+ spawn(env, cmd, options, &logger)
100
+ end
101
+
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ TestFirstMiddleware = Struct.new(:app) do
4
+ def call(env)
5
+ env << :first
6
+ app.call env
7
+ end
8
+ end
9
+
10
+ TestLastMiddleware = Struct.new(:app) do
11
+ def call(env)
12
+ env << :last
13
+ app.call env
14
+ end
15
+ end
16
+
17
+ class MiddlewaresTest
18
+ include Vx::Common::Helper::Middlewares
19
+
20
+ middlewares do
21
+ use TestFirstMiddleware
22
+ use TestLastMiddleware
23
+ end
24
+
25
+ def run
26
+ run_middlewares([]) do |env|
27
+ env << :app
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ describe Vx::Common::Helper::Middlewares do
34
+ let(:klass) { MiddlewaresTest }
35
+ let(:object) { klass.new }
36
+ subject { object }
37
+
38
+ it "should run defined middlewares" do
39
+
40
+ expect(object.run).to eq [:first, :last, :app]
41
+ end
42
+
43
+ end
@@ -0,0 +1,137 @@
1
+ require 'spec_helper'
2
+
3
+ describe Vx::Common::Helper::Shell do
4
+
5
+ let(:klass) { Class.new.tap{|i| i.send :include, described_class } }
6
+ let(:object) { klass.new }
7
+
8
+ subject { object }
9
+
10
+ context "path" do
11
+ it "should create Pathname" do
12
+ expect_method(:path, '/tmp').to eq Pathname.new('/tmp')
13
+ end
14
+ end
15
+
16
+ context "mkdir" do
17
+
18
+ after { FileUtils.rm_rf '/tmp/.a/' }
19
+
20
+ it "should create directories" do
21
+ expect_method(:mkdir, '/tmp/.a/b/c')
22
+ expect(File.directory? '/tmp/.a/b/c')
23
+ end
24
+ end
25
+
26
+ context "rm" do
27
+ before { FileUtils.mkdir_p '/tmp/.a/b/c' }
28
+ after { FileUtils.rm_rf '/tmp/.a' }
29
+
30
+ it "should force remove" do
31
+ expect_method :rm, '/tmp/.a'
32
+ expect(File.exists? '/tmp/.a').to be_false
33
+ end
34
+ end
35
+
36
+ context "recreate" do
37
+ before { FileUtils.mkdir_p '/tmp/.a/b/c' }
38
+ after { FileUtils.rm_rf '/tmp/.a' }
39
+
40
+ it "should remove and create directory" do
41
+ expect_method :recreate, '/tmp/.a/b'
42
+ expect(File.exists? '/tmp/.a/b/c').to be_false
43
+ end
44
+ end
45
+
46
+ context "write_file" do
47
+ let(:fname) { '/tmp/.a' }
48
+ after { FileUtils.rm_f fname }
49
+
50
+ it "should write content to file" do
51
+ expect_method :write_file, fname, 'content', 0611
52
+ expect(File.readable? fname).to be_true
53
+ expect(File.read fname).to eq 'content'
54
+ end
55
+ end
56
+
57
+ context "write_tmp_file" do
58
+ let(:tmp_file) { object.send :write_tmp_file, 'fname', 'content', 0611 }
59
+
60
+ after { FileUtils.rm_f tmp_file.path }
61
+
62
+ it "should create tmp file and write content" do
63
+ expect(tmp_file).to be
64
+ expect(File.readable? tmp_file.path).to be_true
65
+ expect(File.read tmp_file.path).to eq 'content'
66
+ end
67
+ end
68
+
69
+ context "read_file" do
70
+ let(:fname) { '/tmp/.a' }
71
+ before do
72
+ File.open fname, 'w' do |io|
73
+ io << "content"
74
+ end
75
+ end
76
+ after { FileUtils.rm_f fname }
77
+
78
+ it "should read file" do
79
+ expect_method(:read_file, fname).to eq 'content'
80
+ end
81
+
82
+ context "when file does not exists" do
83
+ it "should return nil" do
84
+ expect_method(:read_file, 'not_exists').to be_nil
85
+ end
86
+ end
87
+ end
88
+
89
+ context "bash" do
90
+ let(:output) { '' }
91
+
92
+ context "when command is string" do
93
+ it "should spawn bash command and return exit code" do
94
+ expect_method(:bash, "echo $HOME", &method(:add_to_output)).to eq 0
95
+ expect(output).to eq ENV['HOME'] + "\n"
96
+ end
97
+ end
98
+
99
+ context "when command is a file" do
100
+ let(:fname) { '/tmp/.a' }
101
+
102
+ before do
103
+ File.open fname, 'w' do |io|
104
+ io << "echo $HOME"
105
+ end
106
+ end
107
+
108
+ after { FileUtils.rm_f fname }
109
+
110
+ it "should spawn bash, execute file and return exit code" do
111
+ expect_method(:bash, file: fname, &method(:add_to_output)).to eq 0
112
+ end
113
+ end
114
+
115
+ context "when :ssh options passed" do
116
+ let(:ssh) { 'ssh' }
117
+
118
+ before do
119
+ mock(ssh).spawn("/usr/bin/env -i HOME=${HOME} bash -c command", {}) { 0 }
120
+ end
121
+
122
+ it "should execute command thougth :ssh" do
123
+ expect_method(:bash, 'command', ssh: ssh, &method(:add_to_output)).to eq 0
124
+ end
125
+
126
+ end
127
+
128
+ def add_to_output(out)
129
+ output << out
130
+ end
131
+ end
132
+
133
+ def expect_method(name, *args, &block)
134
+ expect(object.send(name, *args, &block))
135
+ end
136
+
137
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+ require 'vx/common/spawn'
5
+ require 'fileutils'
6
+
7
+ describe Vx::Common::Helper::UploadShCommand do
8
+ let(:object) { Object.new }
9
+ subject { object }
10
+
11
+ before do
12
+ object.extend described_class
13
+ object.extend Vx::Common::Spawn
14
+ end
15
+
16
+ it { should be_respond_to(:upload_sh_command) }
17
+
18
+ context "#upload_sh_command" do
19
+ let(:file) { '/tmp/.test' }
20
+ let(:content) { 'Дима' }
21
+ let(:cmd) { object.upload_sh_command file, content }
22
+
23
+ before { FileUtils.rm_rf file }
24
+ after { FileUtils.rm_rf file }
25
+
26
+ it "should be successful" do
27
+ object.spawn cmd do |out|
28
+ puts " ===> #{out}"
29
+ end
30
+
31
+ expect(File).to be_readable(file)
32
+ expect(File.read file).to eq content
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+
39
+
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe Vx::Common::OutputBuffer do
4
+ let(:collected) { "" }
5
+ let(:write) { ->(str) { collected << str } }
6
+ let(:buffer) { described_class.new(0.5, &write) }
7
+
8
+ after do
9
+ buffer.close
10
+ end
11
+
12
+ it { should be }
13
+
14
+ it "should add string to buffer" do
15
+ buffer << "1"
16
+ buffer << "2"
17
+ expect(collected).to eq ''
18
+ sleep 1
19
+ expect(collected).to eq '12'
20
+
21
+ buffer << '3'
22
+ expect(collected).to eq '12'
23
+ sleep 1
24
+ expect(collected).to eq '123'
25
+ end
26
+
27
+ it "should raise error when add to closed buffer" do
28
+ buffer.close
29
+ expect {
30
+ buffer << "1"
31
+ }.to raise_error
32
+ end
33
+
34
+ it "should flush buffer" do
35
+ buffer << "1"
36
+ sleep 0.1
37
+ expect(collected).to eq ''
38
+ buffer.flush
39
+ expect(collected).to eq '1'
40
+ end
41
+
42
+ end
@@ -0,0 +1,170 @@
1
+ require 'spec_helper'
2
+ require 'vx/message/testing'
3
+
4
+ describe Vx::SCM::Git do
5
+ let(:path) { '/tmp/.test/repo' }
6
+ let(:message) { Vx::Message::PerformBuild.test_message }
7
+ let(:src) { message.src }
8
+ let(:sha) { message.sha }
9
+ let(:deploy_key) { message.deploy_key }
10
+ let(:output) { "" }
11
+ let(:options) { {} }
12
+ let(:git) {
13
+ described_class.new src, sha, path, options, &method(:add_to_output)
14
+ }
15
+
16
+ subject { git }
17
+
18
+ before do
19
+ FileUtils.rm_rf path
20
+ FileUtils.mkdir_p path
21
+ end
22
+
23
+ after { FileUtils.rm_rf path }
24
+
25
+ context "just created" do
26
+ its(:src) { should eq src }
27
+ its(:sha) { should eq sha }
28
+ its(:path) { should eq path }
29
+ its(:branch) { should be_nil }
30
+ its("git_ssh.deploy_key") { should be_nil }
31
+ end
32
+
33
+ context "assign branch" do
34
+ let(:options) { { branch: 'master' } }
35
+ its(:branch) { should eq 'master' }
36
+ end
37
+
38
+ context "assign deploy_key" do
39
+ let(:options) { { deploy_key: deploy_key } }
40
+ its("git_ssh.deploy_key") { should eq deploy_key }
41
+ end
42
+
43
+ context "fetch" do
44
+ let(:options) { { deploy_key: deploy_key } }
45
+ subject { git.fetch }
46
+
47
+ it { should eq 0 }
48
+
49
+ it "should create nessesary directories and checkout sha" do
50
+ subject
51
+ expect(File.directory? path).to be
52
+ expect(File.directory? "#{path}/.git").to be
53
+ Dir.chdir path do
54
+ expect((`git rev-parse HEAD`).strip).to eq sha
55
+ end
56
+ end
57
+
58
+ it "should capture output" do
59
+ subject
60
+ expect(output).to match(Regexp.escape "$ git clone -q --depth=50 #{src} #{path}")
61
+ end
62
+
63
+ context "twice" do
64
+ before { git.fetch }
65
+ it { should eq 0 }
66
+ end
67
+
68
+ context "with error" do
69
+ let(:src) { "/not-exists-repo.git" }
70
+
71
+ it "should return 128 exitstatus and add error to output" do
72
+ expect(subject).to eq 128
73
+ expect(output).to match('does not exist')
74
+ end
75
+ end
76
+ end
77
+
78
+ context "make_fetch_command" do
79
+ include Vx::Common::Spawn
80
+
81
+ let(:options) { { deploy_key: deploy_key, branch: "master" } }
82
+ let(:run) do
83
+ git.open do
84
+ spawn(git_ssh_env, git.make_fetch_command, &method(:add_to_output))
85
+ end
86
+ end
87
+ subject { git.make_fetch_command }
88
+
89
+ before do
90
+ run
91
+ end
92
+
93
+ it { should be }
94
+
95
+ it "should be success" do
96
+ expect(run).to eq 0
97
+ end
98
+
99
+ it "should create nessesary directories and checkout sha" do
100
+ expect(File.directory? path).to be
101
+ expect(File.directory? "#{path}/.git").to be
102
+ Dir.chdir path do
103
+ expect((`git rev-parse HEAD`).strip).to eq sha
104
+ end
105
+ end
106
+
107
+ context "twice" do
108
+ it "should be" do
109
+ code = git.open do
110
+ spawn(git_ssh_env, git.make_fetch_command, &method(:add_to_output))
111
+ end
112
+ expect(code).to eq 0
113
+ end
114
+ end
115
+
116
+ context "with error" do
117
+ let(:src) { "/not-exists-repo.git" }
118
+
119
+ it "should return 128 exitstatus and add error to output" do
120
+ expect(run).to eq 128
121
+ end
122
+ end
123
+
124
+ def git_ssh_env
125
+ { 'GIT_SSH' => git.git_ssh.location.path }
126
+ end
127
+ end
128
+
129
+ context ".make_export_command" do
130
+ let(:options) { { deploy_key: deploy_key } }
131
+ let(:from) { path }
132
+ let(:to) { '/tmp/.test/export' }
133
+ let(:expected) { "(cd '#{ from }' && git checkout-index -a -f --prefix='#{ to}/')" }
134
+ subject { described_class.make_export_command from, to}
135
+ it { should eq expected }
136
+
137
+ context "run" do
138
+ before do
139
+ git.fetch
140
+ system subject
141
+ end
142
+
143
+ it "should be success" do
144
+ expect($?.to_i).to eq 0
145
+ end
146
+
147
+ it "should export repo" do
148
+ expect(File.readable? "#{to}/Gemfile").to be_true
149
+ end
150
+ end
151
+ end
152
+
153
+ context "commit_info" do
154
+ let(:options) { { deploy_key: deploy_key } }
155
+ subject { git.commit_info }
156
+ before { git.fetch }
157
+
158
+ it "should be" do
159
+ expect(subject.sha).to eq 'b665f90239563c030f1b280a434b3d84daeda1bd'
160
+ expect(subject.author).to eq "Dmitry Galinsky"
161
+ expect(subject.email).to eq 'dima.exe@gmail.com'
162
+ expect(subject.message).to eq 'first release'
163
+ end
164
+ end
165
+
166
+ def add_to_output(out)
167
+ puts "==> #{out}"
168
+ output << out
169
+ end
170
+ end