pg_online_schema_change 0.1.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Binary file
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "thor"
2
4
 
3
5
  module PgOnlineSchemaChange
@@ -25,7 +27,7 @@ module PgOnlineSchemaChange
25
27
  def perform
26
28
  client_options = Struct.new(*options.keys.map(&:to_sym)).new(*options.values)
27
29
 
28
- PgOnlineSchemaChange.logger = client_options.verbose
30
+ PgOnlineSchemaChange.logger(verbose: client_options.verbose)
29
31
  PgOnlineSchemaChange::Orchestrate.run!(client_options)
30
32
  end
31
33
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "pg"
2
4
 
3
5
  module PgOnlineSchemaChange
@@ -16,7 +18,9 @@ module PgOnlineSchemaChange
16
18
  @drop = options.drop
17
19
  @kill_backends = options.kill_backends
18
20
  @wait_time_for_lock = options.wait_time_for_lock
21
+
19
22
  handle_copy_statement(options.copy_statement)
23
+ handle_validations
20
24
 
21
25
  @connection = PG.connect(
22
26
  dbname: @dbname,
@@ -26,17 +30,19 @@ module PgOnlineSchemaChange
26
30
  port: @port,
27
31
  )
28
32
 
29
- raise Error, "Not a valid ALTER statement: #{@alter_statement}" unless Query.alter_statement?(@alter_statement)
30
-
31
- unless Query.same_table?(@alter_statement)
32
- raise Error "All statements should belong to the same table: #{@alter_statement}"
33
- end
34
-
35
33
  @table = Query.table(@alter_statement)
36
34
 
37
35
  PgOnlineSchemaChange.logger.debug("Connection established")
38
36
  end
39
37
 
38
+ def handle_validations
39
+ raise Error, "Not a valid ALTER statement: #{@alter_statement}" unless Query.alter_statement?(@alter_statement)
40
+
41
+ return if Query.same_table?(@alter_statement)
42
+
43
+ raise Error "All statements should belong to the same table: #{@alter_statement}"
44
+ end
45
+
40
46
  def handle_copy_statement(statement)
41
47
  return if statement.nil? || statement == ""
42
48
 
@@ -1,4 +1,6 @@
1
- FUNC_FIX_SERIAL_SEQUENCE = <<~SQL.freeze
1
+ # frozen_string_literal: true
2
+
3
+ FUNC_FIX_SERIAL_SEQUENCE = <<~SQL
2
4
  CREATE OR REPLACE FUNCTION fix_serial_sequence(_table regclass, _newtable text)
3
5
  RETURNS void AS
4
6
  $func$
@@ -35,7 +37,7 @@ FUNC_FIX_SERIAL_SEQUENCE = <<~SQL.freeze
35
37
  $func$ LANGUAGE plpgsql VOLATILE;
36
38
  SQL
37
39
 
38
- FUNC_CREATE_TABLE_ALL = <<~SQL.freeze
40
+ FUNC_CREATE_TABLE_ALL = <<~SQL
39
41
  CREATE OR REPLACE FUNCTION create_table_all(source_table text, newsource_table text)
40
42
  RETURNS void language plpgsql
41
43
  as $$
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PgOnlineSchemaChange
2
4
  module Helper
3
5
  def primary_key
@@ -15,7 +17,14 @@ module PgOnlineSchemaChange
15
17
  result = Store.send(:get, method)
16
18
  return result if result
17
19
 
18
- raise ArgumentError, "Method `#{method}` doesn't exist."
20
+ super
21
+ end
22
+
23
+ def respond_to_missing?(method_name, *args)
24
+ result = Store.send(:get, method)
25
+ return true if result
26
+
27
+ super
19
28
  end
20
29
  end
21
30
  end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "securerandom"
2
4
 
3
5
  module PgOnlineSchemaChange
4
6
  class Orchestrate
5
- SWAP_STATEMENT_TIMEOUT = "5s".freeze
7
+ SWAP_STATEMENT_TIMEOUT = "5s"
6
8
 
7
9
  extend Helper
8
10
 
@@ -21,12 +23,20 @@ module PgOnlineSchemaChange
21
23
  Query.run(client.connection, FUNC_FIX_SERIAL_SEQUENCE)
22
24
  Query.run(client.connection, FUNC_CREATE_TABLE_ALL)
