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.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/lib/optional_dependencies.rb +30 -0
  4. data/lib/sql_cmd/agent.rb +32 -0
  5. data/lib/sql_cmd/always_on.rb +267 -0
  6. data/lib/sql_cmd/azure.rb +80 -0
  7. data/lib/sql_cmd/backups.rb +276 -0
  8. data/lib/sql_cmd/config.rb +62 -0
  9. data/lib/sql_cmd/database.rb +618 -0
  10. data/lib/sql_cmd/format.rb +124 -0
  11. data/lib/sql_cmd/query.rb +350 -0
  12. data/lib/sql_cmd/security.rb +21 -0
  13. data/lib/sql_cmd/sql_helper.ps1 +89 -0
  14. data/lib/sql_cmd.rb +44 -0
  15. data/sql_scripts/Agent/CreateSQLJob.sql +81 -0
  16. data/sql_scripts/Agent/JobLastRunInfo.sql +70 -0
  17. data/sql_scripts/Agent/JobRunStatus.sql +21 -0
  18. data/sql_scripts/Agent/SQLAgentStatus.sql +8 -0
  19. data/sql_scripts/AlwaysOn/AddDatabaseToAvailabilityGroupOnSecondary.sql +72 -0
  20. data/sql_scripts/AlwaysOn/AddDatabaseToPrimaryAvailabilityGroup.sql +16 -0
  21. data/sql_scripts/AlwaysOn/AutomaticSeedingProgress.sql +34 -0
  22. data/sql_scripts/AlwaysOn/ConfigurePrimaryForAutomaticSeeding.sql +2 -0
  23. data/sql_scripts/AlwaysOn/ConfigurePrimaryForManualSeeding.sql +2 -0
  24. data/sql_scripts/AlwaysOn/ConfigureSecondaryForAutomaticSeeding.sql +1 -0
  25. data/sql_scripts/AlwaysOn/DropSecondary.sql +58 -0
  26. data/sql_scripts/AlwaysOn/RemoveDatabaseFromGroup.sql +2 -0
  27. data/sql_scripts/AlwaysOn/SynchronizationState.sql +14 -0
  28. data/sql_scripts/Database/BackupDatabase.sql +95 -0
  29. data/sql_scripts/Database/CompressAllTables.sql +100 -0
  30. data/sql_scripts/Database/CreateLogin.sql +16 -0
  31. data/sql_scripts/Database/DropDatabase.sql +51 -0
  32. data/sql_scripts/Database/GetBackupFiles.sql +31 -0
  33. data/sql_scripts/Database/GetBackupHeaders.sql +94 -0
  34. data/sql_scripts/Database/GetFileInfoFromBackup.sql +9 -0
  35. data/sql_scripts/Database/RestoreDatabase.sql +185 -0
  36. data/sql_scripts/Database/SetFullRecovery.sql +19 -0
  37. data/sql_scripts/Database/SetSQLCompatibility.sql +33 -0
  38. data/sql_scripts/Security/AssignDatabaseRoles.sql +44 -0
  39. data/sql_scripts/Security/CreateOrUpdateCredential.sql +11 -0
  40. data/sql_scripts/Security/CreateSqlLogin.sql +20 -0
  41. data/sql_scripts/Security/ExportDatabasePermissions.sql +757 -0
  42. data/sql_scripts/Security/GenerateCreateLoginsScript.sql +144 -0
  43. data/sql_scripts/Security/GenerateValidateLoginsScript.sql +83 -0
  44. data/sql_scripts/Security/GetUserSID.sql +3 -0
  45. data/sql_scripts/Security/UpdateSqlPassword.sql +24 -0
  46. data/sql_scripts/Security/ValidateDatabaseRoles.sql +12 -0
  47. data/sql_scripts/Status/ANSINullsOffTableCount.sql +13 -0
  48. data/sql_scripts/Status/ANSINullsOffTables.sql +9 -0
  49. data/sql_scripts/Status/BackupProgress.sql +17 -0
  50. data/sql_scripts/Status/DatabaseInfo.sql +199 -0
  51. data/sql_scripts/Status/DatabaseSize.sql +26 -0
  52. data/sql_scripts/Status/DiskSpace.sql +14 -0
  53. data/sql_scripts/Status/RestoreProgress.sql +17 -0
  54. data/sql_scripts/Status/SQLSettings.sql +182 -0
  55. data/sql_scripts/Status/UncompressedTableCount.sql +27 -0
  56. 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