func getTagFromImage(image string) string {
	if len(regexp.MustCompile(broker.GCRImageWithTagPattern).FindAllString(image, -1)) > 0 {
		return strings.Split(strings.ReplaceAll(image, "gcr.io/", ""), ":")[1]
	} else {
		return ""
	}
}
Extract tag from image repo:tag format, else return empty string.
package main
import (
	"crypto/sha1"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path"
	"regexp"
	"strings"
	"sync"
	"text/template"
	"time"
	"github.com/Masterminds/sprig"
	broker "selkies.io/controller/pkg"
)
const loopInterval = 2
func main() {
	log.Printf("Starting image puller")
	namespace := os.Getenv("NAMESPACE")
	if len(namespace) == 0 {
		log.Fatal("Missing NAMESPACE env.")
	}
	nodeName := os.Getenv("NODE_NAME")
	if len(nodeName) == 0 {
		log.Fatal("Missing NODE_NAME env.")
	}
	templatePath := os.Getenv("TEMPLATE_PATH")
	if len(templatePath) == 0 {
		templatePath = "/run/image-puller/template/image-pull-job.yaml.tmpl"
	}
	loop := false
	if os.Getenv("IMAGE_PULL_LOOP") == "true" {
		loop = true
	}
	cmd := exec.Command("gcloud", "auth", "configure-docker", "-q")
	stdoutStderr, err := cmd.CombinedOutput()
	if err != nil {
		log.Fatalf("failed to configure docker with gcloud: %s, %v", string(stdoutStderr), err)
	}
	saEmail, err := broker.GetServiceAccountFromMetadataServer()
	if err != nil {
		log.Fatalf("failed to get service account email: %v", err)
	}
	var wg sync.WaitGroup
	for {
		token, err := broker.GetServiceAccountTokenFromMetadataServer(saEmail)
		if err != nil {
			log.Fatalf("failed to get service account token: %v", err)
			if loop {
				time.Sleep(loopInterval * time.Second)
			} else {
				break
			}
		}
		images, err := findImageTags(namespace)
		if err != nil {
			log.Fatal(err)
		}
		nodeImages, err := broker.GetImagesOnNode()
		if err != nil {
			log.Fatal(err)
		}
		currJobs, err := broker.GetJobs(namespace, "app=image-pull")
		if err != nil {
			log.Fatal(err)
		}
		wg.Add(len(images))
		for _, image := range images {
			go func(image, token string) {
				if image[:6] == "gcr.io" {
					imageWithDigest, err := getImageDigest(image, token)
					if err != nil {
						log.Printf("error fetching image digest: %s, %v", image, err)
					} else {
						imageOnNode := false
						for _, nodeImage := range nodeImages {
							if fmt.Sprintf("%s@%s", nodeImage.Repository, nodeImage.Digest) == imageWithDigest {
								imageOnNode = true
							}
						}
						if !imageOnNode {
							jobFound := false
							for _, job := range currJobs {
								if metaValue, ok := job.Metadata["annotations"]; ok {
									annotations := metaValue.(map[string]interface{})
									if imagePullAnnotation, ok := annotations["pod.broker/image-pull"]; ok {
										if imagePullAnnotation.(string) == fmt.Sprintf("%s,%s", nodeName, imageWithDigest) {
											jobFound = true
										}
									} else {
										log.Printf("missing pod.broker/image-pull annotation on job")
									}
								} else {
									log.Printf("failed to get job annotations")
								}
							}
							if !jobFound {
								tag := getTagFromImage(image)
								if err := makeImagePullJob(imageWithDigest, tag, nodeName, namespace, templatePath); err != nil {
									log.Printf("failed to make job: %v", err)
								}
							}
						}
					}
				} else {
					log.Printf("skipping pull of non-gcr image: %s", image)
				}
				wg.Done()
			}(image, token)
		}
		wg.Wait()
		for _, job := range currJobs {
			if metaValue, ok := job.Metadata["annotations"]; ok {
				annotations := metaValue.(map[string]interface{})
				if imagePullAnnotation, ok := annotations["pod.broker/image-pull"]; ok {
					if strings.Split(imagePullAnnotation.(string), ",")[0] == nodeName {
						jobName := job.Metadata["name"].(string)
						if job.Status.Succeeded > 0 {
							log.Printf("deleting completed job: %s", jobName)
							cmd := exec.Command("sh", "-c", fmt.Sprintf("kubectl delete job -n %s %s 1>&2", namespace, jobName))
							stdoutStderr, err := cmd.CombinedOutput()
							if err != nil {
								log.Printf("error calling kubectl to delete job: %v\n%s", err, string(stdoutStderr))
							}
						}
					}
				} else {
					log.Printf("missing pod.broker/image-pull annotation on job")
				}
			}
		}
		if loop {
			time.Sleep(loopInterval * time.Second)
		} else {
			break
		}
	}
}
func findImageTags(namespace string) ([]string, error) {
	uniqueImages := make(map[string]bool, 0)
	appConfigs, err := broker.FetchBrokerAppConfigs(namespace)
	if err != nil {
		log.Printf("failed to fetch broker app configs: %v", err)
	}
	for _, appConfig := range appConfigs {
		uniqueImages[makeImageName(appConfig.Spec.DefaultRepo, appConfig.Spec.DefaultTag)] = true
		for _, imageSpec := range appConfig.Spec.Images {
			uniqueImages[makeImageName(imageSpec.NewRepo, imageSpec.NewTag)] = true
		}
	}
	userConfigs, err := broker.FetchAppUserConfigs()
	if err != nil {
		log.Printf("failed to fetch user app configs: %v", err)
	} else {
		for _, userConfig := range userConfigs {
			uniqueImages[makeImageName(userConfig.Spec.ImageRepo, userConfig.Spec.ImageTag)] = true
		}
	}
	images := make([]string, 0)
	for image := range uniqueImages {
		images = append(images, image)
	}
	return images, nil
}
func makeImageName(repo, tag string) string {
	return fmt.Sprintf("%s:%s", repo, tag)
}
func getImageDigest(image, accessToken string) (string, error) {
	respImage := ""
	imageTags, err := broker.ListGCRImageTags(image, accessToken)
	if err != nil {
		return respImage, err
	}
	if len(regexp.MustCompile(broker.GCRImageWithTagPattern).FindAllString(image, -1)) > 0 {
		imageToks := strings.Split(strings.ReplaceAll(image, "gcr.io/", ""), ":")
		imageRepo := imageToks[0]
		imageTag := imageToks[1]
		for digest, meta := range imageTags.Manifest {
			for _, tag := range meta.Tag {
				if tag == imageTag {
					respImage = fmt.Sprintf("gcr.io/%s@%s", imageRepo, digest)
					break
				}
			}
			if len(respImage) > 0 {
				break
			}
		}
	}
	if len(regexp.MustCompile(broker.GCRImageWithDigestPattern).FindAllString(image, -1)) > 0 {
		imageDigest := strings.Split(strings.ReplaceAll(image, "gcr.io/", ""), "@")[1]
		if _, ok := imageTags.Manifest[imageDigest]; ok {
			respImage = image
		}
	}
	if len(respImage) == 0 {
		return respImage, fmt.Errorf("failed to find digest for image: %s", image)
	}
	return respImage, nil
}