dandelion 0.2.0 → 0.2.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.
data/README.md CHANGED
@@ -15,11 +15,11 @@ Alternatively, you can build the gem yourself:
15
15
 
16
16
  Config
17
17
  ------
18
- Configuration options are specified in a YAML file (Dandelion looks for a file
19
- named `dandelion.yml` by default):
18
+ Configuration options are specified in a YAML file (Dandelion looks for
19
+ `dandelion.yml` by default). Example:
20
20
 
21
21
  # Required
22
- scheme: sftp # sftp/ftp
22
+ scheme: sftp
23
23
  host: example.com
24
24
  username: user
25
25
  password: pass
@@ -29,7 +29,28 @@ named `dandelion.yml` by default):
29
29
  exclude:
30
30
  - .gitignore
31
31
  - dandelion.yml
32
+
33
+ Schemes
34
+ -------
35
+ There is support for multiple backend file transfer schemes. The configuration
36
+ must specify one of these schemes and the set of additional parameters required
37
+ by the given scheme.
38
+
39
+ **SFTP**: `scheme: sftp`
40
+
41
+ Required: `host`, `username`, `password`
42
+ Optional: `path`, `exclude`
32
43
 
44
+ **FTP**: `scheme: ftp`
45
+
46
+ Required: `host`, `username`, `password`
47
+ Optional: `path`, `exclude`
48
+
49
+ **Amazon S3**: `scheme: s3`
50
+
51
+ Required: `access_key_id`, `secret_access_key`, `bucket_name`
52
+ Optional: `path`, `exclude`
53
+
33
54
  Usage
34
55
  -----
35
56
  From the root directory of a Git repository, run:
@@ -10,10 +10,13 @@ Gem::Specification.new do |s|
10
10
  s.email = ['scottbnel@gmail.com']
11
11
  s.homepage = 'http://github.com/scttnlsn/dandelion'
12
12
  s.summary = "dandelion-#{s.version}"
13
- s.description = 'Git repository deployment via FTP/SFTP'
13
+ s.description = 'Incremental Git repository deployment'
14
14
 
15
- s.add_dependency 'net-sftp', '>= 2.0.5'
16
15
  s.add_dependency 'grit', '>= 2.4.1'
16
+
17
+ s.add_development_dependency 'mocha', '>= 0.9.12'
18
+ s.add_development_dependency 'net-sftp', '>= 2.0.5'
19
+ s.add_development_dependency 'aws-s3', '>= 0.6.0'
17
20
 
18
21
  s.files = `git ls-files`.split("\n")
