package main import ( "bytes" "encoding/json" "flag" "fmt" "io" "mime/multipart" "net/http" "os" "path/filepath" "strings" ) type Configuration struct { ServerURL string Repo string Token string FilePatterns []string Files []string } type TaskResponse struct { TaskID int `json:"taskID"` Status int `json:"status"` } func parseFlags() Configuration { var cfg Configuration flag.StringVar(&cfg.ServerURL, "url", "http://localhost:9999", "Базовый URL сервера загрузки") flag.StringVar(&cfg.Repo, "repo", "", "Название репозитория (обязательно)") flag.StringVar(&cfg.Token, "token", "", "Токен авторизации (обязательно)") files := flag.String("files", "", "Список путей к файлам или паттернов (разделены запятой) для загрузки (обязательно)") flag.Parse() missing := false if cfg.Repo == "" { fmt.Println("Ошибка: -repo является обязательным") missing = true } if cfg.Token == "" { fmt.Println("Ошибка: -token является обязательным") missing = true } if *files == "" { fmt.Println("Ошибка: -files является обязательным") missing = true } if missing { flag.Usage() os.Exit(1) } cfg.FilePatterns = splitAndTrim(*files, ",") var allFiles []string for _, pattern := range cfg.FilePatterns { matches, err := filepath.Glob(pattern) if err != nil { fmt.Fprintf(os.Stderr, "Неверный паттерн glob '%s': %v\n", pattern, err) continue } if len(matches) == 0 { fmt.Fprintf(os.Stderr, "Предупреждение: Паттерн '%s' не совпадает ни с одним файлом\n", pattern) continue } allFiles = append(allFiles, matches...) } cfg.Files = unique(allFiles) if len(cfg.Files) == 0 { fmt.Println("Ошибка: Не найдено файлов для загрузки после обработки паттернов") os.Exit(1) } return cfg } func splitAndTrim(s, sep string) []string { parts := strings.Split(s, sep) var trimmed []string for _, part := range parts { part = strings.TrimSpace(part) if part != "" { trimmed = append(trimmed, part) } } return trimmed } func unique(input []string) []string { seen := make(map[string]struct{}) var result []string for _, item := range input { if _, exists := seen[item]; !exists { seen[item] = struct{}{} result = append(result, item) } } return result } func createMultipartRequest(url string, files []string, token string, repo string) (*http.Request, error) { var buf bytes.Buffer writer := multipart.NewWriter(&buf) for _, filePath := range files { file, err := os.Open(filePath) if err != nil { return nil, fmt.Errorf("не удалось открыть файл %s: %w", filePath, err) } defer file.Close() part, err := writer.CreateFormFile("files", filepath.Base(filePath)) if err != nil { return nil, fmt.Errorf("не удалось создать поле формы для файла %s: %w", filePath, err) } _, err = io.Copy(part, file) if err != nil { return nil, fmt.Errorf("не удалось скопировать содержимое файла %s: %w", filePath, err) } } err := writer.WriteField("repo", repo) if err != nil { return nil, fmt.Errorf("не удалось добавить поле repo: %w", err) } err = writer.Close() if err != nil { return nil, fmt.Errorf("не удалось закрыть multipart писатель: %w", err) } req, err := http.NewRequest("POST", url, &buf) if err != nil { return nil, fmt.Errorf("не удалось создать HTTP-запрос: %w", err) } req.Header.Set("Content-Type", writer.FormDataContentType()) req.Header.Set("Authorization", "Bearer "+token) return req, nil } func createTask(cfg Configuration) (int, error) { taskURL := fmt.Sprintf("%s/tasks", strings.TrimRight(cfg.ServerURL, "/")) requestBody, err := json.Marshal(map[string]string{ "Repo": cfg.Repo, }) if err != nil { return 0, fmt.Errorf("не удалось создать тело запроса: %w", err) } req, err := http.NewRequest("POST", taskURL, bytes.NewReader(requestBody)) if err != nil { return 0, fmt.Errorf("не удалось создать HTTP-запрос: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+cfg.Token) client := &http.Client{} resp, err := client.Do(req) if err != nil { return 0, fmt.Errorf("не удалось выполнить HTTP-запрос: %w", err) } defer resp.Body.Close() var taskResp TaskResponse err = json.NewDecoder(resp.Body).Decode(&taskResp) if err != nil { return 0, fmt.Errorf("не удалось декодировать ответ: %w", err) } return taskResp.TaskID, nil } func uploadFiles(cfg Configuration, taskID int) error { uploadURL := fmt.Sprintf("%s/tasks/%d/upload", strings.TrimRight(cfg.ServerURL, "/"), taskID) req, err := createMultipartRequest(uploadURL, cfg.Files, cfg.Token, cfg.Repo) if err != nil { return fmt.Errorf("не удалось создать multipart-запрос: %w", err) } client := &http.Client{} resp, err := client.Do(req) if err != nil { return fmt.Errorf("не удалось выполнить HTTP-запрос: %w", err) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("не удалось прочитать тело ответа: %w", err) } fmt.Println(string(respBody)) return nil } func main() { cfg := parseFlags() taskID, err := createTask(cfg) if err != nil { fmt.Fprintf(os.Stderr, "Ошибка: %v\n", err) os.Exit(1) } uploadFiles(cfg, taskID) }