198 lines
4.3 KiB
Go
198 lines
4.3 KiB
Go
|
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)
|
||
|
}
|
||
|
}
|