19
22
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -0,0 +1,54 @@
1
+ require 'tempfile'
2
+
3
+ module Dandelion
4
+ module Backend
5
+ class MissingFileError < StandardError; end
6
+ class UnsupportedSchemeError < StandardError; end
7
+
8
+ class MissingDependencyError < StandardError
9
+ attr_reader :gems
10
+
11
+ def initialize(gems)
12
+ @gems = gems
13
+ end
14
+ end
15
+
16
+ class Backend
17
+ class << self
18
+ @@backends = {}
19
+
20
+ def create(config)
21
+ Dir.glob(File.join(File.dirname(__FILE__), 'backend', '*.rb')) { |file| require file }
22
+ raise UnsupportedSchemeError unless @@backends.include? config['scheme']
23
+ begin
24
+ @@backends[config['scheme']].new(config)
25
+ rescue LoadError
26
+ raise MissingDependencyError.new(@@backends[config['scheme']].gem_list)
27
+ end
28
+ end
29
+
30
+ def scheme(scheme)
31
+ @@backends[scheme] = self
32
+ end
33
+
34
+ def gems(*gems)
35
+ @gems = gems
36
+ end
37
+
38
+ def gem_list
39
+ @gems
40
+ end
41
+ end
42
+
43
+ protected
44
+
45
+ def temp(file, data)
46
+ tmp = Tempfile.new(file.gsub('/', '.'))
47
+ tmp << data
48
+ tmp.flush
49
+ yield(tmp.path)
50
+ tmp.close
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,80 @@
1
+ require 'dandelion/backend'
2
+
3
+ module Dandelion
4
+ module Backend
5
+ class FTP < Backend
6
+ scheme 'ftp'
7
+
8
+ def initialize(config)
9
+ require 'net/ftp'
10
+ @config = config
11
+ @ftp = Net::FTP.open(@config['host'], @config['username'], @config['password'])
12
+ @ftp.passive = true
13
+ @ftp.chdir(@config['path']) if @config['path']
14
+ end
15
+
16
+ def read(file)
17
+ begin
18
+ # Implementation of FTP#getbinaryfile differs between 1.8
19
+ # and 1.9 so we call FTP#retrbinary directly
20
+ content = ''
21
+ @ftp.retrbinary("RETR #{file}", 4096) do |data|
22
+ content += data
23
+ end
24
+ content
25
+ rescue Net::FTPPermError => e
26
+ raise MissingFileError
27
+ end
28
+ end
29
+
30
+ def write(file, data)
31
+ temp(file, data) do |temp|
32
+ begin
33
+ @ftp.putbinaryfile temp, file
34
+ rescue Net::FTPPermError => e
35
+ mkdir_p File.dirname(file)
36
+ @ftp.putbinaryfile temp, file
37
+ end
38
+ end
39
+ end
40
+
41
+ def delete(file)
42
+ begin
43
+ @ftp.delete file
44
+ cleanup File.dirname(file)
45
+ rescue Net::FTPPermError => e
46
+ end
47
+ end
48
+
49
+ def to_s
50
+ "ftp://#{@config['username']}@#{@config['host']}/#{@config['path']}"
51
+ end
52
+
53
+ private
54
+
55
+ def cleanup(dir)
56
+ unless dir == File.dirname(dir)
57
+ if empty? dir
58
+ @ftp.rmdir dir
59
+ cleanup File.dirname(dir)
60
+ end
61
+ end
62
+ end
63
+
64
+ def empty?(dir)
65
+ return @ftp.nlst(dir).empty?
66
+ end
67
+
68
+ def mkdir_p(dir)
69
+ unless dir == File.dirname(dir)
70
+ begin
71
+ @ftp.mkdir dir
72
+ rescue Net::FTPPermError => e
73
+ mkdir_p File.dirname(dir)
74
+ @ftp.mkdir dir
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,52 @@
1
+ require 'dandelion/backend'
2
+
3
+ module Dandelion
4
+ module Backend
5
+ class S3 < Backend
6
+ scheme 's3'
7
+ gems 'aws-s3'
8
+
9
+ def initialize(config)
10
+ require 'aws/s3'
11
+ @access_key_id = config['access_key_id']
12
+ @secret_access_key = config['secret_access_key']
13
+ @bucket_name = config['bucket_name']
14
+ @path = config['path']
15
+ end
16
+
17
+ def read(file)
18
+ s3connect!
19
+ raise MissingFileError unless AWS::S3::S3Object.exists? path(file), @bucket_name
20
+ AWS::S3::S3Object.value path(file), @bucket_name
21
+ end
22
+
23
+ def write(file, data)
24
+ s3connect!
25
+ AWS::S3::S3Object.store path(file), data, @bucket_name
26
+ end
27
+
28
+ def delete(file)
29
+ s3connect!
30
+ AWS::S3::S3Object.delete path(file), @bucket_name
31
+ end
32
+
33
+ def to_s
34
+ "s3://#{@access_key_id}@#{@bucket_name}/#{@path}"
35
+ end
36
+
37
+ protected
38
+
39
+ def s3connect!
40
+ AWS::S3::Base.establish_connection!(:access_key_id => @access_key_id, :secret_access_key => @secret_access_key, :use_ssl => true) unless AWS::S3::Base.connected?
41
+ end
42
+
43
+ def path(file)
44
+ if @path and !@path.empty?
45
+ "#{@path}/#{file}"
46
+ else
47
+ file
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,90 @@
1
+ require 'dandelion/backend'
2
+ require 'pathname'
3
+
4
+ module Dandelion
5
+ module Backend
6
+ class SFTP < Backend
7
+ scheme 'sftp'
8
+ gems 'net-sftp'
9
+
10
+ def initialize(config)
11
+ require 'net/sftp'
12
+ @config = config
13
+ @sftp = Net::SFTP.start(@config['host'], @config['username'], :password => @config['password'])
14
+ end
15
+
16
+ def read(file)
17
+ begin
18
+ @sftp.file.open(path(file), 'r') do |f|
19
+ f.gets
20
+ end
21
+ rescue Net::SFTP::StatusException => e
22
+ raise unless e.code == 2
23
+ raise MissingFileError
24
+ end
25
+ end
26
+
27
+ def write(file, data)
28
+ temp(file, data) do |temp|
29
+ begin
30
+ @sftp.upload! temp, path(file)
31
+ rescue Net::SFTP::StatusException => e
32
+ raise unless e.code == 2
33
+ mkdir_p File.dirname(path(file))
34
+ @sftp.upload! temp, path(file)
35
+ end
36
+ end
37
+ end
38
+
39
+ def delete(file)
40
+ begin
41
+ @sftp.remove! path(file)
42
+ cleanup File.dirname(path(file))
43
+ rescue Net::SFTP::StatusException => e
44
+ raise unless e.code == 2
45
+ end
46
+ end
47
+
48
+ def to_s
49
+ "sftp://#{@config['username']}@#{@config['host']}/#{@config['path']}"
50
+ end
51
+
52
+ private
53
+
54
+ def cleanpath(path)
55
+ Pathname.new(path).cleanpath.to_s if path
56
+ end
57
+
58
+ def cleanup(dir)
59
+ unless cleanpath(dir) == cleanpath(@config['path']) or dir == File.dirname(dir)
60
+ if empty? dir
61
+ @sftp.rmdir! dir
62
+ cleanup File.dirname(dir)
63
+ end
64
+ end
65
+ end
66
+
67
+ def empty?(dir)
68
+ @sftp.dir.entries(dir).delete_if { |file| file.name == '.' or file.name == '..' }.empty?
69
+ end
70
+
71
+ def mkdir_p(dir)
72
+ begin
73
+ @sftp.mkdir! dir
74
+ rescue Net::SFTP::StatusException => e
75
+ raise unless e.code == 2
76
+ mkdir_p File.dirname(dir)
77
+ retry
78
+ end
79
+ end
80
+
81
+ def path(file)
82
+ if @config['path'] and !@config['path'].empty?
83
+ File.join @config['path'], file
84
+ else
85
+ file
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -1,7 +1,7 @@
1
1
  require 'dandelion'
