pgbundle 0.0.13 → 0.1.0

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
- SHA1:
3
- metadata.gz: 99aec1b0d91ba3849d87a5b292084e7c9349d8f2
4
- data.tar.gz: 459f0a15b90b4f85f2d300da29fba1bd7479646e
2
+ SHA256:
3
+ metadata.gz: 15dfb28b9fdc9700e097a5a57391cf3177ddb20fbcb12535335df559429e2ce5
4
+ data.tar.gz: e961ee85cbea6731f1a8358f1291ac102ad39b15c7e7540cd323716bbedfde4b
5
5
  SHA512:
6
- metadata.gz: 3cc7c2f7a9aabd61863fe38b50939e2acb897cf5636b46cf3073c6a7d54d6b33e7dbddaecdf9577e1743e2e4824bb60dfa644cb542695c4feece06f3c810d4a5
7
- data.tar.gz: dc2163ec515938293c89c4e99b2409c6a56035785f7773b9d5e7f498cdc6cd0d6293363d630dfdbc742378390450d80da3a1558088a87a1d2f9766925f258c59
6
+ metadata.gz: d2f24410fe6dd3bedf3622786caac2ec63055f2bda8452b2445fe55261c222a9e40e09582d155e5b70518cbb3b2ad1be148905f0ed37027e1236f8f8edef48a7
7
+ data.tar.gz: 99bb423f0d8a0daac1c8b3f1cd4e45820e223260749793007a6d4b4034edaaa89e56c5c43f485f7d9c1b7b524a0f6e73673353c92cdf2b0855a59865e1da65e7
@@ -1,11 +1,19 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.9.3
4
3
  - rbx-2
5
4
  - 2.0.0
6
5
  - 2.1.1
6
+ - 2.2.3
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
@@ -0,0 +1,5 @@
1
+ # CHANGELOG
2
+
3
+ ## 0.1.0
4
+
5
+ * Added `password` option for database connection options
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,162 @@ 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
+ flags | optional string used for make results in "make flags && make flags install"
58
+
59
+
60
+ Some Extensions may require other Extensions to allow `pgbundle` to resolve dependencies
61
+ and install it in the right order you can define them with `requires`.
62
+ If the required Extension is not yet available on the target server or the Extension
63
+ requires a specific Version you should define it as well.
64
+ E.g.
65
+
66
+ ```
67
+ # Pgfile
68
+
69
+ database ...
70
+
71
+ pgx 'foo', '0.1.2', github: me/foo
72
+
73
+ # set foo as dependency for bar
74
+ pgx 'bar', '1.2.3', github: me/bar, requires: 'foo'
75
+
76
+ # set bar and boo as dependency for baz
77
+ # will automatically set foo as dependency as well
78
+ pgx 'baz', '0.2.3', github: me/baz, requires: ['bar', 'boo']
79
+ # installs jsquery with flag 'USE_PGXS=1'
80
+ # i.e. running make USE_PGXS=1 && make USE_PGXS=1 install
81
+ pgx 'jsquery', github: 'postgrespro/jsquery', flags: 'USE_PGXS=1'
82
+ ```
83
+
84
+ ## pgbundle commands
85
+
86
+ `pgbundle` comes with four commands. If the optional `pgfile` is not given it assumes
87
+ to find a file named `Pgfile` in the current directory.
88
+
89
+ **check**
90
+
91
+ checks availability of required extensions.
92
+
93
+ ```
94
+ pgbundle check [pgfile]
22
95
  ```
23
96
 
24
- ### install your extension
97
+ `check` does not change anything on your system, it only checks which
98
+ of your specified extensions are available and which are missing.
99
+ It returns with exitcode `1` if any Extension is missing and `0` otherwise.
25
100
 
26
- pgbundle install
27
101
 
28
- installs the extensions and dependencies on your database server
102
+ **install**
29
103
 
30
- ### check your dependencies
104
+ installs extensions
31
105
 
32
- pgbundle check
106
+ ```
107
+ pgbundle install [pgfile] [-f]
108
+ ```
109
+
110
+ `install` tries to install missing Extensions. If `--force` is given it installs
111
+ all Extension even if they are already installed.
112
+
113
+ **create**
114
+
115
+ create the extension at the desired version
116
+
117
+ ```
118
+ pgbundle create [pgfile]
119
+ ```
33
120
 
34
- checks whether all dependencies are available for creation on the database server
121
+ `create` runs the `CREATE EXTENSION` command on the specified databases. If a version
122
+ is specified in the `Pgfile` it tries to install with `CREATE EXTENSION VERSION version`.
123
+ If the Extension is already created but with a wrong version, it will run
124
+ `ALTER EXTENSION extension_name UPDATE TO new_version`.
35
125
 
