1
0
docker-image-sync/main.go

198 lines
4.7 KiB
Go

package main
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"net/http"
"os/exec"
"strconv"
"strings"
"time"
"github.com/go-redis/redis"
)
var (
cache = map[string]string{}
target string
client = &http.Client{}
rc *redis.Client
)
const (
docker = "docker"
)
func main() {
b := flag.String("b", ":8080", "Server Listen Port")
t := flag.String("t", "registry.cn-hangzhou.aliyuncs.com/miaowoo", "Target Repo")
u := flag.String("u", "", "Target Repo UserName")
p := flag.String("p", "", "Target Repo Password")
r := flag.String("r", "127.0.0.1:6379", "Redis Server Host")
flag.Parse()
target = *t
if _, err := exec.LookPath(docker); err != nil {
fmt.Printf("Error Can't Find %s Client...", docker)
return
}
loginResult, err := exec.Command(docker, "login", *t, "-u", *u, "-p", *p).Output()
if err != nil {
fmt.Printf("Error Can't Login To %s Username %s Password %s ...\nError: %s", *t, *u, *p, err.Error())
return
}
fmt.Printf(string(loginResult))
rc = redis.NewClient(&redis.Options{
Addr: *r,
})
_, err = rc.Ping().Result()
if err != nil {
fmt.Printf("Redis Connect Error: %s\n", err.Error())
return
}
fmt.Printf("Redis Server %s Connect Success\n", *r)
startServer(*b)
}
func startServer(b string) {
http.HandleFunc("/", func(resp http.ResponseWriter, req *http.Request) {
if strings.HasPrefix(req.RequestURI, "/api") {
handleAPI(resp, req)
return
}
handleNormal(resp, req)
})
fmt.Println("Start Server Listen On: " + b)
http.ListenAndServe(b, nil)
}
func handleAPI(w http.ResponseWriter, r *http.Request) {
fmt.Println("[A] " + r.RequestURI)
}
func getToken(image string) string {
resp, _ := http.Get("https://dockerauth.cn-hangzhou.aliyuncs.com/auth?service=registry.aliyuncs.com:cn-hangzhou:26842&scope=repository:miaowoo/" + image + ":pull")
var result []byte
result, _ = ioutil.ReadAll(resp.Body)
var auth map[string]string
json.Unmarshal(result, &auth)
return auth["token"]
}
func checkMirror(image string) bool {
i := image
v := "latest"
if strings.Contains(image, ":") {
arr := strings.Split(image, ":")
i = arr[0]
v = arr[1]
}
url := fmt.Sprintf("https://registry.cn-hangzhou.aliyuncs.com/v2/miaowoo/%s/manifests/%s", i, v)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer "+getToken(i))
resp, _ := client.Do(req)
fmt.Println("[C] " + strconv.Itoa(resp.StatusCode))
return resp.StatusCode == 200
}
func handleNormal(resp http.ResponseWriter, req *http.Request) {
fmt.Println("[N] " + req.RequestURI)
origin := subString(req.RequestURI, 1, len(req.RequestURI))
if origin == "" || strings.Contains(origin, "?") || strings.Contains(origin, "=") || strings.Contains(req.RequestURI, ".ico") || strings.Contains(req.RequestURI, ".txt") || strings.Contains(origin, target) {
resp.Write([]byte("Noop..."))
return
}
if _, ok := cache[origin]; ok {
resp.Write([]byte("Image " + origin + " Is Syncing... \nProcess " + string(cache[origin]) + "... \nPlease Wait..."))
return
}
name := strings.Replace(origin, "/", "_", -1)
image := target + "/" + name
out := `<html><body>`
out += `<h3>Image ` + origin + ` Is Tag to ` + image + ` And Push Success</h3>`
if checkMirrorFromRedis(name) || checkMirror(name) {
out += "Alread Transfer"
} else {
cache[origin] = "0"
out += `<h4>Transfer Log</h4>
<pre>
` + transferImage(origin, image) + `
</pre>`
delete(cache, origin)
}
rc.Set(name, time.Now().Unix(), 0)
out += `<h3>You Can Use below Command To Pull...</h3>
<pre>
docker pull ` + image + `
docker tag ` + image + ` ` + origin + `
docker rmi ` + image + `
</pre>`
out += "</body></html>"
resp.Write([]byte(out))
}
func checkMirrorFromRedis(image string) bool {
_, err := rc.Get(image).Result()
return err != redis.Nil
}
func transferImage(origin string, image string) string {
out := ""
cache[origin] = "10"
out += runCommand(docker, "pull", origin)
cache[origin] = "30"
out += runCommand(docker, "tag", origin, image)
cache[origin] = "50"
out += runCommand(docker, "push", image)
cache[origin] = "70"
out += runCommand(docker, "rmi", origin)
cache[origin] = "90"
out += runCommand(docker, "rmi", image)
cache[origin] = "100"
return out
}
func runCommand(command string, args ...string) string {
result, err := exec.Command(command, args...).Output()
out := string(result)
if err != nil {
out = out + err.Error()
}
return out
}
func subString(str string, begin, length int) (substr string) {
// 将字符串的转换成[]rune
rs := []rune(str)
lth := len(rs)
// 简单的越界判断
if begin < 0 {
begin = 0
}
if begin >= lth {
begin = lth
}
end := begin + length
if end > lth {
end = lth
}
// 返回子串
return string(rs[begin:end])
}