mirror of
https://github.com/regclient/regclient.git
synced 2025-04-18 22:44:00 +03:00
I feel like I need to explain, this is all to move the descriptor package. The platform package could not use the predefined errors in types because of a circular dependency from descriptor. The most appropriate way to reorg this is to move descriptor out of the type package since it was more complex than a self contained type. When doing that, type aliases were needed to avoid breaking changes to existing users. Those aliases themselves caused circular dependency loops because of the media types and errors, so those were also pulled out to separate packages. All of the old values were aliased and deprecated, and to fix the linter, those deprecations were fixed by updating the imports... everywhere. Signed-off-by: Brandon Mitchell <git@bmitch.net>
280 lines
8.7 KiB
Go
280 lines
8.7 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"time"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
"github.com/regclient/regclient/config"
|
|
"github.com/regclient/regclient/pkg/template"
|
|
"github.com/regclient/regclient/types/mediatype"
|
|
)
|
|
|
|
// delay checking for at least 5 minutes when rate limit is exceeded
|
|
var rateLimitRetryMin = time.Minute * 5
|
|
var defaultMediaTypes = []string{
|
|
mediatype.Docker2Manifest,
|
|
mediatype.Docker2ManifestList,
|
|
mediatype.OCI1Manifest,
|
|
mediatype.OCI1ManifestList,
|
|
}
|
|
|
|
// Config is parsed configuration file for regsync
|
|
type Config struct {
|
|
Version int `yaml:"version" json:"version"`
|
|
Creds []config.Host `yaml:"creds" json:"creds"`
|
|
Defaults ConfigDefaults `yaml:"defaults" json:"defaults"`
|
|
Sync []ConfigSync `yaml:"sync" json:"sync"`
|
|
}
|
|
|
|
// ConfigDefaults is uses for general options and defaults for ConfigSync entries
|
|
type ConfigDefaults struct {
|
|
Backup string `yaml:"backup" json:"backup"`
|
|
Interval time.Duration `yaml:"interval" json:"interval"`
|
|
Schedule string `yaml:"schedule" json:"schedule"`
|
|
RateLimit ConfigRateLimit `yaml:"ratelimit" json:"ratelimit"`
|
|
Parallel int `yaml:"parallel" json:"parallel"`
|
|
DigestTags *bool `yaml:"digestTags" json:"digestTags"`
|
|
Referrers *bool `yaml:"referrers" json:"referrers"`
|
|
ReferrerFilters []ConfigReferrerFilter `yaml:"referrerFilters" json:"referrerFilters"`
|
|
FastCheck *bool `yaml:"fastCheck" json:"fastCheck"`
|
|
ForceRecursive *bool `yaml:"forceRecursive" json:"forceRecursive"`
|
|
IncludeExternal *bool `yaml:"includeExternal" json:"includeExternal"`
|
|
MediaTypes []string `yaml:"mediaTypes" json:"mediaTypes"`
|
|
Hooks ConfigHooks `yaml:"hooks" json:"hooks"`
|
|
// general options
|
|
BlobLimit int64 `yaml:"blobLimit" json:"blobLimit"`
|
|
CacheCount int `yaml:"cacheCount" json:"cacheCount"`
|
|
CacheTime time.Duration `yaml:"cacheTime" json:"cacheTime"`
|
|
SkipDockerConf bool `yaml:"skipDockerConfig" json:"skipDockerConfig"`
|
|
UserAgent string `yaml:"userAgent" json:"userAgent"`
|
|
}
|
|
|
|
// ConfigRateLimit is for rate limit settings
|
|
type ConfigRateLimit struct {
|
|
Min int `yaml:"min" json:"min"`
|
|
Retry time.Duration `yaml:"retry" json:"retry"`
|
|
}
|
|
|
|
// ConfigSync defines a source/target repository to sync
|
|
type ConfigSync struct {
|
|
Source string `yaml:"source" json:"source"`
|
|
Target string `yaml:"target" json:"target"`
|
|
Type string `yaml:"type" json:"type"`
|
|
Tags AllowDeny `yaml:"tags" json:"tags"`
|
|
Repos AllowDeny `yaml:"repos" json:"repos"`
|
|
DigestTags *bool `yaml:"digestTags" json:"digestTags"`
|
|
Referrers *bool `yaml:"referrers" json:"referrers"`
|
|
ReferrerFilters []ConfigReferrerFilter `yaml:"referrerFilters" json:"referrerFilters"`
|
|
Platform string `yaml:"platform" json:"platform"`
|
|
Platforms []string `yaml:"platforms" json:"platforms"`
|
|
FastCheck *bool `yaml:"fastCheck" json:"fastCheck"`
|
|
ForceRecursive *bool `yaml:"forceRecursive" json:"forceRecursive"`
|
|
IncludeExternal *bool `yaml:"includeExternal" json:"includeExternal"`
|
|
Backup string `yaml:"backup" json:"backup"`
|
|
Interval time.Duration `yaml:"interval" json:"interval"`
|
|
Schedule string `yaml:"schedule" json:"schedule"`
|
|
RateLimit ConfigRateLimit `yaml:"ratelimit" json:"ratelimit"`
|
|
MediaTypes []string `yaml:"mediaTypes" json:"mediaTypes"`
|
|
Hooks ConfigHooks `yaml:"hooks" json:"hooks"`
|
|
}
|
|
|
|
// AllowDeny is an allow and deny list of regex strings
|
|
type AllowDeny struct {
|
|
Allow []string `yaml:"allow" json:"allow"`
|
|
Deny []string `yaml:"deny" json:"deny"`
|
|
}
|
|
|
|
type ConfigReferrerFilter struct {
|
|
ArtifactType string `yaml:"artifactType" json:"artifactType"`
|
|
Annotations map[string]string `yaml:"annotations" json:"annotations"`
|
|
}
|
|
|
|
// ConfigHooks for commands that run during the sync
|
|
type ConfigHooks struct {
|
|
Pre *ConfigHook `yaml:"pre" json:"pre"`
|
|
Post *ConfigHook `yaml:"post" json:"post"`
|
|
Unchanged *ConfigHook `yaml:"unchanged" json:"unchanged"`
|
|
}
|
|
|
|
// ConfigHook identifies the hook type and params
|
|
type ConfigHook struct {
|
|
Type string `yaml:"type" json:"type"`
|
|
Params []string `yaml:"params" json:"params"`
|
|
}
|
|
|
|
// ConfigNew creates an empty configuration
|
|
func ConfigNew() *Config {
|
|
c := Config{
|
|
Creds: []config.Host{},
|
|
Sync: []ConfigSync{},
|
|
}
|
|
return &c
|
|
}
|
|
|
|
// ConfigLoadReader reads the config from an io.Reader
|
|
func ConfigLoadReader(r io.Reader) (*Config, error) {
|
|
c := ConfigNew()
|
|
if err := yaml.NewDecoder(r).Decode(c); err != nil && !errors.Is(err, io.EOF) {
|
|
return nil, err
|
|
}
|
|
// verify loaded version is not higher than supported version
|
|
if c.Version > 1 {
|
|
return c, ErrUnsupportedConfigVersion
|
|
}
|
|
// apply top level defaults
|
|
if c.Defaults.RateLimit.Retry < rateLimitRetryMin {
|
|
c.Defaults.RateLimit.Retry = rateLimitRetryMin
|
|
}
|
|
// apply defaults to each step
|
|
for i := range c.Sync {
|
|
syncSetDefaults(&c.Sync[i], c.Defaults)
|
|
}
|
|
err := configExpandTemplates(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
// ConfigLoadFile loads the config from a specified filename
|
|
func ConfigLoadFile(filename string) (*Config, error) {
|
|
_, err := os.Stat(filename)
|
|
if err == nil {
|
|
//#nosec G304 command is run by a user accessing their own files
|
|
file, err := os.Open(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
c, err := ConfigLoadReader(file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return c, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// ConfigWrite outputs the processed config
|
|
func ConfigWrite(c *Config, w io.Writer) error {
|
|
return yaml.NewEncoder(w).Encode(c)
|
|
}
|
|
|
|
// expand templates in various parts of the config
|
|
func configExpandTemplates(c *Config) error {
|
|
dataSync := struct {
|
|
Sync ConfigSync
|
|
}{}
|
|
for i := range c.Creds {
|
|
val, err := template.String(c.Creds[i].Name, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.Creds[i].Name = val
|
|
val, err = template.String(c.Creds[i].User, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.Creds[i].User = val
|
|
val, err = template.String(c.Creds[i].Pass, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.Creds[i].Pass = val
|
|
val, err = template.String(c.Creds[i].RegCert, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.Creds[i].RegCert = val
|
|
val, err = template.String(c.Creds[i].ClientCert, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.Creds[i].ClientCert = val
|
|
val, err = template.String(c.Creds[i].ClientKey, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.Creds[i].ClientKey = val
|
|
}
|
|
for i := range c.Sync {
|
|
dataSync.Sync = c.Sync[i]
|
|
val, err := template.String(c.Sync[i].Source, dataSync)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.Sync[i].Source = val
|
|
dataSync.Sync.Source = val
|
|
val, err = template.String(c.Sync[i].Target, dataSync)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.Sync[i].Target = val
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// updates sync entry with defaults
|
|
func syncSetDefaults(s *ConfigSync, d ConfigDefaults) {
|
|
if s.Backup == "" && d.Backup != "" {
|
|
s.Backup = d.Backup
|
|
}
|
|
if s.Schedule == "" && d.Schedule != "" {
|
|
s.Schedule = d.Schedule
|
|
}
|
|
if s.Interval == 0 && s.Schedule == "" && d.Interval != 0 {
|
|
s.Interval = d.Interval
|
|
}
|
|
if s.RateLimit.Min == 0 && d.RateLimit.Min != 0 {
|
|
s.RateLimit.Min = d.RateLimit.Min
|
|
}
|
|
if s.RateLimit.Retry == 0 {
|
|
s.RateLimit.Retry = d.RateLimit.Retry
|
|
} else if s.RateLimit.Retry < rateLimitRetryMin {
|
|
s.RateLimit.Retry = rateLimitRetryMin
|
|
}
|
|
if len(s.MediaTypes) == 0 {
|
|
if len(d.MediaTypes) > 0 {
|
|
s.MediaTypes = d.MediaTypes
|
|
} else {
|
|
s.MediaTypes = defaultMediaTypes
|
|
}
|
|
}
|
|
if s.DigestTags == nil {
|
|
b := (d.DigestTags != nil && *d.DigestTags)
|
|
s.DigestTags = &b
|
|
}
|
|
if s.Referrers == nil {
|
|
b := (d.Referrers != nil && *d.Referrers)
|
|
s.Referrers = &b
|
|
}
|
|
if s.ReferrerFilters == nil {
|
|
s.ReferrerFilters = d.ReferrerFilters
|
|
}
|
|
if s.FastCheck == nil {
|
|
b := (d.FastCheck != nil && *d.FastCheck)
|
|
s.FastCheck = &b
|
|
}
|
|
if s.ForceRecursive == nil {
|
|
b := (d.ForceRecursive != nil && *d.ForceRecursive)
|
|
s.ForceRecursive = &b
|
|
}
|
|
if s.IncludeExternal == nil {
|
|
b := (d.IncludeExternal != nil && *d.IncludeExternal)
|
|
s.IncludeExternal = &b
|
|
}
|
|
if s.Hooks.Pre == nil && d.Hooks.Pre != nil {
|
|
s.Hooks.Pre = d.Hooks.Pre
|
|
}
|
|
if s.Hooks.Post == nil && d.Hooks.Post != nil {
|
|
s.Hooks.Post = d.Hooks.Post
|
|
}
|
|
if s.Hooks.Unchanged == nil && d.Hooks.Unchanged != nil {
|
|
s.Hooks.Unchanged = d.Hooks.Unchanged
|
|
}
|
|
}
|