ghcr-manager 0.9.4 → 0.9.6
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.
Potentially problematic release.
This version of ghcr-manager might be problematic. Click here for more details.
- package/CHANGELOG.md +31 -0
- package/README.md +9 -9
- package/dist/cleanup-summary/_cleanup-summary-markdown.js +7 -6
- package/dist/cleanup-summary/_cleanup-summary.d.ts +8 -4
- package/dist/cleanup-summary/_cleanup-summary.js +8 -4
- package/dist/cleanup-summary/index.d.ts +1 -1
- package/dist/cli/_cleanup-command.js +22 -2
- package/dist/cli/_tag-selector-resolver.js +2 -2
- package/dist/core/_digest-tag.d.ts +2 -0
- package/dist/core/_digest-tag.js +12 -0
- package/dist/core/_types.d.ts +2 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.js +1 -0
- package/dist/db/_cleanup-run-writer.js +69 -50
- package/dist/db/_db-merge-cleanup-copy.js +128 -80
- package/dist/db/_db-merge-scan-copy.js +1 -1
- package/dist/db/_manifest-reachability.js +25 -0
- package/dist/db/_scan-writer.js +118 -116
- package/dist/db/index.d.ts +1 -1
- package/dist/db/planner/_planner-direct-target-roots.js +2 -0
- package/dist/db/planner/_planner-direct-target-tags.js +5 -0
- package/dist/db/planner/_planner-output.d.ts +1 -1
- package/dist/db/planner/_planner-output.js +0 -11
- package/dist/db/planner/_planner-repository.d.ts +1 -1
- package/dist/db/planner/_planner-types.d.ts +12 -18
- package/dist/db/planner/index.d.ts +1 -1
- package/package.json +1 -1
- package/resources/sql/schema/001_schema.sql +20 -0
- package/resources/sql/views/003_v_scan_root_manifests.sql +1 -0
- package/resources/sql/views/{004_v_digest_derived_tag_relations.sql → 004_v_digest_tag_relations.sql} +7 -8
- package/resources/sql/views/005_v_cleanup_root_closure_members.sql +2 -1
- package/resources/sql/views/007_v_cleanup_root_decision_readable.sql +67 -0
|
@@ -2,8 +2,8 @@ interface _PlanRootRow {
|
|
|
2
2
|
version_id: number;
|
|
3
3
|
root_digest: string;
|
|
4
4
|
root_manifest_kind: string | null;
|
|
5
|
-
direct_target_reason:
|
|
6
|
-
selection_mode:
|
|
5
|
+
direct_target_reason: DeletePlanSelectionReason;
|
|
6
|
+
selection_mode: DeletePlanSelectionMode;
|
|
7
7
|
}
|
|
8
8
|
interface _PlanTagRow {
|
|
9
9
|
target_tag: string;
|
|
@@ -24,7 +24,7 @@ interface _BlockedRootRow {
|
|
|
24
24
|
blocking_digest: string;
|
|
25
25
|
overlap_digest: string;
|
|
26
26
|
overlap_manifest_kind: string | null;
|
|
27
|
-
block_reason:
|
|
27
|
+
block_reason: DeletePlanBlockReasonCode;
|
|
28
28
|
}
|
|
29
29
|
export interface PlannerLogger {
|
|
30
30
|
trace(message: string): void;
|
|
@@ -37,12 +37,15 @@ export interface ScanRow {
|
|
|
37
37
|
package_name: string;
|
|
38
38
|
scan_completed_at: string;
|
|
39
39
|
}
|
|
40
|
+
export type DeletePlanSelectionMode = "delete-root" | "untag-only";
|
|
41
|
+
export type DeletePlanSelectionReason = "delete-tags-all-tags-selected" | "delete-tags-partial-tag-match" | "delete-untagged" | "keep-n-tagged-overflow" | "keep-n-untagged-overflow";
|
|
42
|
+
export type DeletePlanBlockReasonCode = "overlap-with-retained-root";
|
|
40
43
|
export interface DeletePlanRoot {
|
|
41
44
|
versionId: number;
|
|
42
45
|
digest: string;
|
|
43
46
|
manifestKind?: string;
|
|
44
|
-
reason:
|
|
45
|
-
selectionMode:
|
|
47
|
+
reason: DeletePlanSelectionReason;
|
|
48
|
+
selectionMode: DeletePlanSelectionMode;
|
|
46
49
|
}
|
|
47
50
|
export interface DeletePlanClosureManifest {
|
|
48
51
|
sourceVersionId: number;
|
|
@@ -60,14 +63,14 @@ export interface DeletePlanBlockedRoot {
|
|
|
60
63
|
blockingDigest: string;
|
|
61
64
|
overlapDigest: string;
|
|
62
65
|
overlapManifestKind?: string;
|
|
63
|
-
reason:
|
|
66
|
+
reason: DeletePlanBlockReasonCode;
|
|
64
67
|
}
|
|
65
68
|
export interface DeletePlanRootDecision {
|
|
66
69
|
versionId: number;
|
|
67
70
|
digest: string;
|
|
68
71
|
manifestKind?: string;
|
|
69
|
-
selectionMode:
|
|
70
|
-
selectionReason:
|
|
72
|
+
selectionMode: DeletePlanSelectionMode;
|
|
73
|
+
selectionReason: DeletePlanSelectionReason;
|
|
71
74
|
validationStatus: "fully-deletable" | "blocked" | "untag-only";
|
|
72
75
|
validationReasonCode: "untag-only-partial-tag-match" | "fully-deletable-no-retained-overlap" | "blocked-overlap-with-retained-root";
|
|
73
76
|
validationReason: string;
|
|
@@ -82,7 +85,7 @@ export interface DeletePlanProtectedRoot {
|
|
|
82
85
|
blocks: Array<{
|
|
83
86
|
blockedVersionId: number;
|
|
84
87
|
blockedDigest: string;
|
|
85
|
-
blockReasonCode:
|
|
88
|
+
blockReasonCode: DeletePlanBlockReasonCode;
|
|
86
89
|
overlapDigest: string;
|
|
87
90
|
overlapManifestKind?: string;
|
|
88
91
|
}>;
|
|
@@ -109,15 +112,6 @@ export interface DeletePlan {
|
|
|
109
112
|
olderThan?: string;
|
|
110
113
|
cutoffTimestamp?: string;
|
|
111
114
|
};
|
|
112
|
-
validationSummary: {
|
|
113
|
-
directTargetTagCount: number;
|
|
114
|
-
directTargetRootCount: number;
|
|
115
|
-
deleteRootCandidateCount: number;
|
|
116
|
-
untagOnlyRootCount: number;
|
|
117
|
-
fullyDeletableRootCount: number;
|
|
118
|
-
blockedDeleteRootCount: number;
|
|
119
|
-
protectedRootCount: number;
|
|
120
|
-
};
|
|
121
115
|
directTargetTags: string[];
|
|
122
116
|
directTargetRoots: DeletePlanRoot[];
|
|
123
117
|
rootDecisions: DeletePlanRootDecision[];
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { PlannerRepository } from "./_planner-repository.js";
|
|
2
|
-
export type { DeletePlan, DeletePlanBlockedRoot, DeletePlanClosureManifest, DeletePlanProtectedRoot, DeletePlanRoot, DeletePlanRootDecision } from "./_planner-repository.js";
|
|
2
|
+
export type { DeletePlan, DeletePlanBlockReasonCode, DeletePlanBlockedRoot, DeletePlanClosureManifest, DeletePlanProtectedRoot, DeletePlanRoot, DeletePlanRootDecision, DeletePlanSelectionMode, DeletePlanSelectionReason } from "./_planner-repository.js";
|
package/package.json
CHANGED
|
@@ -34,6 +34,8 @@ CREATE TABLE IF NOT EXISTS tags (
|
|
|
34
34
|
scan_id INTEGER NOT NULL,
|
|
35
35
|
tag TEXT NOT NULL,
|
|
36
36
|
version_id INTEGER NOT NULL,
|
|
37
|
+
is_digest_tag INTEGER NOT NULL,
|
|
38
|
+
CHECK(is_digest_tag IN (0, 1)),
|
|
37
39
|
PRIMARY KEY(scan_id, tag),
|
|
38
40
|
FOREIGN KEY(scan_id, version_id) REFERENCES package_versions(scan_id, version_id)
|
|
39
41
|
);
|
|
@@ -89,6 +91,7 @@ CREATE TABLE IF NOT EXISTS manifest_edges (
|
|
|
89
91
|
parent_digest TEXT NOT NULL,
|
|
90
92
|
child_digest TEXT NOT NULL,
|
|
91
93
|
edge_kind TEXT NOT NULL,
|
|
94
|
+
CHECK(edge_kind IN ('image-child', 'referrer', 'digest-tag-referrer')),
|
|
92
95
|
PRIMARY KEY(scan_id, parent_digest, child_digest, edge_kind),
|
|
93
96
|
FOREIGN KEY(scan_id, parent_digest) REFERENCES manifests(scan_id, digest),
|
|
94
97
|
FOREIGN KEY(scan_id, child_digest) REFERENCES manifests(scan_id, digest)
|
|
@@ -137,6 +140,14 @@ CREATE TABLE IF NOT EXISTS cleanup_root_decisions (
|
|
|
137
140
|
blocking_digest TEXT,
|
|
138
141
|
overlap_digest TEXT,
|
|
139
142
|
PRIMARY KEY(cleanup_run_id, digest),
|
|
143
|
+
CHECK(selection_mode IN ('delete-root', 'untag-only')),
|
|
144
|
+
CHECK(selection_reason IN (
|
|
145
|
+
'delete-tags-all-tags-selected',
|
|
146
|
+
'delete-tags-partial-tag-match',
|
|
147
|
+
'delete-untagged',
|
|
148
|
+
'keep-n-tagged-overflow',
|
|
149
|
+
'keep-n-untagged-overflow'
|
|
150
|
+
)),
|
|
140
151
|
CHECK(validation_status IN ('fully-deletable', 'blocked', 'untag-only')),
|
|
141
152
|
CHECK(validation_reason_code IN (
|
|
142
153
|
'untag-only-partial-tag-match',
|
|
@@ -149,6 +160,15 @@ CREATE TABLE IF NOT EXISTS cleanup_root_decisions (
|
|
|
149
160
|
FOREIGN KEY(scan_id, overlap_digest) REFERENCES manifests(scan_id, digest)
|
|
150
161
|
);
|
|
151
162
|
|
|
163
|
+
CREATE TABLE IF NOT EXISTS cleanup_selected_tags (
|
|
164
|
+
cleanup_run_id INTEGER NOT NULL,
|
|
165
|
+
scan_id INTEGER NOT NULL,
|
|
166
|
+
tag TEXT NOT NULL,
|
|
167
|
+
PRIMARY KEY(cleanup_run_id, tag),
|
|
168
|
+
FOREIGN KEY(cleanup_run_id, scan_id) REFERENCES cleanup_runs(cleanup_run_id, scan_id),
|
|
169
|
+
FOREIGN KEY(scan_id, tag) REFERENCES tags(scan_id, tag)
|
|
170
|
+
);
|
|
171
|
+
|
|
152
172
|
CREATE TABLE IF NOT EXISTS cleanup_protected_root_blocks (
|
|
153
173
|
cleanup_run_id INTEGER NOT NULL,
|
|
154
174
|
scan_id INTEGER NOT NULL,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
DROP VIEW IF EXISTS
|
|
1
|
+
DROP VIEW IF EXISTS v_digest_tag_relations;
|
|
2
2
|
|
|
3
|
-
CREATE VIEW
|
|
3
|
+
CREATE VIEW v_digest_tag_relations AS
|
|
4
4
|
WITH digest_like_tags AS (
|
|
5
5
|
SELECT
|
|
6
6
|
lsp.scan_id,
|
|
@@ -11,7 +11,7 @@ WITH digest_like_tags AS (
|
|
|
11
11
|
m.digest AS artifact_digest,
|
|
12
12
|
m.manifest_kind AS artifact_manifest_kind,
|
|
13
13
|
m.subject_digest AS artifact_subject_digest,
|
|
14
|
-
'sha256:' || SUBSTR(t.tag, 8, 64) AS
|
|
14
|
+
'sha256:' || SUBSTR(t.tag, 8, 64) AS parent_digest,
|
|
15
15
|
SUBSTR(t.tag, 72) AS tag_suffix
|
|
16
16
|
FROM tags t
|
|
17
17
|
JOIN v_latest_scan_per_package lsp
|
|
@@ -32,15 +32,14 @@ SELECT
|
|
|
32
32
|
dlt.artifact_digest,
|
|
33
33
|
dlt.artifact_manifest_kind,
|
|
34
34
|
dlt.artifact_subject_digest,
|
|
35
|
-
dlt.
|
|
35
|
+
dlt.parent_digest,
|
|
36
36
|
dlt.tag_suffix,
|
|
37
37
|
pm.version_id AS parent_version_id,
|
|
38
|
-
pm.digest AS parent_digest,
|
|
39
38
|
pm.manifest_kind AS parent_manifest_kind,
|
|
40
39
|
CASE
|
|
41
|
-
WHEN dlt.artifact_subject_digest = dlt.
|
|
40
|
+
WHEN dlt.artifact_subject_digest = dlt.parent_digest THEN 1
|
|
42
41
|
ELSE 0
|
|
43
|
-
END AS
|
|
42
|
+
END AS subject_matches_parent,
|
|
44
43
|
CASE
|
|
45
44
|
WHEN pm.digest IS NULL THEN 0
|
|
46
45
|
ELSE 1
|
|
@@ -48,4 +47,4 @@ SELECT
|
|
|
48
47
|
FROM digest_like_tags dlt
|
|
49
48
|
LEFT JOIN manifests pm
|
|
50
49
|
ON pm.scan_id = dlt.scan_id
|
|
51
|
-
AND pm.digest = dlt.
|
|
50
|
+
AND pm.digest = dlt.parent_digest;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
DROP VIEW IF EXISTS v_cleanup_root_decision_readable;
|
|
2
|
+
|
|
3
|
+
CREATE VIEW v_cleanup_root_decision_readable AS
|
|
4
|
+
WITH selected_tag_summary AS (
|
|
5
|
+
SELECT
|
|
6
|
+
selected.cleanup_run_id,
|
|
7
|
+
tag.version_id AS root_version_id,
|
|
8
|
+
COUNT(*) AS selected_tag_count,
|
|
9
|
+
GROUP_CONCAT(selected.tag, ', ') AS selected_tags
|
|
10
|
+
FROM cleanup_selected_tags selected
|
|
11
|
+
JOIN tags tag
|
|
12
|
+
ON tag.scan_id = selected.scan_id
|
|
13
|
+
AND tag.tag = selected.tag
|
|
14
|
+
GROUP BY
|
|
15
|
+
selected.cleanup_run_id,
|
|
16
|
+
tag.version_id
|
|
17
|
+
)
|
|
18
|
+
SELECT
|
|
19
|
+
run.cleanup_run_id,
|
|
20
|
+
run.scan_id,
|
|
21
|
+
scan.owner,
|
|
22
|
+
scan.package_name,
|
|
23
|
+
decision.digest AS root_digest,
|
|
24
|
+
root_manifest.version_id AS root_version_id,
|
|
25
|
+
root_manifest.manifest_kind AS root_manifest_kind,
|
|
26
|
+
decision.selection_mode,
|
|
27
|
+
CASE decision.selection_mode
|
|
28
|
+
WHEN 'delete-root' THEN 'delete root'
|
|
29
|
+
WHEN 'untag-only' THEN 'detach selected tags only'
|
|
30
|
+
END AS selection_mode_label,
|
|
31
|
+
decision.selection_reason,
|
|
32
|
+
CASE decision.selection_reason
|
|
33
|
+
WHEN 'delete-tags-all-tags-selected' THEN 'all tags on this root were selected'
|
|
34
|
+
WHEN 'delete-tags-partial-tag-match' THEN 'only part of this root''s tag set was selected'
|
|
35
|
+
WHEN 'delete-untagged' THEN 'root was selected because it is untagged'
|
|
36
|
+
WHEN 'keep-n-tagged-overflow' THEN 'root fell outside the tagged keep window'
|
|
37
|
+
WHEN 'keep-n-untagged-overflow' THEN 'root fell outside the untagged keep window'
|
|
38
|
+
END AS selection_reason_label,
|
|
39
|
+
decision.validation_status,
|
|
40
|
+
CASE decision.validation_status
|
|
41
|
+
WHEN 'fully-deletable' THEN 'root and closure can be deleted'
|
|
42
|
+
WHEN 'untag-only' THEN 'only selected tags can be detached'
|
|
43
|
+
WHEN 'blocked' THEN 'root deletion is blocked'
|
|
44
|
+
END AS validation_status_label,
|
|
45
|
+
decision.validation_reason_code,
|
|
46
|
+
CASE decision.validation_reason_code
|
|
47
|
+
WHEN 'fully-deletable-no-retained-overlap' THEN 'no retained root overlaps this closure'
|
|
48
|
+
WHEN 'untag-only-partial-tag-match' THEN 'matched tags cover only part of the root tag set'
|
|
49
|
+
WHEN 'blocked-overlap-with-retained-root' THEN 'a retained root still requires an overlapping manifest'
|
|
50
|
+
END AS validation_reason_code_label,
|
|
51
|
+
decision.validation_reason,
|
|
52
|
+
decision.blocking_digest,
|
|
53
|
+
decision.overlap_digest,
|
|
54
|
+
COALESCE(tag_summary.selected_tag_count, 0) AS selected_tag_count,
|
|
55
|
+
tag_summary.selected_tags
|
|
56
|
+
FROM cleanup_root_decisions decision
|
|
57
|
+
JOIN cleanup_runs run
|
|
58
|
+
ON run.cleanup_run_id = decision.cleanup_run_id
|
|
59
|
+
AND run.scan_id = decision.scan_id
|
|
60
|
+
JOIN package_scans scan
|
|
61
|
+
ON scan.scan_id = run.scan_id
|
|
62
|
+
JOIN manifests root_manifest
|
|
63
|
+
ON root_manifest.scan_id = decision.scan_id
|
|
64
|
+
AND root_manifest.digest = decision.digest
|
|
65
|
+
LEFT JOIN selected_tag_summary tag_summary
|
|
66
|
+
ON tag_summary.cleanup_run_id = decision.cleanup_run_id
|
|
67
|
+
AND tag_summary.root_version_id = root_manifest.version_id;
|