pgbundle 0.0.15 → 0.0.16

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 11226f3bd159341b9b82f4ee683782e7f26fa677
4
- data.tar.gz: 6f1eebc8ceaad7f92fe56a8341539d04018ad28d
3
+ metadata.gz: c6ade210a8dde8dcdbdacc39434ba2fa11ff0e92
4
+ data.tar.gz: 7e364dac3b3dfa809a770f39661d4c32bf46f7bd
5
5
  SHA512:
6
- metadata.gz: ff317e2a95f75412ea40c79cc8bda263a696907eb11c53e508cb58f5d28d334779a2307567ab4f185db40609b7652b9acdd3bd86935999fbad9c89a675086b5e
7
- data.tar.gz: bfedab2f6a2ec6f8d1f72fc6fe6580dc73be90bb95fe074b1825853a146964ca9838b2d5afff6dc8d2e7852deeb7fea98b09f8bd74272b1b5fe526396eeb0a5d
6
+ metadata.gz: 294bafbc788b72b7c9cbc621ea447d951c028b99dc2e791d6fef5730d45a6eb2037b278408c75d6f557fb33498651ac34b55e40f0ca8d904ab50d8957c456e63
7
+ data.tar.gz: 186fbf3e1cf15be94c2b72eff4cf00e34b8dec4dceaa067638abe54e94f758961ccb836b1cc8e484e0ce29af14d97bc7ba8a5a1727228b34db3a4450bf270e34
data/.travis.yml CHANGED
@@ -7,5 +7,13 @@ rvm:
7
7
  matrix:
8
8
  allow_failures:
9
9
  - rvm: rbx-2
