schema-evolution-manager 0.9.24

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