23
25
 
26
+ setup_store
27
+ end
28
+
29
+ def setup_store
24
30
  # Set this early on to ensure their creation and cleanup (unexpected)
25
31
  # happens at all times. IOW, the calls from Store.get always return
26
32
  # the same value.
27
33
  Store.set(:old_primary_table, "pgosc_op_table_#{client.table}")
28
- Store.set(:audit_table, "pgosc_at_#{client.table}_#{random_string}")
29
- Store.set(:shadow_table, "pgosc_st_#{client.table}_#{random_string}")
34
+ Store.set(:audit_table, "pgosc_at_#{client.table}_#{pgosc_identifier}")
35
+ Store.set(:operation_type_column, "operation_type_#{pgosc_identifier}")
36
+ Store.set(:trigger_time_column, "trigger_time_#{pgosc_identifier}")
37
+ Store.set(:audit_table_pk, "at_#{pgosc_identifier}_id")
38
+ Store.set(:audit_table_pk_sequence, "#{audit_table}_#{audit_table_pk}_seq")
39
+ Store.set(:shadow_table, "pgosc_st_#{client.table}_#{pgosc_identifier}")
30
40
  end
31
41
 
32
42
  def run!(options)
@@ -37,10 +47,10 @@ module PgOnlineSchemaChange
37
47
 
38
48
  setup_audit_table!
39
49
  setup_trigger!
40
- setup_shadow_table!
41
- disable_vacuum!
42
- run_alter_statement!
43
- copy_data!
50
+ setup_shadow_table! # re-uses transaction with serializable
51
+ disable_vacuum! # re-uses transaction with serializable
52
+ run_alter_statement! # re-uses transaction with serializable
53
+ copy_data! # re-uses transaction with serializable
44
54
  run_analyze!
45
55
  replay_and_swap!
46
56
  run_analyze!
@@ -70,7 +80,7 @@ module PgOnlineSchemaChange
70
80
  reader = setup_signals!
71
81
  signal = reader.gets.chomp
72
82
 
73
- while !reader.closed? && IO.select([reader])
83
+ while !reader.closed? && IO.select([reader]) # rubocop:disable Lint/UnreachableLoop
74
84
  logger.info "Signal #{signal} received, cleaning up"
75
85
 
76
86
  client.connection.cancel
@@ -85,7 +95,7 @@ module PgOnlineSchemaChange
85
95
  logger.info("Setting up audit table", { audit_table: audit_table })
86
96
 
87
97
  sql = <<~SQL
88
- CREATE TABLE #{audit_table} (operation_type text, trigger_time timestamp, LIKE #{client.table});
98
+ CREATE TABLE #{audit_table} (#{audit_table_pk} SERIAL PRIMARY KEY, #{operation_type_column} text, #{trigger_time_column} timestamp, LIKE #{client.table});
89
99
  SQL
90
100
 
91
101
  Query.run(client.connection, sql)
@@ -109,13 +119,13 @@ module PgOnlineSchemaChange
109
119
  $$
110
120
  BEGIN
111
121
  IF ( TG_OP = 'INSERT') THEN
112
- INSERT INTO \"#{audit_table}\" select 'INSERT', now(), NEW.* ;
122
+ INSERT INTO \"#{audit_table}\" select nextval(\'#{audit_table_pk_sequence}\'), 'INSERT', clock_timestamp(), NEW.* ;
113
123
  RETURN NEW;
114
124
  ELSIF ( TG_OP = 'UPDATE') THEN
115
- INSERT INTO \"#{audit_table}\" select 'UPDATE', now(), NEW.* ;
125
+ INSERT INTO \"#{audit_table}\" select nextval(\'#{audit_table_pk_sequence}\'), 'UPDATE', clock_timestamp(), NEW.* ;
116
126
  RETURN NEW;
117
127
  ELSIF ( TG_OP = 'DELETE') THEN
118
- INSERT INTO \"#{audit_table}\" select 'DELETE', now(), OLD.* ;
128
+ INSERT INTO \"#{audit_table}\" select nextval(\'#{audit_table_pk_sequence}\'), 'DELETE', clock_timestamp(), OLD.* ;
119
129
  RETURN NEW;
120
130
  END IF;
121
131
  END;
@@ -132,18 +142,28 @@ module PgOnlineSchemaChange
132
142
  end
