186 lines
6.8 KiB
Go
186 lines
6.8 KiB
Go
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
|
||
}
|