Skip to content

Commit 868489b

Browse files
committed
Fix bug where module did not reinstantiate singleton on each Log-Rotate call
2 parents 1f98c5d + 3868a66 commit 868489b

File tree

4 files changed

+1619
-0
lines changed

4 files changed

+1619
-0
lines changed

Diff for: src/Log-Rotate/classes/New-BlockFactory.ps1

+270
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
function New-BlockFactory {
2+
#######################
3+
# BlockFactory Class #
4+
#######################
5+
# BlockFactory is a stateful factory that constructs Block Objects, a Configuration. It keeps a list of Blocks.
6+
$BlockFactory = [PSCustomObject]@{
7+
'Constants' = [scriptblock]{
8+
# Constants
9+
$g_globaloptions_allowed_str = 'compress,compresscmd,uncompresscmd,compressext,compressoptions,uncompressoptions,copy,copytruncate,create,daily,dateext,dateformat,delaycompress,extension,ifempty,mail,mailfirst,maillast,maxage,minsize,missingok,monthly,nocompress,nocopy,nocopytruncate,nocreate,nodelaycompress,nodateext,nomail,nomissing,noolddir,nosharedscripts,noshred,notifempty,olddir,rotate,size,sharedscripts,shred,shredcycle,start,tabooext,weekly,yearly'
10+
$g_options_not_singleline_str = 'postrotate,prerotate,firstaction,lastaction,preremove';
11+
$g_options_not_switches_str = 'compresscmd,uncompresscmd,compressext,compressoptions,uncompressoptions,create,dateformat,extension,include,mail,maxage,minsize,olddir,postrotate,prerotate,firstaction,lastaction,preremove,rotate,size,shredcycle,start,tabooext'
12+
13+
# Constants as arrays
14+
[string[]]$g_globaloptions_allowed = $g_globaloptions_allowed_str.Split(',')
15+
[string[]]$g_options_not_singleline = $g_options_not_singleline_str.Split(',');
16+
[string[]]$g_localoptions_allowed = $g_globaloptions_allowed + $g_options_not_singleline
17+
[string[]]$g_options_not_switches = $g_options_not_switches_str.Split(',')
18+
19+
# Define our config-capturing regexes
20+
[Regex]$g_localconfigs_regex = '([^\n]*)({(?:(?:(firstaction|lastaction|prerotate|postrotate|preremove)(?:\s|.)*?endscript)|[^}])*})'
21+
[Regex]$g_globaloptions_allowed_regex = "(?:^|\n)[^\S\n]*\b($( ($g_globaloptions_allowed -join '|') ))\b(.*)"
22+
[Regex]$g_localoptions_allowed_regex = "\n[^\S\n]*(?:\b($( ($g_globaloptions_allowed -join '|') ))\b(.*)|\b(postrotate|prerotate|firstaction|lastaction|preremove)[^\n]*\n((?:.|\s)*?)\n.*\b(endscript)\b)"
23+
[hashtable]$g_no_yes = @{
24+
'nocompress' = 'compress'
25+
'nocopy' = 'copy'
26+
'nocopytruncate' = 'copytruncate'
27+
'nocreate' = 'create'
28+
'nodelaycompress' = 'delaycompress'
29+
'nodateext' = 'dateext'
30+
'nomail' = 'mail'
31+
'nomissingok' = 'missingok'
32+
'notifempty' = 'ifempty'
33+
'noolddir' = 'olddir'
34+
'nosharedscripts' = 'sharedscripts'
35+
'noshred' = 'shred'
36+
}
37+
#[Regex]$globalconfig_regex = '<?!#(' + ($g_globaloptions_allowed -join '|') + ')'
38+
}
39+
'GlobalOptions' = @{
40+
'compresscmd' = "C:\Program Files\7-Zip\7z.exe"
41+
'uncompresscmd' = "C:\Program Files\7-Zip\7z.exe"
42+
'compressext' = '.7z'
43+
'compressoptions' = 'a -t7z'
44+
'uncompressoptions' = 'x -t7z'
45+
'size' = ''
46+
'dateformat' = '-%Y%m%d'
47+
'nomissingok' = $true
48+
'rotate' = 4
49+
'start' = 1
50+
'tabooext' = '.rpmorig, .rpmsave, .swp, .rpmnew, ~, .cfsaved, .rhn-cfg-tmp-*.'
51+
52+
'force' = $force
53+
}
54+
'Blocks' = [ordered]@{}
55+
'UniqueLogFileNames' = New-Object System.Collections.ArrayList
56+
'PrivateMethods' = [scriptblock]{
57+
function Get-Options {
58+
param (
59+
[string]$configString,
60+
[hashtable]$options_found,
61+
[string[]]$options_allowed,
62+
[Regex]$options_allowed_regex,
63+
[string[]]$options_not_switches
64+
)
65+
if ($g_debugFlag -band 4) { Write-Debug "[BlockFactory][Get-Options] Verbose stream: $VerbosePreference" }
66+
if ($g_debugFlag -band 4) { Write-Debug "[BlockFactory][Get-Options] Debug stream: $DebugPreference" }
67+
if ($g_debugFlag -band 4) { Write-Debug "[BlockFactory][Get-Options] Erroraction: $ErrorActionPreference" }
68+
69+
$matches = $options_allowed_regex.Matches($configString)
70+
if ($matches.success) {
71+
$matches | ForEach-Object {
72+
# Get key and value
73+
$match = $_
74+
$key = if ($match.Groups[1].Value) { $match.Groups[1].Value } else { $match.Groups[3].Value }
75+
$value = if ($match.Groups[2].Value) { $match.Groups[2].Value } else { $match.Groups[4].Value }
76+
77+
#Write-Verbose "`nLine: $line"
78+
#Write-Verbose "key: $key"
79+
#Write-Verbose "value: $value"
80+
#Write-Verbose "Contains: $($options_allowed.Contains($key))"
81+
82+
# Store this option to hashtable. If there are duplicate options, later override earlier ones.
83+
if ($key) {
84+
if ($options_allowed.Contains($key)) {
85+
$options_found[$key] = if ($options_not_switches.Contains($key)) {
86+
# Don't trim if it's an option with a multiline value
87+
if (!$g_options_not_singleline.Contains($key)) {
88+
$value.Trim()
89+
}else {
90+
$value
91+
}
92+
} else { $true }
93+
}
94+
}
95+
}
96+
}
97+
#$options_found
98+
}
99+
100+
function Override-Options {
101+
param (
102+
[hashtable]$child,
103+
[hashtable]$parent
104+
)
105+
# Override my parent options with my options
106+
$my_options = $parent.Clone()
107+
$child.GetEnumerator() | ForEach-Object {
108+
$key = $_.Name
109+
$value = $_.Value
110+
$my_options[$key] = $value
111+
}
112+
113+
# When I said yes, and I didn't say no, but my parent said no, I will still go ahead.
114+
$g_no_yes.GetEnumerator() | ForEach-Object {
115+
$no = $_.Name
116+
$yes = $_.Value
117+
118+
if ( $child.ContainsKey($yes) -and (!$child.ContainsKey($no)) -and $parent.ContainsKey($no) ) {
119+
if ($g_debugFlag -band 4) { Write-Verbose "I said $yes, I didn't say $no, although my parent said $no, I'll still go ahead." }
120+
$my_options.Remove($no)
121+
}
122+
}
123+
$my_options
124+
}
125+
126+
# Returns an array of log files, that match a given blockpath pattern but whose fullpath is not already present in a unique store
127+
function Get-Block-Logs {
128+
param ([object]$blockObject)
129+
130+
if ($g_debugFlag -band 4) { Write-Debug "[BlockFactory][Get-Block-Logs] Verbose stream: $VerbosePreference" }
131+
if ($g_debugFlag -band 4) { Write-Debug "[BlockFactory][Get-Block-Logs] Debug stream: $DebugPreference" }
132+
if ($g_debugFlag -band 4) { Write-Debug "[BlockFactory][Get-Block-Logs] Erroraction: $ErrorActionPreference" }
133+
134+
$blockpath = $blockObject['Path']
135+
$opt_tabooext = $blockObject['Options']['tabooext']
136+
$opt_missingok = if ($blockObject['Options']['notmissingok']) { $false } else { $blockObject['Options']['missingok'] }
137+
138+
# Split the blockpath pattern by spaces, to get either 1) log paths or 2) wildcarded-paths
139+
$logpaths = [System.Collections.Arraylist]@()
140+
$matches = [Regex]::Matches($blockpath, '"([^"]+)"|([^\s]+)')
141+
if ($matches.success) {
142+
$matches | ForEach-Object {
143+
$path = if ($_.Groups[1].Value.Trim()) {
144+
$_.Groups[1].Value
145+
}else {
146+
$_.Groups[2].Value
147+
}
148+
if ($path) {
149+
$logpaths.Add($path) | Out-Null
150+
}
151+
}
152+
}
153+
154+
# Get all the log files matching path patterns defined in the Config.
155+
$logfiles = New-Object System.Collections.Arraylist
156+
foreach ($logpath in $logpaths) {
157+
# Test if the path (without any wildcards) exists
158+
if ($logpath -match '\*') {
159+
160+
# It's a wildcarded path.
161+
Write-Verbose "Considering wildcarded path $logpath"
162+
163+
if (Test-Path -Path $logpath) {
164+
# Add all files that match the wildcard path
165+
$items = Get-ChildItem $logpath -File | Where-Object { (likeIn $_.Name $opt_tabooext.Split(',').Trim()) -eq $false }
166+
$items | ForEach-Object {
167+
$logfiles.Add($_) | Out-Null
168+
}
169+
}else {
170+
if (!$opt_missingok) {
171+
Write-Verbose "Excluding wildcarded path $logpath for rotation, because it doesn't exist!"
172+
}
173+
}
174+
}else {
175+
# It's a non-wildcarded path. Reject if it's a folder.
176+
177+
if (Test-Path -LiteralPath $logpath) {
178+
$item = Get-Item -Path $logpath
179+
if ($item.PSIsContainer) {
180+
# It's a directory. Ignore it.
181+
if (!$opt_missingok) {
182+
Write-Verbose "Excluding path $logpath for rotation, because it is a directory. Directories cannot be rotated. If rotating all the files in the directory, append a wildcard to the end of the path."
183+
}
184+
}else {
185+
# It's a file. Add
186+
$logfiles.Add($item) | Out-Null
187+
}
188+
}else {
189+
if (!$opt_missingok) {
190+
Write-Verbose "Excluding log $logpath for rotation, because it doesn't exist or does not point to file!"
191+
}
192+
}
193+
}
194+
}
195+
196+
# Add unique log files to our list. If a logs already added, it must be a duplicate so we ignore it.
197+
if ($logfiles.Count) {
198+
$logfileCount = $logfiles.Count - 1
199+
foreach ($i in (0..$logfileCount)) {
200+
$logfile = $logfiles[$i]
201+
if ($logfile.FullName -in $this.UniqueLogFileNames) {
202+
Write-Verbose "CONFIG: WARNING - Duplicate Log included: $($logfile.FullName) (matched in block pattern: $blockpath). Skipping rotation for this entry."
203+
$logfiles.Remove($logfile)
204+
}else {
205+
$this.UniqueLogFileNames.Add($logfile.FullName) | Out-Null
206+
}
207+
}
208+
}
209+
210+
$logfiles
211+
}
212+
213+
}
214+
}
215+
$BlockFactory | Add-Member -Name 'Create' -MemberType ScriptMethod -Value {
216+
param ([string]$FullConfig)
217+
218+
if ($g_debugFlag -band 4) { Write-Debug "[BlockFactory][Create] Verbose stream: $VerbosePreference" }
219+
if ($g_debugFlag -band 4) { Write-Debug "[BlockFactory][Create] Debug stream: $DebugPreference" }
220+
if ($g_debugFlag -band 4) { Write-Debug "[BlockFactory][Create] Erroraction: $ErrorActionPreference" }
221+
222+
# Unpack my properties
223+
. $this.Constants
224+
225+
# Unpack my methods
226+
. $this.PrivateMethods
227+
228+
# Parse Full Config for global options as hashtable
229+
if ($g_debugFlag -band 4) { Write-Debug "[BlockFactory][Create][Getting global options]" }
230+
$globalconfig = $g_localconfigs_regex.Replace($FullConfig, '')
231+
Get-Options $globalconfig $this.GlobalOptions $g_globaloptions_allowed $g_globaloptions_allowed_regex $g_options_not_switches
232+
233+
# Parse Full Config for all found local block(s) path pattern, options, and matching log files, storing them as hashtable. Override the global options.
234+
# TODO: Regex for localconfigs to match paths on multiple lines before { }
235+
if ($g_debugFlag -band 4) { Write-Debug "[BlockFactory][Create][Getting block options]" }
236+
$matches = $g_localconfigs_regex.Matches($FullConfig)
237+
if ($matches.success) {
238+
foreach ($localconfig in $matches) {
239+
# NOTE: NOT USED ANYMORE: A block pattern should delimit multiple paths with a single space
240+
#$my_path_pattern = ($localconfig.Groups[1].Value -Split ' ' | Where-Object { $_.Trim() }).Trim() -join ' '
241+
# Just get the raw path pattern
242+
$my_path_pattern = $localconfig.Groups[1].Value.Trim()
243+
if ($my_path_pattern -in $this.Blocks.Keys) {
244+
Write-Verbose "CONFIG: WARNING - Duplicate path pattern $my_path_pattern . Only the latest entry will be used."
245+
}
246+
# Any duplicate block path pattern overrides the previous
247+
$this.Blocks[$my_path_pattern] = @{
248+
'Path' = $my_path_pattern
249+
'Options' = @{}
250+
'LocalOptions' = @{}
251+
'LogFiles' = ''
252+
}
253+
try {
254+
Get-Options $localconfig.Groups[2].Value $this.Blocks[$my_path_pattern]['LocalOptions'] $g_localoptions_allowed $g_localoptions_allowed_regex $g_options_not_switches
255+
$this.Blocks[$my_path_pattern]['Options'] = Override-Options $this.Blocks[$my_path_pattern]['LocalOptions'] $this.GlobalOptions
256+
$this.Blocks[$my_path_pattern]['LogFiles'] = Get-Block-Logs $this.Blocks[$my_path_pattern] $this.UniqueLogFileNames
257+
} catch {
258+
Write-Error "$(Get-Exception-Message($_))" -ErrorAction Continue
259+
}
260+
}
261+
}else {
262+
Write-Verbose "CONFIG: WARNING - No configuration blocks were found."
263+
}
264+
}
265+
$BlockFactory | Add-Member -Name 'GetAll' -MemberType ScriptMethod -Value {
266+
$this.Blocks
267+
}
268+
269+
$BlockFactory
270+
}

0 commit comments

Comments
 (0)