2
+ require 'dandelion/backend'
2
3
  require 'dandelion/deployment'
3
4
  require 'dandelion/git'
4
- require 'dandelion/service'
5
5
  require 'dandelion/version'
6
6
  require 'optparse'
7
7
  require 'yaml'
@@ -120,51 +120,41 @@ module Dandelion
120
120
  end
121
121
 
122
122
  def execute
123
- log.info("Connecting to: #{service.uri}")
124
- deployment(service) do |d|
125
- log.info("Remote revision: #{d.remote_revision || '---'}")
126
- log.info("Local revision: #{d.local_revision}")
127
-
128
- if @command == 'status'
129
- exit
130
- elsif @command == 'deploy'
131
- validate_deployment d
132
- d.deploy
133
- log.info("Deployment complete")
134
- end
123
+ begin
124
+ backend = Backend::Backend.create(@config)
125
+ log.info("Connecting to: #{backend}")
126
+ rescue Backend::MissingDependencyError => e
127
+ log.fatal("The '#{@config['scheme']}' scheme requires additional gems:")
128
+ log.fatal(' ' + e.gems.join("\n ") + "\n")
129
+ log.fatal("Please install the gems: gem install #{e.gems.join(' ')}")
130
+ exit
131
+ rescue Backend::UnsupportedSchemeError
132
+ log.fatal("Unsupported scheme: #{@config['scheme']}")
133
+ exit
135
134
  end
136
- end
137
-
138
- private
139
-
140
- def deployment(service)
135
+
141
136
  begin
142
- deployment = Deployment::DiffDeployment.new(@repo, service, @config['exclude'])
143
- rescue Deployment::RemoteRevisionError
144
- deployment = Deployment::FullDeployment.new(@repo, service, @config['exclude'])
137
+ deployment = Deployment::Deployment.create(@repo, backend, @config['exclude'])
145
138
  rescue Git::DiffError
146
139
  log.fatal('Error: could not generate diff')