36
- ## getting started
126
+ **init**
127
+
128
+ write an initial pgfile to stdout
129
+
130
+ ```
131
+ pgbundle init db_name -u user -h host -p port
132
+ ```
133
+
134
+ `init` is there to help you get started. If you have already a database with installed
135
+ Extensions you get the content for an initial `Pgfile`. Pgbundle will figure out
136
+ which Extension at which Version are already in use and print a reasonable starting
137
+ point for you Pgfile.
138
+ However this is only to help you get started you may wish to specify sources and
139
+ dependencies correctly.
140
+
141
+ ### How it works
142
+
143
+ You may already have noticed that using Extensions on postgres requires two different
144
+ steps. Building the extension on the database cluster with `make install`
145
+ and creating the extension into the database with `CREATE/ALTER EXTENSION`.
146
+ Pgbundle reflects that with the two different commands `install` and `create`.
147
+
148
+ Usually `pgbundle` runs along with your application on your application server
149
+ which often is different from your database machine. Thus the `install` step
150
+ will (if necessary) try to download the source code of the extension into a
151
+ temporary folder and then copy it to your database servers into `/tmp/pgbundle`.
152
+ From there it will run `make clean && make && make install` for each database.
153
+ You may specify as which user you want these commands to run with the `system_user`
154
+ option. Although for security reasons not recommended you can specify to run the
155
+ install step with sudo `use_sudo: true`, but we suggest to give write permission
156
+ for the postgres system user to the install targets. If you are not sure which these
157
+ are run
158
+
159
+ ```
160
+ pg_config
161
+ ```
37
162
 
38
- if your already have some database on your current project you can get a starting point with
163
+ and find the `LIBDIR`, `SHAREDIR` and `DOCDIR`
39
164
 
40
- pgbundle init
165
+ #### master - slave
41
166
 
42
- lets say your database named 'my_project' runs on localhost with user postges
167
+ Every serious production database cluster usually has a slave often ran as Hot Standby.
168
+ You should make sure that all your Extension are also installed on all slaves.
169
+ Because database slaves run as read-only servers any attempt to `CREATE` or `ALTER`
170
+ Extension will fail, these commands should only run on the master server and will
171
+ be replicated to the slave from there. You can tell `pgbundle` that it should skip
172
+ these steps with `slave: true`.
43
173
 
44
- pgbundle init my_project -u postgres -h localhost
45
174
 
@@ -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
@@ -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
@@ -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
@@ -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,29 +1,49 @@
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
7
7
  # on a typical environment ssh access is needed if the database host differs from
8
8
  # the Pgfile host
9
9
  class Database
10
- attr_accessor :name, :user, :host, :system_user, :use_sudo, :port, :force_ssh
10
+ attr_accessor :name, :user, :password, :host, :system_user, :use_sudo, :port, :force_ssh
11
+
11
12
  def initialize(name, opts = {})
12
13
  @name = name
13
14
  @user = opts[:user] || 'postgres'
15
+ @password = opts[:password]
14
16
  @host = opts[:host] || 'localhost'
15
17
  @use_sudo = opts[:use_sudo] || false
16
18
  @system_user = opts[:system_user] || 'postgres'
17
19
  @port = opts[:port] || 5432
18
20
  @force_ssh = opts[:force_ssh] || false
21
+ @slave = opts[:slave] || false
19
22
  end
20
23
 
21
24
  def connection
22
25
  @connection ||= begin
23
- PG.connect(dbname: name, user: user, host: host, port: port)
26
+ PG.connect(connection_opts)
24
27
  end
25
28
  end
26
29
 
30
+ def connection_opts
31
+ {
32
+ dbname: name,
33
+ user: user,
34
+ password: password,
35
+ host: host,
36
+ port: port
37
+ }.compact
38
+ end
39
+
40
+ def to_s
41
+ "host: #{@host}:#{port} db: #{@name}"
42
+ end
43
+
44
+ def slave?
45
+ @slave
46
+ end
27
47
  # executes the given sql on the database connections
28
48
  # redirects all noise to /dev/null
29
49
  def execute(sql)
@@ -52,19 +72,22 @@ module PgBundle
52
72
  end
53
73
 
54
74
  # loads the source, runs make install and removes the source afterwards
55
- def make_install(source, ext_name)
75
+ def make_install(source, ext_name, flags)
76
+ run("mkdir -p -m 0777 /tmp/pgbundle/")
56
77
  remove_source(ext_name)
57
78
  source.load(host, system_user, load_destination(ext_name))
58
- run(make_install_cmd(ext_name))
79
+ run(make_install_cmd(ext_name, flags))
59
80
  remove_source(ext_name)
81
+ source.clean
60
82
  end
61
83
 
62
84
  # loads the source and runs make uninstall
63
- def make_uninstall(source, ext_name)
85
+ def make_uninstall(source, ext_name, flags)
64
86
  remove_source(ext_name)
