1
0
mirror of https://github.com/regclient/regclient.git synced 2025-04-17 11:37:11 +03:00
regclient/regclient.go
Brandon Mitchell 289533b02c
Feat: Move logrus calls into files excluded by wasm
This permits builds of wasm binaries.

Signed-off-by: Brandon Mitchell <git@bmitch.net>
2024-11-12 10:18:47 -05:00

274 lines
8.0 KiB
Go

// Package regclient is used to access OCI registries.
package regclient
import (
"io"
"log/slog"
"time"
"fmt"
"github.com/regclient/regclient/config"
"github.com/regclient/regclient/internal/version"
"github.com/regclient/regclient/scheme"
"github.com/regclient/regclient/scheme/ocidir"
"github.com/regclient/regclient/scheme/reg"
)
const (
// DefaultUserAgent sets the header on http requests.
DefaultUserAgent = "regclient/regclient"
// DockerCertDir default location for docker certs.
DockerCertDir = "/etc/docker/certs.d"
// DockerRegistry is the well known name of Docker Hub, "docker.io".
DockerRegistry = config.DockerRegistry
// DockerRegistryAuth is the name of Docker Hub seen in docker's config.json.
DockerRegistryAuth = config.DockerRegistryAuth
// DockerRegistryDNS is the actual registry DNS name for Docker Hub.
DockerRegistryDNS = config.DockerRegistryDNS
)
// RegClient is used to access OCI distribution-spec registries.
type RegClient struct {
hosts map[string]*config.Host
hostDefault *config.Host
regOpts []reg.Opts
schemes map[string]scheme.API
slog *slog.Logger
userAgent string
}
// Opt functions are used by [New] to create a [*RegClient].
type Opt func(*RegClient)
// New returns a registry client.
func New(opts ...Opt) *RegClient {
var rc = RegClient{
hosts: map[string]*config.Host{},
userAgent: DefaultUserAgent,
regOpts: []reg.Opts{},
schemes: map[string]scheme.API{},
slog: slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})),
}
info := version.GetInfo()
if info.VCSTag != "" {
rc.userAgent = fmt.Sprintf("%s (%s)", rc.userAgent, info.VCSTag)
} else {
rc.userAgent = fmt.Sprintf("%s (%s)", rc.userAgent, info.VCSRef)
}
// inject Docker Hub settings
_ = rc.hostSet(*config.HostNewName(config.DockerRegistryAuth))
for _, opt := range opts {
opt(&rc)
}
// configure regOpts
hostList := []*config.Host{}
for _, h := range rc.hosts {
hostList = append(hostList, h)
}
rc.regOpts = append(rc.regOpts,
reg.WithConfigHosts(hostList),
reg.WithConfigHostDefault(rc.hostDefault),
reg.WithSlog(rc.slog),
reg.WithUserAgent(rc.userAgent),
)
// setup scheme's
rc.schemes["reg"] = reg.New(rc.regOpts...)
rc.schemes["ocidir"] = ocidir.New(
ocidir.WithSlog(rc.slog),
)
rc.slog.Debug("regclient initialized",
slog.String("VCSRef", info.VCSRef),
slog.String("VCSTag", info.VCSTag))
return &rc
}
// WithBlobLimit sets the max size for chunked blob uploads which get stored in memory.
//
// Deprecated: replace with WithRegOpts(reg.WithBlobLimit(limit)), see [WithRegOpts] and [reg.WithBlobLimit].
func WithBlobLimit(limit int64) Opt {
return func(rc *RegClient) {
rc.regOpts = append(rc.regOpts, reg.WithBlobLimit(limit))
}
}
// WithBlobSize overrides default blob sizes.
//
// Deprecated: replace with WithRegOpts(reg.WithBlobSize(chunk, max)), see [WithRegOpts] and [reg.WithBlobSize].
func WithBlobSize(chunk, max int64) Opt {
return func(rc *RegClient) {
rc.regOpts = append(rc.regOpts, reg.WithBlobSize(chunk, max))
}
}
// WithCertDir adds a path of certificates to trust similar to Docker's /etc/docker/certs.d.
//
// Deprecated: replace with WithRegOpts(reg.WithCertDirs(path)), see [WithRegOpts] and [reg.WithCertDirs].
func WithCertDir(path ...string) Opt {
return func(rc *RegClient) {
rc.regOpts = append(rc.regOpts, reg.WithCertDirs(path))
}
}
// WithConfigHost adds a list of config host settings.
func WithConfigHost(configHost ...config.Host) Opt {
return func(rc *RegClient) {
rc.hostLoad("host", configHost)
}
}
// WithConfigHostDefault adds default settings for new hosts.
func WithConfigHostDefault(configHost config.Host) Opt {
return func(rc *RegClient) {
rc.hostDefault = &configHost
}
}
// WithConfigHosts adds a list of config host settings.
//
// Deprecated: replace with [WithConfigHost].
func WithConfigHosts(configHosts []config.Host) Opt {
return WithConfigHost(configHosts...)
}
// WithDockerCerts adds certificates trusted by docker in /etc/docker/certs.d.
func WithDockerCerts() Opt {
return WithCertDir(DockerCertDir)
}
// WithDockerCreds adds configuration from users docker config with registry logins.
// This changes the default value from the config file, and should be added after the config file is loaded.
func WithDockerCreds() Opt {
return func(rc *RegClient) {
configHosts, err := config.DockerLoad()
if err != nil {
rc.slog.Warn("Failed to load docker creds",
slog.String("err", err.Error()))
return
}
rc.hostLoad("docker", configHosts)
}
}
// WithDockerCredsFile adds configuration from a named docker config file with registry logins.
// This changes the default value from the config file, and should be added after the config file is loaded.
func WithDockerCredsFile(fname string) Opt {
return func(rc *RegClient) {
configHosts, err := config.DockerLoadFile(fname)
if err != nil {
rc.slog.Warn("Failed to load docker creds",
slog.String("err", err.Error()))
return
}
rc.hostLoad("docker-file", configHosts)
}
}
// WithRegOpts passes through opts to the reg scheme.
func WithRegOpts(opts ...reg.Opts) Opt {
return func(rc *RegClient) {
if len(opts) == 0 {
return
}
rc.regOpts = append(rc.regOpts, opts...)
}
}
// WithRetryDelay specifies the time permitted for retry delays.
//
// Deprecated: replace with WithRegOpts(reg.WithDelay(delayInit, delayMax)), see [WithRegOpts] and [reg.WithDelay].
func WithRetryDelay(delayInit, delayMax time.Duration) Opt {
return func(rc *RegClient) {
rc.regOpts = append(rc.regOpts, reg.WithDelay(delayInit, delayMax))
}
}
// WithRetryLimit specifies the number of retries for non-fatal errors.
//
// Deprecated: replace with WithRegOpts(reg.WithRetryLimit(retryLimit)), see [WithRegOpts] and [reg.WithRetryLimit].
func WithRetryLimit(retryLimit int) Opt {
return func(rc *RegClient) {
rc.regOpts = append(rc.regOpts, reg.WithRetryLimit(retryLimit))
}
}
// WithSlog configures the slog Logger.
func WithSlog(slog *slog.Logger) Opt {
return func(rc *RegClient) {
rc.slog = slog
}
}
// WithUserAgent specifies the User-Agent http header.
func WithUserAgent(ua string) Opt {
return func(rc *RegClient) {
rc.userAgent = ua
}
}
func (rc *RegClient) hostLoad(src string, hosts []config.Host) {
for _, configHost := range hosts {
if configHost.Name == "" {
if configHost.Pass != "" {
configHost.Pass = "***"
}
if configHost.Token != "" {
configHost.Token = "***"
}
rc.slog.Warn("Ignoring registry config without a name",
slog.Any("entry", configHost))
continue
}
if configHost.Name == DockerRegistry || configHost.Name == DockerRegistryDNS || configHost.Name == DockerRegistryAuth {
configHost.Name = DockerRegistry
if configHost.Hostname == "" || configHost.Hostname == DockerRegistry || configHost.Hostname == DockerRegistryAuth {
configHost.Hostname = DockerRegistryDNS
}
}
tls, _ := configHost.TLS.MarshalText()
rc.slog.Debug("Loading config",
slog.Int64("blobChunk", configHost.BlobChunk),
slog.Int64("blobMax", configHost.BlobMax),
slog.String("helper", configHost.CredHelper),
slog.String("hostname", configHost.Hostname),
slog.Any("mirrors", configHost.Mirrors),
slog.String("name", configHost.Name),
slog.String("pathPrefix", configHost.PathPrefix),
slog.Bool("repoAuth", configHost.RepoAuth),
slog.String("source", src),
slog.String("tls", string(tls)),
slog.String("user", configHost.User))
err := rc.hostSet(configHost)
if err != nil {
rc.slog.Warn("Failed to update host config",
slog.String("host", configHost.Name),
slog.String("user", configHost.User),
slog.String("error", err.Error()))
}
}
}
func (rc *RegClient) hostSet(newHost config.Host) error {
name := newHost.Name
var err error
if _, ok := rc.hosts[name]; !ok {
// merge newHost with default host settings
rc.hosts[name] = config.HostNewDefName(rc.hostDefault, name)
err = rc.hosts[name].Merge(newHost, nil)
} else {
// merge newHost with existing settings
err = rc.hosts[name].Merge(newHost, rc.slog)
}
if err != nil {
return err
}
return nil
}