pgbundle 0.0.15 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|
+
[](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
|