10
- addons:
11
- postgresql: "9.3"
10
+ before_install:
11
+ - wget https://gist.github.com/petere/5893799/raw/apt.postgresql.org.sh
12
+ - wget https://gist.github.com/petere/6023944/raw/pg-travis-test.sh
13
+ - sudo sh ./apt.postgresql.org.sh
14
+ env:
15
+ - PGVERSION=9.1
16
+ - PGVERSION=9.2
17
+ - PGVERSION=9.3
18
+ - PGVERSION=9.4
19
+ script: bash ./cibuild.sh
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![Build Status](https://travis-ci.org/adjust/pgbundle.svg?branch=master)](https://travis-ci.org/adjust/pgbundle)
2
+
1
3
  # pgbundle
2
4
 
3
5
  bundling postgres extension
@@ -11,35 +13,158 @@ bundling postgres extension
11
13
  define your dependent postgres extensions in a Pgfile like this:
12
14
 
13
15
  ```
14
- #Pgfile
16
+ # Pgfile
15
17
 
16
18
  database 'my_database', host: 'my.db.server', use_sudo: true, system_user: 'postgres'
17
19
 
18
20
  pgx 'hstore'
19
21
  pgx 'my_extension', '1.0.2', github: me/my_extension
20
22
  pgx 'my_other_extionsion', :git => 'https://github.com/me/my_other_extionsion.git'
21
- pgx 'my_ltree_dependend_extension', github: me/my_ltree_dependend_extension, require: 'ltree'
23
+ pgx 'my_ltree_dependend_extension', github: me/my_ltree_dependend_extension, requires: 'ltree'
24
+ ```
25
+
26
+ **database**
27
+
28
+ `database` defines on which database(s) the extensions should be installed. The first
29
+ argument is the database name the additional options may specify your setup but
30
+ come with reasonable default values.
31
+
32
+ option | default | desciption
33
+ ------- | ------- | -----------
34
+ user | 'postgres' | the database user (needs privilege to `CREATE EXTENSION`)
35
+ host | 'localhost' | the database host (needs to be accessible from where `pgbundle` runs)
36
+ use_sudo | false | if true use `sudo` to run `make install` if needed
37
+ system_user | 'postgres' | the (os) system user that is allowed to install an extension (through make)
38
+ port | 5432 | the database port
39
+ force_ssh | false | run commands via ssh even if host is `localhost`
40
+ slave | false | defines if the database runs as a read-only slave thus skips any `CREATE` command
41
+
42
+ **pgx**
43
+
44
+ The `pgx` command defines you actual Extension. The first argument specifies the Extension name,
45
+ the second optional parameter defines the required Version. If the Extension is not yet
46
+ installed on the server you may wish to define how `pgbundle` can find it's source to build
47
+ and install it. And which Extensions may be required
48
+
49
+ option | description
50
+ ------ | -----------
51
+ git | any git repository pgbundle can clone from
52
+ github | any github repository in the form `user/repository`
53
+ branch | an optional branch name for git or github sources defaults to `master`
54
+ requires | an optional extension that the extension depends on
55
+ path | any absolute or relative local path e.g. './foo/bar'
56
+ pgxn | any repository available on http://pgxn.org/
57
+
58
+
59
+ Some Extensions may require other Extensions to allow `pgbundle` to resolve dependencies
60
+ and install it in the right order you can define them with `requires`.
61
+ If the required Extension is not yet available on the target server or the Extension
62
+ requires a specific Version you should define it as well.
63
+ E.g.
64
+
65
+ ```
66
+ # Pgfile
67
+
68
+ database ...
69
+
70
+ pgx 'foo', '0.1.2', github: me/foo
71
+
72
+ # set foo as dependency for bar
73
+ pgx 'bar', '1.2.3', github: me/bar, requires: 'foo'
74
+
75
+ # set bar and boo as dependency for baz
76
+ # will automatically set foo as dependency as well
77
+ pgx 'baz', '0.2.3', github: me/baz, requires: ['bar', 'boo']
78
+ ```
79
+
80
+ ## pgbundle commands
81
+
82
+ `pgbundle` comes with four commands. If the optional `pgfile` is not given it assumes
83
+ to find a file named `Pgfile` in the current directory.
84
+
85
+ **check**
86
+
87
+ checks availability of required extensions.
88
+
89
+ ```
90
+ pgbundle check [pgfile]
22
91
  ```
23
92
 
24
- ### install your extension
93
+ `check` does not change anything on your system, it only checks which
94
+ of your specified extensions are available and which are missing.
95
+ It returns with exitcode `1` if any Extension is missing and `0` otherwise.
25
96
 
26
- pgbundle install
27
97
 
28
- installs the extensions and dependencies on your database server
98
+ **install**
29
99
 
30
- ### check your dependencies
100
+ installs extensions
31
101
 
32
- pgbundle check
102
+ ```
103
+ pgbundle install [pgfile] [-f]
104
+ ```
105
+
106
+ `install` tries to install missing Extensions. If `--force` is given it installs
107
+ all Extension even if they are already installed.
108
+
109
+ **create**
110
+
111
+ create the extension at the desired version
112
+
113
+ ```
114
+ pgbundle create [pgfile]
115
+ ```
33
116
 
34
- checks whether all dependencies are available for creation on the database server
117
+ `create` runs the `CREATE EXTENSION` command on the specified databases. If a version
118
+ is specified in the `Pgfile` it tries to install with `CREATE EXTENSION VERSION version`.
119
+ If the Extension is already created but with a wrong version, it will run
120
+ `ALTER EXTENSION extension_name UPDATE TO new_version`.
35
121
 
36
- ## getting started
122
+ **init**
123
+
124
+ write an initial pgfile to stdout
125
+
126
+ ```
127
+ pgbundle init db_name -u user -h host -p port
128
+ ```
129
+
130
+ `init` is there to help you get started. If you have already a database with installed
131
+ Extensions you get the content for an initial `Pgfile`. Pgbundle will figure out
132
+ which Extension at which Version are already in use and print a reasonable starting
133
+ point for you Pgfile.
134
+ However this is only to help you get started you may wish to specify sources and
135
+ dependencies correctly.
136
+
137
+ ### How it works
138
+
139
+ You may already have noticed that using Extensions on postgres requires two different
140
+ steps. Building the extension on the database cluster with `make install`
141
+ and creating the extension into the database with `CREATE/ALTER EXTENSION`.
142
+ Pgbundle reflects that with the two different commands `install` and `create`.
143
+
144
+ Usually `pgbundle` runs along with your application on your application server
145
+ which often is different from your database machine. Thus the `install` step
146
+ will (if necessary) try to download the source code of the extension into a
147
+ temporary folder and then copy it to your database servers into `/tmp/pgbundle`.
148
+ From there it will run `make clean && make && make install` for each database.
149
+ You may specify as which user you want these commands to run with the `system_user`
150
+ option. Although for security reasons not recommended you can specify to run the
151
+ install step with sudo `use_sudo: true`, but we suggest to give write permission
152
+ for the postgres system user to the install targets. If you are not sure which these
153
+ are run
154
+
155
+ ```
156
+ pg_config
157
+ ```
37
158
 
38
- if your already have some database on your current project you can get a starting point with
159
+ and find the `LIBDIR`, `SHAREDIR` and `DOCDIR`
39
160
 
40
- pgbundle init
161
+ #### master - slave
41
162
 
42
- lets say your database named 'my_project' runs on localhost with user postges
163
+ Every serious production database cluster usually has a slave often ran as Hot Standby.
164
+ You should make sure that all your Extension are also installed on all slaves.
165
+ Because database slaves run as read-only servers any attempt to `CREATE` or `ALTER`
166
+ Extension will fail, these commands should only run on the master server and will
167
+ be replicated to the slave from there. You can tell `pgbundle` that it should skip
168
+ these steps with `slave: true`.
43
169
 
44
- pgbundle init my_project -u postgres -h localhost
45
170
 
data/bin/pgbundle CHANGED
@@ -2,7 +2,6 @@
2
2
  require 'thor'
3
3
  require 'thor/group'
4
4
  require 'pgbundle'
5
- require 'pry'
6
5
 
7
6
  module PgBundle
8
7
  class Cli < Thor
@@ -10,17 +9,21 @@ module PgBundle
10
9
  method_options %w( force -f ) => :boolean
11
10
  def install(pgfile = 'Pgfile')
12
11
  if options.force?
13
- installed = definition(pgfile).install!
12
+ installed = definitions(pgfile).inject({}){|m, d| m[d.database] = d.install!; m}
14
13
  else
15
- definition(pgfile).available_extensions.each do |dep|
16
- say_status('exists', dep.name)
14
+ definitions(pgfile).each do |d|
15
+ d.available_extensions.each do |dep|
16
+ say_status('exists', "#{d.database}: #{dep.name}")
17
+ end
17
18
  end
18
19
 
19
- installed = definition(pgfile).install
20
+ installed = definitions(pgfile).inject({}){|m, d| m[d.database] = d.install; m}
20
21
  end
21
22
 
22
- installed.each do |d|
23
- say_status('install', d.name, :yellow)
23
+ installed.each do |db, deps|
24
+ deps.each do |d|
25
+ say_status('install', "#{db}: #{d.name}", :yellow)
26
+ end
24
27
  end
25
28
  rescue InstallError, ExtensionCreateError, CircularDependencyError => e
26
29
  say_status('error', e.message, :red)
@@ -30,15 +33,17 @@ module PgBundle
30
33
  desc 'check', 'checks availability of required extensions'
31
34
  def check(pgfile = 'Pgfile')
32
35
  missing = false
33
- definition(pgfile).check.each do |d|
34
- if d[:created]
35
- say_status('created', d[:name])
36
- else
37
- unless d[:installed]
38
- say_status('missing', d[:name], :red)
39
- missing = true
36
+ definitions(pgfile).each do |df|
37
+ df.check.each do |d|
38
+ if d[:created]
39
+ say_status('created', "#{df.database}: #{d[:name]}")
40
+ else
41
+ unless d[:installed]
42
+ say_status('missing', "#{df.database}: #{d[:name]}", :red)
43
+ missing = true
44
+ end
45
+ say_status('installed', "#{df.database}: #{d[:name]}", :yellow) if d[:installed]
40
46
  end
41
- say_status('installed', d[:name], :yellow) if d[:installed]
42
47
  end
43
48
  end
44
49
  exit 1 if missing
@@ -46,14 +51,17 @@ module PgBundle
46
51
 
47
52
  desc 'create', 'create the extension at the desired version'
48
53
  def create(pgfile = 'Pgfile')
49
- definition(pgfile).create.each do | d |
50
- say_status('created', d.name)
54
+ definitions(pgfile).each do |df|
55
+ df.create.each do | d |
56
+ say_status('created', "#{df.database}: #{d.name}")
57
+ end
51
58
  end
52
59
  end
53
60
 
54
61
  desc 'init', 'write an initial pgfile to stdout'
55
62
  method_options %w( user -u ) => :string
56
63
  method_options %w( host -h ) => :string
64
+ method_options %w( port -p ) => :string
57
65
  def init(db_name)
58
66
  definition = PgBundle::Definition.new
59
67
  definition.database = PgBundle::Database.new(db_name, options)
@@ -62,9 +70,9 @@ module PgBundle
62
70
  end
63
71
 
64
72
  no_commands do
65
- def definition(pgfile)
66
- definition = Dsl.new.eval_pgfile(pgfile)
67
- definition.link_dependencies
73
+ def definitions(pgfile)
74
+ definitions = Dsl.new.eval_pgfile(pgfile)
75
+ definitions.map(&:link_dependencies)
68
76
  end
69
77
  end
70
78
  end
data/cibuild.sh ADDED
@@ -0,0 +1,28 @@
1
+ #!/bin/bash
2
+
3
+ set -eux
4
+
5
+ sudo apt-get update
6
+
7
+ packages="postgresql-$PGVERSION postgresql-server-dev-$PGVERSION postgresql-common"
8
+
9
+ # bug: http://www.postgresql.org/message-id/20130508192711.GA9243@msgid.df7cb.de
10
+ sudo update-alternatives --remove-all postmaster.1.gz
11
+
12
+ # stop all existing instances (because of https://github.com/travis-ci/travis-cookbooks/pull/221)
13
+ sudo service postgresql stop
14
+ # and make sure they don't come back
15
+ echo 'exit 0' | sudo tee /etc/init.d/postgresql
16
+ sudo chmod a+x /etc/init.d/postgresql
17
+
18
+ sudo apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" install $packages
19
+
20
+ status=0
21
+ sudo pg_createcluster --start $PGVERSION test -p 55435 -- -A trust
22
+ # make all PG_CONFIG=/usr/lib/postgresql/$PGVERSION/bin/pg_config
23
+ # sudo make install PG_CONFIG=/usr/lib/postgresql/$PGVERSION/bin/pg_config
24
+ # PGPORT=55435 make installcheck PGUSER=postgres PG_CONFIG=/usr/lib/postgresql/$PGVERSION/bin/pg_config || status=$?
25
+
26
+ # if test -f regression.diffs; then cat regression.diffs; fi
27
+ # exit $status
28
+ PGPORT=55435 rake
@@ -13,6 +13,10 @@ module PgBundle
13
13
  fail NotImplementedError
14
14
  end
15
15
 
16
+ def clean
17
+ fail NotImplementedError
18
+ end
19
+
16
20
  private
17
21
 
18
22
  def copy_local(source, dest)
@@ -1,6 +1,6 @@
1
1
  require 'pg'
2
2
  require 'net/ssh'
3
- require 'pry'
3
+
4
4
  module PgBundle
5
5
  # The Database class defines on which database the extensions should be installed
6
6
  # Note to install an extension the code must be compiled on the database server
@@ -16,6 +16,7 @@ module PgBundle
16
16
  @system_user = opts[:system_user] || 'postgres'
17
17
  @port = opts[:port] || 5432
18
18
  @force_ssh = opts[:force_ssh] || false
19
+ @slave = opts[:slave] || false
19
20
  end
20
21
 
21
22
  def connection
@@ -24,6 +25,13 @@ module PgBundle
24
25
  end
25
26
  end
26
27
 
28
+ def to_s
29
+ "host: #{@host}:#{port} db: #{@name}"
30
+ end
31
+
32
+ def slave?
33
+ @slave
34
+ end
27
35
  # executes the given sql on the database connections
28
36
  # redirects all noise to /dev/null
29
37
  def execute(sql)
@@ -58,6 +66,7 @@ module PgBundle
58
66
  source.load(host, system_user, load_destination(ext_name))
59
67
  run(make_install_cmd(ext_name))
60
68
  remove_source(ext_name)
69
+ source.clean
61
70
  end
62
71
 
63
72
  # loads the source and runs make uninstall
@@ -66,6 +75,7 @@ module PgBundle
66
75
  source.load(host, system_user, load_destination(ext_name))
67
76
  run(make_uninstall_cmd(ext_name))
68
77
  remove_source(ext_name)
78
+ source.clean
69
79
  end
70
80
 
71
81
  def drop_extension(name)
@@ -113,7 +123,8 @@ module PgBundle
113
123
  end
114
124
 
115
125
  def local(cmd)
116
- %x(#{cmd})
126
+ res = %x((#{cmd}) 2>&1 )
127
+ raise res unless $?.success?
117
128
  end
118
129
 
119
130
  def remote(cmd)
@@ -49,7 +49,7 @@ module PgBundle
49
49
  end
50
50
 
51
51
  def init
52
- ["database '#{database.name}', host: '#{database.host}', user: #{database.user}, system_user: #{database.system_user}, use_sudo: #{database.use_sudo}"] +
52
+ ["database '#{database.name}', host: '#{database.host}', user: '#{database.user}', system_user: '#{database.system_user}', use_sudo: #{database.use_sudo}"] +
53
53
  database.current_definition.map do |r|
54
54
  name, version = r['name'], r['version']
55
55
  requires = r['requires'] ? ", requires: " + r['requires'].gsub(/[{},]/,{'{' => '%w(', '}' =>')', ','=> ' '}) : ''
@@ -57,7 +57,7 @@ module PgBundle
57
57
  end
58
58
  end
59
59
 
60
- # returns an array hashes with dependency information
60
+ # returns an array of hashes with dependency information
61
61
  # [{name: 'foo', installed: true, created: false }]
62
62
  def check
63
63
  link_dependencies
data/lib/pgbundle/dsl.rb CHANGED
@@ -5,12 +5,13 @@ module PgBundle
5
5
  def initialize
6
6
  @definition = Definition.new
7
7
  @extensions = []
8
+ @databases = []
8
9
  end
9
10
 
10
11
  def eval_pgfile(pgfile, contents=nil)
11
12
  contents ||= File.read(pgfile.to_s)
12
13
  instance_eval(contents)
13
- @definition
14
+ @databases.map{|d| df = @definition.clone; df.database = d; df}
14
15
  rescue SyntaxError => e
15
16
  syntax_msg = e.message.gsub("#{pgfile}:", 'on line ')
16
17
  raise PgfileError, "Pgfile syntax error #{syntax_msg}"
@@ -24,7 +25,7 @@ module PgBundle
24
25
 
25
26
  def database(*args)
26
27
  opts = extract_options!(args)
27
- @definition.database = Database.new(args.first, opts)
28
+ @databases << Database.new(args.first, opts)
28
29
  end
29
30
 
30
31
  def pgx(*args)
@@ -14,6 +14,7 @@ module PgBundle
14
14
  def initialize(*args)
15
15
  opts = args.last.is_a?(Hash) ? args.pop : {}
16
16
  @name, @version = args
17
+ validate(opts)
17
18
  self.dependencies = opts[:requires]
18
19
  set_source(opts)
19
20
  end
@@ -108,7 +109,6 @@ module PgBundle
108
109
  unless dependencies.empty?
109
110
  install_dependencies(database, force)
110
111
  end
111
-
112
112
  make_install(database, force)
113
113
  raise ExtensionNotFound.new(name, version) unless installed?(database)
114
114
 
@@ -126,6 +126,7 @@ module PgBundle
126
126
  # create the extension along with it's dependencies in a transaction
127
127
  def create_with_dependencies(database)
128
128
  return true if created?(database)
129
+ return false if database.slave?
129
130
 
130
131
  database.transaction do |con|
131
132
  begin
@@ -181,6 +182,20 @@ module PgBundle
181
182
 
182
183
  private
183
184
 
185
+ # validates the options hash
186
+ def validate(opts)
187
+ opts = opts.clone
188
+ opts.delete(:requires)
189
+ opts.delete(:branch)
190
+ if opts.size > 1
191
+ fail PgfileError.new "multiple sources given for #{name} #{opts}"
192
+ end
193
+
194
+ unless opts.empty? || [:path, :git, :github, :pgxn].include?(opts.keys.first)
195
+ fail PgfileError.new "invalid source #{opts.keys.first} for #{name}"
196
+ end
197
+ end
198
+
184
199
  # adds dependencies that are required but not defined yet
185
200
  def add_missing_required_dependencies(database)
186
201
  requires = requires(database)
@@ -223,7 +238,7 @@ module PgBundle
223
238
  end
224
239
 
225
240
  def drop_extension(database)
226
- database.drop_extension(name)
241
+ database.drop_extension(name) unless database.slave?
227
242
  end
228
243
 
229
244
  # loads the source and runs make install
@@ -266,6 +281,7 @@ module PgBundle
266
281
 
267
282
  # hard checks that the dependency can be created running CREATE command in a transaction
268
283
  def creatable!(database)
284
+ return false if database.slave?
269
285
  database.transaction_rollback do |con|
270
286
  begin
271
287
  create_dependencies(con)
@@ -274,6 +290,8 @@ module PgBundle
274
290
  raise ExtensionNotFound.new(name, version)
275
291
  rescue PG::UndefinedObject => err
276
292
  raise MissingDependency.new(name, err.message)
293
+ rescue PG::ReadOnlySqlTransaction
294
+ raise ReadOnlyDb.new(database, name)
277
295
  end
278
296
  end
279
297
 
@@ -286,7 +304,7 @@ module PgBundle
286
304
  database.execute 'BEGIN'
287
305
  begin
288
306
  database.execute update_stmt
289
- rescue PG::UndefinedFile, PG::UndefinedObject => err
307
+ rescue PG::UndefinedFile, PG::UndefinedObject, PG::ReadOnlySqlTransaction => err
290
308
  @error = err.message
291
309
  result = false
292
310
  end
@@ -298,8 +316,12 @@ module PgBundle
298
316
  def set_source(opts)
299
317
  if opts[:path]
300
318
  @source = PathSource.new(opts[:path])
319
+ elsif opts[:git]
320
+ @source = GitSource.new(opts[:git], opts[:branch])
301
321
  elsif opts[:github]
302
322
  @source = GithubSource.new(opts[:github], opts[:branch])
323
+ elsif opts[:pgxn]
324
+ @source = PgxnSource.new(name, version)
303
325
  end
304
326
  end
305
327
  end
@@ -0,0 +1,43 @@
1
+ require 'tmpdir'
2
+ module PgBundle
3
+ # The GithubSource class defines a Github Source
4
+ class GitSource < BaseSource
5
+ attr_reader :branch
6
+
7
+ def initialize(path, branch = 'master')
8
+ @branch = branch || 'master'
9
+ super(path)
10
+ end
11
+
12
+ def load(host, user, dest)
13
+ clone
14
+ if host == 'localhost'
15
+ copy_local("#{clone_dir}/", dest)
16
+ else
17
+ copy_to_remote(host, user, "#{clone_dir}/", dest)
18
+ end
19
+ end
20
+
21
+ def clean
22
+ FileUtils.remove_dir(clone_dir, true)
23
+ end
24
+
25
+ private
26
+
27
+ def clone
28
+ res = %x((rm -rf #{clone_dir} && #{git_command} && rm -rf #{clone_dir}/.git}) 2>&1)
29
+ unless $?.success?
30
+ fail GitCommandError, git_command, res
31
+ end
32
+ end
33
+
34
+ # git clone user@git-server:project_name.git -b branch_name /some/folder
35
+ def git_command
36
+ "git clone #{path} -b #{branch} --quiet --depth=1 #{clone_dir}"
37
+ end
38
+
39
+ def clone_dir
40
+ @clone_dir ||= Dir.mktmpdir
41
+ end
42
+ end
43
+ end
@@ -1,39 +1,13 @@
1
1
  require 'tmpdir'
2
2
  module PgBundle
3
3
  # The GithubSource class defines a Github Source
4
- class GithubSource < BaseSource
4
+ class GithubSource < GitSource
5
5
  attr_reader :branch
6
6
 
7
7
  def initialize(path, branch = 'master')
8
- @branch = branch || 'master'
9
- super(path)
10
- end
11
-
12
- def load(host, user, dest)
13
- clone
14
- if host == 'localhost'
15
- copy_local("#{clone_dir}/", dest)
16
- else
17
- copy_to_remote(host, user, "#{clone_dir}/", dest)
18
- end
19
- end
20
-
21
- private
22
-
23
- def clone
24
- %x((rm -rf #{clone_dir} && #{git_command} && rm -rf #{clone_dir}/.git}) 2>&1)
25
- unless $?.success?
26
- fail GitCommandError, git_command
27
- end
28
- end
29
-
30
- # git clone user@git-server:project_name.git -b branch_name /some/folder
31
- def git_command
32
- "git clone git@github.com:#{path}.git -b #{branch} --quiet --depth=1 #{clone_dir}"
33
- end
34
-
35
- def clone_dir
36
- @clone_dir ||= Dir.mktmpdir
8
+ branch = branch || 'master'
9
+ path = "git@github.com:#{path}.git"
10
+ super(path, branch)
37
11
  end
38
12
  end
39
13
  end
@@ -11,5 +11,8 @@ module PgBundle
11
11
  copy_to_remote(host, user, path, dest)
12
12
  end
13
13
  end
14
+
15
+ def clean
16
+ end
14
17
  end
15
18
  end
@@ -0,0 +1,61 @@
1
+ require 'tmpdir'
2
+ require 'open-uri'
3
+ require 'zip'
4
+
5
+ module PgBundle
6
+ # The GithubSource class defines a Github Source
7
+ class PgxnSource < BaseSource
8
+
9
+ def initialize(dist, version)
10
+ @dist, @version = dist, version
11
+ path = "http://master.pgxn.org/dist/%{dist}/%{version}/%{dist}-%{version}.zip" % {dist: dist, version: version}
12
+ super(path)
13
+ end
14
+
15
+ def load(host, user, dest)
16
+ download
17
+ unzip
18
+ if host == 'localhost'
19
+ copy_local("#{download_dir}/#{@dist}-#{@version}", dest)
20
+ else
21
+ copy_to_remote(host, user, "#{download_dir}/#{@dist}-#{@version}", dest)
22
+ end
23
+ end
24
+
25
+ def clean
26
+ FileUtils.remove_dir(download_dir, true)
27
+ end
28
+
29
+ private
30
+
31
+ def download
32
+ begin
33
+ File.open(zipfile, "wb") do |saved_file|
34
+ open(@path, "rb") do |read_file|
35
+ saved_file.write(read_file.read)
36
+ end
37
+ end
38
+ rescue OpenURI::HTTPError => e
39
+ raise PgxnError.new(path, e.message)
40
+ end
41
+ end
42
+
43
+ def zipfile
44
+ "#{download_dir}/#{@dist}.zip"
45
+ end
46
+
47
+ def unzip
48
+ Zip::ZipFile.open(zipfile) do |zip_file|
49
+ zip_file.each do |f|
50
+ f_path=File.join(download_dir, f.name)
51
+ FileUtils.mkdir_p(File.dirname(f_path))
52
+ zip_file.extract(f, f_path) unless File.exist?(f_path)
53
+ end
54
+ end
55
+ end
56
+
57
+ def download_dir
58
+ @clone_dir ||= Dir.mktmpdir
59
+ end
60
+ end
61
+ end
@@ -1,3 +1,3 @@
1
1
  module Pgbundle
2
- VERSION = '0.0.15'
2
+ VERSION = '0.0.16'
3
3
  end
data/lib/pgbundle.rb CHANGED
@@ -7,7 +7,9 @@ module PgBundle
7
7
  autoload :Extension, 'pgbundle/extension'
8
8
  autoload :BaseSource, 'pgbundle/base_source'
9
9
  autoload :PathSource, 'pgbundle/path_source'
10
+ autoload :GitSource, 'pgbundle/git_source'
10
11
  autoload :GithubSource, 'pgbundle/github_source'
12
+ autoload :PgxnSource, 'pgbundle/pgxn_source'
11
13
 
12
14
  class PgfileError < StandardError
13
15
  end
@@ -44,6 +46,12 @@ module PgBundle
44
46
  end
45
47
  end
46
48
 
49
+ class ReadOnlyDb < ExtensionCreateError
50
+ def initialize(db, base_name)
51
+ super "Can't install Extension #{base_name}, Database #{db} is read only"
52
+ end
53
+ end
54
+
47
55
  class MissingDependency < ExtensionCreateError
48
56
  def initialize(base_name, dependen_msg)
49
57
  required = dependen_msg[/required extension \"(.*?)\" is not installed/, 1]
@@ -52,8 +60,14 @@ module PgBundle
52
60
  end
53
61
 
54
62
  class GitCommandError < InstallError
55
- def initialize(dest)
56
- super "Failed to load git repository cmd: '#{dest}'"
63
+ def initialize(dest, details = nil)
64
+ super "Failed to load git repository cmd: '#{dest}'\n failed: #{details}"
65
+ end
66
+ end
67
+
68
+ class PgxnError < InstallError
69
+ def initialize(dest, message = nil)
70
+ super "Failed to load from pgxn: '#{dest}'\n failed: #{message}"
57
71
  end
58
72
  end
59
73
  end
data/pgbundle.gemspec CHANGED
@@ -21,10 +21,10 @@ Gem::Specification.new do |spec|
21
21
  spec.add_dependency 'thor'
22
22
  spec.add_dependency 'net-ssh'
23
23
  spec.add_dependency 'net-scp'
24
+ spec.add_dependency 'zip'
24
25
  #https://bitbucket.org/ged/ruby-pg/wiki/Home
25
26
  spec.add_dependency 'pg', '> 0.17'
26
27
  spec.add_development_dependency 'rspec', '~> 2.14.0'
27
- spec.add_development_dependency "bundler", "~> 1.5"
28
+ spec.add_development_dependency "bundler", ">= 1.5.0"
28
29
  spec.add_development_dependency "rake"
29
- spec.add_development_dependency "pry"
30
30
  end
data/spec/Pgfile CHANGED
@@ -1,4 +1,4 @@
1
- database 'pgbundle_test', host: 'localhost', port: 54321
1
+ database 'pgbundle_test', host: 'localhost'
2
2
 
3
3
  pgx 'hstore'
4
4
  pgx 'bar', path: './spec/sample_extensions/bar', requires: 'ltree'
data/spec/dsl_spec.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe PgBundle::Dsl do
4
- subject { PgBundle::Dsl.new.eval_pgfile(File.expand_path('../Pgfile', __FILE__)) }
4
+ subject { PgBundle::Dsl.new.eval_pgfile(File.expand_path('../Pgfile', __FILE__)).first }
5
5
 
6
6
  its(:database) { should be_a PgBundle::Database }
7
- its('database.port') { should be 54321 }
7
+ its('database.host') { should eq 'localhost' }
8
8
  its(:extensions) { should be_a Hash }
9
9
 
10
10
  context 'parsing options' do
@@ -65,6 +65,22 @@ describe PgBundle::Extension do
65
65
  end
66
66
  end
67
67
 
68
+ context 'installing from github' do
69
+ subject { PgBundle::Extension.new('ltree', '1.0', git: 'https://github.com/adjust/ltree.git') }
70
+
71
+ before do
72
+ allow_any_instance_of(PgBundle::GithubSource)
73
+ .to receive(:clone_dir)
74
+ .and_return('/tmp/pgbundle/tree_tmp')
75
+
76
+ subject.install(database, true)
77
+ end
78
+
79
+ it 'cleans the tmp directory after install' do
80
+ expect(Dir.exists?('/tmp/pgbundle/tree_tmp')).to be_false
81
+ end
82
+ end
83
+
68
84
  context 'require not found' do
69
85
  subject { PgBundle::Extension.new('foo', '0.0.2', path: './spec/sample_extensions/foo', requires: PgBundle::Extension.new('noope')) }
70
86
 
data/spec/spec_helper.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  require 'rubygems'
2
2
  require 'pgbundle'
3
3
  require 'pg'
4
- require 'pry'
5
4
 
6
5
  Dir.glob('spec/support/**/*.rb').each { |f| require f }
7
6
 
@@ -12,14 +11,19 @@ RSpec.configure do |config|
12
11
  config.filter_run focus: true
13
12
  config.run_all_when_everything_filtered = true
14
13
  config.before(:suite) do
15
- conn = PG.connect(dbname: 'postgres', user: 'postgres')
14
+ system "mkdir -p -m 0777 /tmp/pgbundle/"
15
+ conn = PG.connect(dbname: 'postgres', user: 'postgres', host: 'localhost', port: ENV['PGPORT'] || 5432)
16
16
  conn.exec('CREATE DATABASE pgbundle_test')
17
17
  conn.close
18
18
  end
19
19
 
20
20
  config.after(:suite) do
21
- conn = PG.connect(dbname: 'postgres', user: 'postgres')
22
- conn.exec("SELECT pg_terminate_backend(pid) from pg_stat_activity WHERE datname ='pgbundle_test'")
21
+ conn = PG.connect(dbname: 'postgres', user: 'postgres', host: 'localhost', port: ENV['PGPORT'] || 5432)
22
+ if ENV['PGVERSION']=='9.1'
23
+ conn.exec("SELECT pg_terminate_backend(procpid) from pg_stat_activity WHERE datname ='pgbundle_test'")
24
+ else
25
+ conn.exec("SELECT pg_terminate_backend(pid) from pg_stat_activity WHERE datname ='pgbundle_test'")
26
+ end
23
27
  conn.exec('DROP DATABASE IF EXISTS pgbundle_test')
24
28
  conn.close
25
29
  end
@@ -42,6 +46,6 @@ RSpec.configure do |config|
42
46
  end
43
47
 
44
48
  def database
45
- @db ||= PgBundle::Database.new('pgbundle_test')
49
+ @db ||= PgBundle::Database.new('pgbundle_test', user: 'postgres', host: 'localhost', port: ENV['PGPORT'] || 5432, use_sudo: ENV['TRAVIS'])
46
50
  end
47
51
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pgbundle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.15
4
+ version: 0.0.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Manuel Kniep
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-08 00:00:00.000000000 Z
11
+ date: 2015-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: zip
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: pg
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -82,34 +96,20 @@ dependencies:
82
96
  version: 2.14.0
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: bundler
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '1.5'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '1.5'
97
- - !ruby/object:Gem::Dependency
98
- name: rake
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - ">="
102
102
  - !ruby/object:Gem::Version
103
- version: '0'
103
+ version: 1.5.0
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
- version: '0'
110
+ version: 1.5.0
111
111
  - !ruby/object:Gem::Dependency
112
- name: pry
112
+ name: rake
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - ">="
@@ -137,14 +137,17 @@ files:
137
137
  - README.md
138
138
  - Rakefile
139
139
  - bin/pgbundle
140
+ - cibuild.sh
140
141
  - lib/pgbundle.rb
141
142
  - lib/pgbundle/base_source.rb
142
143
  - lib/pgbundle/database.rb
143
144
  - lib/pgbundle/definition.rb
144
145
  - lib/pgbundle/dsl.rb
145
146
  - lib/pgbundle/extension.rb
147
+ - lib/pgbundle/git_source.rb
146
148
  - lib/pgbundle/github_source.rb
147
149
  - lib/pgbundle/path_source.rb
150
+ - lib/pgbundle/pgxn_source.rb
148
151
  - lib/pgbundle/version.rb
149
152
  - pgbundle.gemspec
150
153
  - spec/Pgfile