pgbundle 0.0.15 → 0.0.16
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +10 -2
- data/README.md +138 -13
- data/bin/pgbundle +28 -20
- data/cibuild.sh +28 -0
- data/lib/pgbundle/base_source.rb +4 -0
- data/lib/pgbundle/database.rb +13 -2
- data/lib/pgbundle/definition.rb +2 -2
- data/lib/pgbundle/dsl.rb +3 -2
- data/lib/pgbundle/extension.rb +25 -3
- data/lib/pgbundle/git_source.rb +43 -0
- data/lib/pgbundle/github_source.rb +4 -30
- data/lib/pgbundle/path_source.rb +3 -0
- data/lib/pgbundle/pgxn_source.rb +61 -0
- data/lib/pgbundle/version.rb +1 -1
- data/lib/pgbundle.rb +16 -2
- data/pgbundle.gemspec +2 -2
- data/spec/Pgfile +1 -1
- data/spec/dsl_spec.rb +2 -2
- data/spec/extension_spec.rb +16 -0
- data/spec/spec_helper.rb +9 -5
- metadata +22 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c6ade210a8dde8dcdbdacc39434ba2fa11ff0e92
|
4
|
+
data.tar.gz: 7e364dac3b3dfa809a770f39661d4c32bf46f7bd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
11
|
-
|
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,
|
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
|
-
|
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
|
-
|
98
|
+
**install**
|
29
99
|
|
30
|
-
|
100
|
+
installs extensions
|
31
101
|
|
32
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
159
|
+
and find the `LIBDIR`, `SHAREDIR` and `DOCDIR`
|
39
160
|
|
40
|
-
|
161
|
+
#### master - slave
|
41
162
|
|
42
|
-
|
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 =
|
12
|
+
installed = definitions(pgfile).inject({}){|m, d| m[d.database] = d.install!; m}
|
14
13
|
else
|
15
|
-
|
16
|
-
|
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 =
|
20
|
+
installed = definitions(pgfile).inject({}){|m, d| m[d.database] = d.install; m}
|
20
21
|
end
|
21
22
|
|
22
|
-
installed.each do |
|
23
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
50
|
-
|
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
|
66
|
-
|
67
|
-
|
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
|
data/lib/pgbundle/base_source.rb
CHANGED
data/lib/pgbundle/database.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'pg'
|
2
2
|
require 'net/ssh'
|
3
|
-
|
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)
|
data/lib/pgbundle/definition.rb
CHANGED
@@ -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
|
-
@
|
28
|
+
@databases << Database.new(args.first, opts)
|
28
29
|
end
|
29
30
|
|
30
31
|
def pgx(*args)
|
data/lib/pgbundle/extension.rb
CHANGED
@@ -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 <
|
4
|
+
class GithubSource < GitSource
|
5
5
|
attr_reader :branch
|
6
6
|
|
7
7
|
def initialize(path, branch = 'master')
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
data/lib/pgbundle/path_source.rb
CHANGED
@@ -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
|
data/lib/pgbundle/version.rb
CHANGED
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", "
|
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
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.
|
7
|
+
its('database.host') { should eq 'localhost' }
|
8
8
|
its(:extensions) { should be_a Hash }
|
9
9
|
|
10
10
|
context 'parsing options' do
|
data/spec/extension_spec.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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.
|
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:
|
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:
|
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:
|
110
|
+
version: 1.5.0
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
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
|