147
140
  log.fatal('Try merging remote changes before running dandelion again')
148
141
  exit
149
142
  end
150
- if block_given?
151
- yield(deployment)
152
- else
153
- deployment
154
- end
155
- end
156
-
157
- def service
158
- if @config['scheme'] == 'sftp'
159
- Service::SFTP.new(@config['host'], @config['username'], @config['password'], @config['path'])
160
- elsif @config['scheme'] == 'ftp'
161
- Service::FTP.new(@config['host'], @config['username'], @config['password'], @config['path'])
162
- else
163
- log.fatal("Unsupported scheme: #{@config['scheme']}")
143
+
144
+ log.info("Remote revision: #{deployment.remote_revision || '---'}")
145
+ log.info("Local revision: #{deployment.local_revision}")
146
+
147
+ if @command == 'status'
164
148
  exit
149
+ elsif @command == 'deploy'
150
+ validate_deployment deployment
151
+ deployment.deploy
152
+ log.info("Deployment complete")
165
153
  end
166
154
  end
167
155
 
156
+ private
157
+
168
158
  def validate_deployment(deployment)
169
159
  begin
170
160
  @repo.remote_list.each do |remote|
@@ -6,9 +6,19 @@ module Dandelion
6
6
  class FastForwardError < StandardError; end
7
7
 
8
8
  class Deployment
9
- def initialize(repo, service, exclude = nil, revision = 'HEAD')
9
+ class << self
10
+ def create(repo, backend, exclude)
11
+ begin
12
+ DiffDeployment.new(repo, backend, exclude)
13
+ rescue RemoteRevisionError
14
+ FullDeployment.new(repo, backend, exclude)
15
+ end
16
+ end
17
+ end
18
+
19
+ def initialize(repo, backend, exclude = nil, revision = 'HEAD')
10
20
  @repo = repo
11
- @service = service
21
+ @backend = backend
12
22
  @exclude = exclude || []
13
23
  @tree = Git::Tree.new(@repo, revision)
14
24
  end
@@ -21,12 +31,8 @@ module Dandelion
21
31
  nil
22
32
  end
23
33
 
24
- def remote_uri
25
- @service.uri
26
- end
27
-
28
34
  def write_revision
29
- @service.write('.revision', local_revision)
35
+ @backend.write('.revision', local_revision)
30
36
  end
31
37
 
32
38
  def validate_state(remote = nil)
@@ -50,8 +56,8 @@ module Dandelion
50
56
  end
51
57
 
52
58
  class DiffDeployment < Deployment
53
- def initialize(repo, service, exclude = nil, revision = 'HEAD')
54
- super(repo, service, exclude, revision)
59
+ def initialize(repo, backend, exclude = nil, revision = 'HEAD')
60
+ super(repo, backend, exclude, revision)
55
61
  @diff = Git::Diff.new(@repo, read_remote_revision, revision)
56
62
  end
57
63
 
@@ -77,7 +83,7 @@ module Dandelion
77
83
  log.info("Skipping file: #{file}")
78
84
  else
79
85
  log.info("Uploading file: #{file}")
80
- @service.write(file, @tree.show(file))
86
+ @backend.write(file, @tree.show(file))
81
87
  end
82
88
  end
83
89
  end
@@ -88,7 +94,7 @@ module Dandelion
88
94
  log.info("Skipping file: #{file}")
89
95
  else
90
96
  log.info("Deleting file: #{file}")
91
- @service.delete(file)
97
+ @backend.delete(file)
92
98
  end
93
99
  end
94
100
  end
@@ -105,8 +111,8 @@ module Dandelion
105
111
 
106
112
  def read_remote_revision
107
113
  begin
108
- @service.read('.revision').chomp
109
- rescue Service::MissingFileError
114
+ @backend.read('.revision').chomp
115
+ rescue Backend::MissingFileError
110
116
  raise RemoteRevisionError
111
117
  end
112
118
  end
@@ -119,7 +125,7 @@ module Dandelion
119
125
  log.info("Skipping file: #{file}")
120
126
  else
121
127
  log.info("Uploading file: #{file}")
122
- @service.write(file, @tree.show(file))
128
+ @backend.write(file, @tree.show(file))
123
129
  end
124
130
  end
125
131
  write_revision