133
143
 
134
144
  def setup_shadow_table!
145
+ # re-uses transaction with serializable
146
+ # This ensures that all queries from here till copy_data run with serializable.
147
+ # This is to to ensure that once the trigger is added to the primay table
148
+ # and contents being copied into the shadow, after a delete all on audit table,
149
+ # any replaying of rows that happen next from audit table do not contain
150
+ # any duplicates. We are ensuring there are no race conditions between
151
+ # adding the trigger, till the copy ends, since they all happen in the
152
+ # same serializable transaction.
153
+ Query.run(client.connection, "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE", true)
135
154
  logger.info("Setting up shadow table", { shadow_table: shadow_table })
136
155
 
137
- Query.run(client.connection, "SELECT create_table_all('#{client.table}', '#{shadow_table}');")
156
+ Query.run(client.connection, "SELECT create_table_all('#{client.table}', '#{shadow_table}');", true)
138
157
 
139
158
  # update serials
140
- Query.run(client.connection, "SELECT fix_serial_sequence('#{client.table}', '#{shadow_table}');")
159
+ Query.run(client.connection, "SELECT fix_serial_sequence('#{client.table}', '#{shadow_table}');", true)
141
160
  end
142
161
 
143
- # Disabling vacuum to avoid any issues during the process
144
162
  def disable_vacuum!
145
- result = Query.storage_parameters_for(client, client.table) || ""
146
- primary_table_storage_parameters = Store.set(:primary_table_storage_parameters, result)
163
+ # re-uses transaction with serializable
164
+ # Disabling vacuum to avoid any issues during the process
165
+ result = Query.storage_parameters_for(client, client.table, true) || ""
166
+ Store.set(:primary_table_storage_parameters, result)
147
167
 
148
168
  logger.debug("Disabling vacuum on shadow and audit table",
149
169
  { shadow_table: shadow_table, audit_table: audit_table })
@@ -156,32 +176,38 @@ module PgOnlineSchemaChange
156
176
  autovacuum_enabled = false, toast.autovacuum_enabled = false
157
177
  );
158
178
  SQL
159
- Query.run(client.connection, sql)
179
+ Query.run(client.connection, sql, true)
160
180
  end
161
181
 
162
182
  def run_alter_statement!
183
+ # re-uses transaction with serializable
163
184
  statement = Query.alter_statement_for(client, shadow_table)
164
185
  logger.info("Running alter statement on shadow table",
165
186
  { shadow_table: shadow_table, parent_table: client.table })
166
- Query.run(client.connection, statement)
187
+ Query.run(client.connection, statement, true)
167
188
 
168
189
  Store.set(:dropped_columns_list, Query.dropped_columns(client))
169
190
  Store.set(:renamed_columns_list, Query.renamed_columns(client))
170
191
  end
171
192
 
172
- # Begin the process to copy data into copy table
173
- # depending on the size of the table, this can be a time
174
- # taking operation.
175
193
  def copy_data!
176
- logger.info("Copying contents..", { shadow_table: shadow_table, parent_table: client.table })
194
+ # re-uses transaction with serializable
195
+ # Begin the process to copy data into copy table
196
+ # depending on the size of the table, this can be a time
197
+ # taking operation.
198
+ logger.info("Clearing contents of audit table before copy..", { shadow_table: shadow_table, parent_table: client.table })
199
+ Query.run(client.connection, "DELETE FROM #{audit_table}", true)
177
200
 
201
+ logger.info("Copying contents..", { shadow_table: shadow_table, parent_table: client.table })
178
202
  if client.copy_statement
179
203
  query = format(client.copy_statement, shadow_table: shadow_table)
180
- return Query.run(client.connection, query)
204
+ return Query.run(client.connection, query, true)
181
205
  end
182
206
 
183
207
  sql = Query.copy_data_statement(client, shadow_table)
184
- Query.run(client.connection, sql)
208
+ Query.run(client.connection, sql, true)
209
+ ensure
210
+ Query.run(client.connection, "COMMIT;") # commit the serializable transaction
185
211
  end
186
212
 
187
213
  def replay_and_swap!
@@ -255,8 +281,10 @@ module PgOnlineSchemaChange
255
281
  Query.run(client.connection, sql)
256
282
  end
257
283
 
