From ad7010d297b8e624a78ab7199af09b033e940e3a Mon Sep 17 00:00:00 2001 From: cuisongliu Date: Mon, 13 Oct 2025 15:27:44 +0800 Subject: [PATCH] feat(image-cri-shim): sync inline registries to registry.d (#6075) feat: sync inline registries to registry.d --- .../image-cri-shim/pkg/types/config.go | 76 +++++++++++++++++++ .../image-cri-shim/pkg/types/config_test.go | 52 +++++++++++++ 2 files changed, 128 insertions(+) diff --git a/lifecycle/staging/src/github.com/labring/image-cri-shim/pkg/types/config.go b/lifecycle/staging/src/github.com/labring/image-cri-shim/pkg/types/config.go index b436b4fe4..7f809d8cc 100644 --- a/lifecycle/staging/src/github.com/labring/image-cri-shim/pkg/types/config.go +++ b/lifecycle/staging/src/github.com/labring/image-cri-shim/pkg/types/config.go @@ -17,6 +17,7 @@ limitations under the License. package types import ( + "bytes" "crypto/sha256" "errors" "fmt" @@ -63,6 +64,7 @@ type Config struct { ReloadInterval metav1.Duration `json:"reloadInterval"` Auth string `json:"auth"` RegistryDir string `json:"registry.d"` + Registries []Registry `json:"registries" yaml:"registries,omitempty"` } type ShimAuthConfig struct { @@ -78,6 +80,9 @@ func registryMatchDomain(reg Registry) string { func (c *Config) PreProcess() (*ShimAuthConfig, error) { c.RegistryDir = NormalizeRegistryDir(c.RegistryDir) + if err := syncRegistriesToDir(c.RegistryDir, c.Registries); err != nil { + return nil, err + } registries, err := loadRegistriesFromDir(c.RegistryDir) if err != nil { return nil, err @@ -208,6 +213,77 @@ func NormalizeRegistryDir(dir string) string { return dir } +func syncRegistriesToDir(dir string, registries []Registry) error { + if len(registries) == 0 { + return nil + } + + dir = NormalizeRegistryDir(dir) + if err := os.MkdirAll(dir, 0o755); err != nil { + return fmt.Errorf("ensure registry.d %s: %w", dir, err) + } + + for _, reg := range registries { + address := strings.TrimSpace(reg.Address) + if address == "" { + continue + } + + host := registryFileName(address) + if host == "" { + logger.Warn("skip registry entry %q: unable to determine filename", address) + continue + } + + data, err := yaml.Marshal(reg) + if err != nil { + return fmt.Errorf("marshal registry %s: %w", host, err) + } + + path := filepath.Join(dir, fmt.Sprintf("%s.yaml", host)) + if existing, err := os.ReadFile(path); err == nil { + if bytes.Equal(bytes.TrimSpace(existing), bytes.TrimSpace(data)) { + continue + } + } else if !errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("read registry config %s: %w", path, err) + } + + if err := os.WriteFile(path, data, 0o644); err != nil { + return fmt.Errorf("write registry config %s: %w", path, err) + } + logger.Debug("synced registry config to %s", path) + } + + return nil +} + +func registryFileName(address string) string { + if address == "" { + return "" + } + + raw := strings.TrimSpace(address) + if raw == "" { + return "" + } + + if u, err := url.Parse(raw); err == nil { + host := u.Host + if host != "" { + return host + } + // Some registry addresses might be provided without scheme, causing Parse to treat them as paths. + } + + trimmed := strings.TrimPrefix(raw, "http://") + trimmed = strings.TrimPrefix(trimmed, "https://") + if idx := strings.Index(trimmed, "/"); idx != -1 { + trimmed = trimmed[:idx] + } + return strings.TrimSpace(trimmed) +} + func loadRegistriesFromDir(dir string) ([]Registry, error) { files, err := listRegistryConfigFiles(dir) if err != nil { diff --git a/lifecycle/staging/src/github.com/labring/image-cri-shim/pkg/types/config_test.go b/lifecycle/staging/src/github.com/labring/image-cri-shim/pkg/types/config_test.go index bc78a6be5..379f4bc72 100644 --- a/lifecycle/staging/src/github.com/labring/image-cri-shim/pkg/types/config_test.go +++ b/lifecycle/staging/src/github.com/labring/image-cri-shim/pkg/types/config_test.go @@ -77,3 +77,55 @@ func TestRegistriesLoadedFromDir(t *testing.T) { t.Fatalf("expected credentials for 192.168.64.1:5000, got %#v", auth.CRIConfigs["192.168.64.1:5000"]) } } + +func TestRegistriesFieldSyncedToDir(t *testing.T) { + dir := t.TempDir() + registries := []Registry{ + {Address: "https://hub.192.168.64.4.nip.io", Auth: "admin:syncpass"}, + {Address: "http://192.168.64.1:5000"}, + } + + cfg := &Config{ + ImageShimSocket: "/var/run/image-cri-shim.sock", + RuntimeSocket: "/run/containerd/containerd.sock", + Address: "https://registry.internal", + Force: true, + Auth: "sync:user", + RegistryDir: dir, + Registries: registries, + } + + auth, err := cfg.PreProcess() + if err != nil { + t.Fatalf("preprocess with inline registries failed: %v", err) + } + + expectedFiles := map[string]Registry{ + "hub.192.168.64.4.nip.io.yaml": registries[0], + "192.168.64.1:5000.yaml": registries[1], + } + for name, want := range expectedFiles { + path := filepath.Join(dir, name) + data, err := os.ReadFile(path) + if err != nil { + t.Fatalf("expected registry file %s to be written: %v", path, err) + } + var got Registry + if err := yaml.Unmarshal(data, &got); err != nil { + t.Fatalf("failed to parse registry file %s: %v", path, err) + } + if got != want { + t.Fatalf("registry file %s content mismatch: got %#v, want %#v", path, got, want) + } + } + + firstDomain := registryMatchDomain(registries[0]) + if authCfg, ok := auth.CRIConfigs[firstDomain]; !ok || authCfg.Username != "admin" || authCfg.Password != "syncpass" { + t.Fatalf("expected credentials for %s, got %#v", firstDomain, auth.CRIConfigs[firstDomain]) + } + + secondDomain := registryMatchDomain(registries[1]) + if !auth.SkipLoginRegistries[secondDomain] { + t.Fatalf("expected %s to skip login, got %#v", secondDomain, auth.SkipLoginRegistries) + } +}