Skip to content

Commit 1494f4e

Browse files
authored
Merge pull request #1 from launchdarkly-labs/test-ld-cleanup
Test: Add LaunchDarkly cleanup workflow
2 parents 25f2c18 + caba57b commit 1494f4e

File tree

6 files changed

+129
-40
lines changed

6 files changed

+129
-40
lines changed

.github/workflows/pr-cleanup-report.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@ jobs:
2121
LD_ACCESS_TOKEN: ${{ secrets.LD_ACCESS_TOKEN }}
2222
shell: pwsh
2323
run: |
24+
echo "Starting LaunchDarkly cleanup analysis..."
25+
echo "Using config file: ./config/projects-config.json"
2426
./scripts/ld-flag-cleanup.ps1 `
2527
-ConfigFile "./config/projects-config.json" # replace with your own config file
28+
echo "Analysis complete. Checking artifacts..."
29+
ls -la artifacts/
2630
2731
- name: Upload artifacts
2832
uses: actions/upload-artifact@v4

artifacts/ld-cleanup-report.csv

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,19 @@
2727
"cxld-nice-mulberry","test,production","release-force-update","Release: Force Update","inactive","temporary",,"1750877940984","False","False","True","READY FOR ARCHIVAL never requested/evaluated","never","91"
2828
"cxld-nice-mulberry","test,production","release-ddos-protection","Release: DDOS Protection","inactive","temporary",,"1750877940213","False","False","True","READY FOR ARCHIVAL never requested/evaluated","never","91"
2929
"tarq-scratchpad","test,production","flag-with-shared-dependency","Flag with shared dependency","inactive","temporary","9/14/2021 7:58:13 PM","1621271439001","False","False","True","READY FOR ARCHIVAL","1471","1591"
30-
"tarq-scratchpad","test,production","release-widget-frontend","Release: Widget Frontend","inactive","temporary","11/14/2021 10:50:20 PM","1610632840104","False","False","True","READY FOR ARCHIVAL","1409","1714"
30+
"tarq-scratchpad","test,production","release-widget-frontend","Release: Widget Frontend","inactive","temporary","11/14/2021 10:50:20 PM","1610632840104","False","False","True","READY FOR ARCHIVAL","1410","1714"
3131
"tarq-scratchpad","test,production","show-table-row","show table row","inactive","temporary","9/14/2021 8:02:14 PM","1631649551480","False","False","True","READY FOR ARCHIVAL","1471","1471"
3232
"tarq-scratchpad","test,production","db-migration-add-ttl-column","DB: Migration: Add TTL Column","inactive","temporary","9/14/2021 7:58:13 PM","1618513236804","False","False","True","READY FOR ARCHIVAL","1471","1623"
33-
"tarq-scratchpad","test,production","anon-flag","Anon Flag","inactive","temporary","9/14/2021 7:58:13 PM","1621040310021","False","False","True","READY FOR ARCHIVAL","1471","1593"
34-
"tarq-scratchpad","test,production","enable-shiny-new-feature","Enable Shiny New Feature","inactive","temporary","11/14/2021 10:50:20 PM","1621546773552","False","False","True","READY FOR ARCHIVAL","1409","1587"
33+
"tarq-scratchpad","test,production","anon-flag","Anon Flag","inactive","temporary","9/14/2021 7:58:13 PM","1621040310021","False","False","True","READY FOR ARCHIVAL","1471","1594"
34+
"tarq-scratchpad","test,production","enable-shiny-new-feature","Enable Shiny New Feature","inactive","temporary","11/14/2021 10:50:20 PM","1621546773552","False","False","True","READY FOR ARCHIVAL","1410","1588"
3535
"tarq-scratchpad","test,production","release-widget","Release: Widget","inactive","temporary","9/14/2021 8:02:14 PM","1631029618511","False","False","True","READY FOR ARCHIVAL","1471","1478"
36-
"tarq-scratchpad","test,production","testflag","testflag","inactive","temporary","9/14/2021 7:58:13 PM","1614113602175","False","False","True","READY FOR ARCHIVAL","1471","1673"
36+
"tarq-scratchpad","test,production","testflag","testflag","inactive","temporary","9/14/2021 7:58:13 PM","1614113602175","False","False","True","READY FOR ARCHIVAL","1471","1674"
3737
"tarq-scratchpad","test,production","migrate-fraud-detection-backend","Migrate: Fraud Detection Backend","inactive","temporary","9/14/2021 7:58:13 PM","1599570554942","False","False","True","READY FOR ARCHIVAL","1471","1842"
3838
"tarq-scratchpad","test,production","flag-with-no-shared-dependency","Flag with no shared dependency","inactive","temporary","9/14/2021 7:58:13 PM","1621273561445","False","False","True","READY FOR ARCHIVAL","1471","1591"
39-
"tarq-scratchpad","test,production","child-flag","Child Flag","inactive","temporary","9/14/2021 7:58:13 PM","1621039686062","False","False","True","READY FOR ARCHIVAL","1471","1593"
39+
"tarq-scratchpad","test,production","child-flag","Child Flag","inactive","temporary","9/14/2021 7:58:13 PM","1621039686062","False","False","True","READY FOR ARCHIVAL","1471","1594"
4040
"tarq-scratchpad","test,production","master-new-branding","Master: New Branding","inactive","temporary","9/14/2021 7:58:13 PM","1611254310843","False","False","True","READY FOR ARCHIVAL","1471","1707"
4141
"tarq-scratchpad","test,production","release-profile-v-2","Release: Profile v2","inactive","temporary","9/14/2021 7:58:13 PM","1597063187352","False","False","True","READY FOR ARCHIVAL","1471","1871"
42-
"tarq-scratchpad","test,production","master-flag","Master Flag","inactive","temporary","11/14/2021 10:50:20 PM","1578409488522","False","False","True","READY FOR ARCHIVAL","1409","2087"
43-
"tarq-scratchpad","test,production","auth-flag","Auth Flag","inactive","temporary","9/14/2021 7:58:13 PM","1621040337230","False","False","True","READY FOR ARCHIVAL","1471","1593"
42+
"tarq-scratchpad","test,production","master-flag","Master Flag","inactive","temporary","11/14/2021 10:50:20 PM","1578409488522","False","False","True","READY FOR ARCHIVAL","1410","2087"
43+
"tarq-scratchpad","test,production","auth-flag","Auth Flag","inactive","temporary","9/14/2021 7:58:13 PM","1621040337230","False","False","True","READY FOR ARCHIVAL","1471","1594"
4444
"tarq-scratchpad","test,production","supports-profile-v-2","Supports: Profile v2","inactive","temporary","9/14/2021 7:58:13 PM","1597063078446","False","False","True","READY FOR ARCHIVAL","1471","1871"
45-
"tarq-scratchpad","test,production","hello-ios-boolean","hello-ios-boolean","inactive","temporary","9/14/2021 7:58:13 PM","1614296599663","False","False","True","READY FOR ARCHIVAL","1471","1671"
45+
"tarq-scratchpad","test,production","hello-ios-boolean","hello-ios-boolean","inactive","temporary","9/14/2021 7:58:13 PM","1614296599663","False","False","True","READY FOR ARCHIVAL","1471","1672"

artifacts/ld-cleanup-report.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@
539539
"reasons": [
540540
"READY FOR ARCHIVAL"
541541
],
542-
"lastRequestedDays": 1409,
542+
"lastRequestedDays": 1410,
543543
"createdDays": 1714
544544
},
545545
{
@@ -594,7 +594,7 @@
594594
"READY FOR ARCHIVAL"
595595
],
596596
"lastRequestedDays": 1471,
597-
"createdDays": 1593
597+
"createdDays": 1594
598598
},
599599
{
600600
"project": "tarq-scratchpad",
@@ -611,8 +611,8 @@
611611
"reasons": [
612612
"READY FOR ARCHIVAL"
613613
],
614-
"lastRequestedDays": 1409,
615-
"createdDays": 1587
614+
"lastRequestedDays": 1410,
615+
"createdDays": 1588
616616
},
617617
{
618618
"project": "tarq-scratchpad",
@@ -648,7 +648,7 @@
648648
"READY FOR ARCHIVAL"
649649
],
650650
"lastRequestedDays": 1471,
651-
"createdDays": 1673
651+
"createdDays": 1674
652652
},
653653
{
654654
"project": "tarq-scratchpad",
@@ -702,7 +702,7 @@
702702
"READY FOR ARCHIVAL"
703703
],
704704
"lastRequestedDays": 1471,
705-
"createdDays": 1593
705+
"createdDays": 1594
706706
},
707707
{
708708
"project": "tarq-scratchpad",
@@ -755,7 +755,7 @@
755755
"reasons": [
756756
"READY FOR ARCHIVAL"
757757
],
758-
"lastRequestedDays": 1409,
758+
"lastRequestedDays": 1410,
759759
"createdDays": 2087
760760
},
761761
{
@@ -774,7 +774,7 @@
774774
"READY FOR ARCHIVAL"
775775
],
776776
"lastRequestedDays": 1471,
777-
"createdDays": 1593
777+
"createdDays": 1594
778778
},
779779
{
780780
"project": "tarq-scratchpad",
@@ -810,6 +810,6 @@
810810
"READY FOR ARCHIVAL"
811811
],
812812
"lastRequestedDays": 1471,
813-
"createdDays": 1671
813+
"createdDays": 1672
814814
}
815815
]

config/cleanup-rules.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ checkForFlagType: true # whether to check consider the flag type (tempo
1010
checkForFlagStatus: true # whether to check the flag status (new/active/launched/inactive)
1111

1212
# Debug output
13-
printDebugLogs: 3 # number of flags to show debug info for (0 = no debug logs)
13+
printDebugLogs: 0 # number of flags to show debug info for (0 = no debug logs)

scripts/ld-flag-cleanup.ps1

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -322,12 +322,24 @@ foreach ($projectConfig in $projectsToProcess) {
322322
}
323323
}
324324

325-
# Determine purpose based on aggregated status
326-
# For code removal: status should be 'launched' (serving same variation to everyone)
327-
# For archival: status should be 'inactive' (not serving any traffic)
328-
$purpose = if ($aggregatedStatus.status -eq "launched") { "codeRemoval" } else { "archival" }
325+
# Determine purpose based on code references first, then status
326+
# Flags with code references should NEVER be considered for archival
327+
$hasCodeRefs = $false
328+
if ($flag.codeReferences -and $flag.codeReferences.items -and $flag.codeReferences.items.Count -gt 0) {
329+
$hasCodeRefs = $true
330+
}
331+
332+
if ($hasCodeRefs) {
333+
# Flags with code references should only be considered for code removal
334+
$purpose = "codeRemoval"
335+
} else {
336+
# Flags without code references can be considered for archival based on status
337+
# For code removal: status should be 'launched' (serving same variation to everyone)
338+
# For archival: status should be 'inactive' (not serving any traffic)
339+
$purpose = if ($aggregatedStatus.status -eq "launched") { "codeRemoval" } else { "archival" }
340+
}
329341

330-
Write-Verbose "Processing flag: $($flag.key) (aggregated across $($environments.Count) environments, status: $($aggregatedStatus.status), purpose: $purpose)"
342+
Write-Verbose "Processing flag: $($flag.key) (aggregated across $($environments.Count) environments, status: $($aggregatedStatus.status), hasCodeRefs: $hasCodeRefs, purpose: $purpose)"
331343
$decision = Invoke-FlagProcessing -Flag $flag -StatusLookup $aggregatedStatus -Project $project -Env $environments -Rules $rules -Purpose $purpose
332344
[void]$candidates.Add($decision)
333345
$script:processedFlags++

scripts/ld-helpers.ps1

Lines changed: 90 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -215,26 +215,84 @@ function Test-Flag {
215215
$neverRequested = $null -eq $lastReqDays
216216
$neverCreated = $null -eq $createdDays
217217

218-
# Since flags are pre-filtered by ldcli, we only need to check status
218+
# Safety check: Flags with code references should NEVER be marked for archival
219+
# But only if code reference checking is enabled
220+
if ($Rules.checkForCodeReferences -and $Context.HasCodeRefs -and $Purpose -eq "archival") {
221+
[void]$reasons.Add("SAFETY CHECK: Flag has code references, cannot be archived")
222+
$readyForArchival = $false
223+
# Convert to code removal purpose instead
224+
$Purpose = "codeRemoval"
225+
}
226+
227+
# Validate all criteria based on configuration and purpose
228+
$validationPassed = $true
229+
230+
# 1. Creation date validation (always required)
231+
if ($neverCreated) {
232+
[void]$reasons.Add("creation date unknown")
233+
$validationPassed = $false
234+
} elseif ($createdDays -lt $Rules.daysSinceCreation) {
235+
[void]$reasons.Add("created $([int]$createdDays) days ago (need >$($Rules.daysSinceCreation) days)")
236+
$validationPassed = $false
237+
}
238+
239+
# 2. Evaluation date validation (different for each purpose)
240+
if ($Purpose -eq "codeRemoval") {
241+
# For code removal: must have recent evaluations
242+
if ($neverRequested) {
243+
[void]$reasons.Add("never evaluated (need recent evaluations for code removal)")
244+
$validationPassed = $false
245+
} elseif ($lastReqDays -gt $Rules.daysSinceLastEvaluation) {
246+
[void]$reasons.Add("last evaluated $([int]$lastReqDays) days ago (need <$($Rules.daysSinceLastEvaluation) days)")
247+
$validationPassed = $false
248+
}
249+
} elseif ($Purpose -eq "archival") {
250+
# For archival: should NOT have recent evaluations
251+
if (-not $neverRequested -and $lastReqDays -le $Rules.daysSinceLastEvaluation) {
252+
[void]$reasons.Add("evaluated $([int]$lastReqDays) days ago (need >$($Rules.daysSinceLastEvaluation) days for archival)")
253+
$validationPassed = $false
254+
}
255+
}
256+
257+
# 3. Flag type validation (if enabled)
258+
if ($Rules.checkForFlagType -and $Context.FlagType -ne "temporary") {
259+
[void]$reasons.Add("flag type=$($Context.FlagType) (need temporary)")
260+
$validationPassed = $false
261+
}
262+
263+
# 4. Flag status validation (if enabled)
219264
if ($Rules.checkForFlagStatus) {
220-
if ($Purpose -eq "codeRemoval" -and $Context.Status -eq "launched") {
221-
$readyForCodeRemoval = $true
222-
[void]$reasons.Add("READY FOR CODE REMOVAL")
223-
} elseif ($Purpose -eq "archival" -and $Context.Status -eq "inactive") {
224-
$readyForArchival = $true
225-
[void]$reasons.Add("READY FOR ARCHIVAL")
226-
} else {
227-
[void]$reasons.Add("status=$($Context.Status) (need launched for code removal, inactive for archival)")
265+
if ($Purpose -eq "codeRemoval" -and $Context.Status -ne "launched") {
266+
[void]$reasons.Add("status=$($Context.Status) (need launched for code removal)")
267+
$validationPassed = $false
268+
} elseif ($Purpose -eq "archival" -and $Context.Status -ne "inactive") {
269+
[void]$reasons.Add("status=$($Context.Status) (need inactive for archival)")
270+
$validationPassed = $false
228271
}
229-
} else {
230-
# If not checking status, all pre-filtered flags are ready
272+
}
273+
274+
# 5. Code references validation (if enabled)
275+
if ($Rules.checkForCodeReferences) {
276+
if ($Purpose -eq "codeRemoval" -and -not $Context.HasCodeRefs) {
277+
[void]$reasons.Add("no code references (need code references for code removal)")
278+
$validationPassed = $false
279+
} elseif ($Purpose -eq "archival" -and $Context.HasCodeRefs) {
280+
[void]$reasons.Add("has code references (need no code references for archival)")
281+
$validationPassed = $false
282+
}
283+
}
284+
285+
# Set final decision based on validation
286+
if ($validationPassed) {
231287
if ($Purpose -eq "codeRemoval") {
232288
$readyForCodeRemoval = $true
233289
[void]$reasons.Add("READY FOR CODE REMOVAL")
234290
} elseif ($Purpose -eq "archival") {
235291
$readyForArchival = $true
236292
[void]$reasons.Add("READY FOR ARCHIVAL")
237293
}
294+
} else {
295+
[void]$reasons.Add("NOT READY - validation failed")
238296
}
239297

240298
# Add information about never being requested
@@ -304,12 +362,13 @@ function Test-Flag {
304362
function New-PrSummary {
305363
param($Candidates, $Rules, [string]$OutPath)
306364

307-
$codeRemovalCandidates = $Candidates | Where-Object { $_.readyForCodeRemoval } | Select-Object -First 10
308-
$archivalCandidates = $Candidates | Where-Object { $_.readyForArchival } | Select-Object -First 10
365+
# Get all candidates from all projects, not just the first 10
366+
$codeRemovalCandidates = $Candidates | Where-Object { $_.readyForCodeRemoval }
367+
$archivalCandidates = $Candidates | Where-Object { $_.readyForArchival }
309368

310369
$lines = @("# LaunchDarkly Flag Cleanup Report",
311370
"",
312-
"**Rules**: temporary flags, created >$($Rules.creationDays) days ago, evaluation threshold: $($Rules.evaluationDays) days",
371+
"**Rules**: temporary flags, created >$($Rules.daysSinceCreation) days ago, evaluation threshold: $($Rules.daysSinceLastEvaluation) days",
313372
"")
314373

315374
if ($codeRemovalCandidates.Count -gt 0) {
@@ -319,19 +378,33 @@ function New-PrSummary {
319378
$lines += "| Project | Env | Key | Status | LastRequested | Created |"
320379
$lines += "|---|---|---|---|---|---|"
321380
foreach ($c in $codeRemovalCandidates) {
322-
$lines += "| $($c.project) | $($c.environment) | `$($c.key)` | $($c.status) | $($c.lastRequested) | $($c.createdDate) |"
381+
# Format the data properly
382+
$lastRequested = if ($c.lastRequested) { $c.lastRequested } else { "never" }
383+
$createdDate = if ($c.createdDate) {
384+
[DateTimeOffset]::FromUnixTimeMilliseconds($c.createdDate).ToString("yyyy-MM-dd")
385+
} else {
386+
"unknown"
387+
}
388+
$lines += "| $($c.project) | $($c.environment) | $($c.key) | $($c.status) | $lastRequested | $createdDate |"
323389
}
324390
$lines += ""
325391
}
326392

327393
if ($archivalCandidates.Count -gt 0) {
328394
$lines += "## Ready for Archival ($($archivalCandidates.Count) flags)"
329-
$lines += "Flags that are inactive, have no recent evaluations, and no code references:"
395+
$lines += "Flags that are inactive, have no recent evaluations, and have NO code references:"
330396
$lines += ""
331397
$lines += "| Project | Env | Key | Status | LastRequested | Created |"
332398
$lines += "|---|---|---|---|---|---|"
333399
foreach ($c in $archivalCandidates) {
334-
$lines += "| $($c.project) | $($c.environment) | `$($c.key)` | $($c.status) | $($c.lastRequested) | $($c.createdDate) |"
400+
# Format the data properly
401+
$lastRequested = if ($c.lastRequested) { $c.lastRequested } else { "never" }
402+
$createdDate = if ($c.createdDate) {
403+
[DateTimeOffset]::FromUnixTimeMilliseconds($c.createdDate).ToString("yyyy-MM-dd")
404+
} else {
405+
"unknown"
406+
}
407+
$lines += "| $($c.project) | $($c.environment) | $($c.key) | $($c.status) | $lastRequested | $createdDate |"
335408
}
336409
$lines += ""
337410
}

0 commit comments

Comments
 (0)