258
- private def random_string
259
- @random_string ||= SecureRandom.hex(3)
284
+ private
285
+
286
+ def pgosc_identifier
287
+ @pgosc_identifier ||= SecureRandom.hex(3)
260
288
  end
261
289
  end
262
290
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "pg_query"
2
4
  require "pg"
3
5
 
@@ -5,7 +7,7 @@ module PgOnlineSchemaChange
5
7
  class Query
6
8
  extend Helper
7
9
 
8
- INDEX_SUFFIX = "_pgosc".freeze
10
+ INDEX_SUFFIX = "_pgosc"
9
11
  DROPPED_COLUMN_TYPE = :AT_DropColumn
10
12
  RENAMED_COLUMN_TYPE = :AT_RenameColumn
11
13
  LOCK_ATTEMPT = 4
@@ -15,28 +17,28 @@ module PgOnlineSchemaChange
15
17
  PgQuery.parse(query).tree.stmts.all? do |statement|
16
18
  statement.stmt.alter_table_stmt.instance_of?(PgQuery::AlterTableStmt) || statement.stmt.rename_stmt.instance_of?(PgQuery::RenameStmt)
17
19
  end
18
- rescue PgQuery::ParseError => e
20
+ rescue PgQuery::ParseError
19
21
  false
20
22
  end
21
23
 
22
24
  def same_table?(query)
23
- tables = PgQuery.parse(query).tree.stmts.map do |statement|
25
+ tables = PgQuery.parse(query).tree.stmts.filter_map do |statement|
24
26
  if statement.stmt.alter_table_stmt.instance_of?(PgQuery::AlterTableStmt)
25
27
  statement.stmt.alter_table_stmt.relation.relname
26
28
  elsif statement.stmt.rename_stmt.instance_of?(PgQuery::RenameStmt)
27
29
  statement.stmt.rename_stmt.relation.relname
28
30
  end
29
- end.compact
31
+ end
30
32
 
31
33
  tables.uniq.count == 1
32
- rescue PgQuery::ParseError => e
34
+ rescue PgQuery::ParseError
33
35
  false
34
36
  end
35
37
 
36
38
  def table(query)
37
- from_rename_statement = PgQuery.parse(query).tree.stmts.map do |statement|
39
+ from_rename_statement = PgQuery.parse(query).tree.stmts.filter_map do |statement|
38
40
  statement.stmt.rename_stmt&.relation&.relname
39
- end.compact[0]
41
+ end[0]
40
42
  PgQuery.parse(query).tables[0] || from_rename_statement
41
43
  end
42
44
 
@@ -48,7 +50,7 @@ module PgOnlineSchemaChange
48
50
  connection.async_exec("BEGIN;")
49
51
 
50
52
  result = connection.async_exec(query, &block)
51
- rescue Exception
53
+ rescue Exception # rubocop:disable Lint/RescueException
52
54
  connection.cancel if connection.transaction_status != PG::PQTRANS_IDLE
53
55
  connection.block
54
56
  logger.info("Exception raised, rolling back query", { rollback: true, query: query })
@@ -60,7 +62,7 @@ module PgOnlineSchemaChange
60
62
  result
61
63
  end
62
64
 
63
- def table_columns(client, table = nil)
65
+ def table_columns(client, table = nil, reuse_trasaction = false)
64
66
  sql = <<~SQL
65
67
  SELECT attname as column_name, format_type(atttypid, atttypmod) as type, attnum as column_position FROM pg_attribute
66
68
  WHERE attrelid = \'#{table || client.table}\'::regclass AND attnum > 0 AND NOT attisdropped
@@ -68,7 +70,7 @@ module PgOnlineSchemaChange
68
70
  SQL
69
71
  mapped_columns = []
70
72
 
71
- run(client.connection, sql) do |result|
73
+ run(client.connection, sql, reuse_trasaction) do |result|
72
74
  mapped_columns = result.map do |row|
73
75
  row["column_name_regular"] = row["column_name"]
74
76
  row["column_name"] = client.connection.quote_ident(row["column_name"])
@@ -144,11 +146,11 @@ module PgOnlineSchemaChange
144
146
  end
145
147
 
146
148
  references.map do |row|