@@ -1,3 +1,3 @@
1
1
  module Dandelion
2
- VERSION = '0.2.0'
2
+ VERSION = '0.2.1'
3
3
  end
@@ -1,6 +1,5 @@
1
1
  require 'dandelion'
2
2
  require 'dandelion/deployment'
3
- require 'dandelion/service'
4
3
  require 'test/unit'
5
4
 
6
5
  def fixture(name)
@@ -57,7 +56,7 @@ class MockRepo
57
56
  end
58
57
  end
59
58
 
60
- class MockService
59
+ class MockBackend
61
60
  attr_reader :reads, :writes, :deletes
62
61
 
63
62
  def initialize(remote_revision)
@@ -87,8 +86,8 @@ class TestDiffDeployment < Test::Unit::TestCase
87
86
  @head_revision = '0ca605e9f0f1d42ce8193ac36db11ec3cc9efc08'
88
87
  @remote_revision = 'ff1f1d4bd0c99e1c9cca047c46b2194accf89504'
89
88
  @repo = MockRepo.new
90
- @service = MockService.new(@remote_revision)
91
- @diff_deployment = Dandelion::Deployment::DiffDeployment.new(@repo, @service, [], @head_revision)
89
+ @backend = MockBackend.new(@remote_revision)
90
+ @diff_deployment = Dandelion::Deployment::DiffDeployment.new(@repo, @backend, [], @head_revision)
92
91
  end
93
92
 
94
93
  def test_diff_deployment_local_revision
@@ -101,7 +100,7 @@ class TestDiffDeployment < Test::Unit::TestCase
101
100
 
102
101
  def test_diff_deployment_write_revision
103
102
  @diff_deployment.write_revision
104
- assert_equal @head_revision, @service.writes['.revision']
103
+ assert_equal @head_revision, @backend.writes['.revision']
105
104
  end
106
105
 
107
106
  def test_diff_deployment_revisions_match
@@ -114,10 +113,10 @@ class TestDiffDeployment < Test::Unit::TestCase
114
113
 
115
114
  def test_diff_deployment_deploy
116
115
  @diff_deployment.deploy
117
- assert_equal 3, @service.writes.length
118
- assert_equal 'bar', @service.writes['foo']
119
- assert_equal 'bar', @service.writes['baz/foo']
120
- assert_equal @head_revision, @service.writes['.revision']
121
- assert_equal ['foobar'], @service.deletes
116
+ assert_equal 3, @backend.writes.length
117
+ assert_equal 'bar', @backend.writes['foo']
118
+ assert_equal 'bar', @backend.writes['baz/foo']
119
+ assert_equal @head_revision, @backend.writes['.revision']
120
+ assert_equal ['foobar'], @backend.deletes
122
121
  end
123
122
  end
