deplomat 0.1.1

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.
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: []