147
- if row["definition"].end_with?("NOT VALID")
148
- add_statement = "ALTER TABLE #{row["table_on"]} ADD CONSTRAINT #{row["constraint_name"]} #{row["definition"]};"
149
- else
150
- add_statement = "ALTER TABLE #{row["table_on"]} ADD CONSTRAINT #{row["constraint_name"]} #{row["definition"]} NOT VALID;"
151
- end
149
+ add_statement = if row["definition"].end_with?("NOT VALID")
150
+ "ALTER TABLE #{row["table_on"]} ADD CONSTRAINT #{row["constraint_name"]} #{row["definition"]};"
151
+ else
152
+ "ALTER TABLE #{row["table_on"]} ADD CONSTRAINT #{row["constraint_name"]} #{row["definition"]} NOT VALID;"
153
+ end
152
154
 
153
155
  drop_statement = "ALTER TABLE #{row["table_on"]} DROP CONSTRAINT #{row["constraint_name"]};"
154
156
 
@@ -210,13 +212,13 @@ module PgOnlineSchemaChange
210
212
  columns.first
211
213
  end
212
214
 
213
- def storage_parameters_for(client, table)
215
+ def storage_parameters_for(client, table, reuse_trasaction = false)
214
216
  query = <<~SQL
215
217
  SELECT array_to_string(reloptions, ',') as params FROM pg_class WHERE relname=\'#{table}\';
216
218
  SQL
217
219
 
218
220
  columns = []
219
- run(client.connection, query) do |result|
221
+ run(client.connection, query, reuse_trasaction) do |result|
220
222
  columns = result.map { |row| row["params"] }
221
223
  end
222
224
 
@@ -266,8 +268,8 @@ module PgOnlineSchemaChange
266
268
  run(client.connection, query, true)
267
269
  end
268
270
 
269
- def copy_data_statement(client, shadow_table)
270
- select_columns = table_columns(client).map do |entry|
271
+ def copy_data_statement(client, shadow_table, reuse_trasaction = false)
272
+ select_columns = table_columns(client, client.table, reuse_trasaction).map do |entry|
271
273
  entry["column_name_regular"]
272
274
  end
273
275
 
@@ -291,7 +293,7 @@ module PgOnlineSchemaChange
291
293
  client.connection.quote_ident(select_column)
292
294
  end
293
295
 
294
- sql = <<~SQL
296
+ <<~SQL
295
297
  INSERT INTO #{shadow_table}(#{insert_into_columns.join(", ")})
296
298
  SELECT #{select_columns.join(", ")}
297
299
  FROM ONLY #{client.table}
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
4
+
1
5
  module PgOnlineSchemaChange
2
6
  class Replay
3
7
  extend Helper
@@ -5,7 +9,6 @@ module PgOnlineSchemaChange
5
9
  class << self
6
10
  PULL_BATCH_COUNT = 1000
7
11
  DELTA_COUNT = 20
8
- RESERVED_COLUMNS = %w[operation_type trigger_time].freeze
9
12
 
10
13
  # This, picks PULL_BATCH_COUNT rows by primary key from audit_table,
11
14
  # replays it on the shadow_table. Once the batch is done,
@@ -25,7 +28,7 @@ module PgOnlineSchemaChange
25
28
 
26
29
  def rows_to_play(reuse_trasaction = false)
27
30
  select_query = <<~SQL
28
- SELECT * FROM #{audit_table} ORDER BY #{primary_key} LIMIT #{PULL_BATCH_COUNT};
31
+ SELECT * FROM #{audit_table} ORDER BY #{audit_table_pk} LIMIT #{PULL_BATCH_COUNT};
29
32
  SQL
30
33
 
31
34
  rows = []
@@ -34,6 +37,10 @@ module PgOnlineSchemaChange
34
37
  rows
35
38
  end
36
39
 
40
+ def reserved_columns
41
+ @reserved_columns ||= [trigger_time_column, operation_type_column, audit_table_pk]
42
+ end
43
+
37
44
  def play!(rows, reuse_trasaction = false)
38
45
  logger.info("Replaying rows, count: #{rows.size}")
39
46
 
@@ -44,7 +51,7 @@ module PgOnlineSchemaChange
44
51
 
45
52
  # Remove audit table cols, since we will be
46
53
  # re-mapping them for inserts and updates
47
- RESERVED_COLUMNS.each do |col|
54
+ reserved_columns.each do |col|
48
55
  new_row.delete(col)
49
56
  end
50
57
 
