|  | // This executable was helpful when trying to run commands across all of our k8s nodes. | 
|  | // When dealing with "failed to garbage collect required amount of images" logs and | 
|  | // FreeDiskSpaceFailed, ImageGCFailed events, we found it helpful to run | 
|  | // docker image prune --all --force across all the nodes after gathering information about | 
|  | // affected nodes. | 
|  | // Feel free to edit/adapt this script for future node inquiries. | 
|  | package main | 
|  |  | 
|  | import ( | 
|  | "context" | 
|  | "fmt" | 
|  | "os/exec" | 
|  | "regexp" | 
|  | "strings" | 
|  | ) | 
|  |  | 
|  | const ( | 
|  | // TODO(kjlubick) these could be flags if we really needed them to be | 
|  | projectID = "skia-public" | 
|  | zoneID    = "us-central1-a" | 
|  | sshKey    = "/home/kjlubick/.ssh/google_compute_engine" | 
|  | user      = "kjlubick" | 
|  | ) | 
|  |  | 
|  | func getIPAddress(ctx context.Context, node string) string { | 
|  | ipCmd := exec.CommandContext(ctx, "gcloud", "compute", "instances", "describe", node, | 
|  | `--format=get(networkInterfaces[0].accessConfigs[0].natIP)`, "--project", projectID, "--zone", zoneID) | 
|  | out, err := ipCmd.Output() | 
|  | if err != nil { | 
|  | ee := err.(*exec.ExitError) | 
|  | panic(err.Error() + ee.String() + string(ee.Stderr)) | 
|  | } | 
|  | return strings.TrimSpace(string(out)) | 
|  | } | 
|  |  | 
|  | func runSSHCommand(ctx context.Context, node, sshCommand string) { | 
|  | ip := getIPAddress(ctx, node) | 
|  | sshCmd := exec.CommandContext(ctx, "ssh", user+"@"+ip, | 
|  | "-o", "ProxyCommand=corp-ssh-helper %h %p", | 
|  | "-o", "StrictHostKeyChecking=no", | 
|  | "-i", sshKey, | 
|  | sshCommand, | 
|  | ) | 
|  | out, err := sshCmd.Output() | 
|  | if err != nil { | 
|  | ee := err.(*exec.ExitError) | 
|  | panic(err.Error() + ee.String() + string(ee.Stderr)) | 
|  | } | 
|  | fmt.Printf("%s\t%s", node, string(out)) | 
|  | } | 
|  |  | 
|  | func getCreatedTimestamp(ctx context.Context, node string) string { | 
|  | cmd := exec.CommandContext(ctx, "kubectl", "describe", "node", node) | 
|  | out, err := cmd.Output() | 
|  | if err != nil { | 
|  | panic(err.Error()) | 
|  | } | 
|  | findCreation := regexp.MustCompile(`CreationTimestamp:\s*(.+?)\n`) | 
|  | if match := findCreation.FindStringSubmatch(string(out)); len(match) > 0 { | 
|  | return match[1] | 
|  | } | 
|  | return "<unknown>" | 
|  | } | 
|  |  | 
|  | func main() { | 
|  | ctx, cancel := context.WithCancel(context.Background()) | 
|  | defer cancel() | 
|  | cmd := exec.CommandContext(ctx, "kubectl", "get", "nodes", "--output=name") | 
|  | out, err := cmd.Output() | 
|  | if err != nil { | 
|  | panic(err.Error()) | 
|  | } | 
|  | nodes := strings.Split(string(out), "\n") | 
|  | if len(nodes) == 0 { | 
|  | panic("No nodes found") | 
|  | } | 
|  | for _, node := range nodes { | 
|  | node = strings.TrimPrefix(node, "node/") | 
|  | if len(node) == 0 { | 
|  | continue | 
|  | } | 
|  | runSSHCommand(ctx, node, "docker image prune --all --force") | 
|  | } | 
|  | } |