registry-view/config.go
2026-01-27 09:55:57 +07:00

186 lines
6.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"encoding/base64"
"encoding/json"
"fmt"
"net/url"
"os"
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
)
// Config представляет структуру конфигурации
type Config struct {
Registry struct {
URL string `yaml:"url"`
Username string `yaml:"username"`
Password string `yaml:"password"`
} `yaml:"registry"`
Debug bool `yaml:"debug"`
UseDockerConfig bool `yaml:"use_docker_config"` // Использовать ~/.docker/config.json
}
// DockerConfig представляет структуру Docker config.json
type DockerConfig struct {
Auths map[string]DockerAuth `json:"auths"`
}
// DockerAuth представляет учетные данные для одного registry
type DockerAuth struct {
Auth string `json:"auth"` // base64(username:password)
Username string `json:"username"` // альтернативный формат
Password string `json:"password"` // альтернативный формат
}
// LoadDockerConfig загружает учетные данные из ~/.docker/config.json
func LoadDockerConfig(registryURL string) (username, password string, err error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", "", fmt.Errorf("не удалось получить домашнюю директорию: %w", err)
}
dockerConfigPath := filepath.Join(homeDir, ".docker", "config.json")
data, err := os.ReadFile(dockerConfigPath)
if err != nil {
return "", "", fmt.Errorf("не удалось прочитать Docker config: %w", err)
}
var dockerConfig DockerConfig
if err := json.Unmarshal(data, &dockerConfig); err != nil {
return "", "", fmt.Errorf("ошибка парсинга Docker config: %w", err)
}
// Парсим URL registry для поиска подходящей записи
registryHost, err := extractRegistryHost(registryURL)
if err != nil {
return "", "", fmt.Errorf("ошибка парсинга URL registry: %w", err)
}
// Ищем подходящий registry в конфиге
// Проверяем точное совпадение и варианты с/без протокола
var auth DockerAuth
found := false
// Варианты для поиска
searchKeys := []string{
registryURL, // полный URL
registryHost, // только хост
strings.TrimPrefix(registryURL, "https://"), // без https://
strings.TrimPrefix(registryURL, "http://"), // без http://
}
for _, key := range searchKeys {
if a, ok := dockerConfig.Auths[key]; ok {
auth = a
found = true
fmt.Printf("[DEBUG] Найдены учетные данные в Docker config по ключу: %s\n", key)
break
}
}
// Если не нашли точное совпадение, пробуем частичное
if !found {
for dockerKey, a := range dockerConfig.Auths {
if strings.Contains(registryHost, dockerKey) || strings.Contains(dockerKey, registryHost) {
auth = a
found = true
break
}
}
}
if !found {
return "", "", fmt.Errorf("не найдены учетные данные для registry %s в Docker config", registryURL)
}
// Извлекаем username и password
if auth.Auth != "" {
// Формат: base64(username:password)
decoded, err := base64.StdEncoding.DecodeString(auth.Auth)
if err != nil {
return "", "", fmt.Errorf("ошибка декодирования auth: %w", err)
}
parts := strings.SplitN(string(decoded), ":", 2)
if len(parts) != 2 {
return "", "", fmt.Errorf("неверный формат auth в Docker config")
}
return parts[0], parts[1], nil
} else if auth.Username != "" && auth.Password != "" {
// Альтернативный формат: отдельные поля
return auth.Username, auth.Password, nil
}
return "", "", fmt.Errorf("не найдены учетные данные в Docker config для %s", registryURL)
}
// extractRegistryHost извлекает хост из URL registry
func extractRegistryHost(registryURL string) (string, error) {
u, err := url.Parse(registryURL)
if err != nil {
return "", err
}
return u.Host, nil
}
// LoadConfig загружает конфигурацию из файла
func LoadConfig(configPath string) (*Config, error) {
// Если путь не указан, пробуем стандартные места
if configPath == "" {
configPath = "config.yaml"
}
data, err := os.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("ошибка чтения файла конфигурации %s: %w", configPath, err)
}
var config Config
if err := yaml.Unmarshal(data, &config); err != nil {
return nil, fmt.Errorf("ошибка парсинга конфигурации: %w", err)
}
// Валидация обязательных полей
if config.Registry.URL == "" {
return nil, fmt.Errorf("registry.url обязателен в конфигурации")
}
// Если включено использование Docker config, всегда используем его
if config.UseDockerConfig {
username, password, err := LoadDockerConfig(config.Registry.URL)
if err != nil {
return nil, fmt.Errorf("не удалось загрузить учетные данные из Docker config: %w", err)
}
config.Registry.Username = username
config.Registry.Password = password
if config.Debug {
fmt.Printf("[DEBUG] Загружены учетные данные из Docker config для %s\n", config.Registry.URL)
fmt.Printf("[DEBUG] Username: %s\n", username)
}
} else if config.Registry.Username == "" || config.Registry.Password == "" {
// Если не указаны учетные данные в YAML, пробуем загрузить из Docker config как fallback
username, password, err := LoadDockerConfig(config.Registry.URL)
if err != nil {
return nil, fmt.Errorf("не указаны учетные данные в config.yaml и не удалось загрузить из Docker config: %w", err)
}
config.Registry.Username = username
config.Registry.Password = password
if config.Debug {
fmt.Printf("[DEBUG] Автоматически загружены учетные данные из Docker config (fallback)\n")
fmt.Printf("[DEBUG] Username: %s\n", username)
}
} else if config.Debug {
fmt.Printf("[DEBUG] Используются учетные данные из config.yaml\n")
fmt.Printf("[DEBUG] Username: %s\n", config.Registry.Username)
}
// Финальная проверка
if config.Registry.Username == "" || config.Registry.Password == "" {
return nil, fmt.Errorf("не указаны учетные данные (username/password). Укажите в config.yaml или используйте use_docker_config: true")
}
return &config, nil
}