@@ -0,0 +1,47 @@
1
+ require 'dandelion/backend/ftp'
2
+ require 'mocha'
3
+ require 'net/ftp'
4
+ require 'test/unit'
5
+
6
+ class TestFTP < Test::Unit::TestCase
7
+ def setup
8
+ @ftp = mock()
9
+ Net::FTP.stubs(:open).returns(@ftp)
10
+ @ftp.expects(:passive=).with(true).once
11
+ @ftp.expects(:chdir).with('foo').once
12
+ @backend = Dandelion::Backend::FTP.new('path' => 'foo')
13
+ class << @backend
14
+ def temp(file, data)
15
+ yield(:temp)
16
+ end
17
+ end
18
+ end
19
+
20
+ def test_read
21
+ @ftp.expects(:retrbinary).with('RETR bar', 4096).once
22
+ @ftp.expects(:retrbinary).with('RETR bar/baz', 4096).once
23
+ @ftp.expects(:retrbinary).with('RETR bar/baz/qux', 4096).once
24
+ @backend.read('bar')
25
+ @backend.read('bar/baz')
26
+ @backend.read('bar/baz/qux')
27
+ end
28
+
29
+ def test_write
30
+ @ftp.expects(:putbinaryfile).with(:temp, 'bar').once
31
+ @ftp.expects(:putbinaryfile).with(:temp, 'bar/baz').once
32
+ @backend.write('bar', 'baz')
33
+ @backend.write('bar/baz', 'qux')
34
+ end
35
+
36
+ def test_delete
37
+ @ftp.expects(:delete).with('bar').once
38
+ @ftp.expects(:delete).with('bar/baz').once
39
+ @ftp.expects(:delete).with('bar/baz/qux').once
40
+ @ftp.expects(:rmdir).with('bar').twice
41
+ @ftp.expects(:rmdir).with('bar/baz').once
42
+ @backend.stubs(:empty?).returns(true)
43
+ @backend.delete('bar')
44
+ @backend.delete('bar/baz')
45
+ @backend.delete('bar/baz/qux')
46
+ end
47
+ end
@@ -0,0 +1,47 @@
1
+ require 'dandelion/backend/sftp'
2
+ require 'mocha'
3
+ require 'net/sftp'
4
+ require 'test/unit'
5
+
6
+ class TestSFTP < Test::Unit::TestCase
7
+ def setup
8
+ @sftp = mock()
9
+ Net::SFTP.stubs(:start).returns(@sftp)
10
+ @backend = Dandelion::Backend::SFTP.new('path' => 'foo')
11
+ class << @backend
12
+ def temp(file, data)
13
+ yield(:temp)
14
+ end
15
+ end
16
+ end
17
+
18
+ def test_read
19
+ file = mock()
20
+ @sftp.stubs(:file).returns(file)
21
+ file.expects(:open).with('foo/bar', 'r').once
22
+ file.expects(:open).with('foo/bar/baz', 'r').once
23
+ file.expects(:open).with('foo/bar/baz/qux', 'r').once
24
+ @backend.read('bar')
25
+ @backend.read('bar/baz')
26
+ @backend.read('bar/baz/qux')
27
+ end
28
+
29
+ def test_write
30
+ @sftp.expects(:upload!).with(:temp, 'foo/bar').once
31
+ @sftp.expects(:upload!).with(:temp, 'foo/bar/baz').once
32
+ @backend.write('bar', 'baz')
33
+ @backend.write('bar/baz', 'qux')
34
+ end
35
+
36
+ def test_delete
37
+ @sftp.expects(:remove!).with('foo/bar').once
38
+ @sftp.expects(:remove!).with('foo/bar/baz').once
39
+ @sftp.expects(:remove!).with('foo/bar/baz/qux').once
40
+ @sftp.expects(:rmdir!).with('foo/bar').twice
41
+ @sftp.expects(:rmdir!).with('foo/bar/baz').once
42
+ @backend.stubs(:empty?).returns(true)
43
+ @backend.delete('bar')
44
+ @backend.delete('bar/baz')
45
+ @backend.delete('bar/baz/qux')
46
+ end
47
+ end
metadata CHANGED
@@ -1,62 +1,69 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: dandelion
3
- version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 2
8
- - 0
9
- version: 0.2.0
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ prerelease:
10
6
  platform: ruby
11
- authors:
7
+ authors:
12
8
  - Scott Nelson
13
9
  autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
-
17
- date: 2011-04-17 00:00:00 -04:00
12
+ date: 2011-05-19 00:00:00.000000000 -04:00
18
13
  default_executable:
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
21
- name: net-sftp
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: grit
17
+ requirement: &2161286340 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: 2.4.1
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *2161286340
26
+ - !ruby/object:Gem::Dependency
27
+ name: mocha
28
+ requirement: &2161285840 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: 0.9.12
34
+ type: :development
22
35
  prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
36
+ version_requirements: *2161285840
37
+ - !ruby/object:Gem::Dependency
38
+ name: net-sftp
39
+ requirement: &2161285380 !ruby/object:Gem::Requirement
24
40
  none: false
25
- requirements:
26
- - - ">="
27
- - !ruby/object:Gem::Version
28
- segments:
29
- - 2
30
- - 0
31
- - 5
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
32
44
  version: 2.0.5
33
- type: :runtime
34
- version_requirements: *id001
35
- - !ruby/object:Gem::Dependency
36
- name: grit
45
+ type: :development
37
46
  prerelease: false
38
- requirement: &id002 !ruby/object:Gem::Requirement
47
+ version_requirements: *2161285380
48
+ - !ruby/object:Gem::Dependency
49
+ name: aws-s3
50
+ requirement: &2161284920 !ruby/object:Gem::Requirement
39
51
  none: false
