sql_cmd 0.3.0
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 +7 -0
- data/LICENSE +201 -0
- data/lib/optional_dependencies.rb +30 -0
- data/lib/sql_cmd/agent.rb +32 -0
- data/lib/sql_cmd/always_on.rb +267 -0
- data/lib/sql_cmd/azure.rb +80 -0
- data/lib/sql_cmd/backups.rb +276 -0
- data/lib/sql_cmd/config.rb +62 -0
- data/lib/sql_cmd/database.rb +618 -0
- data/lib/sql_cmd/format.rb +124 -0
- data/lib/sql_cmd/query.rb +350 -0
- data/lib/sql_cmd/security.rb +21 -0
- data/lib/sql_cmd/sql_helper.ps1 +89 -0
- data/lib/sql_cmd.rb +44 -0
- data/sql_scripts/Agent/CreateSQLJob.sql +81 -0
- data/sql_scripts/Agent/JobLastRunInfo.sql +70 -0
- data/sql_scripts/Agent/JobRunStatus.sql +21 -0
- data/sql_scripts/Agent/SQLAgentStatus.sql +8 -0
- data/sql_scripts/AlwaysOn/AddDatabaseToAvailabilityGroupOnSecondary.sql +72 -0
- data/sql_scripts/AlwaysOn/AddDatabaseToPrimaryAvailabilityGroup.sql +16 -0
- data/sql_scripts/AlwaysOn/AutomaticSeedingProgress.sql +34 -0
- data/sql_scripts/AlwaysOn/ConfigurePrimaryForAutomaticSeeding.sql +2 -0
- data/sql_scripts/AlwaysOn/ConfigurePrimaryForManualSeeding.sql +2 -0
- data/sql_scripts/AlwaysOn/ConfigureSecondaryForAutomaticSeeding.sql +1 -0
- data/sql_scripts/AlwaysOn/DropSecondary.sql +58 -0
- data/sql_scripts/AlwaysOn/RemoveDatabaseFromGroup.sql +2 -0
- data/sql_scripts/AlwaysOn/SynchronizationState.sql +14 -0
- data/sql_scripts/Database/BackupDatabase.sql +95 -0
- data/sql_scripts/Database/CompressAllTables.sql +100 -0
- data/sql_scripts/Database/CreateLogin.sql +16 -0
- data/sql_scripts/Database/DropDatabase.sql +51 -0
- data/sql_scripts/Database/GetBackupFiles.sql +31 -0
- data/sql_scripts/Database/GetBackupHeaders.sql +94 -0
- data/sql_scripts/Database/GetFileInfoFromBackup.sql +9 -0
- data/sql_scripts/Database/RestoreDatabase.sql +185 -0
- data/sql_scripts/Database/SetFullRecovery.sql +19 -0
- data/sql_scripts/Database/SetSQLCompatibility.sql +33 -0
- data/sql_scripts/Security/AssignDatabaseRoles.sql +44 -0
- data/sql_scripts/Security/CreateOrUpdateCredential.sql +11 -0
- data/sql_scripts/Security/CreateSqlLogin.sql +20 -0
- data/sql_scripts/Security/ExportDatabasePermissions.sql +757 -0
- data/sql_scripts/Security/GenerateCreateLoginsScript.sql +144 -0
- data/sql_scripts/Security/GenerateValidateLoginsScript.sql +83 -0
- data/sql_scripts/Security/GetUserSID.sql +3 -0
- data/sql_scripts/Security/UpdateSqlPassword.sql +24 -0
- data/sql_scripts/Security/ValidateDatabaseRoles.sql +12 -0
- data/sql_scripts/Status/ANSINullsOffTableCount.sql +13 -0
- data/sql_scripts/Status/ANSINullsOffTables.sql +9 -0
- data/sql_scripts/Status/BackupProgress.sql +17 -0
- data/sql_scripts/Status/DatabaseInfo.sql +199 -0
- data/sql_scripts/Status/DatabaseSize.sql +26 -0
- data/sql_scripts/Status/DiskSpace.sql +14 -0
- data/sql_scripts/Status/RestoreProgress.sql +17 -0
- data/sql_scripts/Status/SQLSettings.sql +182 -0
- data/sql_scripts/Status/UncompressedTableCount.sql +27 -0
- metadata +224 -0
@@ -0,0 +1,276 @@
|
|
1
|
+
module SqlCmd
|
2
|
+
module_function
|
3
|
+
|
4
|
+
attr_accessor :full_backup_method # set this to a lambda if you would like full non-copy only backups to use a custom backup method such as for using Red-gate or SQLSafe
|
5
|
+
|
6
|
+
def get_backup_sql_server_settings(connection_string)
|
7
|
+
sql_server_settings = get_sql_server_settings(connection_string)
|
8
|
+
sql_server_settings = get_sql_server_settings(to_integrated_security(connection_string)) if sql_server_settings.nil? || sql_server_settings['BackupDir'].nil? || sql_server_settings['BackupDir'] == 'null'
|
9
|
+
raise "FATAL: Current user #{ENV['user'] || ENV['username']} does not have access to backup database!" if sql_server_settings.nil? || sql_server_settings['BackupDir'].nil? || sql_server_settings['BackupDir'] == 'null'
|
10
|
+
sql_server_settings
|
11
|
+
end
|
12
|
+
|
13
|
+
# sql_server_settings can be for any server that has network access to these files
|
14
|
+
def get_unc_backup_files(sql_server_settings, backup_folder, backup_basename, log_only: false, all_time_stamps: false)
|
15
|
+
backup_folder = EasyFormat::Directory.ensure_trailing_slash(backup_folder).tr('\\', '/')
|
16
|
+
backup_file_extension = backup_basename.slice!(/\.(trn|bak)/i)
|
17
|
+
backup_file_extension ||= log_only ? 'trn' : 'bak'
|
18
|
+
backup_file_extension = backup_file_extension.reverse.chomp('.').reverse
|
19
|
+
backup_files = Dir.glob("#{backup_folder}#{backup_basename}*").grep(/#{Regexp.escape(backup_basename)}(_\d{6})?(\.part\d+)?\.#{backup_file_extension}$/i)
|
20
|
+
return [backup_files, backup_basename] if all_time_stamps
|
21
|
+
most_recent_backup_files_and_basename(sql_server_settings, backup_files, backup_basename)
|
22
|
+
end
|
23
|
+
|
24
|
+
# get a list of backup files from a URL
|
25
|
+
#
|
26
|
+
# options:
|
27
|
+
# log_only: determines whether to look for log backup files or normal backup files. (.trn vs .bak) default: false
|
28
|
+
# all_time_stamps: returns all backup sets found matching the basename and not just the most recent. default: false
|
29
|
+
# storage_account_name: (required if using Azure blob storage) Azure blob storage account name.
|
30
|
+
# storage_access_key: (required if using Azure blob storage) Azure blob storage access key.
|
31
|
+
def get_url_backup_files(sql_server_settings, backup_url, backup_basename, options = {}, log_only: false, all_time_stamps: false)
|
32
|
+
if azure_blob_storage_url?(backup_url)
|
33
|
+
backup_file_extension = backup_basename.slice!(/\.(trn|bak)/i)
|
34
|
+
backup_file_extension ||= log_only ? 'trn' : 'bak'
|
35
|
+
backup_file_extension = backup_file_extension.reverse.chomp('.').reverse
|
36
|
+
backup_files = azure_blob_storage_list_backup_files(backup_url, backup_basename, options['storage_account_name'], options['storage_access_key'])
|
37
|
+
backup_files.select! { |f, _modified| f =~ /#{Regexp.escape(backup_file_extension)}$/i }
|
38
|
+
return [backup_files.keys, backup_basename] if options['all_time_stamps']
|
39
|
+
most_recent_backup_files_and_basename(sql_server_settings, backup_files.keys, backup_basename, options)
|
40
|
+
else
|
41
|
+
EasyIO.logger.warn 'Non-azure blob storage URLs are not currently supported.'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def azure_blob_storage_url?(backup_url)
|
46
|
+
return false if backup_url.nil? || backup_url.empty?
|
47
|
+
(backup_url =~ /blob\.core\.windows\.net/i) >= 0
|
48
|
+
end
|
49
|
+
|
50
|
+
# Get a list of backup files from blob storage
|
51
|
+
def azure_blob_storage_list_backup_files(backup_url, backup_basename, storage_account_name, storage_access_key)
|
52
|
+
files = SqlCmd::Azure::AttachedStorage.list(storage_account_name, storage_access_key, storage_url: backup_url, filename_prefix: backup_basename)
|
53
|
+
base_url = backup_url[%r{[a-z]+://[^/]+/[^/]+}i] # URL without directories
|
54
|
+
files.map { |f, properties| ["#{base_url}/#{f}", properties[:last_modified]] }.to_h
|
55
|
+
end
|
56
|
+
|
57
|
+
def backup_location_and_basename(start_time, connection_string, database_name, options = {}, backup_url: nil)
|
58
|
+
start_time = SqlCmd.unify_start_time(start_time)
|
59
|
+
database_info = SqlCmd::Database.info(connection_string, database_name)
|
60
|
+
server_settings = get_sql_server_settings(connection_string)
|
61
|
+
backup_type = backup_url.nil? || backup_url.empty? ? 'DISK' : 'URL'
|
62
|
+
if database_info['DatabaseNotFound']
|
63
|
+
backup_name = "#{database_name}_#{EasyTime.yyyymmdd(start_time)}"
|
64
|
+
return [backup_url, backup_name] if backup_type == 'URL'
|
65
|
+
backup_unc_location = SqlCmd.config['sql_cmd']['backups']['backup_to_host_sql_server'] ? "\\\\#{server_settings['ServerName']}\\#{SqlCmd.config['sql_cmd']['backups']['default_backup_share']}" : SqlCmd.config['sql_cmd']['backups']['default_destination']
|
66
|
+
return Dir.glob("#{backup_unc_location}/#{backup_name}*".tr('\\', '/')).empty? ? [nil, nil] : [backup_unc_location, backup_name]
|
67
|
+
end
|
68
|
+
|
69
|
+
backup_file_path = database_info['BackupFileLocation']
|
70
|
+
backup_file = ::File.basename(backup_file_path)
|
71
|
+
backup_name = backup_basename(backup_file)
|
72
|
+
return [nil, backup_name] if backup_type == 'URL'
|
73
|
+
backup_unc_location = to_unc_path(::File.dirname(backup_file_path), server_settings['ServerName'])
|
74
|
+
backup_folder = if ::File.exist?("#{backup_unc_location}/#{backup_file}")
|
75
|
+
backup_unc_location
|
76
|
+
elsif ::File.exist?("\\\\#{server_settings['ServerName']}\\#{SqlCmd.config['sql_cmd']['backups']['default_backup_share']}\\#{backup_file}")
|
77
|
+
"\\\\#{server_settings['ServerName']}\\#{SqlCmd.config['sql_cmd']['backups']['default_backup_share']}"
|
78
|
+
end
|
79
|
+
return [nil, nil] unless defined?(backup_folder)
|
80
|
+
[backup_folder, backup_name]
|
81
|
+
end
|
82
|
+
|
83
|
+
# sql_server_settings can be for any server that has network access to these files
|
84
|
+
def backup_sets_from_unc_path(sql_server_settings, backup_folder, backup_basename, log_only: false, database_backup_header: nil, restored_database_lsn: nil)
|
85
|
+
backup_files, backup_basename = get_unc_backup_files(sql_server_settings, backup_folder, backup_basename, log_only: log_only, all_time_stamps: log_only)
|
86
|
+
backup_sets = log_only ? relevant_log_backup_sets(sql_server_settings, backup_files, database_backup_header, restored_database_lsn) : { backup_basename => backup_files }
|
87
|
+
EasyIO.logger.debug "Database backup sets found: #{JSON.pretty_generate(backup_sets)}"
|
88
|
+
backup_sets
|
89
|
+
end
|
90
|
+
|
91
|
+
def backup_sets_from_url(sql_server_settings, backup_url, backup_basename, options, log_only: false, database_backup_header: nil, restored_database_lsn: nil)
|
92
|
+
backup_files, backup_basename = get_url_backup_files(sql_server_settings, backup_url, backup_basename, options, log_only: log_only, all_time_stamps: log_only)
|
93
|
+
backup_sets = log_only ? relevant_log_backup_sets(sql_server_settings, backup_files, database_backup_header, restored_database_lsn) : { backup_basename => backup_files }
|
94
|
+
EasyIO.logger.debug "Database backup sets found: #{JSON.pretty_generate(backup_sets)}"
|
95
|
+
backup_sets
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns a single string to be used for the source for a RESTORE command from an array of backup file paths
|
99
|
+
def backup_fileset_names(backup_files)
|
100
|
+
result = ''
|
101
|
+
backup_files.each do |backup_file|
|
102
|
+
backup_type = backup_file =~ /^http/i ? 'URL' : 'DISK'
|
103
|
+
current_file = " #{backup_type} = N''#{backup_file}'',"
|
104
|
+
current_file = backup_type =~ /url/i ? current_file.tr('\\', '/') : current_file.tr('/', '\\')
|
105
|
+
result << current_file
|
106
|
+
end
|
107
|
+
result.chomp(',')
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns the headers from the backup set provided. Pass an array of path strings to the backup files.
|
111
|
+
def get_sql_backup_headers(connection_string, backup_files, options = {})
|
112
|
+
EasyFormat.validate_parameters(method(__method__), binding, %W(options))
|
113
|
+
disk_backup_files = backup_fileset_names(backup_files)
|
114
|
+
sql_script = ::File.read("#{sql_script_dir}/Database/GetBackupHeaders.sql")
|
115
|
+
options['bkupfiles'] = disk_backup_files
|
116
|
+
options['credential'] ||= options['storage_account_name'] || ''
|
117
|
+
SqlCmd::Security.create_credential(connection_string, options['credential'], options['storage_account_name'], options['storage_access_key'], options) unless options['credential'].nil? || options['credential'].empty?
|
118
|
+
execute_query(connection_string, sql_script, return_type: :first_table, values: options, retries: 3, retry_delay: 10)
|
119
|
+
end
|
120
|
+
|
121
|
+
def gap_in_log_backups?(sql_server_settings, database_backup_header, restored_database_lsn, backup_folder, backup_basename, options = {})
|
122
|
+
last_full_backup_lsn = database_backup_header['LastLSN']
|
123
|
+
return true if last_full_backup_lsn.nil? # if the last full backup does not exist, behave as if there is a gap in the log backups
|
124
|
+
backup_sets = backup_sets_from_unc_path(sql_server_settings, backup_folder, backup_basename, database_backup_header: database_backup_header, restored_database_lsn: restored_database_lsn, log_only: true)
|
125
|
+
return true if backup_sets.nil? # nil is returned if the backup is too new for the restored database LSN, therefore there's a gap
|
126
|
+
return false if backup_sets.empty? # if no log backup sets were current, behave as if there is no gap since a log backup hasn't yet been taken since the backup
|
127
|
+
first_lsn_from_log_backups = get_sql_backup_headers(sql_server_settings['connection_string'], backup_sets.first.last, options).first['FirstLSN']
|
128
|
+
EasyIO.logger.debug "LastLSN from full backup: #{last_full_backup_lsn} | First LSN from log backups: #{first_lsn_from_log_backups}"
|
129
|
+
last_full_backup_lsn < first_lsn_from_log_backups && restored_database_lsn < first_lsn_from_log_backups
|
130
|
+
end
|
131
|
+
|
132
|
+
# Returns the data and log file information contained in the backup files.
|
133
|
+
def get_backup_file_info(connection_string, backup_files, options)
|
134
|
+
EasyFormat.validate_parameters(method(__method__), binding)
|
135
|
+
disk_backup_files = backup_fileset_names(backup_files)
|
136
|
+
sql_script = ::File.read("#{sql_script_dir}/Database/GetFileInfoFromBackup.sql")
|
137
|
+
options['bkupfiles'] = disk_backup_files
|
138
|
+
options['credential'] ||= options['storage_account_name'] || ''
|
139
|
+
execute_query(connection_string, sql_script, return_type: :first_table, values: options, retries: 3)
|
140
|
+
end
|
141
|
+
|
142
|
+
def get_backup_logical_names(connection_string, backup_files, options)
|
143
|
+
EasyFormat.validate_parameters(method(__method__), binding)
|
144
|
+
sql_backup_file_info = SqlCmd.get_backup_file_info(connection_string, backup_files, options)
|
145
|
+
data_file_logical_name = sql_backup_file_info.select { |file| file['Type'] == 'D' }.first['LogicalName']
|
146
|
+
log_file_logical_name = sql_backup_file_info.select { |file| file['Type'] == 'L' }.first['LogicalName']
|
147
|
+
[data_file_logical_name, log_file_logical_name]
|
148
|
+
end
|
149
|
+
|
150
|
+
def sql_server_backup_files(sql_server_settings, backup_basename, log_only: false)
|
151
|
+
values = { 'targetfolder' => sql_server_settings['BackupDir'],
|
152
|
+
'bkupname' => backup_basename,
|
153
|
+
'logonly' => log_only }
|
154
|
+
sql_script = ::File.read("#{sql_script_dir}/Database/GetBackupFiles.sql")
|
155
|
+
backup_files_results = execute_query(sql_server_settings['connection_string'], sql_script, return_type: :first_table, values: values, retries: 3)
|
156
|
+
backup_files = []
|
157
|
+
backup_files_results.each do |file|
|
158
|
+
backup_files.push("#{EasyFormat::Directory.ensure_trailing_slash(sql_server_settings['BackupDir'])}#{file['FileName']}")
|
159
|
+
end
|
160
|
+
if log_only
|
161
|
+
database_backup_files = sql_server_backup_files(sql_server_settings, backup_basename)
|
162
|
+
database_backup_header = get_sql_backup_headers(sql_server_settings['connection_string'], database_backup_files).first
|
163
|
+
return relevant_log_backup_sets(sql_server_settings, backup_files, database_backup_header, 0)
|
164
|
+
end
|
165
|
+
most_recent_backup_files_and_basename(sql_server_settings, backup_files, backup_basename)
|
166
|
+
end
|
167
|
+
|
168
|
+
def most_recent_backup_files_and_basename(sql_server_settings, backup_files, backup_basename, options = {})
|
169
|
+
backup_sets = backup_sets_from_backup_files(backup_files)
|
170
|
+
if backup_sets.keys.count > 1 # if there is more than 1 backup set, find the most recent
|
171
|
+
backup_headers = {}
|
172
|
+
backup_sets.each do |basename, files|
|
173
|
+
backup_headers[basename] = get_sql_backup_headers(sql_server_settings['connection_string'], files, options).first
|
174
|
+
end
|
175
|
+
backup_basename = backup_headers.max_by { |_basename, header| header['BackupFinishDate'] }.first
|
176
|
+
elsif backup_sets.keys.count == 0 # if there are no backup sets, use an empty array
|
177
|
+
backup_sets[backup_basename] = []
|
178
|
+
end
|
179
|
+
[backup_sets[backup_basename], backup_basename]
|
180
|
+
end
|
181
|
+
|
182
|
+
def relevant_log_backup_sets(sql_server_settings, backup_files, database_backup_header, restored_database_lsn, options = {})
|
183
|
+
restored_database_lsn ||= 0
|
184
|
+
backup_sets = backup_sets_from_backup_files(backup_files)
|
185
|
+
database_backup_lsn = database_backup_header['DatabaseBackupLSN']
|
186
|
+
EasyIO.logger.debug "Database backup LSN: #{database_backup_lsn}"
|
187
|
+
backup_headers = backup_sets.each_with_object({}) { |(basename, files), headers| headers[basename] = get_sql_backup_headers(sql_server_settings['connection_string'], files, options).first }
|
188
|
+
backup_sets = backup_sets.sort_by { |basename, _files| backup_headers[basename]['LastLSN'] }.to_h
|
189
|
+
backup_headers = backup_headers.sort_by { |_basename, backup_header| backup_header['LastLSN'] }.to_h
|
190
|
+
EasyIO.logger.debug "Backup sets after sorting: #{JSON.pretty_generate(backup_sets)}"
|
191
|
+
backup_sets.each { |basename, _files| EasyIO.logger.debug "Backup header for #{basename}: FirstLSN: #{backup_headers[basename]['FirstLSN']} | LastLSN: #{backup_headers[basename]['LastLSN']}" }
|
192
|
+
start_lsn = nil
|
193
|
+
current_lsn = database_backup_header['LastLSN']
|
194
|
+
backup_headers.each do |basename, backup_header|
|
195
|
+
start_lsn ||= backup_header['FirstLSN']
|
196
|
+
EasyIO.logger.debug "Current LSN: #{current_lsn}"
|
197
|
+
EasyIO.logger.debug "Current header (#{basename}) - FirstLSN: #{backup_headers[basename]['FirstLSN']} | LastLSN: #{backup_headers[basename]['LastLSN']} | DatabaseBackupLSN: #{backup_headers[basename]['DatabaseBackupLSN']}"
|
198
|
+
unless backup_header['DatabaseBackupLSN'] == database_backup_lsn
|
199
|
+
EasyIO.logger.debug "Current backup is from a different database backup as the DatabaseBackupLSN (#{backup_header['DatabaseBackupLSN']}) doesn't match the database backup LSN (#{database_backup_lsn}). Removing backup set..."
|
200
|
+
backup_sets.delete(basename)
|
201
|
+
next
|
202
|
+
end
|
203
|
+
if backup_header['LastLSN'] < database_backup_lsn || backup_header['LastLSN'] < restored_database_lsn
|
204
|
+
EasyIO.logger.debug "Current backup LastLSN (#{backup_header['FirstLSN']}) older than database backup LSN (#{database_backup_lsn}) or restored database LSN (#{restored_database_lsn}). Removing backup set..."
|
205
|
+
backup_sets.delete(basename)
|
206
|
+
next
|
207
|
+
end
|
208
|
+
if backup_header['FirstLSN'] > current_lsn # remove previous backup sets if there's a gap
|
209
|
+
EasyIO.logger.debug "Gap found between previous backup LastLSN (#{current_lsn}) and current backup FirstLSN #{backup_header['FirstLSN']}. Updating starting point..." unless current_lsn == 0
|
210
|
+
start_lsn = backup_header['FirstLSN']
|
211
|
+
if start_lsn > restored_database_lsn # if the starting point is beyond the restored database LastLSN, no backups can be applied
|
212
|
+
EasyIO.logger.warn "Gap found in log backups. The previous backup ends at LSN #{current_lsn} and the next log backup starts at LSN #{backup_header['FirstLSN']}!"
|
213
|
+
return nil
|
214
|
+
end
|
215
|
+
end
|
216
|
+
current_lsn = backup_header['LastLSN']
|
217
|
+
end
|
218
|
+
backup_headers.each { |basename, backup_header| backup_sets.delete(basename) unless backup_header['LastLSN'] > start_lsn } # remove any obsolete backup sets
|
219
|
+
EasyIO.logger.debug "Backup sets after removing obsolete sets: #{JSON.pretty_generate(backup_sets)}"
|
220
|
+
backup_sets
|
221
|
+
end
|
222
|
+
|
223
|
+
def backup_sets_from_backup_files(backup_files)
|
224
|
+
backup_sets = {}
|
225
|
+
backup_files.each do |file|
|
226
|
+
current_basename = ::File.basename(file).sub(/(\.part\d+)?\.(bak|trn)$/i, '') # determine basename of current file
|
227
|
+
backup_sets[current_basename] = [] if backup_sets[current_basename].nil?
|
228
|
+
backup_sets[current_basename].push(file)
|
229
|
+
end
|
230
|
+
backup_sets
|
231
|
+
end
|
232
|
+
|
233
|
+
# get a list of backup files and the backup basename
|
234
|
+
#
|
235
|
+
# options:
|
236
|
+
# log_only: determines whether to look for log backup files or normal backup files. (.trn vs .bak) default: false
|
237
|
+
# all_time_stamps: returns all backup sets found matching the basename and not just the most recent. default: false
|
238
|
+
# storage_account_name: (required if using Azure blob storage) Azure blob storage account name.
|
239
|
+
# storage_access_key: (required if using Azure blob storage) Azure blob storage access key.
|
240
|
+
def get_backup_files(sql_server_settings, options = {}, backup_folder: nil, backup_url: nil, backup_basename: nil)
|
241
|
+
if !backup_url.nil? && !backup_url.empty?
|
242
|
+
get_url_backup_files(sql_server_settings, backup_url, backup_basename, options)
|
243
|
+
elsif backup_folder.start_with?('\\\\')
|
244
|
+
get_unc_backup_files(sql_server_settings, backup_folder, backup_basename, log_only: options['log_only'])
|
245
|
+
else
|
246
|
+
sql_server_backup_files(sql_server_settings, backup_basename, log_only: log_only)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# Get size of uncompressed database from backup header in MB
|
251
|
+
def get_backup_size(sql_backup_header)
|
252
|
+
sql_backup_header['BackupSize'].to_f / 1024 / 1024
|
253
|
+
end
|
254
|
+
|
255
|
+
# messages:
|
256
|
+
# :none
|
257
|
+
# :prebackup || :prerestore - Output info messages pertaining to a backup or restore
|
258
|
+
# returns:
|
259
|
+
# :current
|
260
|
+
# :outdated
|
261
|
+
# :nobackup
|
262
|
+
def check_header_date(sql_backup_header, backup_start_time, messages = :none)
|
263
|
+
backup_start_time = SqlCmd.unify_start_time(backup_start_time)
|
264
|
+
return :nobackup if sql_backup_header.nil? || sql_backup_header.empty?
|
265
|
+
EasyIO.logger.info "Last backup for [#{sql_backup_header['DatabaseName']}] completed: #{sql_backup_header['BackupFinishDate']}" if [:prebackup, :prerestore].include?(messages)
|
266
|
+
backup_finish_time = sql_backup_header['BackupFinishDate']
|
267
|
+
raise "BackupFinishDate missing from backup header: #{sql_backup_header}" if backup_finish_time.nil?
|
268
|
+
if backup_finish_time > backup_start_time
|
269
|
+
EasyIO.logger.info 'Backup is current. Bypassing database backup.' if messages == :prebackup
|
270
|
+
EasyIO.logger.info 'Backup is current. Proceeding with restore...' if messages == :prerestore
|
271
|
+
:current
|
272
|
+
else
|
273
|
+
:outdated
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module SqlCmd
|
2
|
+
module_function
|
3
|
+
|
4
|
+
def config
|
5
|
+
@config ||= EasyJSON.config(defaults: defaults, required_keys: required_keys)
|
6
|
+
end
|
7
|
+
|
8
|
+
def required_keys
|
9
|
+
{
|
10
|
+
'sql_cmd' => {
|
11
|
+
'backups' => {
|
12
|
+
'always_on_backup_temp_dir' => nil,
|
13
|
+
'default_destination' => nil,
|
14
|
+
},
|
15
|
+
},
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def defaults
|
20
|
+
{
|
21
|
+
'paths' => {
|
22
|
+
'cache' => Dir.tmpdir,
|
23
|
+
},
|
24
|
+
'environment' => {
|
25
|
+
'timezone' => 'UTC',
|
26
|
+
},
|
27
|
+
'logging' => {
|
28
|
+
'level' => 'info',
|
29
|
+
'verbose' => false, # show queries and returned json
|
30
|
+
},
|
31
|
+
'sql_cmd' => {
|
32
|
+
'backups' => {
|
33
|
+
'always_on_backup_temp_dir' => nil, # where backups will go when adding to availability groups and seeding_mode is manual or nil
|
34
|
+
'always_on_backup_url' => nil, # where backups will go when adding to availability groups and seeding_mode is manual or nil - supercedes temp dir above
|
35
|
+
'default_destination' => nil, # where backups will go by default
|
36
|
+
'backup_to_host_sql_server' => false, # if set to true, will backup databases to the SQL host instead of the default destination
|
37
|
+
'default_backup_share' => nil, # the name of the windows share relative to SQL hosts where backups go when set to backup to sql hosts
|
38
|
+
'free_space_threshold' => 5, # raises an exception if a backup or restore operation would bring free space on the target location below this threshold
|
39
|
+
'compress_backups' => false,
|
40
|
+
},
|
41
|
+
'exports' => {
|
42
|
+
'include_table_permissions' => false,
|
43
|
+
},
|
44
|
+
'paths' => {
|
45
|
+
'sql_script_dir' => ::File.expand_path("#{__dir__}/../../sql_scripts"),
|
46
|
+
'powershell_helper_script' => ::File.expand_path("#{__dir__}/sql_helper.ps1"),
|
47
|
+
},
|
48
|
+
},
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
def sql_script_dir
|
53
|
+
SqlCmd.config['sql_cmd']['paths']['sql_script_dir']
|
54
|
+
end
|
55
|
+
|
56
|
+
def apply_log_level
|
57
|
+
config_level = config['logging']['level']
|
58
|
+
EasyIO.logger.level = EasyIO.levels[config_level]
|
59
|
+
end
|
60
|
+
|
61
|
+
apply_log_level
|
62
|
+
end
|