ghcr-manager 0.9.7 → 0.9.8
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.
- package/CHANGELOG.md +39 -1
- package/LICENSE +1 -1
- package/README.md +37 -56
- package/dist/cleanup-summary/_cleanup-summary-markdown.js +7 -10
- package/dist/cleanup-summary/_cleanup-summary.d.ts +3 -4
- package/dist/cleanup-summary/_cleanup-summary.js +2 -3
- package/dist/cli/_cleanup-command.js +1 -1
- package/dist/cli/_tag-selector-resolver.js +29 -9
- package/dist/cli/index.js +0 -4
- package/dist/core/_types.d.ts +1 -6
- package/dist/core/_types.js +1 -1
- package/dist/db/_db-merge-scan-copy.js +3 -2
- package/dist/db/_manifest-reachability.js +47 -8
- package/dist/db/_scan-writer.js +1 -13
- package/dist/db/planner/_planner-direct-target-root-options.d.ts +11 -0
- package/dist/db/planner/_planner-direct-target-root-options.js +1 -0
- package/dist/db/planner/_planner-direct-target-root-tag-filters.d.ts +9 -0
- package/dist/db/planner/_planner-direct-target-root-tag-filters.js +42 -0
- package/dist/db/planner/_planner-direct-target-roots-combined-sql.d.ts +7 -0
- package/dist/db/planner/_planner-direct-target-roots-combined-sql.js +198 -0
- package/dist/db/planner/_planner-direct-target-roots-combined.d.ts +4 -0
- package/dist/db/planner/_planner-direct-target-roots-combined.js +10 -0
- package/dist/db/planner/_planner-direct-target-roots-tagged.d.ts +4 -0
- package/dist/db/planner/_planner-direct-target-roots-tagged.js +125 -0
- package/dist/db/planner/_planner-direct-target-roots.d.ts +2 -12
- package/dist/db/planner/_planner-direct-target-roots.js +8 -203
- package/dist/db/planner/_planner-direct-target-tags.js +3 -4
- package/dist/db/planner/_planner-output.js +28 -8
- package/dist/db/planner/_planner-plan-artifacts-blocked-roots-sql.d.ts +1 -0
- package/dist/db/planner/_planner-plan-artifacts-blocked-roots-sql.js +65 -0
- package/dist/db/planner/_planner-plan-artifacts-closure-sql.d.ts +1 -0
- package/dist/db/planner/_planner-plan-artifacts-closure-sql.js +195 -0
- package/dist/db/planner/_planner-plan-artifacts-supported-untag-only-sql.d.ts +1 -0
- package/dist/db/planner/_planner-plan-artifacts-supported-untag-only-sql.js +86 -0
- package/dist/db/planner/_planner-plan-artifacts.js +26 -128
- package/dist/db/planner/_planner-sql.js +13 -2
- package/dist/db/planner/_planner-types.d.ts +2 -0
- package/dist/db/planner/_planner-types.js +1 -0
- package/dist/execute/_plan-executor.js +7 -11
- package/dist/execute/_types.d.ts +2 -19
- package/dist/execute/_untag-client.d.ts +2 -2
- package/dist/execute/_untag-client.js +1 -42
- package/dist/execute/index.d.ts +1 -1
- package/dist/ingest/github/_manifest-kind.d.ts +6 -0
- package/dist/ingest/github/_manifest-kind.js +16 -2
- package/package.json +16 -10
- package/resources/sql/schema/001_schema.sql +14 -4
- package/dist/cli/_untag-command.d.ts +0 -1
- package/dist/cli/_untag-command.js +0 -58
- package/dist/db/_manifest-kind-refinement.d.ts +0 -2
- package/dist/db/_manifest-kind-refinement.js +0 -43
- package/resources/sql/views/002_v_scan_root_manifests.sql +0 -44
- package/resources/sql/views/003_v_digest_tag_relations.sql +0 -50
- package/resources/sql/views/004_v_cleanup_root_closure_members.sql +0 -101
- package/resources/sql/views/005_v_cleanup_blocking_overlaps.sql +0 -42
- package/resources/sql/views/006_v_cleanup_root_decision_readable.sql +0 -67
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { buildTagSelectorPredicate } from "./_planner-tag-selectors.js";
|
|
2
|
+
export function buildDirectTargetRootTagFilters(sql, scanId, options) {
|
|
3
|
+
const selectedTagPredicate = options.deleteTags.length > 0
|
|
4
|
+
? buildTagSelectorPredicate(sql.database, "t.tag", options.deleteTags, options.useRegex ?? false)
|
|
5
|
+
: undefined;
|
|
6
|
+
const excludedTagPredicate = options.excludeTags.length > 0
|
|
7
|
+
? buildTagSelectorPredicate(sql.database, "xt.tag", options.excludeTags, options.useRegex ?? false)
|
|
8
|
+
: undefined;
|
|
9
|
+
const selectedTagDigestFlag = options.deleteOrphanedImages ? 1 : 0;
|
|
10
|
+
const selectedTagsSql = selectedTagPredicate
|
|
11
|
+
? `
|
|
12
|
+
SELECT DISTINCT t.version_id, t.tag
|
|
13
|
+
FROM tags t
|
|
14
|
+
WHERE t.scan_id = ?
|
|
15
|
+
AND t.is_digest_tag = ?
|
|
16
|
+
AND (${selectedTagPredicate.sql})
|
|
17
|
+
`
|
|
18
|
+
: `
|
|
19
|
+
SELECT NULL AS version_id, NULL AS tag
|
|
20
|
+
WHERE 1 = 0
|
|
21
|
+
`;
|
|
22
|
+
const selectedParams = selectedTagPredicate ? [scanId, selectedTagDigestFlag, ...selectedTagPredicate.params] : [];
|
|
23
|
+
const excludedVersionsSql = excludedTagPredicate
|
|
24
|
+
? `
|
|
25
|
+
SELECT DISTINCT xt.version_id
|
|
26
|
+
FROM tags xt
|
|
27
|
+
WHERE xt.scan_id = ?
|
|
28
|
+
AND xt.is_digest_tag = 0
|
|
29
|
+
AND (${excludedTagPredicate.sql})
|
|
30
|
+
`
|
|
31
|
+
: `
|
|
32
|
+
SELECT NULL AS version_id
|
|
33
|
+
WHERE 1 = 0
|
|
34
|
+
`;
|
|
35
|
+
const excludedParams = excludedTagPredicate ? [scanId, ...excludedTagPredicate.params] : [];
|
|
36
|
+
return {
|
|
37
|
+
selectedTagsSql,
|
|
38
|
+
selectedParams,
|
|
39
|
+
excludedVersionsSql,
|
|
40
|
+
excludedParams
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { DirectTargetRootOptions } from "./_planner-direct-target-root-options.js";
|
|
2
|
+
export interface CombinedDirectTargetRootsQuery {
|
|
3
|
+
query: string;
|
|
4
|
+
baseParams: Array<number | string>;
|
|
5
|
+
tailParams: Array<number | string>;
|
|
6
|
+
}
|
|
7
|
+
export declare function buildCombinedDirectTargetRootsQuery(scanId: number, options: DirectTargetRootOptions, selectedTagsSql: string, excludedVersionsSql: string): CombinedDirectTargetRootsQuery;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
export function buildCombinedDirectTargetRootsQuery(scanId, options, selectedTagsSql, excludedVersionsSql) {
|
|
2
|
+
const baseParams = [scanId];
|
|
3
|
+
const cutoffSql = options.cutoffTimestamp ? "AND created_at < ?" : "";
|
|
4
|
+
if (options.cutoffTimestamp) {
|
|
5
|
+
baseParams.push(options.cutoffTimestamp);
|
|
6
|
+
}
|
|
7
|
+
const taggedBranchEnabled = options.deleteTagsRequested || options.keepNTagged !== undefined ? 1 : 0;
|
|
8
|
+
const deleteTagsRequested = options.deleteTagsRequested ? 1 : 0;
|
|
9
|
+
const deleteOrphanedImages = options.deleteOrphanedImages ? 1 : 0;
|
|
10
|
+
const keepNTaggedActive = options.keepNTagged !== undefined ? 1 : 0;
|
|
11
|
+
const deleteUntagged = options.deleteUntagged ? 1 : 0;
|
|
12
|
+
const keepNUntaggedActive = options.keepNUntagged !== undefined ? 1 : 0;
|
|
13
|
+
const tailParams = [
|
|
14
|
+
deleteOrphanedImages,
|
|
15
|
+
deleteOrphanedImages,
|
|
16
|
+
taggedBranchEnabled,
|
|
17
|
+
deleteTagsRequested,
|
|
18
|
+
deleteTagsRequested,
|
|
19
|
+
keepNTaggedActive,
|
|
20
|
+
deleteTagsRequested,
|
|
21
|
+
keepNTaggedActive,
|
|
22
|
+
options.keepNTagged ?? 0,
|
|
23
|
+
deleteUntagged,
|
|
24
|
+
keepNUntaggedActive,
|
|
25
|
+
deleteUntagged,
|
|
26
|
+
deleteUntagged,
|
|
27
|
+
keepNUntaggedActive,
|
|
28
|
+
options.keepNUntagged ?? 0
|
|
29
|
+
];
|
|
30
|
+
const query = `
|
|
31
|
+
WITH base_manifests AS (
|
|
32
|
+
SELECT
|
|
33
|
+
m.version_id,
|
|
34
|
+
m.digest AS root_digest,
|
|
35
|
+
m.manifest_kind AS root_manifest_kind,
|
|
36
|
+
pv.created_at
|
|
37
|
+
FROM manifests m
|
|
38
|
+
JOIN package_versions pv
|
|
39
|
+
ON pv.scan_id = m.scan_id
|
|
40
|
+
AND pv.version_id = m.version_id
|
|
41
|
+
WHERE m.scan_id = ?
|
|
42
|
+
${cutoffSql}
|
|
43
|
+
),
|
|
44
|
+
tag_counts AS (
|
|
45
|
+
SELECT
|
|
46
|
+
t.version_id,
|
|
47
|
+
COUNT(t.tag) AS tag_count
|
|
48
|
+
FROM tags t
|
|
49
|
+
WHERE t.scan_id = ?
|
|
50
|
+
AND t.is_digest_tag = 0
|
|
51
|
+
GROUP BY t.version_id
|
|
52
|
+
),
|
|
53
|
+
parented_digests AS (
|
|
54
|
+
SELECT DISTINCT me.child_digest
|
|
55
|
+
FROM manifest_edges me
|
|
56
|
+
WHERE me.scan_id = ?
|
|
57
|
+
AND me.edge_kind != 'digest-tag-referrer'
|
|
58
|
+
),
|
|
59
|
+
root_candidates AS (
|
|
60
|
+
SELECT
|
|
61
|
+
bm.version_id,
|
|
62
|
+
bm.root_digest,
|
|
63
|
+
bm.root_manifest_kind,
|
|
64
|
+
bm.created_at,
|
|
65
|
+
COALESCE(tc.tag_count, 0) AS tag_count,
|
|
66
|
+
CASE WHEN COALESCE(tc.tag_count, 0) > 0 THEN 1 ELSE 0 END AS is_tagged,
|
|
67
|
+
CASE WHEN pd.child_digest IS NULL THEN 0 ELSE 1 END AS has_ancestor
|
|
68
|
+
FROM base_manifests bm
|
|
69
|
+
LEFT JOIN tag_counts tc
|
|
70
|
+
ON tc.version_id = bm.version_id
|
|
71
|
+
LEFT JOIN parented_digests pd
|
|
72
|
+
ON pd.child_digest = bm.root_digest
|
|
73
|
+
),
|
|
74
|
+
selected_tags AS (
|
|
75
|
+
${selectedTagsSql}
|
|
76
|
+
),
|
|
77
|
+
excluded_versions AS (
|
|
78
|
+
${excludedVersionsSql}
|
|
79
|
+
),
|
|
80
|
+
matched_tag_counts AS (
|
|
81
|
+
SELECT
|
|
82
|
+
st.version_id,
|
|
83
|
+
COUNT(DISTINCT st.tag) AS matched_tag_count
|
|
84
|
+
FROM selected_tags st
|
|
85
|
+
GROUP BY st.version_id
|
|
86
|
+
),
|
|
87
|
+
eligible_tagged_roots AS (
|
|
88
|
+
SELECT
|
|
89
|
+
rc.version_id,
|
|
90
|
+
rc.root_digest,
|
|
91
|
+
rc.root_manifest_kind,
|
|
92
|
+
rc.created_at,
|
|
93
|
+
CASE
|
|
94
|
+
WHEN ? = 1 AND rc.tag_count = 0 AND COALESCE(mtc.matched_tag_count, 0) > 0
|
|
95
|
+
THEN COALESCE(mtc.matched_tag_count, 0)
|
|
96
|
+
ELSE rc.tag_count
|
|
97
|
+
END AS total_tag_count,
|
|
98
|
+
COALESCE(mtc.matched_tag_count, 0) AS matched_tag_count
|
|
99
|
+
FROM root_candidates rc
|
|
100
|
+
LEFT JOIN matched_tag_counts mtc
|
|
101
|
+
ON mtc.version_id = rc.version_id
|
|
102
|
+
LEFT JOIN excluded_versions ev
|
|
103
|
+
ON ev.version_id = rc.version_id
|
|
104
|
+
WHERE (
|
|
105
|
+
rc.is_tagged = 1
|
|
106
|
+
OR (? = 1 AND COALESCE(mtc.matched_tag_count, 0) > 0)
|
|
107
|
+
)
|
|
108
|
+
AND ev.version_id IS NULL
|
|
109
|
+
AND ? = 1
|
|
110
|
+
),
|
|
111
|
+
ranked_tagged_roots AS (
|
|
112
|
+
SELECT
|
|
113
|
+
version_id,
|
|
114
|
+
root_digest,
|
|
115
|
+
root_manifest_kind,
|
|
116
|
+
total_tag_count,
|
|
117
|
+
matched_tag_count,
|
|
118
|
+
ROW_NUMBER() OVER (
|
|
119
|
+
ORDER BY created_at DESC, version_id DESC, root_digest DESC
|
|
120
|
+
) AS recency_rank
|
|
121
|
+
FROM eligible_tagged_roots
|
|
122
|
+
WHERE ? = 0
|
|
123
|
+
OR matched_tag_count > 0
|
|
124
|
+
),
|
|
125
|
+
final_tagged_targets AS (
|
|
126
|
+
SELECT
|
|
127
|
+
version_id,
|
|
128
|
+
root_digest,
|
|
129
|
+
root_manifest_kind,
|
|
130
|
+
CASE
|
|
131
|
+
WHEN ? = 0
|
|
132
|
+
THEN 'keep-n-tagged-overflow'
|
|
133
|
+
WHEN ? = 1 AND total_tag_count = matched_tag_count
|
|
134
|
+
THEN 'keep-n-tagged-overflow'
|
|
135
|
+
WHEN total_tag_count = matched_tag_count
|
|
136
|
+
THEN 'delete-tags-all-tags-selected'
|
|
137
|
+
ELSE 'delete-tags-partial-tag-match'
|
|
138
|
+
END AS direct_target_reason,
|
|
139
|
+
CASE
|
|
140
|
+
WHEN ? = 0
|
|
141
|
+
THEN 'delete-root'
|
|
142
|
+
WHEN total_tag_count = matched_tag_count
|
|
143
|
+
THEN 'delete-root'
|
|
144
|
+
ELSE 'untag-only'
|
|
145
|
+
END AS selection_mode
|
|
146
|
+
FROM ranked_tagged_roots
|
|
147
|
+
WHERE ? = 0
|
|
148
|
+
OR recency_rank > ?
|
|
149
|
+
),
|
|
150
|
+
ranked_untagged_roots AS (
|
|
151
|
+
SELECT
|
|
152
|
+
rc.version_id,
|
|
153
|
+
rc.root_digest,
|
|
154
|
+
rc.root_manifest_kind,
|
|
155
|
+
ROW_NUMBER() OVER (
|
|
156
|
+
ORDER BY rc.created_at DESC, rc.version_id DESC, rc.root_digest DESC
|
|
157
|
+
) AS recency_rank
|
|
158
|
+
FROM root_candidates rc
|
|
159
|
+
WHERE rc.is_tagged = 0
|
|
160
|
+
AND rc.has_ancestor = 0
|
|
161
|
+
AND (? = 1 OR ? = 1)
|
|
162
|
+
),
|
|
163
|
+
final_untagged_targets AS (
|
|
164
|
+
SELECT
|
|
165
|
+
version_id,
|
|
166
|
+
root_digest,
|
|
167
|
+
root_manifest_kind,
|
|
168
|
+
CASE
|
|
169
|
+
WHEN ? = 1
|
|
170
|
+
THEN 'delete-untagged'
|
|
171
|
+
ELSE 'keep-n-untagged-overflow'
|
|
172
|
+
END AS direct_target_reason,
|
|
173
|
+
'delete-root' AS selection_mode
|
|
174
|
+
FROM ranked_untagged_roots
|
|
175
|
+
WHERE ? = 1
|
|
176
|
+
OR (? = 1 AND recency_rank > ?)
|
|
177
|
+
)
|
|
178
|
+
SELECT
|
|
179
|
+
version_id,
|
|
180
|
+
root_digest,
|
|
181
|
+
root_manifest_kind,
|
|
182
|
+
direct_target_reason,
|
|
183
|
+
selection_mode
|
|
184
|
+
FROM final_tagged_targets
|
|
185
|
+
|
|
186
|
+
UNION ALL
|
|
187
|
+
|
|
188
|
+
SELECT
|
|
189
|
+
version_id,
|
|
190
|
+
root_digest,
|
|
191
|
+
root_manifest_kind,
|
|
192
|
+
direct_target_reason,
|
|
193
|
+
selection_mode
|
|
194
|
+
FROM final_untagged_targets
|
|
195
|
+
ORDER BY root_digest
|
|
196
|
+
`;
|
|
197
|
+
return { query, baseParams, tailParams };
|
|
198
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DirectTargetRootOptions } from "./_planner-direct-target-root-options.js";
|
|
2
|
+
import type { PlannerSql } from "./_planner-sql.js";
|
|
3
|
+
import { type DeletePlanRoot } from "./_planner-types.js";
|
|
4
|
+
export declare function listCombinedDirectTargetRoots(sql: PlannerSql, scanId: number, options: DirectTargetRootOptions): DeletePlanRoot[];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { buildDirectTargetRootTagFilters } from "./_planner-direct-target-root-tag-filters.js";
|
|
2
|
+
import { buildCombinedDirectTargetRootsQuery } from "./_planner-direct-target-roots-combined-sql.js";
|
|
3
|
+
import { mapPlanRootRow } from "./_planner-types.js";
|
|
4
|
+
export function listCombinedDirectTargetRoots(sql, scanId, options) {
|
|
5
|
+
const { selectedTagsSql, selectedParams, excludedVersionsSql, excludedParams } = buildDirectTargetRootTagFilters(sql, scanId, options);
|
|
6
|
+
const { query, baseParams, tailParams } = buildCombinedDirectTargetRootsQuery(scanId, options, selectedTagsSql, excludedVersionsSql);
|
|
7
|
+
return sql
|
|
8
|
+
.all(query, [...baseParams, scanId, scanId, ...selectedParams, ...excludedParams, ...tailParams])
|
|
9
|
+
.map(mapPlanRootRow);
|
|
10
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { PlannerSql } from "./_planner-sql.js";
|
|
2
|
+
import { type DeletePlanRoot } from "./_planner-types.js";
|
|
3
|
+
import type { DirectTargetRootOptions } from "./_planner-direct-target-root-options.js";
|
|
4
|
+
export declare function listTaggedOnlyDirectTargetRoots(sql: PlannerSql, scanId: number, options: DirectTargetRootOptions): DeletePlanRoot[];
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { buildTagSelectorPredicate } from "./_planner-tag-selectors.js";
|
|
2
|
+
import { mapPlanRootRow } from "./_planner-types.js";
|
|
3
|
+
export function listTaggedOnlyDirectTargetRoots(sql, scanId, options) {
|
|
4
|
+
const selectedTagPredicate = options.deleteTags.length > 0
|
|
5
|
+
? buildTagSelectorPredicate(sql.database, "t.tag", options.deleteTags, options.useRegex ?? false)
|
|
6
|
+
: undefined;
|
|
7
|
+
const excludedTagPredicate = options.excludeTags.length > 0
|
|
8
|
+
? buildTagSelectorPredicate(sql.database, "xt.tag", options.excludeTags, options.useRegex ?? false)
|
|
9
|
+
: undefined;
|
|
10
|
+
const params = [];
|
|
11
|
+
const cutoffSql = options.cutoffTimestamp ? "AND pv.created_at < ?" : "";
|
|
12
|
+
if (options.cutoffTimestamp) {
|
|
13
|
+
params.push(options.cutoffTimestamp);
|
|
14
|
+
}
|
|
15
|
+
const selectedTagDigestFlag = options.deleteOrphanedImages ? 1 : 0;
|
|
16
|
+
const selectedTagsSql = selectedTagPredicate
|
|
17
|
+
? `
|
|
18
|
+
SELECT DISTINCT t.version_id, t.tag
|
|
19
|
+
FROM tags t
|
|
20
|
+
WHERE t.scan_id = ?
|
|
21
|
+
AND t.is_digest_tag = ?
|
|
22
|
+
AND (${selectedTagPredicate.sql})
|
|
23
|
+
`
|
|
24
|
+
: `
|
|
25
|
+
SELECT NULL AS version_id, NULL AS tag
|
|
26
|
+
WHERE 1 = 0
|
|
27
|
+
`;
|
|
28
|
+
if (selectedTagPredicate) {
|
|
29
|
+
params.push(scanId, selectedTagDigestFlag, ...selectedTagPredicate.params);
|
|
30
|
+
}
|
|
31
|
+
const excludedVersionsSql = excludedTagPredicate
|
|
32
|
+
? `
|
|
33
|
+
SELECT DISTINCT xt.version_id
|
|
34
|
+
FROM tags xt
|
|
35
|
+
WHERE xt.scan_id = ?
|
|
36
|
+
AND xt.is_digest_tag = 0
|
|
37
|
+
AND (${excludedTagPredicate.sql})
|
|
38
|
+
`
|
|
39
|
+
: `
|
|
40
|
+
SELECT NULL AS version_id
|
|
41
|
+
WHERE 1 = 0
|
|
42
|
+
`;
|
|
43
|
+
if (excludedTagPredicate) {
|
|
44
|
+
params.push(scanId, ...excludedTagPredicate.params);
|
|
45
|
+
}
|
|
46
|
+
const deleteOrphanedImages = options.deleteOrphanedImages ? 1 : 0;
|
|
47
|
+
const query = `
|
|
48
|
+
WITH selected_tags AS (
|
|
49
|
+
${selectedTagsSql}
|
|
50
|
+
),
|
|
51
|
+
excluded_versions AS (
|
|
52
|
+
${excludedVersionsSql}
|
|
53
|
+
),
|
|
54
|
+
matched_tag_counts AS (
|
|
55
|
+
SELECT
|
|
56
|
+
st.version_id,
|
|
57
|
+
COUNT(DISTINCT st.tag) AS matched_tag_count
|
|
58
|
+
FROM selected_tags st
|
|
59
|
+
GROUP BY st.version_id
|
|
60
|
+
),
|
|
61
|
+
tagged_versions AS (
|
|
62
|
+
SELECT
|
|
63
|
+
m.version_id,
|
|
64
|
+
m.digest AS root_digest,
|
|
65
|
+
m.manifest_kind AS root_manifest_kind,
|
|
66
|
+
COUNT(t.tag) AS total_tag_count
|
|
67
|
+
FROM manifests m
|
|
68
|
+
JOIN package_versions pv
|
|
69
|
+
ON pv.scan_id = m.scan_id
|
|
70
|
+
AND pv.version_id = m.version_id
|
|
71
|
+
LEFT JOIN tags t
|
|
72
|
+
ON t.scan_id = m.scan_id
|
|
73
|
+
AND t.version_id = m.version_id
|
|
74
|
+
AND t.is_digest_tag = 0
|
|
75
|
+
WHERE m.scan_id = ?
|
|
76
|
+
${cutoffSql}
|
|
77
|
+
GROUP BY
|
|
78
|
+
m.version_id,
|
|
79
|
+
m.digest,
|
|
80
|
+
m.manifest_kind
|
|
81
|
+
),
|
|
82
|
+
eligible_tagged_roots AS (
|
|
83
|
+
SELECT
|
|
84
|
+
tv.version_id,
|
|
85
|
+
tv.root_digest,
|
|
86
|
+
tv.root_manifest_kind,
|
|
87
|
+
CASE
|
|
88
|
+
WHEN ? = 1 AND tv.total_tag_count = 0 AND COALESCE(mtc.matched_tag_count, 0) > 0
|
|
89
|
+
THEN COALESCE(mtc.matched_tag_count, 0)
|
|
90
|
+
ELSE tv.total_tag_count
|
|
91
|
+
END AS total_tag_count,
|
|
92
|
+
COALESCE(mtc.matched_tag_count, 0) AS matched_tag_count
|
|
93
|
+
FROM tagged_versions tv
|
|
94
|
+
LEFT JOIN matched_tag_counts mtc
|
|
95
|
+
ON mtc.version_id = tv.version_id
|
|
96
|
+
LEFT JOIN excluded_versions ev
|
|
97
|
+
ON ev.version_id = tv.version_id
|
|
98
|
+
WHERE (
|
|
99
|
+
tv.total_tag_count > 0
|
|
100
|
+
OR (? = 1 AND COALESCE(mtc.matched_tag_count, 0) > 0)
|
|
101
|
+
)
|
|
102
|
+
AND ev.version_id IS NULL
|
|
103
|
+
AND matched_tag_count > 0
|
|
104
|
+
)
|
|
105
|
+
SELECT
|
|
106
|
+
version_id,
|
|
107
|
+
root_digest,
|
|
108
|
+
root_manifest_kind,
|
|
109
|
+
CASE
|
|
110
|
+
WHEN total_tag_count = matched_tag_count
|
|
111
|
+
THEN 'delete-tags-all-tags-selected'
|
|
112
|
+
ELSE 'delete-tags-partial-tag-match'
|
|
113
|
+
END AS direct_target_reason,
|
|
114
|
+
CASE
|
|
115
|
+
WHEN total_tag_count = matched_tag_count
|
|
116
|
+
THEN 'delete-root'
|
|
117
|
+
ELSE 'untag-only'
|
|
118
|
+
END AS selection_mode
|
|
119
|
+
FROM eligible_tagged_roots
|
|
120
|
+
ORDER BY root_digest
|
|
121
|
+
`;
|
|
122
|
+
return sql
|
|
123
|
+
.all(query, [...params, scanId, deleteOrphanedImages, deleteOrphanedImages])
|
|
124
|
+
.map(mapPlanRootRow);
|
|
125
|
+
}
|
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
import { PlannerSql } from "./_planner-sql.js";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
deleteTags: string[];
|
|
5
|
-
deleteTagsRequested: boolean;
|
|
6
|
-
deleteOrphanedImages?: boolean;
|
|
7
|
-
excludeTags: string[];
|
|
8
|
-
deleteUntagged: boolean;
|
|
9
|
-
keepNTagged?: number;
|
|
10
|
-
keepNUntagged?: number;
|
|
11
|
-
useRegex?: boolean;
|
|
12
|
-
cutoffTimestamp?: string;
|
|
13
|
-
}
|
|
2
|
+
import type { DeletePlanRoot } from "./_planner-types.js";
|
|
3
|
+
import type { DirectTargetRootOptions } from "./_planner-direct-target-root-options.js";
|
|
14
4
|
export declare class PlannerDirectTargetRoots {
|
|
15
5
|
#private;
|
|
16
6
|
constructor(sql: PlannerSql);
|
|
@@ -1,212 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { listCombinedDirectTargetRoots } from "./_planner-direct-target-roots-combined.js";
|
|
2
|
+
import { listTaggedOnlyDirectTargetRoots } from "./_planner-direct-target-roots-tagged.js";
|
|
3
3
|
export class PlannerDirectTargetRoots {
|
|
4
4
|
#sql;
|
|
5
5
|
constructor(sql) {
|
|
6
6
|
this.#sql = sql;
|
|
7
7
|
}
|
|
8
8
|
list(scanId, options) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
: undefined;
|
|
15
|
-
const params = [scanId];
|
|
16
|
-
const cutoffSql = options.cutoffTimestamp ? "AND created_at < ?" : "";
|
|
17
|
-
if (options.cutoffTimestamp) {
|
|
18
|
-
params.push(options.cutoffTimestamp);
|
|
9
|
+
if (options.deleteTagsRequested &&
|
|
10
|
+
options.keepNTagged === undefined &&
|
|
11
|
+
!options.deleteUntagged &&
|
|
12
|
+
options.keepNUntagged === undefined) {
|
|
13
|
+
return listTaggedOnlyDirectTargetRoots(this.#sql, scanId, options);
|
|
19
14
|
}
|
|
20
|
-
|
|
21
|
-
const selectedTagsSql = selectedTagPredicate
|
|
22
|
-
? `
|
|
23
|
-
SELECT DISTINCT t.version_id, t.tag
|
|
24
|
-
FROM tags t
|
|
25
|
-
WHERE t.scan_id = ?
|
|
26
|
-
AND t.is_digest_tag = ?
|
|
27
|
-
AND (${selectedTagPredicate.sql})
|
|
28
|
-
`
|
|
29
|
-
: `
|
|
30
|
-
SELECT NULL AS version_id, NULL AS tag
|
|
31
|
-
WHERE 1 = 0
|
|
32
|
-
`;
|
|
33
|
-
if (selectedTagPredicate) {
|
|
34
|
-
params.push(scanId, selectedTagDigestFlag, ...selectedTagPredicate.params);
|
|
35
|
-
}
|
|
36
|
-
const excludedVersionsSql = excludedTagPredicate
|
|
37
|
-
? `
|
|
38
|
-
SELECT DISTINCT xt.version_id
|
|
39
|
-
FROM tags xt
|
|
40
|
-
WHERE xt.scan_id = ?
|
|
41
|
-
AND xt.is_digest_tag = 0
|
|
42
|
-
AND (${excludedTagPredicate.sql})
|
|
43
|
-
`
|
|
44
|
-
: `
|
|
45
|
-
SELECT NULL AS version_id
|
|
46
|
-
WHERE 1 = 0
|
|
47
|
-
`;
|
|
48
|
-
if (excludedTagPredicate) {
|
|
49
|
-
params.push(scanId, ...excludedTagPredicate.params);
|
|
50
|
-
}
|
|
51
|
-
const taggedBranchEnabled = options.deleteTagsRequested || options.keepNTagged !== undefined ? 1 : 0;
|
|
52
|
-
const deleteTagsRequested = options.deleteTagsRequested ? 1 : 0;
|
|
53
|
-
const deleteOrphanedImages = options.deleteOrphanedImages ? 1 : 0;
|
|
54
|
-
const keepNTaggedActive = options.keepNTagged !== undefined ? 1 : 0;
|
|
55
|
-
const deleteUntagged = options.deleteUntagged ? 1 : 0;
|
|
56
|
-
const keepNUntaggedActive = options.keepNUntagged !== undefined ? 1 : 0;
|
|
57
|
-
const paramsTail = [
|
|
58
|
-
deleteOrphanedImages,
|
|
59
|
-
deleteOrphanedImages,
|
|
60
|
-
taggedBranchEnabled,
|
|
61
|
-
deleteTagsRequested,
|
|
62
|
-
deleteTagsRequested,
|
|
63
|
-
keepNTaggedActive,
|
|
64
|
-
deleteTagsRequested,
|
|
65
|
-
keepNTaggedActive,
|
|
66
|
-
options.keepNTagged ?? 0,
|
|
67
|
-
deleteUntagged,
|
|
68
|
-
keepNUntaggedActive,
|
|
69
|
-
deleteUntagged,
|
|
70
|
-
deleteUntagged,
|
|
71
|
-
keepNUntaggedActive,
|
|
72
|
-
options.keepNUntagged ?? 0
|
|
73
|
-
];
|
|
74
|
-
const sql = `
|
|
75
|
-
WITH root_candidates AS (
|
|
76
|
-
SELECT
|
|
77
|
-
root_version_id AS version_id,
|
|
78
|
-
root_digest,
|
|
79
|
-
root_manifest_kind,
|
|
80
|
-
created_at,
|
|
81
|
-
tag_count,
|
|
82
|
-
is_tagged
|
|
83
|
-
FROM v_scan_root_manifests
|
|
84
|
-
WHERE scan_id = ?
|
|
85
|
-
AND has_ancestor = 0
|
|
86
|
-
${cutoffSql}
|
|
87
|
-
),
|
|
88
|
-
selected_tags AS (
|
|
89
|
-
${selectedTagsSql}
|
|
90
|
-
),
|
|
91
|
-
excluded_versions AS (
|
|
92
|
-
${excludedVersionsSql}
|
|
93
|
-
),
|
|
94
|
-
matched_tag_counts AS (
|
|
95
|
-
SELECT
|
|
96
|
-
st.version_id,
|
|
97
|
-
COUNT(DISTINCT st.tag) AS matched_tag_count
|
|
98
|
-
FROM selected_tags st
|
|
99
|
-
GROUP BY st.version_id
|
|
100
|
-
),
|
|
101
|
-
eligible_tagged_roots AS (
|
|
102
|
-
SELECT
|
|
103
|
-
rc.version_id,
|
|
104
|
-
rc.root_digest,
|
|
105
|
-
rc.root_manifest_kind,
|
|
106
|
-
rc.created_at,
|
|
107
|
-
CASE
|
|
108
|
-
WHEN ? = 1 AND rc.tag_count = 0 AND COALESCE(mtc.matched_tag_count, 0) > 0
|
|
109
|
-
THEN COALESCE(mtc.matched_tag_count, 0)
|
|
110
|
-
ELSE rc.tag_count
|
|
111
|
-
END AS total_tag_count,
|
|
112
|
-
COALESCE(mtc.matched_tag_count, 0) AS matched_tag_count
|
|
113
|
-
FROM root_candidates rc
|
|
114
|
-
LEFT JOIN matched_tag_counts mtc
|
|
115
|
-
ON mtc.version_id = rc.version_id
|
|
116
|
-
LEFT JOIN excluded_versions ev
|
|
117
|
-
ON ev.version_id = rc.version_id
|
|
118
|
-
WHERE (
|
|
119
|
-
rc.is_tagged = 1
|
|
120
|
-
OR (? = 1 AND COALESCE(mtc.matched_tag_count, 0) > 0)
|
|
121
|
-
)
|
|
122
|
-
AND ev.version_id IS NULL
|
|
123
|
-
AND ? = 1
|
|
124
|
-
),
|
|
125
|
-
ranked_tagged_roots AS (
|
|
126
|
-
SELECT
|
|
127
|
-
version_id,
|
|
128
|
-
root_digest,
|
|
129
|
-
root_manifest_kind,
|
|
130
|
-
total_tag_count,
|
|
131
|
-
matched_tag_count,
|
|
132
|
-
ROW_NUMBER() OVER (
|
|
133
|
-
ORDER BY created_at DESC, version_id DESC, root_digest DESC
|
|
134
|
-
) AS recency_rank
|
|
135
|
-
FROM eligible_tagged_roots
|
|
136
|
-
WHERE ? = 0
|
|
137
|
-
OR matched_tag_count > 0
|
|
138
|
-
),
|
|
139
|
-
final_tagged_targets AS (
|
|
140
|
-
SELECT
|
|
141
|
-
version_id,
|
|
142
|
-
root_digest,
|
|
143
|
-
root_manifest_kind,
|
|
144
|
-
CASE
|
|
145
|
-
WHEN ? = 0
|
|
146
|
-
THEN 'keep-n-tagged-overflow'
|
|
147
|
-
WHEN ? = 1 AND total_tag_count = matched_tag_count
|
|
148
|
-
THEN 'keep-n-tagged-overflow'
|
|
149
|
-
WHEN total_tag_count = matched_tag_count
|
|
150
|
-
THEN 'delete-tags-all-tags-selected'
|
|
151
|
-
ELSE 'delete-tags-partial-tag-match'
|
|
152
|
-
END AS direct_target_reason,
|
|
153
|
-
CASE
|
|
154
|
-
WHEN ? = 0
|
|
155
|
-
THEN 'delete-root'
|
|
156
|
-
WHEN total_tag_count = matched_tag_count
|
|
157
|
-
THEN 'delete-root'
|
|
158
|
-
ELSE 'untag-only'
|
|
159
|
-
END AS selection_mode
|
|
160
|
-
FROM ranked_tagged_roots
|
|
161
|
-
WHERE ? = 0
|
|
162
|
-
OR recency_rank > ?
|
|
163
|
-
),
|
|
164
|
-
ranked_untagged_roots AS (
|
|
165
|
-
SELECT
|
|
166
|
-
rc.version_id,
|
|
167
|
-
rc.root_digest,
|
|
168
|
-
rc.root_manifest_kind,
|
|
169
|
-
ROW_NUMBER() OVER (
|
|
170
|
-
ORDER BY rc.created_at DESC, rc.version_id DESC, rc.root_digest DESC
|
|
171
|
-
) AS recency_rank
|
|
172
|
-
FROM root_candidates rc
|
|
173
|
-
WHERE rc.is_tagged = 0
|
|
174
|
-
AND (? = 1 OR ? = 1)
|
|
175
|
-
),
|
|
176
|
-
final_untagged_targets AS (
|
|
177
|
-
SELECT
|
|
178
|
-
version_id,
|
|
179
|
-
root_digest,
|
|
180
|
-
root_manifest_kind,
|
|
181
|
-
CASE
|
|
182
|
-
WHEN ? = 1
|
|
183
|
-
THEN 'delete-untagged'
|
|
184
|
-
ELSE 'keep-n-untagged-overflow'
|
|
185
|
-
END AS direct_target_reason,
|
|
186
|
-
'delete-root' AS selection_mode
|
|
187
|
-
FROM ranked_untagged_roots
|
|
188
|
-
WHERE ? = 1
|
|
189
|
-
OR (? = 1 AND recency_rank > ?)
|
|
190
|
-
)
|
|
191
|
-
SELECT
|
|
192
|
-
version_id,
|
|
193
|
-
root_digest,
|
|
194
|
-
root_manifest_kind,
|
|
195
|
-
direct_target_reason,
|
|
196
|
-
selection_mode
|
|
197
|
-
FROM final_tagged_targets
|
|
198
|
-
|
|
199
|
-
UNION ALL
|
|
200
|
-
|
|
201
|
-
SELECT
|
|
202
|
-
version_id,
|
|
203
|
-
root_digest,
|
|
204
|
-
root_manifest_kind,
|
|
205
|
-
direct_target_reason,
|
|
206
|
-
selection_mode
|
|
207
|
-
FROM final_untagged_targets
|
|
208
|
-
ORDER BY root_digest
|
|
209
|
-
`;
|
|
210
|
-
return this.#sql.all(sql, [...params, ...paramsTail]).map(mapPlanRootRow);
|
|
15
|
+
return listCombinedDirectTargetRoots(this.#sql, scanId, options);
|
|
211
16
|
}
|
|
212
17
|
}
|
|
@@ -38,12 +38,11 @@ export class PlannerDirectTargetTags {
|
|
|
38
38
|
JOIN package_versions pv
|
|
39
39
|
ON pv.scan_id = t.scan_id
|
|
40
40
|
AND pv.version_id = t.version_id
|
|
41
|
-
JOIN
|
|
42
|
-
ON
|
|
43
|
-
AND
|
|
41
|
+
JOIN manifests m
|
|
42
|
+
ON m.scan_id = pv.scan_id
|
|
43
|
+
AND m.version_id = pv.version_id
|
|
44
44
|
WHERE t.scan_id = ?
|
|
45
45
|
AND t.is_digest_tag = ?
|
|
46
|
-
AND roots.has_ancestor = 0
|
|
47
46
|
AND (${selectedTagPredicate.sql})
|
|
48
47
|
${excludedRootSql}
|
|
49
48
|
${olderThanSql}
|