package main
import (
"regexp"
"strconv"
"strings"
)
type podPhase int
const (
podUnknown podPhase = iota
podStarting
podReady
podErrImageNeverPull
podOtherError
)
type podStatus struct {
Name string
ReadyContainers, TotalContainers int
RawStatus string
Phase podPhase
}
// parsePodStatus reads one line of `kubectl -n gitfed get pods --no-headers`
// output: NAME READY STATUS RESTARTS AGE. Returns ok=false if out has
// no pod line yet (nothing scheduled) rather than a zero-value guess.
func parsePodStatus(out string) (podStatus, bool) {
var line string
for _, l := range strings.Split(strings.TrimSpace(out), "\n") {
if strings.HasPrefix(strings.TrimSpace(l), "gitfed-") {
line = l
break
}
}
if line == "" {
return podStatus{}, false
}
fields := strings.Fields(line)
if len(fields) < 3 {
return podStatus{}, false
}
ps := podStatus{Name: fields[0], RawStatus: fields[2]}
if ready, total, ok := parseReadyCount(fields[1]); ok {
ps.ReadyContainers, ps.TotalContainers = ready, total
}
switch {
case ps.ReadyContainers == ps.TotalContainers && ps.TotalContainers > 0 && ps.RawStatus == "Running":
ps.Phase = podReady
case ps.RawStatus == "ErrImageNeverPull":
ps.Phase = podErrImageNeverPull
case ps.RawStatus == "Pending" || ps.RawStatus == "ContainerCreating" || ps.RawStatus == "Running":
ps.Phase = podStarting
default:
ps.Phase = podOtherError
}
return ps, true
}
func parseReadyCount(s string) (ready, total int, ok bool) {
a, b, found := strings.Cut(s, "/")
if !found {
return 0, 0, false
}
ready, err1 := strconv.Atoi(a)
total, err2 := strconv.Atoi(b)
return ready, total, err1 == nil && err2 == nil
}
var deploymentImageRe = regexp.MustCompile(`image:\s*(gitfed:\S+)`)
// expectedImageTag extracts the image reference deployment.yaml asks for
// — both containers (server/web) always carry the same tag (see
// deploy/k8s/deployment.yaml), so the first match is authoritative.
func expectedImageTag(deploymentYAML string) (string, bool) {
m := deploymentImageRe.FindStringSubmatch(deploymentYAML)
if m == nil {
return "", false
}
return m[1], true
}
var importedImageRe = regexp.MustCompile(`(?:docker\.io/library/)?(gitfed:\S+)`)
// importedImageTags extracts every "gitfed:X.Y.Z"-shaped reference out of
// `k3s ctr images ls` output, however containerd chose to qualify it
// (bare "gitfed:1.2.4" or "docker.io/library/gitfed:1.2.4" are both seen
// in practice depending on how the image was built/imported).
func importedImageTags(ctrImagesOutput string) []string {
var tags []string
seen := map[string]bool{}
for _, m := range importedImageRe.FindAllStringSubmatch(ctrImagesOutput, -1) {
if !seen[m[1]] {
seen[m[1]] = true
tags = append(tags, m[1])
}
}
return tags
}
type tagDiagnosis struct {
Expected string
Imported []string
Matches bool
}
// diagnoseImageTag is screen 7b's core logic: is the tag deployment.yaml
// asks for actually among what's been imported into containerd? This is
// the automated version of the manual `grep`/`k3s ctr images ls` compare
// INSTALL.md's troubleshooting section walks through by hand.
func diagnoseImageTag(deploymentYAML, ctrImagesOutput string) tagDiagnosis {
expected, _ := expectedImageTag(deploymentYAML)
imported := importedImageTags(ctrImagesOutput)
d := tagDiagnosis{Expected: expected, Imported: imported}
for _, tag := range imported {
if tag == expected {
d.Matches = true
break
}
}
return d
}