75f3dd141c
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CI and Release / lint-frontend (push) Has been cancelled
CI and Release / dockerfile-scan (push) Has been cancelled
CI and Release / test-frontend (push) Has been cancelled
CI and Release / lint-verification-agent (push) Has been cancelled
CI and Release / test-verification-agent (push) Has been cancelled
CI and Release / e2e-verification-agent (push) Has been cancelled
CI and Release / test-backend (push) Has been cancelled
CI and Release / build-dev-image (push) Has been cancelled
CI and Release / push-dev-image (push) Has been cancelled
CI and Release / build-image (push) Has been cancelled
CI and Release / build-verification-image (push) Has been cancelled
CI and Release / determine-version (push) Has been cancelled
CI and Release / push-image (push) Has been cancelled
CI and Release / push-verification-image (12) (push) Has been cancelled
CI and Release / lint-backend (push) Has been cancelled
CI and Release / push-verification-image (17) (push) Has been cancelled
CI and Release / push-verification-image (18) (push) Has been cancelled
CI and Release / release (push) Has been cancelled
CI and Release / publish-helm-chart (push) Has been cancelled
CI and Release / push-verification-image (13) (push) Has been cancelled
CI and Release / push-verification-image (14) (push) Has been cancelled
CI and Release / push-verification-image (15) (push) Has been cancelled
CI and Release / push-verification-image (16) (push) Has been cancelled
113 lines
2.9 KiB
Go
113 lines
2.9 KiB
Go
package restore
|
|
|
|
import (
|
|
"regexp"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
const ignoredErrorsMarker = "errors ignored on restore:"
|
|
|
|
var extensionNamePattern = regexp.MustCompile(`extension "([^"]+)"`)
|
|
|
|
// IsMissingExtensionOnly reports whether a non-zero pg_restore exit is explained
|
|
// solely by extensions absent from this environment — i.e. the data itself
|
|
// restored. pg_restore runs without --exit-on-error, so it tails "errors ignored
|
|
// on restore: N"; this returns true only when every visible item error is
|
|
// extension-related and their count equals N. The count guard matters because
|
|
// StderrTail is capped at 8192 bytes: a truncated tail could hide a non-extension
|
|
// error, so when N can't be fully accounted for we refuse to tolerate.
|
|
func IsMissingExtensionOnly(stderrTail string) bool {
|
|
ignoredCount, hasMarker := parseIgnoredErrorCount(stderrTail)
|
|
if !hasMarker || ignoredCount < 1 {
|
|
return false
|
|
}
|
|
|
|
itemErrors := queryErrorLines(stderrTail)
|
|
if len(itemErrors) != ignoredCount {
|
|
return false
|
|
}
|
|
|
|
for _, line := range itemErrors {
|
|
if !isExtensionErrorLine(line) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// ExtractUnavailableExtensions returns the de-duplicated, sorted names of the
|
|
// extensions pg_restore could not create, for an operator-facing log line.
|
|
func ExtractUnavailableExtensions(stderrTail string) []string {
|
|
seen := map[string]struct{}{}
|
|
|
|
var names []string
|
|
|
|
for _, line := range queryErrorLines(stderrTail) {
|
|
if !isExtensionErrorLine(line) {
|
|
continue
|
|
}
|
|
|
|
for _, match := range extensionNamePattern.FindAllStringSubmatch(line, -1) {
|
|
name := match[1]
|
|
if _, isDup := seen[name]; isDup {
|
|
continue
|
|
}
|
|
|
|
seen[name] = struct{}{}
|
|
names = append(names, name)
|
|
}
|
|
}
|
|
|
|
slices.Sort(names)
|
|
|
|
return names
|
|
}
|
|
|
|
func parseIgnoredErrorCount(stderrTail string) (int, bool) {
|
|
idx := strings.LastIndex(strings.ToLower(stderrTail), ignoredErrorsMarker)
|
|
if idx < 0 {
|
|
return 0, false
|
|
}
|
|
|
|
fields := strings.Fields(stderrTail[idx+len(ignoredErrorsMarker):])
|
|
if len(fields) == 0 {
|
|
return 0, false
|
|
}
|
|
|
|
count, err := strconv.Atoi(fields[0])
|
|
if err != nil {
|
|
return 0, false
|
|
}
|
|
|
|
return count, true
|
|
}
|
|
|
|
func queryErrorLines(stderrTail string) []string {
|
|
var lines []string
|
|
|
|
for line := range strings.SplitSeq(stderrTail, "\n") {
|
|
if strings.Contains(strings.ToLower(line), "could not execute query: error:") {
|
|
lines = append(lines, line)
|
|
}
|
|
}
|
|
|
|
return lines
|
|
}
|
|
|
|
// isExtensionErrorLine matches the phrasings a missing extension produces — the
|
|
// failed CREATE EXTENSION and its cascading COMMENT ON EXTENSION. The "extension"
|
|
// guard keeps unrelated cascades (`type "..." does not exist`) out.
|
|
func isExtensionErrorLine(line string) bool {
|
|
lowered := strings.ToLower(line)
|
|
if !strings.Contains(lowered, "extension") {
|
|
return false
|
|
}
|
|
|
|
return strings.Contains(lowered, "is not available") ||
|
|
strings.Contains(lowered, "could not open extension control file") ||
|
|
strings.Contains(lowered, "does not exist")
|
|
}
|