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 }