@@ -73,7 +80,7 @@ module PgOnlineSchemaChange
73
80
  client.connection.escape_string(value)
74
81
  end
75
82
 
76
- case row["operation_type"]
83
+ case row[operation_type_column]
77
84
  when "INSERT"
78
85
  values = new_row.map { |_, val| "'#{val}'" }.join(",")
79
86
 
@@ -110,13 +117,15 @@ module PgOnlineSchemaChange
110
117
  Query.run(client.connection, to_be_replayed.join, reuse_trasaction)
111
118
 
112
119
  # Delete items from the audit now that are replayed
113
- if to_be_deleted_rows.count >= 1
114
- delete_query = <<~SQL
115
- DELETE FROM #{audit_table} WHERE #{primary_key} IN (#{to_be_deleted_rows.join(",")})
116
- SQL
117
- Query.run(client.connection, delete_query, reuse_trasaction)
118
- end
120
+ return unless to_be_deleted_rows.count >= 1
121
+
122
+ delete_query = <<~SQL
123
+ DELETE FROM #{audit_table} WHERE #{primary_key} IN (#{to_be_deleted_rows.join(",")})
124
+ SQL
125
+ Query.run(client.connection, delete_query, reuse_trasaction)
119
126
  end
120
127
  end
121
128
  end
122
129
  end
130
+
131
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
@@ -1,17 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "pg_query"
2
4
  require "pg"
3
5
 
4
6
  module PgOnlineSchemaChange
5
7
  class Store
6
8
  class << self
7
- @@object = {}
9
+ @object = {}
8
10
 
9
11
  def get(key)
10
- @@object[key.to_s] || @@object[key.to_sym]
12
+ @object ||= {}
13
+ @object[key.to_s] || @object[key.to_sym]
11
14
  end
12
15
 
13
16
  def set(key, value)
14
- @@object[key.to_sym] = value
17
+ @object ||= {}
18
+ @object[key.to_sym] = value
15
19
  end
16
20
  end
17
21
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgOnlineSchemaChange
4
- VERSION = "0.1.0"
4
+ VERSION = "0.4.0"
5
5
  end
@@ -18,16 +18,12 @@ module PgOnlineSchemaChange
18
18
  class CountBelowDelta < StandardError; end
19
19
  class AccessExclusiveLockNotAcquired < StandardError; end
20
20
 
21
- def self.logger=(verbose)
22
- @@logger ||= begin
23
- logger = Ougai::Logger.new($stdout)
24
- logger.level = verbose ? Ougai::Logger::TRACE : Ougai::Logger::INFO
25
- logger.with_fields = { version: PgOnlineSchemaChange::VERSION }
26
- logger
27
- end
28
- end
29
-
30
- def self.logger
31
- @@logger
21
+ def self.logger(verbose: false)
22
+ @logger ||= begin
23
+ logger = Ougai::Logger.new($stdout)
24
+ logger.level = verbose ? Ougai::Logger::TRACE : Ougai::Logger::INFO
25
+ logger.with_fields = { version: PgOnlineSchemaChange::VERSION }
26
+ logger
27
+ end
32
28
  end
33
29
  end
@@ -0,0 +1,28 @@
1
+ export VERSION=$1
2
+ echo "VERSION: ${VERSION}"
3
+
4
+ echo "=== Pushing tags to github ===="
5
+ git tag v$VERSION
6
+ git push origin --tags
7
+
8
+ echo "=== Building Gem ===="
9
+ gem build pg_online_schema_change.gemspec
10
+
11
+ echo "=== Pushing gem ===="
12
+ gem push pg_online_schema_change-$VERSION.gem
13
+
14
+ echo "=== Sleeping for 5s ===="
15
+ sleep 5
16
+
17
+ echo "=== Building Image ===="
18
+ docker build . --build-arg VERSION=$VERSION -t shayonj/pg-osc:$VERSION
19
+
20
+ echo "=== Tagging Image ===="
21
+ docker image tag shayonj/pg-osc:$VERSION shayonj/pg-osc:latest
22
+
23
+ echo "=== Pushing Image ===="
24
+ docker push shayonj/pg-osc:$VERSION
25
+ docker push shayonj/pg-osc:latest
26
+
27
+ echo "=== Cleaning up ===="
28
+ rm pg_online_schema_change-$VERSION.gem