From 13897488dfdf4ea4a9bbd88c039fd8ef5f4888e8 Mon Sep 17 00:00:00 2001 From: Maxim Slipenko Date: Fri, 29 Nov 2024 17:32:44 +0300 Subject: [PATCH] init --- .gitignore | 1 + Makefile | 25 ++++++++ go.mod | 3 + main.go | 183 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 212 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 go.mod create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..efa6632 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +bin/* \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..464a476 --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +BINARY_NAME=aides-uploader + +SRC_DIR=. +BUILD_DIR=./bin + +GO=go + +build: + mkdir -p $(BUILD_DIR) + $(GO) build -o $(BUILD_DIR)/$(BINARY_NAME) $(SRC_DIR) + +run: build + $(BUILD_DIR)/$(BINARY_NAME) + +test: + $(GO) test ./... + +clean: + rm -rf $(BUILD_DIR) + +deps: + $(GO) mod tidy + $(GO) mod download + +all: build diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..981d5c4 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module code.alt-gnome.ru/aides-infra/aides-uploader + +go 1.23.2 diff --git a/main.go b/main.go new file mode 100644 index 0000000..8409a77 --- /dev/null +++ b/main.go @@ -0,0 +1,183 @@ +package main + +import ( + "bytes" + "flag" + "fmt" + "io" + "mime/multipart" + "net/http" + "os" + "path/filepath" + "strings" +) + +type Configuration struct { + ServerURL string + Repo string + TaskID string + Token string + FilePatterns []string + Files []string +} + +func parseFlags() Configuration { + var cfg Configuration + + flag.StringVar(&cfg.ServerURL, "url", "http://localhost:9999", "Базовый URL сервера загрузки") + flag.StringVar(&cfg.Repo, "repo", "", "Название репозитория (обязательно)") + flag.StringVar(&cfg.TaskID, "task", "", "ID задачи (обязательно)") + flag.StringVar(&cfg.Token, "token", "", "Токен авторизации (обязательно)") + files := flag.String("files", "", "Список путей к файлам или паттернов (разделены запятой) для загрузки (обязательно)") + + flag.Parse() + + missing := false + if cfg.Repo == "" { + fmt.Println("Ошибка: -repo является обязательным") + missing = true + } + if cfg.TaskID == "" { + fmt.Println("Ошибка: -task является обязательным") + 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) (*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.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 uploadFiles(cfg Configuration) error { + uploadURL := fmt.Sprintf("%s/upload/%s/task/%s", strings.TrimRight(cfg.ServerURL, "/"), cfg.Repo, cfg.TaskID) + + req, err := createMultipartRequest(uploadURL, cfg.Files, cfg.Token) + 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) + } + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("загрузка не удалась с кодом %d: %s", resp.StatusCode, string(respBody)) + } + + fmt.Println("Загрузка успешна:") + fmt.Println(string(respBody)) + + return nil +} + +func main() { + cfg := parseFlags() + + err := uploadFiles(cfg) + if err != nil { + fmt.Fprintf(os.Stderr, "Ошибка: %v\n", err) + os.Exit(1) + } +}