schema-evolution-manager 0.9.24

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.
@@ -0,0 +1 @@
1
+ load File.join(File.dirname(__FILE__), '../lib/schema-evolution-manager.rb')
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env ruby
2
+ # == Creates a tarball containing SQL scripts that might need to be
3
+ # applied to a specific postgresql database. This tarball can then
4
+ # be uploaded, unpacked, and schema changes applied using
5
+ # schema-evolution-manager scripts
6
+ #
7
+ # == Usage
8
+ # sem-dist [--artifact_name <tag> --tag <tag>]
9
+ #
10
+ # name
11
+ # Optional artifact_name - defaults to the current directory name. Used in the
12
+ # actual artifact name which has the format <artifact_name>-<tag>.tar.gz
13
+ #
14
+ # tag
15
+ # Optional tag - if specified, we create a distribution file for this tag.
16
+ # If not specified, we create a new tag.
17
+ #
18
+ # == Example
19
+ # sem-dist
20
+ # Creates a new tag and generates the distribution file
21
+ #
22
+ # sem-dist --tag 0.0.1
23
+ # Creates a distribution file for the specified tag
24
+ #
25
+
26
+ load File.join(File.dirname(__FILE__), 'sem-config')
27
+ SchemaEvolutionManager::Library.set_verbose(true)
28
+
29
+ args = SchemaEvolutionManager::Args.from_stdin(:optional => ['artifact_name', 'tag'])
30
+
31
+ # On MAC OS X, use gnutar to avoid warnings like
32
+ # Ignoring unknown extended header keyword `SCHILY.ino'
33
+ tar_cmd = `which gnutar 2> /dev/null`.strip
34
+ if tar_cmd == ""
35
+ tar_cmd = "tar"
36
+ end
37
+
38
+ if args.tag
39
+ tag = args.tag
40
+ else
41
+ if latest = SchemaEvolutionManager::Library.latest_tag
42
+ suggested_tag = latest.next_micro.to_version_string
43
+ else
44
+ suggested_tag = "0.0.1"
45
+ end
46
+
47
+ tag = SchemaEvolutionManager::Ask.for_string("Version:", :default => suggested_tag)
48
+ if !SchemaEvolutionManager::Library.tag_exists?(tag)
49
+ SchemaEvolutionManager::Library.git_create_tag(tag)
50
+ end
51
+ end
52
+ SchemaEvolutionManager::Library.git_assert_tag_exists(tag)
53
+
54
+ changes = SchemaEvolutionManager::Library.git_changes(:tag => tag)
55
+ repo_path = SchemaEvolutionManager::Library.normalize_path(`pwd`.strip)
56
+
57
+ if args.artifact_name
58
+ artifact_name = args.artifact_name
59
+ else
60
+ artifact_name = File.basename(repo_path)
61
+ end
62
+
63
+ filename = "%s-%s" % [artifact_name, tag]
64
+
65
+ dist_dir = File.join(repo_path, "dist")
66
+ SchemaEvolutionManager::Library.ensure_dir!(dist_dir)
67
+ tarball = File.join(dist_dir, "#{filename}.tar")
68
+
69
+ SchemaEvolutionManager::Library.with_temp_file do |tmp|
70
+ tmpdir = File.join(tmp, filename)
71
+ SchemaEvolutionManager::Library.ensure_dir!(tmpdir)
72
+
73
+ SchemaEvolutionManager::Library.system_or_error("cp -R %s %s" % [File.join(repo_path, "scripts"), File.join(tmpdir, "scripts")])
74
+ File.open(File.join(tmpdir, "CHANGES"), "w") { |out| out << changes }
75
+
76
+ Dir.chdir(tmp) do
77
+ SchemaEvolutionManager::Library.system_or_error("#{tar_cmd} cf #{filename}.tar #{filename}")
78
+ FileUtils.cp("#{filename}.tar", tarball)
79
+ end
80
+ end
81
+
82
+ gzip_file = "#{tarball}.gz"
83
+ if File.exists?(gzip_file)
84
+ puts "Removing old gzip file at #{gzip_file}"
85
+ FileUtils.rm(gzip_file)
86
+ end
87
+
88
+ command = "gzip #{tarball}"
89
+ SchemaEvolutionManager::Library.system_or_error(command)
90
+
91
+ puts ""
92
+ puts "Created distribution file at #{gzip_file}"
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+ # Usage
3
+ #
4
+ # sem-info command [arg1...]
5
+ #
6
+ # sem-info tag exists 1.2.3
7
+ # Returns true if the specified tag exists,
8
+ # false otherwise
9
+ #
10
+ # sem-info tag latest
11
+ # Display the latest tag
12
+ #
13
+ # sem-info version
14
+ # Display the version of schema-evolution-manager
15
+ #
16
+ # sem-info tag next [major|minor|micro]
17
+ # Output information on the next tag. Defaults to micro
18
+ # version increment from latest tag
19
+ #
20
+ # Description
21
+ #
22
+ # Utility script to pull information from the schema-evolution-manager
23
+ # libraries. Initially designed to help with wrapper scripts that
24
+ # needed information (like the next minor tag)
25
+ #
26
+
27
+ load File.join(File.dirname(__FILE__), 'sem-config')
28
+
29
+ command = ARGV.shift.to_s.strip
30
+
31
+ if command == "tag"
32
+ puts SchemaEvolutionManager::SemInfo.tag(ARGV)
33
+
34
+ elsif command == "version"
35
+ puts SchemaEvolutionManager::SemInfo.version(ARGV)
36
+
37
+ else
38
+ if command != ""
39
+ puts "ERROR: Unrecognized command[%s]" % command
40
+ puts ""
41
+ end
42
+ SchemaEvolutionManager::RdocUsage.printAndExit(1)
43
+ end
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env ruby
2
+ # == Initializes an existing git repository to support schema evolutions managed
3
+ # by schema-evolution-manager
4
+ #
5
+ # == Usage
6
+ # sem-init --dir <dir> --url <url>
7
+ # or
8
+ # sem-init --dir <dir> --host <database host> --user <db user> --name <db name>
9
+ #
10
+ # dir: The directory containing a git repository that will contain the schema evolution files
11
+ # url: The connection string for the psql database
12
+ #
13
+ # == Example
14
+ # git init /tmp/example_repo
15
+ # bin/sem-init --dir /tmp/example_repo --url postgresql://username@localhost/sample
16
+ #
17
+
18
+ load File.join(File.dirname(__FILE__), 'sem-config')
19
+ SchemaEvolutionManager::Library.set_verbose(true)
20
+
21
+ args = SchemaEvolutionManager::Args.from_stdin(:required => %w(dir), :optional => %w(url host port name user))
22
+ SchemaEvolutionManager::Preconditions.check_state(File.directory?(args.dir), "Dir[%s] does not exist" % args.dir)
23
+
24
+ if args.url.nil? && args.name.nil?
25
+ puts "Must specify either url or name"
26
+ exit(1)
27
+ end
28
+
29
+ db = SchemaEvolutionManager::Db.from_args(args)
30
+
31
+ def copy_file(source, target, substitutions)
32
+ template = SchemaEvolutionManager::Template.new
33
+ substitutions.each do |name, value|
34
+ template.add(name, value)
35
+ end
36
+ final = template.parse(IO.read(source))
37
+ File.open(target, "w") do |os|
38
+ os << final
39
+ end
40
+ end
41
+
42
+ template_dir = File.join(SchemaEvolutionManager::Library.base_dir, "template")
43
+ SchemaEvolutionManager::Preconditions.check_state(File.directory?(template_dir),
44
+ "Could not find schema-evolution-manager/template subdir. Expected it at[%s]" % [template_dir])
45
+
46
+ subs = {
47
+ "url" => db.url,
48
+ "add_script_path" => File.join(SchemaEvolutionManager::Library.base_dir, "bin/sem-add")
49
+ }
50
+
51
+ puts "chdir %s" % args.dir
52
+ Dir.chdir(args.dir) do
53
+
54
+ wrappers = []
55
+ Dir.glob("#{template_dir}/*").each do |path|
56
+ wrapper = File.basename(path)
57
+ if !File.exists?(wrapper)
58
+ puts "Creating wrapper script #{wrapper}"
59
+ copy_file(path, wrapper, subs)
60
+ SchemaEvolutionManager::Library.system_or_error("chmod +x #{wrapper}")
61
+ SchemaEvolutionManager::Library.system_or_error("git add #{wrapper}")
62
+ wrappers << wrapper
63
+ end
64
+ end
65
+ if !wrappers.empty?
66
+ SchemaEvolutionManager::Library.system_or_error("git commit -m 'Add schema-evolution-manager wrapper scripts' #{wrappers.join(" ")}")
67
+ end
68
+
69
+ readme = "README.md"
70
+ if !File.exists?(readme)
71
+ puts "Creating #{readme}"
72
+ copy_file("schema-evolution-manager/template/README.md", "README.md", subs)
73
+ SchemaEvolutionManager::Library.system_or_error("git add #{readme}")
74
+ SchemaEvolutionManager::Library.system_or_error("git commit -m 'Adding README.md' README.md")
75
+ end
76
+
77
+ if !File.exists?("scripts")
78
+ SchemaEvolutionManager::Library.system_or_error("mkdir scripts")
79
+ SchemaEvolutionManager::Library.system_or_error("touch scripts/.exists")
80
+ SchemaEvolutionManager::Library.system_or_error("git add scripts/.exists")
81
+ SchemaEvolutionManager::Library.system_or_error("git commit -m 'Adding scripts directory' scripts/.exists")
82
+ end
83
+
84
+ # Create the first git tag if necessary
85
+ tags = SchemaEvolutionManager::Library.system_or_error("git tag -l")
86
+ if tags.nil? || tags == ""
87
+ puts "Creating initial git tag (0.0.1)"
88
+ SchemaEvolutionManager::Library.git_create_tag("0.0.1")
89
+ end
90
+
91
+ if SchemaEvolutionManager::Library.git_has_remote?
92
+ SchemaEvolutionManager::Library.system_or_error("git push origin master")
93
+ end
94
+
95
+ end
@@ -0,0 +1,26 @@
1
+ require 'fileutils'
2
+ require 'pathname'
3
+
4
+ dir = File.dirname(__FILE__)
5
+ lib_dir = File.join(dir, "schema-evolution-manager")
6
+
7
+ load File.join(lib_dir, 'preconditions.rb')
8
+ load File.join(lib_dir, 'rdoc_usage.rb')
9
+ load File.join(lib_dir, 'library.rb')
10
+
11
+ # Need to set base_dir early - version.rb depends on dir being set
12
+ SchemaEvolutionManager::Library.set_base_dir(File.join(dir, '..'))
13
+
14
+ load File.join(lib_dir, 'sem_version.rb')
15
+ load File.join(lib_dir, 'ask.rb')
16
+ load File.join(lib_dir, 'version.rb')
17
+ load File.join(lib_dir, 'args.rb')
18
+ load File.join(lib_dir, 'scripts.rb')
19
+ load File.join(lib_dir, 'db.rb')
20
+ load File.join(lib_dir, 'apply_util.rb')
21
+ load File.join(lib_dir, 'baseline_util.rb')
22
+ load File.join(lib_dir, 'template.rb')
23
+ load File.join(lib_dir, 'install_template.rb')
24
+ load File.join(lib_dir, 'script_error.rb')
25
+ load File.join(lib_dir, 'sem_info.rb')
26
+ load File.join(lib_dir, 'migration_file.rb')
@@ -0,0 +1,60 @@
1
+ module SchemaEvolutionManager
2
+
3
+ class ApplyUtil
4
+
5
+ def initialize(db, opts={})
6
+ @dry_run = opts.delete(:dry_run)
7
+ if @dry_run.nil?
8
+ @dry_run = true
9
+ end
10
+
11
+ @db = Preconditions.assert_class(db, Db)
12
+ @scripts = Scripts.new(@db, Scripts::SCRIPTS)
13
+ end
14
+
15
+ def dry_run?
16
+ @dry_run
17
+ end
18
+
19
+ # Applies scripts in order, returning number of scripts applied
20
+ def apply!(dir)
21
+ Preconditions.check_state(File.directory?(dir),
22
+ "Dir[%s] does not exist" % dir)
23
+
24
+ count = 0
25
+ @scripts.each_pending(dir) do |filename, path|
26
+ count += 1
27
+ if @dry_run
28
+ puts "[DRY RUN] Applying #{filename}"
29
+ apply_dry_run(filename, path)
30
+ else
31
+ puts "Applying #{filename}"
32
+ apply_real(filename, path)
33
+ end
34
+ end
35
+ count
36
+ end
37
+
38
+ private
39
+ def apply_dry_run(filename, path)
40
+ puts path
41
+ puts ""
42
+ end
43
+
44
+ def apply_real(filename, path)
45
+ have_error = true
46
+ begin
47
+ @db.psql_file(path)
48
+ have_error = false
49
+ ensure
50
+ if have_error
51
+ raise ScriptError.new(@db, filename)
52
+ end
53
+ end
54
+
55
+ @scripts.record_as_run!(filename)
56
+ end
57
+
58
+ end
59
+
60
+ end
@@ -0,0 +1,159 @@
1
+ module SchemaEvolutionManager
2
+
3
+ # Container for common args, mainly to have stricter validation on
4
+ # inputs. Tried to use GetoptLong but could not write solid unit
5
+ # tests around it... so we have our own internal simple implementation.
6
+ class Args
7
+
8
+ if !defined?(FLAGS_WITH_ARGUMENTS)
9
+ FLAGS_WITH_ARGUMENTS = {
10
+ :artifact_name => "Specifies the name of the artifact. Tag will be appeneded to this name",
11
+ :user => "Connect to the database as this username instead of the default",
12
+ :host => "Specifies the host name of the machine on which the server is running",
13
+ :port => "Specifies the port on which the server is running",
14
+ :name => "Specifies the name of the database to which to connect",
15
+ :url => "Connect to the database as this username instead of the default",
16
+ :dir => "Path to a directory",
17
+ :tag => "A git tag (e.g. 0.0.1)",
18
+ :prefix => "Configure installer to use this prefix"
19
+ }
20
+
21
+ FLAGS_NO_ARGUMENTS = {
22
+ :dry_run => "Include flag to echo commands that will run without actually executing them",
23
+ :help => "Display help",
24
+ :verbose => "Enable verbose logging of all system calls",
25
+ }
26
+ end
27
+
28
+ attr_reader :artifact_name, :host, :port, :name, :prefix, :url, :user, :dir, :dry_run, :tag
29
+
30
+ # args: Actual string arguments
31
+ # :required => list of parameters that are required
32
+ # :optional => list of parameters that are optional
33
+ def initialize(args, opts={})
34
+ Preconditions.assert_class_or_nil(args, String)
35
+ required = (opts.delete(:required) || []).map { |flag| format_flag(flag) }
36
+ optional = (opts.delete(:optional) || []).map { |flag| format_flag(flag) }
37
+ Preconditions.assert_class(required, Array)
38
+ Preconditions.assert_class(optional, Array)
39
+ Preconditions.assert_empty_opts(opts)
40
+ Preconditions.check_state(optional.size + required.size > 0,
41
+ "Must have at least 1 optional or required parameter")
42
+
43
+ if !optional.include?(:help)
44
+ optional << :help
45
+ end
46
+ if !optional.include?(:verbose)
47
+ optional << :verbose
48
+ end
49
+
50
+ found_arguments = parse_string_arguments(args)
51
+ missing = required.select { |field| blank?(found_arguments[field]) }
52
+
53
+ @artifact_name = found_arguments.delete(:artifact_name)
54
+ @host = found_arguments.delete(:host)
55
+ @port = found_arguments.delete(:port)
56
+ @name = found_arguments.delete(:name)
57
+ @prefix = found_arguments.delete(:prefix)
58
+ @url = found_arguments.delete(:url)
59
+ @user = found_arguments.delete(:user)
60
+ @dir = found_arguments.delete(:dir)
61
+ @tag = found_arguments.delete(:tag)
62
+
63
+ @dry_run = found_arguments.delete(:dry_run)
64
+ @help = found_arguments.delete(:help)
65
+ @verbose = found_arguments.delete(:verbose)
66
+
67
+ Preconditions.check_state(found_arguments.empty?,
68
+ "Did not handle all flags: %s" % found_arguments.keys.join(" "))
69
+
70
+ if @help
71
+ RdocUsage.printAndExit(0)
72
+ end
73
+
74
+ if @verbose
75
+ Library.set_verbose(true)
76
+ end
77
+
78
+ if !missing.empty?
79
+ missing_fields_error(required, optional, missing)
80
+ end
81
+ end
82
+
83
+ # Hack to minimize bleeding from STDIN. Returns an instance of Args class
84
+ def Args.from_stdin(opts)
85
+ values = ARGV.join(" ")
86
+ Args.new(values, opts)
87
+ end
88
+
89
+ private
90
+ def blank?(value)
91
+ value.to_s.strip == ""
92
+ end
93
+
94
+ def missing_fields_error(required, optional, fields)
95
+ Preconditions.assert_class(fields, Array)
96
+ Preconditions.check_state(!fields.empty?, "Missing fields cannot be empty")
97
+
98
+ title = fields.size == 1 ? "Missing parameter" : "Missing parameters"
99
+ sorted = fields.sort_by { |f| f.to_s }
100
+
101
+ puts "**************************************************"
102
+ puts "ERROR: #{title}: #{sorted.join(", ")}"
103
+ puts "**************************************************"
104
+ puts help_parameters("Required parameters", required)
105
+ puts help_parameters("Optional parameters", optional)
106
+ exit(1)
107
+ end
108
+
109
+ def help_parameters(title, parameters)
110
+ docs = []
111
+ if !parameters.empty?
112
+ docs << ""
113
+ docs << title
114
+ docs << "-------------------"
115
+ parameters.each do |flag|
116
+ documentation = FLAGS_WITH_ARGUMENTS[flag] || FLAGS_NO_ARGUMENTS[flag]
117
+ Preconditions.check_not_null(documentation, "No documentation found for flag[%s]" % flag)
118
+ docs << " --#{flag}"
119
+ docs << " " + documentation
120
+ docs << ""
121
+ end
122
+ end
123
+ docs.join("\n")
124
+ end
125
+
126
+
127
+ def parse_string_arguments(args)
128
+ Preconditions.assert_class_or_nil(args, String)
129
+ found = {}
130
+ index = 0
131
+ values = args.to_s.strip.split(/\s+/)
132
+ while index < values.length do
133
+ flag = format_flag(values[index])
134
+ index += 1
135
+
136
+ if FLAGS_WITH_ARGUMENTS.has_key?(flag)
137
+ found[flag] = values[index]
138
+ index += 1
139
+
140
+ elsif FLAGS_NO_ARGUMENTS.has_key?(flag)
141
+ found[flag] = true
142
+
143
+ else
144
+ raise "Unknown flag[%s]" % flag
145
+ end
146
+
147
+ end
148
+ found
149
+ end
150
+
151
+ # Strip leading dashes and convert to symbol
152
+ def format_flag(flag)
153
+ Preconditions.assert_class(flag, String)
154
+ flag.sub(/^\-\-/, '').to_sym
155
+ end
156
+
157
+ end
158
+
159
+ end