code/autoregistry/proxy/docker.go

198 lines
4.3 KiB
Go
Raw Permalink Normal View History

2024-08-31 17:58:24 +00:00
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)
}
}