40
- requirements:
41
- - - ">="
42
- - !ruby/object:Gem::Version
43
- segments:
44
- - 2
45
- - 4
46
- - 1
47
- version: 2.4.1
48
- type: :runtime
49
- version_requirements: *id002
50
- description: Git repository deployment via FTP/SFTP
51
- email:
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: 0.6.0
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: *2161284920
59
+ description: Incremental Git repository deployment
60
+ email:
52
61
  - scottbnel@gmail.com
53
- executables:
62
+ executables:
54
63
  - dandelion
55
64
  extensions: []
56
-
57
65
  extra_rdoc_files: []
58
-
59
- files:
66
+ files:
60
67
  - .gitignore
61
68
  - Gemfile
62
69
  - README.md
@@ -64,14 +71,18 @@ files:
64
71
  - bin/dandelion
65
72
  - dandelion.gemspec
66
73
  - lib/dandelion.rb
74
+ - lib/dandelion/backend.rb
75
+ - lib/dandelion/backend/ftp.rb
76
+ - lib/dandelion/backend/s3.rb
77
+ - lib/dandelion/backend/sftp.rb
67
78
  - lib/dandelion/cli.rb
68
79
  - lib/dandelion/deployment.rb
69
80
  - lib/dandelion/git.rb
70
- - lib/dandelion/service.rb
71
81
  - lib/dandelion/version.rb
72
82
  - test/fixtures/diff
73
83
  - test/fixtures/ls_tree
74
84
  - test/test_diff_deployment.rb
85
+ - test/test_ftp.rb
75
86
  - test/test_git.git/HEAD
76
87
  - test/test_git.git/config
77
88
  - test/test_git.git/description
@@ -99,42 +110,37 @@ files:
99
110
  - test/test_git.git/objects/ff/1f1d4bd0c99e1c9cca047c46b2194accf89504
100
111
  - test/test_git.git/refs/heads/master
101
112
  - test/test_git.rb
113
+ - test/test_sftp.rb
102
114
  has_rdoc: true
103
115
  homepage: http://github.com/scttnlsn/dandelion
104
116
  licenses: []
105
-
106
117
  post_install_message:
107
118
  rdoc_options: []
108
-
109
- require_paths:
119
+ require_paths:
110
120
  - lib
111
- required_ruby_version: !ruby/object:Gem::Requirement
121
+ required_ruby_version: !ruby/object:Gem::Requirement
112
122
  none: false
113
- requirements:
114
- - - ">="
115
- - !ruby/object:Gem::Version
116
- segments:
117
- - 0
118
- version: "0"
119
- required_rubygems_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ! '>='
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
128
  none: false
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- segments:
125
- - 0
126
- version: "0"
129
+ requirements:
130
+ - - ! '>='
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
127
133
  requirements: []
128
-
129
134
  rubyforge_project:
130
- rubygems_version: 1.3.7
135
+ rubygems_version: 1.6.2
131
136
  signing_key:
132
137
  specification_version: 3
133
- summary: dandelion-0.2.0
134
- test_files:
138
+ summary: dandelion-0.2.1
139
+ test_files:
135
140
  - test/fixtures/diff
136
141
  - test/fixtures/ls_tree
137
142
  - test/test_diff_deployment.rb
143
+ - test/test_ftp.rb
138
144
  - test/test_git.git/HEAD
139
145
  - test/test_git.git/config
140
146
  - test/test_git.git/description
@@ -162,3 +168,4 @@ test_files:
162
168
  - test/test_git.git/objects/ff/1f1d4bd0c99e1c9cca047c46b2194accf89504
163
169
  - test/test_git.git/refs/heads/master
164
170
  - test/test_git.rb
