dandelion 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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