feat(agent): job runner, compose override executor and swarm executor (#7)
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
)
|
||||
|
||||
// RegistryAuth holds inline credentials from a docker config.json, already
|
||||
// encoded as X-Registry-Auth values. A nil *RegistryAuth means anonymous
|
||||
// access.
|
||||
type RegistryAuth struct {
|
||||
byHost map[string]string
|
||||
}
|
||||
|
||||
type dockerConfigFile struct {
|
||||
Auths map[string]struct {
|
||||
Auth string `json:"auth"`
|
||||
IdentityToken string `json:"identitytoken"`
|
||||
} `json:"auths"`
|
||||
CredsStore string `json:"credsStore"`
|
||||
CredHelpers map[string]string `json:"credHelpers"`
|
||||
}
|
||||
|
||||
// LoadRegistryAuth reads dockerConfigDir/config.json. An empty dir returns
|
||||
// nil, nil. Only inline `auth` and `identitytoken` entries are supported;
|
||||
// credential helpers are not available inside the agent container, which is
|
||||
// logged once.
|
||||
func LoadRegistryAuth(dockerConfigDir string, log *slog.Logger) (*RegistryAuth, error) {
|
||||
if dockerConfigDir == "" {
|
||||
return nil, nil
|
||||
}
|
||||
path := filepath.Join(dockerConfigDir, "config.json")
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read %s: %w", path, err)
|
||||
}
|
||||
var cfg dockerConfigFile
|
||||
if err := json.Unmarshal(raw, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("parse %s: %w", path, err)
|
||||
}
|
||||
if (cfg.CredsStore != "" || len(cfg.CredHelpers) > 0) && log != nil {
|
||||
log.Warn("registry_cred_helpers_unsupported", "path", path)
|
||||
}
|
||||
a := &RegistryAuth{byHost: make(map[string]string)}
|
||||
for key, entry := range cfg.Auths {
|
||||
host := normaliseRegistryKey(key)
|
||||
ac := registry.AuthConfig{ServerAddress: host}
|
||||
switch {
|
||||
case entry.IdentityToken != "":
|
||||
ac.IdentityToken = entry.IdentityToken
|
||||
case entry.Auth != "":
|
||||
dec, err := base64.StdEncoding.DecodeString(entry.Auth)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: auth for %s is not base64", path, key)
|
||||
}
|
||||
user, pass, ok := strings.Cut(string(dec), ":")
|
||||
if !ok {
|
||||
return nil, errors.New(path + ": auth for " + key + " is not user:password")
|
||||
}
|
||||
ac.Username, ac.Password = user, pass
|
||||
default:
|
||||
continue
|
||||
}
|
||||
enc, err := registry.EncodeAuthConfig(ac)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encode auth for %s: %w", key, err)
|
||||
}
|
||||
a.byHost[host] = enc
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// normaliseRegistryKey reduces a config.json key to a bare registry host,
|
||||
// mapping every Docker Hub spelling to "docker.io".
|
||||
func normaliseRegistryKey(key string) string {
|
||||
k := strings.TrimPrefix(strings.TrimPrefix(key, "https://"), "http://")
|
||||
if i := strings.Index(k, "/"); i >= 0 {
|
||||
k = k[:i]
|
||||
}
|
||||
switch k {
|
||||
case "index.docker.io", "docker.io", "registry-1.docker.io":
|
||||
return "docker.io"
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
// For returns the X-Registry-Auth value for repo's registry, or "".
|
||||
func (a *RegistryAuth) For(repo string) string {
|
||||
if a == nil {
|
||||
return ""
|
||||
}
|
||||
named, err := reference.ParseNormalizedNamed(repo)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return a.byHost[reference.Domain(named)]
|
||||
}
|
||||
Reference in New Issue
Block a user