65
87
  source.load(host, system_user, load_destination(ext_name))
66
- run(make_uninstall_cmd(ext_name))
88
+ run(make_uninstall_cmd(ext_name, flags))
67
89
  remove_source(ext_name)
90
+ source.clean
68
91
  end
69
92
 
70
93
  def drop_extension(name)
@@ -90,17 +113,17 @@ module PgBundle
90
113
  run("rm -rf #{load_destination(name)}")
91
114
  end
92
115
 
93
- def make_install_cmd(name)
116
+ def make_install_cmd(name, flags)
94
117
  <<-CMD.gsub(/\s+/, ' ').strip
95
118
  cd #{load_destination(name)} &&
96
- #{sudo} make clean &&
97
- #{sudo} make &&
98
- #{sudo} make install
119
+ #{sudo} make #{flags} clean &&
120
+ #{sudo} make #{flags} &&
121
+ #{sudo} make #{flags} install
99
122
  CMD
100
123
  end
101
124
 
102
- def make_uninstall_cmd(name)
103
- "cd #{load_destination(name)} && #{sudo} make uninstall"
125
+ def make_uninstall_cmd(name, flags)
126
+ "cd #{load_destination(name)} && #{sudo} make #{flags} uninstall"
104
127
  end
105
128
 
106
129
  def run(cmd)
@@ -112,7 +135,8 @@ module PgBundle
112
135
  end
113
136
 
114
137
  def local(cmd)
