pgbundle 0.0.13 → 0.1.0

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