deplomat 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 67aed2b09a78c6ff894531f065ba7db0bdc60e20
4
+ data.tar.gz: 848f9cba73c651699f2496b5b871448fbc15fcb6
5
+ SHA512:
6
+ metadata.gz: 7723cc145303719df54eb436a43a012b02e5180ab8fc56d08d121fe3ec1ed6a4b1fffa8befa7dd4d4cb22db0178e9d214e480b4884a062ebe4b5a42357f1eb2e
7
+ data.tar.gz: 6298c99481b28878c15763b0aeed01e99697c3621a677f35027cedb50410f9faaae0ab4f526e753be48d6741acbedd56f214d56f8c3422eef3c3252be32684d3
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "sys-proctable"
4
+ gem "colorize"
5
+
6
+ group :development do
7
+ gem "bundler", "~> 1.0"
8
+ gem "jeweler"
9
+ gem "rspec"
10
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,78 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ addressable (2.4.0)
5
+ builder (3.2.2)
6
+ colorize (0.7.7)
7
+ descendants_tracker (0.0.4)
8
+ thread_safe (~> 0.3, >= 0.3.1)
9
+ diff-lcs (1.2.5)
10
+ faraday (0.9.2)
11
+ multipart-post (>= 1.2, < 3)
12
+ git (1.3.0)
13
+ github_api (0.13.1)
14
+ addressable (~> 2.4.0)
15
+ descendants_tracker (~> 0.0.4)
16
+ faraday (~> 0.8, < 0.10)
17
+ hashie (>= 3.4)
18
+ multi_json (>= 1.7.5, < 2.0)
19
+ oauth2
20
+ hashie (3.4.4)
21
+ highline (1.7.8)
22
+ jeweler (2.1.1)
23
+ builder
24
+ bundler (>= 1.0)
25
+ git (>= 1.2.5)
26
+ github_api
27
+ highline (>= 1.6.15)
28
+ nokogiri (>= 1.5.10)
29
+ rake
30
+ rdoc
31
+ semver
32
+ json (1.8.3)
33
+ jwt (1.5.1)
34
+ mini_portile2 (2.0.0)
35
+ multi_json (1.11.3)
36
+ multi_xml (0.5.5)
37
+ multipart-post (2.0.0)
38
+ nokogiri (1.6.7.2)
39
+ mini_portile2 (~> 2.0.0.rc2)
40
+ oauth2 (1.1.0)
41
+ faraday (>= 0.8, < 0.10)
42
+ jwt (~> 1.0, < 1.5.2)
43
+ multi_json (~> 1.3)
44
+ multi_xml (~> 0.5)
45
+ rack (>= 1.2, < 3)
46
+ rack (1.6.4)
47
+ rake (11.1.2)
48
+ rdoc (4.2.2)
49
+ json (~> 1.4)
50
+ rspec (3.4.0)
51
+ rspec-core (~> 3.4.0)
52
+ rspec-expectations (~> 3.4.0)
53
+ rspec-mocks (~> 3.4.0)
54
+ rspec-core (3.4.4)
55
+ rspec-support (~> 3.4.0)
56
+ rspec-expectations (3.4.0)
57
+ diff-lcs (>= 1.2.0, < 2.0)
58
+ rspec-support (~> 3.4.0)
59
+ rspec-mocks (3.4.1)
60
+ diff-lcs (>= 1.2.0, < 2.0)
61
+ rspec-support (~> 3.4.0)
62
+ rspec-support (3.4.1)
63
+ semver (1.0.1)
64
+ sys-proctable (1.0.0)
65
+ thread_safe (0.3.5)
66
+
67
+ PLATFORMS
68
+ ruby
69
+
70
+ DEPENDENCIES
71
+ bundler (~> 1.0)
72
+ colorize
73
+ jeweler
74
+ rspec
75
+ sys-proctable
76
+
77
+ BUNDLED WITH
78
+ 1.12.0
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2016 Roman Snitko
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ = deplomat
2
+
3
+ Stack agnostic deployment system that uses bash and ssh commands.
data/Rakefile ADDED
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
17
+ gem.name = "deplomat"
18
+ gem.homepage = "http://github.com/snitko/deplomat"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Stack agnostic deployment system that uses bash and ssh commands}
21
+ gem.description = %Q{Stack agnostic deployment system that uses bash and ssh commands}
22
+ gem.email = "roman.snitko@gmail.com"
23
+ gem.authors = ["Roman Snitko"]
24
+ # dependencies defined in Gemfile
25
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
data/deplomat.gemspec ADDED
@@ -0,0 +1,77 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+ # stub: deplomat 0.1.1 ruby lib
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "deplomat"
9
+ s.version = "0.1.1"
10
+
11
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
+ s.require_paths = ["lib"]
13
+ s.authors = ["Roman Snitko"]
14
+ s.date = "2016-07-05"
15
+ s.description = "Stack agnostic deployment system that uses bash and ssh commands"
16
+ s.email = "roman.snitko@gmail.com"
17
+ s.extra_rdoc_files = [
18
+ "LICENSE.txt",
19
+ "README.rdoc"
20
+ ]
21
+ s.files = [
22
+ ".document",
23
+ ".rspec",
24
+ "Gemfile",
25
+ "Gemfile.lock",
26
+ "LICENSE.txt",
27
+ "README.rdoc",
28
+ "Rakefile",
29
+ "VERSION",
30
+ "examples/simple_deploy.rb",
31
+ "lib/deplomat.rb",
32
+ "lib/deplomat/directives.rb",
33
+ "lib/deplomat/exceptions.rb",
34
+ "lib/deplomat/local_node.rb",
35
+ "lib/deplomat/node.rb",
36
+ "lib/deplomat/remote_node.rb",
37
+ "spec/directives_spec.rb",
38
+ "spec/fixtures/cleaning/.keep",
39
+ "spec/fixtures/dir1/file1",
40
+ "spec/fixtures/dir1/file2",
41
+ "spec/fixtures/upload1/uploaded_file1",
42
+ "spec/fixtures/upload1/uploaded_file2",
43
+ "spec/local_node_spec.rb",
44
+ "spec/node_spec.rb",
45
+ "spec/remote_node_spec.rb",
46
+ "spec/spec_helper.rb"
47
+ ]
48
+ s.homepage = "http://github.com/snitko/deplomat"
49
+ s.licenses = ["MIT"]
50
+ s.rubygems_version = "2.5.1"
51
+ s.summary = "Stack agnostic deployment system that uses bash and ssh commands"
52
+
53
+ if s.respond_to? :specification_version then
54
+ s.specification_version = 4
55
+
56
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
57
+ s.add_runtime_dependency(%q<sys-proctable>, [">= 0"])
58
+ s.add_runtime_dependency(%q<colorize>, [">= 0"])
59
+ s.add_development_dependency(%q<bundler>, ["~> 1.0"])
60
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
61
+ s.add_development_dependency(%q<rspec>, [">= 0"])
62
+ else
63
+ s.add_dependency(%q<sys-proctable>, [">= 0"])
64
+ s.add_dependency(%q<colorize>, [">= 0"])
65
+ s.add_dependency(%q<bundler>, ["~> 1.0"])
66
+ s.add_dependency(%q<jeweler>, [">= 0"])
67
+ s.add_dependency(%q<rspec>, [">= 0"])
68
+ end
69
+ else
70
+ s.add_dependency(%q<sys-proctable>, [">= 0"])
71
+ s.add_dependency(%q<colorize>, [">= 0"])
72
+ s.add_dependency(%q<bundler>, ["~> 1.0"])
73
+ s.add_dependency(%q<jeweler>, [">= 0"])
74
+ s.add_dependency(%q<rspec>, [">= 0"])
75
+ end
76
+ end
77
+
@@ -0,0 +1,22 @@
1
+ # This is an example of a deployment script. This file is not executable and uses a simple DSL
2
+ # which is implemented using Ruby. Thus, it may contain valid ruby code, but it's not necessary
3
+ # to know Ruby to write deployment scripts - all that's required is to know a few simple common
4
+ # deplomat directives.
5
+
6
+ # executes right away, but only in particular environment
7
+ execute_in env: "staging" do
8
+ $db = new_node host: "0.0.0.0", port: 1234, user: "deploy"
9
+ $app = new_node host: "0.0.0.0", port: 1234, user: "deploy"
10
+ end
11
+
12
+ # doesn't execute right away and awaits until called with `execute :compile_assets`
13
+ partial 'compile_assets' do
14
+ $db.execute "echo compiling assets..."
15
+ $app.execute "echo compiling assets..."
16
+ end
17
+
18
+ partial 'run_migrations' do |args|
19
+ args[:server].execute "rake db:migrate"
20
+ end
21
+
22
+ execute_partial 'run_migrations', node: $app
@@ -0,0 +1,25 @@
1
+ $partials = {}
2
+
3
+ def execute_in(env:)
4
+ yield if $env == env
5
+ end
6
+
7
+ def execute_partial(name, args={})
8
+ print_to_terminal $partials[name][:before_message]
9
+ $partials[name][:block].call(args)
10
+ print_to_terminal $partials[name][:after_message]
11
+ end
12
+
13
+ def partial(name, message: [], &block)
14
+ message = [message] if message.kind_of?(String)
15
+ before_message = message[0]
16
+ after_message = message[1]
17
+ $partials[name] = { block: block, before_message: before_message, after_message: after_message }
18
+ end
19
+
20
+ def print_to_terminal(message, color: nil, newline: true)
21
+ return unless message
22
+ message += "\n" if newline
23
+ message = message.send(color) if color
24
+ $stdout.print message
25
+ end
@@ -0,0 +1,4 @@
1
+ module Deplomat
2
+ class ExecutionError < Exception;end
3
+ class NoSuchPathError < Exception;end
4
+ end
@@ -0,0 +1,13 @@
1
+ module Deplomat
2
+
3
+ class LocalNode < Node
4
+
5
+ private
6
+
7
+ def path_exist?(path)
8
+ File.exist?(path)
9
+ end
10
+
11
+ end
12
+
13
+ end
@@ -0,0 +1,149 @@
1
+ module Deplomat
2
+
3
+ class Node
4
+
5
+ attr_accessor :log_to, :raise_exceptions, :logfile
6
+ attr_reader :current_path
7
+
8
+ def initialize(logfile: "#{Dir.pwd}/deplomat.log", log_to: [:stdout])
9
+ @log_to = log_to
10
+ @logfile = logfile
11
+ end
12
+
13
+ def execute(command, path=@current_path, message: [], stdout_source: :stdout, log_command: true)
14
+
15
+ message = [message] if message.kind_of?(String)
16
+ log(message[0] + "\n", color: 'white') unless message.empty? || message.nil?
17
+
18
+ # Respect current_path
19
+ command_to_log = command
20
+ if path
21
+ command_to_log = "#{command}\n(in #{path})"
22
+ command = "cd #{path} && #{command}"
23
+ end
24
+
25
+ out = ""
26
+ status = nil
27
+ Open3.popen3(command) do |stdin, stdout, stderr, thread|
28
+
29
+ # Sometimes, programs write in stderr, although there are no errors.
30
+ # rake assets:precompile does that, for example.
31
+ stdout_source_object = (stdout_source == :stderr ? stderr : stdout)
32
+
33
+ log("--> " + command_to_log + "\n", color: "white") if log_command
34
+ while line=stdout_source_object.gets do
35
+ out << line
36
+ log(" #{line}")
37
+ end
38
+
39
+ error_out = ""
40
+ status = thread.value.exitstatus.to_i
41
+ if status > 0
42
+ while o = stderr.gets
43
+ error_out += o
44
+ end
45
+ log(error_out + "\n", color: 'red')
46
+ if @raise_exceptions
47
+ self.close if self.respond_to?(:close)
48
+ raise Deplomat::ExecutionError
49
+ end
50
+ end
51
+ yield if block_given?
52
+ end
53
+
54
+ log(message[1] + "\n", color: 'white') unless message.empty? || message.nil?
55
+
56
+ return { status: status, out: out }
57
+ end
58
+
59
+ def copy(what, where)
60
+ execute("rsync -ar #{what} #{where}")
61
+ end
62
+ alias :cp :copy
63
+
64
+ def move(what, where)
65
+ execute("mv #{what} #{where}")
66
+ end
67
+ alias :mv :move
68
+
69
+ def remove(what)
70
+ execute("rm -rf #{what}")
71
+ end
72
+ alias :rm :remove
73
+
74
+ def create_file(filename)
75
+ execute("touch #{filename}")
76
+ end
77
+ alias :touch :create_file
78
+
79
+ def create_dir(dirname)
80
+ execute("mkdir #{dirname}")
81
+ end
82
+ alias :mkdir :create_dir
83
+
84
+ def create_symlink(source, target)
85
+ execute("ln -s #{source} #{target}")
86
+ end
87
+ alias :ln :create_symlink
88
+
89
+ def cd(path)
90
+ @current_path = if path =~ /\A[\/~]/ || path.nil?
91
+ path
92
+ else
93
+ File.expand_path("#{@current_path}#{path}")
94
+ end
95
+ raise Deplomat::NoSuchPathError, @current_path if !@current_path.nil? && !path_exist?(@current_path)
96
+
97
+ # Making sure we don't end up with double // at the end of the path
98
+ @current_path = @current_path.chomp("/") + "/"
99
+ end
100
+
101
+ def git_push(remote="origin", branch="master")
102
+ execute("git push -u #{remote} #{branch}")
103
+ end
104
+
105
+ def git_pull(remote="origin", branch="master")
106
+ execute("git pull #{remote} #{branch}")
107
+ end
108
+
109
+ def git_merge(source="origin", target="master")
110
+ execute("git merge #{source} #{target}")
111
+ end
112
+
113
+ def git_checkout(target)
114
+ execute("git checkout #{target}")
115
+ end
116
+
117
+ def clean(path: @current_path, except: [], leave: [0, :last])
118
+ # Gets us all entries sorted by date, most recent ones first
119
+ entries_by_date = execute("ls -t", path)[:out].split("\n")
120
+ # Don't do anything with entries listed in :except
121
+ entries_by_date = entries_by_date - except
122
+ if leave
123
+ entries_by_date.reverse! if leave[1] == :first
124
+ entries_by_date = entries_by_date[leave[0]..entries_by_date.length]
125
+ end
126
+ entries_by_date.each { |entry| remove("#{path}#{entry}") }
127
+ end
128
+
129
+ private
130
+
131
+ def log(line, color: 'light_black')
132
+ @message_color = color
133
+ # Only calls log methods mentioned in the @log_to property
134
+ @log_to.each { |logger| self.send("log_to_#{logger}", line) }
135
+ end
136
+
137
+ def log_to_file(line)
138
+ if @logfile
139
+ open(@logfile, 'a') { |f| f.puts line }
140
+ end
141
+ end
142
+
143
+ def log_to_stdout(line)
144
+ print_to_terminal(line, color: @message_color, newline: false)
145
+ end
146
+
147
+ end
148
+
149
+ end
@@ -0,0 +1,63 @@
1
+ module Deplomat
2
+ class RemoteNode < Node
3
+
4
+ def initialize(host:, port: 22, user: "deploy")
5
+ super()
6
+
7
+ # Create ControlMasters dir, user might not have it
8
+ unless File.exists?("#{ENV['HOME']}/.ssh/controlmasters/")
9
+ Dir.mkdir("#{ENV['HOME']}/.ssh/controlmasters/")
10
+ end
11
+
12
+ # Establish connection
13
+ first_ssh_command = "ssh -MNfS #{ENV['HOME']}/.ssh/controlmasters/%r@%h:%p #{user}@#{host} -p #{port} -o ControlPersist=10m"
14
+ system first_ssh_command
15
+
16
+ # get background process id by the full command name
17
+ Sys::ProcTable.ps.each do |process|
18
+ if process.cmdline.match(first_ssh_command)
19
+ @pid = process.pid.to_i
20
+ puts "Connected with ssh, host #{host}, pid #{@pid}."
21
+ break
22
+ end
23
+ end
24
+
25
+ @ssh_command = "ssh -S #{ENV['HOME']}/.ssh/controlmasters/%r@%h:%p #{user}@#{host} -p #{port}"
26
+
27
+ @host = host
28
+ @user = user
29
+ @port = port
30
+ end
31
+
32
+ alias :local_execute :execute
33
+ def execute(command, path=@current_path, message: [], env_vars: '', login_shell: false, stdout_source: :stdout)
34
+
35
+ log("(#{@host}) --> " + command + "\n", color: "white")
36
+ command = "#{env_vars} cd #{path} && #{command}" if path
37
+ command = "bash -l -c \"#{command}\"" if login_shell
38
+ super("#{@ssh_command} '#{command}'", nil, message: message, stdout_source: stdout_source, log_command: false)
39
+ end
40
+
41
+ def upload(what, where)
42
+ local_execute "rsync -arz #{what} #{@user}@#{@host}:#{where} --port=#{@port}"
43
+ end
44
+
45
+ def close
46
+ begin
47
+ puts "Closing connection to #{@host}."
48
+ Process.kill 'KILL', @pid
49
+ rescue Errno::ESRCH
50
+ puts "WARNING: no process with #{@pid} found, no connection to close!"
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def path_exist?(path)
57
+ old_raise_exceptions = @raise_exceptions
58
+ @raise_exceptions = false
59
+ !(execute("ls #{path}", nil){ @raise_exceptions = old_raise_exceptions}[:status] > 0)
60
+ end
61
+
62
+ end
63
+ end
data/lib/deplomat.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'sys/proctable'
2
+ require 'open3'
3
+ require 'colorize'
4
+ require_relative 'deplomat/exceptions'
5
+ require_relative 'deplomat/node'
6
+ require_relative 'deplomat/local_node'
7
+ require_relative 'deplomat/remote_node'
8
+ require_relative 'deplomat/directives'
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe "directives" do
4
+
5
+ it "executes arbitrary ruby code in a particular environment" do
6
+ $env = 'staging'
7
+ expect(self).to receive(:hello).exactly(1).times
8
+ execute_in(env: 'staging') { hello() }
9
+ execute_in(env: 'production') { hello() }
10
+ end
11
+
12
+ it "creates a partial" do
13
+ partial("hello_partial") { hello() }
14
+ expect($partials["hello_partial"][:block]).to be_kind_of(Proc)
15
+ end
16
+
17
+ it "executes a partial" do
18
+ expect(self).to receive(:hello).with("hello").exactly(1).times
19
+ partial("hello_partial") { |args| hello(args[:x]) }
20
+ execute_partial "hello_partial", x: 'hello'
21
+ end
22
+
23
+ it "passes before and after messages"
24
+
25
+ end
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,180 @@
1
+ require 'spec_helper'
2
+
3
+ describe Deplomat::LocalNode do
4
+
5
+ before(:each) do
6
+ @fixtures_dir = Dir.pwd + "/spec/fixtures"
7
+ @localhost = Deplomat::LocalNode.new
8
+ @localhost.log_to = []
9
+ @localhost.raise_exceptions = false
10
+ end
11
+
12
+ after(:each) do
13
+ FileUtils.rm_r(Dir.glob("#{@fixtures_dir}/dir2/*"))
14
+ end
15
+
16
+ describe "filesystem operations" do
17
+
18
+ it "copies files to another folder" do
19
+ @localhost.copy("#{@fixtures_dir}/dir1/*", "#{@fixtures_dir}/dir2/")
20
+ expect(File.exist?("#{@fixtures_dir}/dir2/file1")).to be_truthy
21
+ expect(File.exist?("#{@fixtures_dir}/dir2/file2")).to be_truthy
22
+ end
23
+
24
+ it "moves files and dirs to another directory" do
25
+ @localhost.move("#{@fixtures_dir}/dir1/*", "#{@fixtures_dir}/dir2/")
26
+ expect(File.exist?("#{@fixtures_dir}/dir2/file1")).to be_truthy
27
+ expect(File.exist?("#{@fixtures_dir}/dir2/file2")).to be_truthy
28
+ expect(File.exist?("#{@fixtures_dir}/dir2/subdir1")).to be_truthy
29
+ expect(File.exist?("#{@fixtures_dir}/dir1/file1")).to be_falsy
30
+ expect(File.exist?("#{@fixtures_dir}/dir1/file2")).to be_falsy
31
+ expect(File.exist?("#{@fixtures_dir}/dir1/subdir1")).to be_falsy
32
+
33
+ @localhost.move("#{@fixtures_dir}/dir2/*", "#{@fixtures_dir}/dir1/")
34
+ expect(File.exist?("#{@fixtures_dir}/dir1/file1")).to be_truthy
35
+ expect(File.exist?("#{@fixtures_dir}/dir1/file2")).to be_truthy
36
+ expect(File.exist?("#{@fixtures_dir}/dir1/subdir1")).to be_truthy
37
+ expect(File.exist?("#{@fixtures_dir}/dir2/file1")).to be_falsy
38
+ expect(File.exist?("#{@fixtures_dir}/dir2/file2")).to be_falsy
39
+ expect(File.exist?("#{@fixtures_dir}/dir2/subdir1")).to be_falsy
40
+ end
41
+
42
+ it "removes files and dirs" do
43
+ @localhost.copy("#{@fixtures_dir}/dir1/*", "#{@fixtures_dir}/dir2/")
44
+ @localhost.remove("#{@fixtures_dir}/dir2/*")
45
+ expect(File.exist?("#{@fixtures_dir}/dir2/file1")).to be_falsy
46
+ expect(File.exist?("#{@fixtures_dir}/dir2/file2")).to be_falsy
47
+ expect(File.exist?("#{@fixtures_dir}/dir2/subdir1")).to be_falsy
48
+ end
49
+
50
+ it "changes current directory" do
51
+ @localhost.cd("/etc")
52
+ expect(@localhost.current_path).to eq("/etc/")
53
+ expect(-> { @localhost.cd("/non-existent-path") }).to raise_exception(Deplomat::NoSuchPathError)
54
+ end
55
+
56
+ it "executes commands from the current directory" do
57
+ @localhost.cd("#{@fixtures_dir}/dir1")
58
+ expect(@localhost.execute("ls")[:out]).to eq("file1\nfile2\nsubdir1\n")
59
+ end
60
+
61
+ it "creates an empty file" do
62
+ @localhost.cd("#{@fixtures_dir}/dir2")
63
+ @localhost.touch("myfile")
64
+ expect(@localhost.execute("ls")[:out]).to have_files("myfile")
65
+ end
66
+
67
+ it "creates a directory" do
68
+ @localhost.cd("#{@fixtures_dir}/dir2")
69
+ @localhost.mkdir("mydir")
70
+ expect(@localhost.execute("ls")[:out]).to have_files("mydir")
71
+ end
72
+
73
+ it "creates a symlink" do
74
+ @localhost.cd("#{@fixtures_dir}/dir2")
75
+ @localhost.create_symlink("../dir1", "./")
76
+ expect(@localhost.execute("ls dir1")[:out]).to have_files("file1", "file2", "subdir1")
77
+ end
78
+
79
+ end
80
+
81
+ describe "handling status and errors" do
82
+
83
+ it "returns the status" do
84
+ expect(@localhost.execute("ls #{@fixtures_dir}/dir1/")[:status]).to eq(0)
85
+ expect(@localhost.execute("ls #{@fixtures_dir}/non-existent-dir/")[:status]).to eq(2)
86
+ end
87
+
88
+ it "raises execution error when command fails" do
89
+ @localhost.raise_exceptions = true
90
+ expect( -> { @localhost.execute("ls #{@fixtures_dir}/non-existent-dir/")[:status] }).to raise_exception(Deplomat::ExecutionError)
91
+ end
92
+
93
+ end
94
+
95
+ describe "cleaning" do
96
+
97
+ before(:each) do
98
+ @localhost.cd("#{@fixtures_dir}/cleaning")
99
+ @localhost.execute("touch file1 file2 file3")
100
+ @localhost.execute("mkdir current")
101
+ sleep(0.01)
102
+ @localhost.execute("mkdir dir1 dir2 dir3")
103
+ end
104
+
105
+ after(:each) do
106
+ @localhost.execute("rm -rf #{@fixtures_dir}/cleaning/*")
107
+ end
108
+
109
+ it "cleans all the files and dirs in a given directory" do
110
+ @localhost.clean
111
+ expect(@localhost.execute('ls')[:out]).to be_empty
112
+ end
113
+
114
+ it "cleans all but last 3 entries in a given directory" do
115
+ @localhost.clean(leave: [3, :last])
116
+ expect(@localhost.execute('ls')[:out]).to eq("dir1\ndir2\ndir3\n")
117
+ end
118
+
119
+ it "cleans all, but :except entries in a given directory" do
120
+ @localhost.clean(except: ["current"])
121
+ expect(@localhost.execute('ls')[:out]).to eq("current\n")
122
+ end
123
+
124
+ end
125
+
126
+ describe "git commands" do
127
+
128
+ before(:all) do
129
+ @fixtures_dir = Dir.pwd + "/spec/fixtures"
130
+ @localhost_git = Deplomat::LocalNode.new
131
+ @localhost_git.log_to = [:stdout]
132
+ @localhost_git.cd(@fixtures_dir)
133
+ @localhost_git.mkdir("git_repo")
134
+ @localhost_git.cd("git_repo")
135
+ @localhost_git.touch("file1")
136
+ @localhost_git.execute("git init && git add .")
137
+ @localhost_git.execute("git remote add origin deploy@deplomat-test-node:/home/deploy/deplomat/.git_repo")
138
+ @remote_node = Deplomat::RemoteNode.new(host: "deplomat-test-node", user: "deploy")
139
+ @remote_node.log_to = [:stdout]
140
+ @remote_node.execute("cd ~/deplomat && mkdir .git_repo && cd .git_repo && git --bare init")
141
+ @localhost_git.cd("#{@fixtures_dir}/git_repo")
142
+ @localhost_git.execute("git commit -m \"Initial commit\"")
143
+
144
+ @localhost_git.raise_exceptions = true
145
+ @remote_node.raise_exceptions = true
146
+ end
147
+
148
+ after(:all) do
149
+ @remote_node.remove("~/deplomat/.git_repo")
150
+ @localhost_git.remove("#{@fixtures_dir}/git_repo")
151
+ end
152
+
153
+ it "pushes to remote" do
154
+ expect(@localhost_git.git_push[:status]).to eq(0)
155
+ end
156
+
157
+ it "pulls from remote" do
158
+ expect(@localhost_git.git_pull[:status]).to eq(0)
159
+ end
160
+
161
+ it "merges branches" do
162
+ @localhost_git.execute("git checkout -b dev1")
163
+ @localhost_git.touch("file2")
164
+ @localhost_git.execute("git add . && git commit -m \"adding file2\"")
165
+ @localhost_git.execute("git checkout master")
166
+ expect(@localhost_git.git_merge("dev1", "master")[:status]).to eq(0)
167
+ expect(File.exist?("#{@fixtures_dir}/git_repo/file2")).to be_truthy
168
+ end
169
+
170
+ it "checks out into a branch" do
171
+ @localhost_git.execute("git branch dev2")
172
+ @localhost_git.touch("file3")
173
+ @localhost_git.execute("git add . && git commit -m \"adding file3\"")
174
+ @localhost_git.git_checkout("dev2")
175
+ expect(File.exist?("#{@fixtures_dir}/git_repo/file3")).to be_falsy
176
+ end
177
+
178
+ end
179
+
180
+ end
data/spec/node_spec.rb ADDED
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe Deplomat::Node do
4
+
5
+ describe "logging" do
6
+
7
+ before(:each) do
8
+ @fixtures_dir = Dir.pwd + "/spec/fixtures"
9
+ @node = Deplomat::Node.new(logfile: "#{@fixtures_dir}/deplomat.log", log_to: [:file, :stdout])
10
+ end
11
+
12
+ after(:each) do
13
+ @node.log_to = []
14
+ @node.remove("#{@fixtures_dir}/deplomat.log")
15
+ end
16
+
17
+ it "logs to the stdout" do
18
+ expect($stdout).to receive(:print).exactly(4).times
19
+ @node.execute("ls #{@fixtures_dir}/dir1")
20
+ end
21
+
22
+ it "logs to a log file" do
23
+ @node.log_to = [:file]
24
+ @node.execute("ls #{@fixtures_dir}/dir1")
25
+ log = File.readlines("#{@fixtures_dir}/deplomat.log")
26
+ expect(log.size).to eq(4)
27
+ end
28
+
29
+ it "puts additional messages into the terminal when required" do
30
+ expect($stdout).to receive(:print).once.with("hello\n".white)
31
+ expect($stdout).to receive(:print).once.with("--> ls /home/roman/Dropbox/Work/my_libs/deplomat/spec/fixtures/dir1\n".white)
32
+ expect($stdout).to receive(:print).once.with(" file1\n".light_black)
33
+ expect($stdout).to receive(:print).once.with(" file2\n".light_black)
34
+ expect($stdout).to receive(:print).once.with(" subdir1\n".light_black)
35
+ expect($stdout).to receive(:print).once.with("bye\n".white)
36
+ @node.execute("ls #{@fixtures_dir}/dir1", message: ["hello", "bye"])
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,132 @@
1
+ require 'spec_helper'
2
+
3
+ describe Deplomat::RemoteNode do
4
+
5
+ before(:all) do
6
+ @fixtures_dir = "/home/deploy/deplomat"
7
+ @local_fixtures_dir = Dir.pwd + "/spec/fixtures"
8
+ @node = Deplomat::RemoteNode.new(host: "deplomat-test-node", user: "deploy")
9
+ @node.log_to = []
10
+ end
11
+
12
+ before(:each) do
13
+ @node.raise_exceptions = true
14
+ end
15
+
16
+ after(:all) do
17
+ @node.close
18
+ end
19
+
20
+ after(:each) do
21
+ @node.raise_exceptions = false
22
+ @node.execute("rm -rf #{@fixtures_dir}/dir2/*")
23
+ @node.execute("rm -rf #{@fixtures_dir}/uploaded1/*")
24
+ @node.execute("rm -rf #{@fixtures_dir}/uploaded2/*")
25
+ end
26
+
27
+ describe "filesystem operations" do
28
+
29
+ it "copies files to another folder" do
30
+ @node.copy("#{@fixtures_dir}/dir1/*", "#{@fixtures_dir}/dir2/")
31
+ expect(@node.execute("ls #{@fixtures_dir}/dir2/")[:out]).to have_files("file1", "file2", "subdir1")
32
+ end
33
+
34
+ it "moves files and dirs from one directory into another" do
35
+ @node.move("#{@fixtures_dir}/dir1/*", "#{@fixtures_dir}/dir2/")
36
+ expect(@node.execute("ls #{@fixtures_dir}/dir1/")[:out]).to eq("")
37
+ expect(@node.execute("ls #{@fixtures_dir}/dir2/")[:out]).to have_files("file1", "file2", "subdir1")
38
+ @node.move("#{@fixtures_dir}/dir2/*", "#{@fixtures_dir}/dir1/")
39
+ expect(@node.execute("ls #{@fixtures_dir}/dir2/")[:out]).to eq("")
40
+ expect(@node.execute("ls #{@fixtures_dir}/dir1/")[:out]).to have_files("file1", "file2", "subdir1")
41
+ end
42
+
43
+ it "removes files and dirs" do
44
+ @node.copy("#{@fixtures_dir}/dir1/*", "#{@fixtures_dir}/dir2/")
45
+ expect(@node.execute("ls #{@fixtures_dir}/dir2/")[:out]).to have_files("file1", "file2", "subdir1")
46
+ @node.remove("#{@fixtures_dir}/dir2/*")
47
+ expect(@node.execute("ls #{@fixtures_dir}/dir2/")[:out]).to eq("")
48
+ end
49
+
50
+ it "uploads files from localhost to the node" do
51
+ @node.upload("#{@local_fixtures_dir}/upload1/*", "#{@fixtures_dir}/uploaded1/")
52
+ expect(@node.execute("ls #{@fixtures_dir}/uploaded1/")[:out]).to have_files("uploaded_file1", "uploaded_file2")
53
+ end
54
+
55
+ it "changes current directory" do
56
+ #@node.cd("/etc")
57
+ #expect(@node.current_path).to eq("/etc/")
58
+ expect(-> { @node.cd("/non-existent-path") }).to raise_exception(Deplomat::NoSuchPathError)
59
+ end
60
+
61
+ it "executes commands from the current directory" do
62
+ @node.cd("#{@fixtures_dir}/dir1")
63
+ expect(@node.execute("ls")[:out]).to have_files("file1", "file2", "subdir1")
64
+ end
65
+
66
+ it "creates an empty file" do
67
+ @node.cd("#{@fixtures_dir}/dir2")
68
+ @node.touch("myfile")
69
+ expect(@node.execute("ls")[:out]).to have_files("myfile")
70
+ end
71
+
72
+ it "creates a directory" do
73
+ @node.cd("#{@fixtures_dir}/dir2")
74
+ @node.mkdir("mydir")
75
+ expect(@node.execute("ls")[:out]).to have_files("mydir")
76
+ end
77
+
78
+ it "creates a symlink" do
79
+ @node.cd("#{@fixtures_dir}/dir2")
80
+ @node.create_symlink("../dir1", "./")
81
+ expect(@node.execute("ls dir1")[:out]).to have_files("file1", "file2", "subdir1")
82
+ end
83
+
84
+ end
85
+
86
+ describe "handling status and errors" do
87
+
88
+ it "returns the status" do
89
+ @node.raise_exceptions = false
90
+ expect(@node.execute("ls #{@fixtures_dir}/dir1/")[:status]).to eq(0)
91
+ expect(@node.execute("ls #{@fixtures_dir}/non-existent-dir/")[:status]).to eq(2)
92
+ end
93
+
94
+ it "raises execution error when command fails" do
95
+ @node.raise_exceptions = true
96
+ expect( -> { @node.execute("ls #{@fixtures_dir}/non-existent-dir/")[:status] }).to raise_exception(Deplomat::ExecutionError)
97
+ end
98
+
99
+ end
100
+
101
+ describe "cleaning" do
102
+
103
+ before(:each) do
104
+ @node.cd("#{@fixtures_dir}/cleaning")
105
+ @node.execute("touch file1 file2 file3")
106
+ @node.execute("mkdir current")
107
+ sleep(0.01)
108
+ @node.execute("mkdir dir1 dir2 dir3")
109
+ end
110
+
111
+ after(:each) do
112
+ @node.execute("rm -rf #{@fixtures_dir}/cleaning/*")
113
+ end
114
+
115
+ it "cleans all the files and dirs in a given directory" do
116
+ @node.clean
117
+ expect(@node.execute('ls')[:out]).to be_empty
118
+ end
119
+
120
+ it "cleans all but last 3 entries in a given directory" do
121
+ @node.clean(leave: [3, :last])
122
+ expect(@node.execute('ls')[:out]).to eq("dir1\ndir2\ndir3\n")
123
+ end
124
+
125
+ it "cleans all, but :except entries in a given directory" do
126
+ @node.clean(except: ["current"])
127
+ expect(@node.execute('ls')[:out]).to eq("current\n")
128
+ end
129
+
130
+ end
131
+
132
+ end
@@ -0,0 +1,17 @@
1
+ require 'rspec/expectations'
2
+ require "fileutils"
3
+ require_relative "../lib/deplomat"
4
+
5
+ RSpec::Matchers.define :have_files do |*expected|
6
+ match do |actual|
7
+ actual = actual.split("\n")
8
+ actual.pop if actual.last == ""
9
+ expected.each do |e|
10
+ return false unless actual.include?(e)
11
+ end
12
+ true
13
+ end
14
+ failure_message do |actual|
15
+ "expected these files in the directory:\n\t#{actual.split("\n").inspect}\nwould include all of these:\n\t#{expected.inspect}"
16
+ end
17
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: deplomat
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Roman Snitko
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-07-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sys-proctable
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: colorize
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: jeweler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Stack agnostic deployment system that uses bash and ssh commands
84
+ email: roman.snitko@gmail.com
85
+ executables: []
86
+ extensions: []
87
+ extra_rdoc_files:
88
+ - LICENSE.txt
89
+ - README.rdoc
90
+ files:
91
+ - ".document"
92
+ - ".rspec"
93
+ - Gemfile
94
+ - Gemfile.lock
95
+ - LICENSE.txt
96
+ - README.rdoc
97
+ - Rakefile
98
+ - VERSION
99
+ - deplomat.gemspec
100
+ - examples/simple_deploy.rb
101
+ - lib/deplomat.rb
102
+ - lib/deplomat/directives.rb
103
+ - lib/deplomat/exceptions.rb
104
+ - lib/deplomat/local_node.rb
105
+ - lib/deplomat/node.rb
106
+ - lib/deplomat/remote_node.rb
107
+ - spec/directives_spec.rb
108
+ - spec/fixtures/cleaning/.keep
109
+ - spec/fixtures/dir1/file1
110
+ - spec/fixtures/dir1/file2
111
+ - spec/fixtures/upload1/uploaded_file1
112
+ - spec/fixtures/upload1/uploaded_file2
113
+ - spec/local_node_spec.rb
114
+ - spec/node_spec.rb
115
+ - spec/remote_node_spec.rb
116
+ - spec/spec_helper.rb
117
+ homepage: http://github.com/snitko/deplomat
118
+ licenses:
119
+ - MIT
120
+ metadata: {}
121
+ post_install_message:
122
+ rdoc_options: []
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ requirements: []
136
+ rubyforge_project:
137
+ rubygems_version: 2.5.1
138
+ signing_key:
139
+ specification_version: 4
140
+ summary: Stack agnostic deployment system that uses bash and ssh commands
141
+ test_files: []