-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
support containerd and docker mirror registry (#1)
- Loading branch information
Showing
11 changed files
with
705 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
MAKEFLAGS += --jobs all | ||
GO:=CGO_ENABLED=0 GODEBUG=httpmuxgo121=1 go | ||
|
||
default: test build | ||
|
||
test: | ||
${GO} vet ./... | ||
${GO} test ./... | ||
|
||
build: test | ||
${GO} build -ldflags "\ | ||
-X main.version=$(shell git describe --tags --always) \ | ||
-X main.date=$(shell date +%Y-%m-%d)" \ | ||
-o bin/contatto . | ||
run: build | ||
./bin/contatto proxy -c config.toml | ||
|
||
clean: | ||
rm -f ./bin/contatto |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
package conf | ||
|
||
import ( | ||
"bytes" | ||
"encoding/base64" | ||
"encoding/json" | ||
"log" | ||
"log/slog" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
"sync" | ||
"text/template" | ||
|
||
"github.com/BurntSushi/toml" | ||
"gopkg.in/yaml.v3" | ||
) | ||
|
||
var ( | ||
Branch, Version, Date string | ||
|
||
RegM = map[string]*Registry{} | ||
RegHostM = map[string]*Registry{} | ||
RuleHostM = map[string]*MirrorRule{} | ||
DockerAuth = map[string]*dockerAuth{} | ||
|
||
OnMissing func(any) (string, error) | ||
bufferPool = sync.Pool{ | ||
New: func() any { return bytes.NewBuffer(nil) }, | ||
} | ||
) | ||
|
||
var Config struct { | ||
Addr string | ||
MirrorRegistry string | ||
OnMissing string | ||
onMissingTpl *template.Template | ||
Registries []Registry | ||
MirrorRules []MirrorRule | ||
} | ||
|
||
type Registry struct { | ||
Name string | ||
Host string | ||
Insecure bool | ||
DockerConfigFile string | ||
UserName string | ||
Password string | ||
} | ||
|
||
type dockerAuth struct { | ||
UserName string | ||
Password string | ||
} | ||
|
||
func InitConfig(file string) error { | ||
f, err := os.Open(file) | ||
if err != nil { | ||
slog.Error("open config file", "file", file, "err", err) | ||
return err | ||
} | ||
defer f.Close() | ||
|
||
switch filepath.Ext(file) { | ||
case ".json": | ||
if err := json.NewDecoder(f).Decode(&Config); err != nil { | ||
log.Fatal(err) | ||
} | ||
case ".toml": | ||
if _, err := toml.NewDecoder(f).Decode(&Config); err != nil { | ||
log.Fatal(err) | ||
} | ||
case ".yaml", ".yml": | ||
if err := yaml.NewDecoder(f).Decode(&Config); err != nil { | ||
log.Fatal(err) | ||
} | ||
} | ||
|
||
slog.Info("Starting with config", "branch", Branch, "version", Version, "date", Date, "config", Config) | ||
|
||
Config.OnMissing = strings.TrimSpace(Config.OnMissing) | ||
if Config.OnMissing != "" { | ||
Config.onMissingTpl, err = template.New(".").Parse(Config.OnMissing) | ||
if err != nil { | ||
slog.Error("failed to parse on missing", "err", err) | ||
return err | ||
} | ||
|
||
OnMissing = func(param any) (string, error) { | ||
buf := bufferPool.Get().(*bytes.Buffer) | ||
defer func() { | ||
buf.Reset() | ||
bufferPool.Put(buf) | ||
}() | ||
|
||
if err := Config.onMissingTpl.Execute(buf, param); err != nil { | ||
return "", err | ||
} | ||
|
||
return buf.String(), nil | ||
} | ||
|
||
} | ||
|
||
RegM = map[string]*Registry{} | ||
RegHostM = map[string]*Registry{} | ||
for i, reg := range Config.Registries { | ||
RegM[reg.Name] = &Config.Registries[i] | ||
RegHostM[reg.Host] = &Config.Registries[i] | ||
if reg.DockerConfigFile != "" && reg.UserName == "" { | ||
user, password, err := readAuthFromDockerConfig(reg.DockerConfigFile, reg.Host) | ||
if err != nil { | ||
slog.Error("failed to read auth from docker config", "err", err) | ||
return err | ||
} | ||
|
||
Config.Registries[i].UserName = user | ||
Config.Registries[i].Password = password | ||
} | ||
} | ||
|
||
RuleHostM = map[string]*MirrorRule{} | ||
for i, rule := range Config.MirrorRules { | ||
src, ok := RegM[rule.RawRegName] | ||
if !ok { | ||
slog.Error("config registry not found", "reg", rule.RawRegName) | ||
return err | ||
} | ||
|
||
Config.MirrorRules[i].mirrorPathTpl, err = template.New(".").Parse(rule.MirrorPathTpl) | ||
if err != nil { | ||
slog.Error("failed to parse mirror path template", "err", err) | ||
return err | ||
} | ||
|
||
RuleHostM[src.Host] = &Config.MirrorRules[i] | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// https://github.com/docker/cli/blob/a18c896928828eca5eb91e816f009268fe0cd995/cli/config/configfile/file.go#L232 | ||
func readAuthFromDockerConfig(configFile, registryHost string) (user, password string, err error) { | ||
f, err := os.Open(configFile) | ||
if err != nil { | ||
slog.Error("open docker config file", "file", configFile, "err", err) | ||
return "", "", err | ||
} | ||
defer f.Close() | ||
|
||
var dockerConfig struct { | ||
Auths map[string]struct { | ||
Auth string `json:"auth"` | ||
} `json:"auths"` | ||
} | ||
|
||
de := json.NewDecoder(f) | ||
if err := de.Decode(&dockerConfig); err != nil { | ||
slog.Error("failed to decode docker config", "err", err) | ||
return "", "", err | ||
} | ||
|
||
auth, ok := dockerConfig.Auths[registryHost] | ||
if !ok { | ||
slog.Error("registry not found in docker config", "registry", registryHost) | ||
return "", "", err | ||
} | ||
|
||
authStr := auth.Auth | ||
decLen := base64.StdEncoding.DecodedLen(len(authStr)) | ||
decoded := make([]byte, decLen) | ||
authByte := []byte(authStr) | ||
n, err := base64.StdEncoding.Decode(decoded, authByte) | ||
if err != nil { | ||
slog.Error("failed to decode auth", "registry", registryHost, "err", err) | ||
return "", "", err | ||
} | ||
if n > decLen { | ||
slog.Error("something went wrong decoding auth config", "registry", registryHost) | ||
return "", "", err | ||
} | ||
|
||
userName, password, ok := strings.Cut(string(decoded), ":") | ||
if !ok || userName == "" { | ||
slog.Error("failed to parse auth", "registry", registryHost, "err", err) | ||
return "", "", err | ||
} | ||
|
||
return userName, strings.Trim(password, "\x00"), nil | ||
} | ||
|
||
type MirrorRule struct { | ||
RawRegName string | ||
MirrorPathTpl string // rendering a image path: /wweir/alpine:latest | ||
mirrorPathTpl *template.Template | ||
} | ||
|
||
func (r *MirrorRule) RenderMirrorPath(param any) (string, error) { | ||
buf := bufferPool.Get().(*bytes.Buffer) | ||
defer func() { | ||
buf.Reset() | ||
bufferPool.Put(buf) | ||
}() | ||
|
||
if err := r.mirrorPathTpl.Execute(buf, param); err != nil { | ||
return "", err | ||
} | ||
|
||
return buf.String(), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# 服务端口配置 | ||
Addr = "127.0.0.1:8080" | ||
# Docker 用户配置文件,一般是 $HOME/.docker/config.json,留空则不从 docker 配置中读取用户名和密码 | ||
MirrorRegistry = "local-registry" | ||
OnMissing = """ | ||
ssh <PROXY_SERVER> sh -c ' | ||
docker pull {{.raw}} | ||
docker tag {{.raw}} {{.mirror}} | ||
docker push {{.mirror}} | ||
docker rmi {{.raw}} {{.mirror}}' | ||
""" | ||
|
||
# 镜像仓库配置MirrorRegistry = "swr"# 镜像仓库, 应当设置为自主可控的镜像仓库地址 | ||
[[Registries]] | ||
Name = "local-registry" | ||
# mirror registry 地址 | ||
Host = "localhost:5000" | ||
# 从 docker 配置文件中读取用户名和密码 | ||
DockerConfigFile = "<HOME>/.docker/config.json" | ||
## 使用指定的用户名和密码进行身份验证,优先级高于 Docker 存储的令牌 | ||
# UserName = "my_username_1" | ||
# Password = "my_password_1" | ||
|
||
[[Registries]] | ||
Name = "docker-hub" | ||
Host = "docker.io" | ||
|
||
# 镜像规则配置 | ||
[[MirrorRules]] | ||
RawRegName = "docker-hub" | ||
MirrorPathTpl = "wweir/{{.Project}}-{{.Repo}}:{{.Tag}}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
Addr: "127.0.0.1:8080" | ||
MirrorRegistry: local-registry | ||
OnMissing: | | ||
ssh <PROXY_SERVER> sh -c ' | ||
docker pull {{.raw}} | ||
docker tag {{.raw}} {{.mirror}} | ||
docker push {{.mirror}} | ||
docker rmi {{.raw}} {{.mirror}}' | ||
Registries: | ||
- Name: local-registry | ||
Host: "localhost:5000" | ||
DockerConfigFile: <HOME>/.docker/config.json | ||
- Name: docker-hub | ||
Host: docker.io | ||
MirrorRules: | ||
- RawRegName: docker-hub | ||
MirrorPathTpl: "wweir/{{.Project}}-{{.Repo}}:{{.Tag}}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# 服务端口配置 | ||
Addr = "127.0.0.1:8080" | ||
# Docker 用户配置文件,一般是 $HOME/.docker/config.json,留空则不从 docker 配置中读取用户名和密码 | ||
MirrorRegistry = "swr" | ||
OnMissing = """ | ||
ssh bwg sh -c 'docker pull {{.raw}} && docker tag {{.raw}} {{.mirror}} && docker push {{.mirror}}' | ||
""" | ||
|
||
# 镜像仓库配置MirrorRegistry = "swr"# 镜像仓库, 应当设置为自主可控的镜像仓库地址 | ||
[[Registries]] | ||
Name = "swr" | ||
Host = "swr.cn-east-3.myhuaweicloud.com" | ||
## 从 docker 配置文件中读取用户名和密码 | ||
DockerConfigFile = "/home/wweir/.docker/config.json" | ||
## 使用指定的用户名和密码进行身份验证,优先级高于 Docker 存储的令牌 | ||
# UserName = "my_username_1" | ||
# Password = "my_password_1" | ||
|
||
[[Registries]] | ||
Name = "docker-hub" | ||
Host = "docker.io" | ||
|
||
# 镜像规则配置 | ||
[[MirrorRules]] | ||
RawRegName = "docker-hub" | ||
MirrorPathTpl = "wweir/{{.Project}}-{{.Repo}}:{{.Tag}}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
module github.com/wweir/contatto | ||
|
||
go 1.23 | ||
|
||
toolchain go1.23.0 | ||
|
||
require ( | ||
github.com/BurntSushi/toml v1.4.0 | ||
github.com/alecthomas/kong v1.2.1 | ||
github.com/containerd/containerd/v2 v2.0.0-rc.4 | ||
github.com/julienschmidt/httprouter v1.3.0 | ||
gopkg.in/yaml.v3 v3.0.1 | ||
) | ||
|
||
require ( | ||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect | ||
github.com/containerd/errdefs v0.2.0 // indirect | ||
github.com/containerd/log v0.1.0 // indirect | ||
github.com/containerd/platforms v0.2.1 // indirect | ||
github.com/felixge/httpsnoop v1.0.4 // indirect | ||
github.com/go-logr/logr v1.4.2 // indirect | ||
github.com/go-logr/stdr v1.2.2 // indirect | ||
github.com/klauspost/compress v1.17.9 // indirect | ||
github.com/moby/locker v1.0.1 // indirect | ||
github.com/opencontainers/go-digest v1.0.0 // indirect | ||
github.com/opencontainers/image-spec v1.1.0 // indirect | ||
github.com/sirupsen/logrus v1.9.3 // indirect | ||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect | ||
go.opentelemetry.io/otel v1.30.0 // indirect | ||
go.opentelemetry.io/otel/metric v1.30.0 // indirect | ||
go.opentelemetry.io/otel/trace v1.30.0 // indirect | ||
golang.org/x/sync v0.8.0 // indirect | ||
golang.org/x/sys v0.25.0 // indirect | ||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect | ||
google.golang.org/grpc v1.66.2 // indirect | ||
) |
Oops, something went wrong.