171
+ - test/test_sftp.rb
@@ -1,168 +0,0 @@
1
- require 'net/ftp'
2
- require 'net/sftp'
3
- require 'tempfile'
4
-
5
- module Dandelion
6
- module Service
7
- class MissingFileError < StandardError; end
8
-
9
- class Service
10
- def initialize(host, username, path)
11
- @host = host
12
- @username = username
13
- @path = path
14
- end
15
-
16
- def uri
17
- "#{@scheme}://#{@username}@#{@host}/#{@path}"
18
- end
19
-
20
- protected
21
-
22
- def temp(file, data)
23
- tmp = Tempfile.new(file.gsub('/', '.'))
24
- tmp << data
25
- tmp.flush
26
- yield(tmp.path)
27
- tmp.close
28
- end
29
- end
30
-
31
- class FTP < Service
32
- def initialize(host, username, password, path)
33
- super(host, username, path)
34
- @scheme = 'ftp'
35
- @ftp = Net::FTP.open(host, username, password)
36
- @ftp.passive = true
37
- @ftp.chdir(path)
38
- end
39
-
40
- def read(file)
41
- begin
42
- # Implementation of FTP#getbinaryfile differs between 1.8
43
- # and 1.9 so we call FTP#retrbinary directly
44
- content = ''
45
- @ftp.retrbinary("RETR #{file}", 4096) do |data|
46
- content += data
47
- end
48
- content
49
- rescue Net::FTPPermError => e
50
- raise MissingFileError
51
- end
52
- end
53
-
54
- def write(file, data)
55
- # Creates directory only if necessary
56
- mkdir_p(File.dirname(file))
57
-
58
- temp(file, data) do |temp|
59
- @ftp.putbinaryfile(temp, file)
60
- end
61
- end
62
-
63
- def delete(file)
64
- begin
65
- @ftp.delete(file)
66
- cleanup(File.dirname(file))
67
- rescue Net::FTPPermError => e
68
- end
69
- end
70
-
71
- private
72
-
73
- def cleanup(dir)
74
- unless File.identical?(dir, @path)
75
- if empty?(dir)
76
- @ftp.rmdir(dir)
77
- cleanup(File.dirname(dir))
78
- end
79
- end
80
- end
81
-
82
- def empty?(dir)
83
- return @ftp.nlst(dir).empty?
84
- end
85
-
86
- def mkdir_p(dir)
87
- return if dir == "."
88
- parent_dir = File.dirname(dir)
89
- file_names = @ftp.nlst(parent_dir)
90
- unless file_names.include? dir
91
- mkdir_p(parent_dir)
92
- @ftp.mkdir(dir)
93
- end
94
-
95
- end
96
- end
97
-
98
- class SFTP < Service
99
- def initialize(host, username, password, path)
100
- super(host, username, path)
101
- @scheme = 'sftp'
102
- @sftp = Net::SFTP.start(host, username, :password => password)
103
- end
104
-
105
- def read(file)
106
- begin
107
- @sftp.file.open(File.join(@path, file), 'r') do |f|
108
- f.gets
109
- end
110
- rescue Net::SFTP::StatusException => e
111
- raise unless e.code == 2
112
- raise MissingFileError
113
- end
114
- end
115
-
116
- def write(file, data)
117
- path = File.join(@path, file)
118
- begin
119
- dir = File.dirname(path)
120
- @sftp.stat!(dir)
121
- rescue Net::SFTP::StatusException => e
122
- raise unless e.code == 2
123
- mkdir_p(dir)
124
- end
125
- temp(file, data) do |temp|
126
- @sftp.upload!(temp, path)
127
- end
128
- end
129
-
130
- def delete(file)
131
- begin
132
- path = File.join(@path, file)
133
- @sftp.remove!(path)
134
- cleanup(File.dirname(path))
135
- rescue Net::SFTP::StatusException => e
136
- raise unless e.code == 2
137
- end
138
- end
139
-
140
- private
141
-
142
- def cleanup(dir)
143
- unless File.identical?(dir, @path)
144
- if empty?(dir)
145
- @sftp.rmdir!(dir)
146
- cleanup(File.dirname(dir))
147
- end
148
- end
149
- end
150
-
151
- def empty?(dir)
152
- @sftp.dir.entries(dir).map do |entry|
153
- entry.name unless entry.name == '.' or entry.name == '..'
154
- end.compact.empty?
155
- end
156
-
157
- def mkdir_p(dir)
158
- begin
159
- @sftp.mkdir!(dir)
160
- rescue Net::SFTP::StatusException => e
161
- raise unless e.code == 2
162
- mkdir_p(File.dirname(dir))
163
- mkdir_p(dir)
164
- end
165
- end
166
- end
167
- end
168
- end