package main import ( "bufio" "context" "encoding/json" "errors" "fmt" "github.com/docker/docker/api/types/image" "github.com/docker/docker/client" "io" "log" "os" "regexp" "strings" "time" ) // based on an example from https://www.loginradius.com/blog/engineering/build-push-docker-images-golang/ type ErrorLine struct { Error string `json:"error"` ErrorDetail ErrorDetail `json:"errorDetail"` } type ErrorDetail struct { Message string `json:"message"` } // docker errors are so critical that the safest thing is to just exit and // get restarted. func exitOnDockerError() { if err := recover(); err != nil { log.Fatalf("Exiting the proxy because of a docker error\n", err) os.Exit(1) } } func print(rd io.Reader) error { var lastLine string scanner := bufio.NewScanner(rd) for scanner.Scan() { lastLine = scanner.Text() log.Println(scanner.Text()) } errLine := &ErrorLine{} json.Unmarshal([]byte(lastLine), errLine) if errLine.Error != "" { return errors.New(errLine.Error) } if err := scanner.Err(); err != nil { return err } return nil } func GetDockerClient() *client.Client { cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) if err != nil { panic(err) } return cli } func GetDockerImageId(registry, image_name, tag_name string) (imageId, imageName string) { defer exitOnDockerError() full_image := registry + "/" + image_name if strings.HasPrefix(tag_name, "sha256:") || strings.HasPrefix(tag_name, "sha512:") { full_image += "@" + tag_name } else { full_image += ":" + tag_name } cli := GetDockerClient() images, err := cli.ImageList(context.Background(), image.ListOptions{}) if err != nil { panic(err) } for _, image_obj := range images { for _, tag := range image_obj.RepoTags { if tag == full_image { return image_obj.ID, full_image } } for _, tag := range image_obj.RepoDigests { if tag == full_image { // now we need to find an image tag that is in the // registry and that has a regular tag so we can push it. for _, pushTag := range image_obj.RepoTags { if strings.HasPrefix(pushTag, registry+"/") { return image_obj.ID, pushTag } } } } } return "", "" } var lockChannel = make(chan string, 1) func PushDockerImage(full_image string) { defer exitOnDockerError() log.Printf("Image %s is unavailable in backend", full_image) // only do one push at a time to be sure. select { case lockChannel <- "locking": { defer func() { <-lockChannel }() } case <-time.After(2 * time.Minute): { // kubernetes will retry anyway when a pull fails. log.Printf("Lock not acquired within the timout, client will have to retry pull later") return } } log.Printf("Got lock for pushing %s, starting push", full_image) cli := GetDockerClient() closer, err := cli.ImagePush(context.Background(), full_image, image.PushOptions{ RegistryAuth: "abc123", // dummy }) if err != nil { panic(err) } defer closer.Close() err = print(closer) if err != nil { panic(err) } } func TestDocker(host, port string) { cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) if err != nil { panic(err) } images, err := cli.ImageList(context.Background(), image.ListOptions{}) if err != nil { panic(err) } for _, image := range images { for _, tag := range image.RepoTags { log.Println(image.ID, tag) } } // Now push an image closer, err := cli.ImagePush(context.Background(), host+":"+port+"/alpine:latest", image.PushOptions{ All: false, RegistryAuth: "abc123", // dummy PrivilegeFunc: nil, Platform: nil, }) if err != nil { panic(err) } defer closer.Close() err = print(closer) if err != nil { panic(err) } // parsing image and tag from a context path path := "v2/alpine/manifests/latest" r := regexp.MustCompile("^v2/(.*)/manifests/([^/]+)$") matches := r.FindStringSubmatch(path) fmt.Println("Matches ", len(matches), matches) if len(matches) == 3 { fmt.Println("Match ", matches[1], matches[2]) } else { fmt.Println("No match", matches) } } func CheckDockerAvailable() { log.Printf("Checking connection to docker daemon\n") client := GetDockerClient() _, err := client.ImageList(context.Background(), image.ListOptions{}) if err != nil { panic(err) } }