115
- %x(#{cmd})
138
+ res = %x((#{cmd}) 2>&1 )
139
+ raise res unless $?.success?
116
140
  end
117
141
 
118
142
  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
@@ -4,13 +4,14 @@ module PgBundle
4
4
  class Dsl
5
5
  def initialize
6
6
  @definition = Definition.new
7
- @extensions = []
7
+ @databases = []
8
8
  end
9
9
 
10
10
  def eval_pgfile(pgfile, contents=nil)
11
11
  contents ||= File.read(pgfile.to_s)
12
12
  instance_eval(contents)
13
- @definition
13
+ raise PgfileError, "no databases defined" if @databases.size == 0
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)
@@ -10,11 +10,13 @@ module PgBundle
10
10
  # or install it along with it's dependencies
11
11
  # extension.install(database)
12
12
  class Extension
13
- attr_accessor :name, :version, :source, :resolving_dependencies
13
+ attr_accessor :name, :version, :source, :resolving_dependencies,:flags
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]
19
+ self.flags = opts[:flags]
18
20
  set_source(opts)
19
21
  end
20
22
 
@@ -108,7 +110,6 @@ module PgBundle
108
110
  unless dependencies.empty?
109
111
  install_dependencies(database, force)
110
112
  end
111
-
112
113
  make_install(database, force)
113
114
  raise ExtensionNotFound.new(name, version) unless installed?(database)
114
115
 
@@ -126,6 +127,7 @@ module PgBundle
126
127
  # create the extension along with it's dependencies in a transaction
127
128
  def create_with_dependencies(database)
128
129
  return true if created?(database)
130
+ return false if database.slave?
129
131
 
130
132
  database.transaction do |con|
131
133
  begin
@@ -181,6 +183,21 @@ module PgBundle
181
183
 
182
184
  private
183
185
 
186
+ # validates the options hash
187
+ def validate(opts)
188
+ opts = opts.clone
189
+ opts.delete(:requires)
190
+ opts.delete(:branch)
191
+ opts.delete(:flags)
192
+ if opts.size > 1
193
+ fail PgfileError.new "multiple sources given for #{name} #{opts}"
194
+ end
195
+
196
+ unless opts.empty? || [:path, :git, :github, :pgxn].include?(opts.keys.first)
197
+ fail PgfileError.new "invalid source #{opts.keys.first} for #{name}"
198
+ end
199
+ end
200
+
184
201
  # adds dependencies that are required but not defined yet
185
202
  def add_missing_required_dependencies(database)
186
203
  requires = requires(database)
@@ -218,12 +235,12 @@ module PgBundle
218
235
  # loads the source and runs make uninstall
219
236
  # returns: self
220
237
  def make_uninstall(database)
221
- database.make_uninstall(source, name)
238
+ database.make_uninstall(source, name, flags)
222
239
  self
223
240
  end
224
241
 
225
242
  def drop_extension(database)
226
- database.drop_extension(name)
243
+ database.drop_extension(name) unless database.slave?
227
244
  end
228
245
 
229
246
  # loads the source and runs make install
@@ -233,7 +250,7 @@ module PgBundle
233
250
 
234
251
  fail SourceNotFound, name if source.nil?
235
252
 
236
- database.make_install(source, name)
253
+ database.make_install(source, name, flags)
237
254
  self
238
255
  end
239
256
 
@@ -266,6 +283,7 @@ module PgBundle
266
283
 
267
284
  # hard checks that the dependency can be created running CREATE command in a transaction
268
285
  def creatable!(database)
286
+ return false if database.slave?
269
287
  database.transaction_rollback do |con|
270
288
  begin
271
289
  create_dependencies(con)
@@ -274,6 +292,8 @@ module PgBundle
274
292
  raise ExtensionNotFound.new(name, version)
275
293
  rescue PG::UndefinedObject => err
276
294
  raise MissingDependency.new(name, err.message)
295
+ rescue PG::ReadOnlySqlTransaction
296
+ raise ReadOnlyDb.new(database, name)
277
297
  end
278
298
  end
279
299
 
@@ -286,7 +306,7 @@ module PgBundle
286
306
  database.execute 'BEGIN'
287
307
  begin
288
308
  database.execute update_stmt
289
- rescue PG::UndefinedFile, PG::UndefinedObject => err
309
+ rescue PG::UndefinedFile, PG::UndefinedObject, PG::ReadOnlySqlTransaction => err
290
310
  @error = err.message
291
311
  result = false
292
312
  end
@@ -298,8 +318,12 @@ module PgBundle
298
318
  def set_source(opts)
299
319
  if opts[:path]
300
320
  @source = PathSource.new(opts[:path])
321
+ elsif opts[:git]
322
+ @source = GitSource.new(opts[:git], opts[:branch])
301
323
  elsif opts[:github]
302
324
  @source = GithubSource.new(opts[:github], opts[:branch])
325
+ elsif opts[:pgxn]
326
+ @source = PgxnSource.new(name, version)
303
327
  end
304
328
  end
305
329
  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.13'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -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 "rake"
29
- spec.add_development_dependency "pry"
28
+ spec.add_development_dependency "bundler", ">= 1.5.0"
29
+ spec.add_development_dependency "rake", "<= 11.0.0"
30
30
  end
@@ -1,7 +1,8 @@
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'
5
5
  pgx 'baz', '0.0.2', path: './spec/sample_extensions/baz', requires: 'foo'
6
6
  pgx 'foo', '0.0.1', path: './spec/sample_extensions/foo'
7
7
  pgx 'myext', github: 'adjust/numhstore', branch: 'topic'
8
+ pgx 'jsquery', github: 'postgrespro/jsquery', flags: 'USE_PGXS=1'
@@ -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
 
@@ -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.13
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Manuel Kniep
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-08 00:00:00.000000000 Z
11
+ date: 2020-08-13 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,46 +96,32 @@ 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
+ - - "<="
116
116
  - !ruby/object:Gem::Version
117
- version: '0'
117
+ version: 11.0.0
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - ">="
122
+ - - "<="
123
123
  - !ruby/object:Gem::Version
124
- version: '0'
124
+ version: 11.0.0
125
125
  description: bundler like postgres extension manager
126
126
  email:
127
127
  - manuel@adjust.com
@@ -132,19 +132,23 @@ extra_rdoc_files: []
132
132
  files:
133
133
  - ".gitignore"
134
134
  - ".travis.yml"
135
+ - CHANGELOG.md
135
136
  - Gemfile
136
137
  - LICENSE.txt
137
138
  - README.md
138
139
  - Rakefile
139
140
  - bin/pgbundle
141
+ - cibuild.sh
140
142
  - lib/pgbundle.rb
141
143
  - lib/pgbundle/base_source.rb
142
144
  - lib/pgbundle/database.rb
143
145
  - lib/pgbundle/definition.rb
144
146
  - lib/pgbundle/dsl.rb
145
147
  - lib/pgbundle/extension.rb
148
+ - lib/pgbundle/git_source.rb
146
149
  - lib/pgbundle/github_source.rb
147
150
  - lib/pgbundle/path_source.rb
151
+ - lib/pgbundle/pgxn_source.rb
148
152
  - lib/pgbundle/version.rb
149
153
  - pgbundle.gemspec
150
154
  - spec/Pgfile
@@ -169,7 +173,7 @@ homepage: http://github.com/adjust/pgbundle
169
173
  licenses:
170
174
  - MIT
171
175
  metadata: {}
172
- post_install_message:
176
+ post_install_message:
173
177
  rdoc_options: []
174
178
  require_paths:
175
179
  - lib
@@ -184,9 +188,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
184
188
  - !ruby/object:Gem::Version
185
189
  version: '0'
186
190
  requirements: []
187
- rubyforge_project:
188
- rubygems_version: 2.2.1
189
- signing_key:
191
+ rubygems_version: 3.1.2
192
+ signing_key:
190
193
  specification_version: 4
191
194
  summary: bundling postgres extension
192
195
  test_files: