package abfapi import ( "bytes" "crypto/sha1" "encoding/base64" "encoding/json" "errors" "fmt" "io" "io/ioutil" "log" "mime/multipart" "net/http" "net/url" "os" "path/filepath" "strings" "time" "github.com/gregjones/httpcache" "github.com/gregjones/httpcache/diskcache" ) const ( maxFileSize = 32 * 1024 * 1024 blockSize = 1024 * 1024 ) var symbols = map[string][]string{ "basic": {"b", "k", "m", "g", "t"}, "basic_long": {"byte", "kilo", "mega", "giga", "tera"}, "iec": {"bi", "ki", "mi", "gi", "ti"}, "iec_long": {"byte", "kibi", "mebi", "gibi", "tebi"}, } type AbfJson struct { login string password string abfURL string fileStoreURL string base64AuthStr string log *log.Logger client *http.Client } func NewAbfJson(abfURL, fileStoreURL, login, password string, logger *log.Logger) (*AbfJson, error) { if !strings.HasPrefix(fileStoreURL, "http://") && !strings.HasPrefix(fileStoreURL, "https://") { return nil, errors.New("file-store URL has to start with \"http(s)://\"") } cacheDir := filepath.Join(os.TempDir(), "abf_cache") os.MkdirAll(cacheDir, 0755) cache := diskcache.New(cacheDir) transport := httpcache.NewTransport(cache) client := &http.Client{ Transport: transport, Timeout: 60 * time.Second, // Установите фиксированное значение таймаута или используйте другой способ установки } lpw := fmt.Sprintf("%s:%s", login, password) encodedLpw := base64.StdEncoding.EncodeToString([]byte(lpw)) return &AbfJson{ login: login, password: password, abfURL: strings.TrimSuffix(abfURL, "/"), fileStoreURL: strings.TrimSuffix(fileStoreURL, "/"), base64AuthStr: encodedLpw, log: logger, client: client, }, nil } func (a *AbfJson) bytesToHuman(n int64, format string, symbols string) string { if n < 0 { panic(fmt.Sprintf("n < 0 (%d)", n)) } sym := symbols if strings.HasSuffix(sym, "_long") { sym = sym[:len(sym)-6] } prefix := map[string]int64{ "k": 1 << 10, "m": 1 << 20, "g": 1 << 30, "t": 1 << 40, "ki": 1 << 10, "mi": 1 << 20, "gi": 1 << 30, "ti": 1 << 40, } for _, suffix := range symbols { if n >= prefix[string(suffix)] { n /= prefix[string(suffix)] } else { return fmt.Sprintf(format, n, suffix) } } return fmt.Sprintf(format, n, sym) } func (a *AbfJson) getURLContents(path string, params url.Values, method string, body io.Reader) ([]byte, error) { var fullURL string if strings.HasPrefix(path, "/api/v1/upload") { fullURL = a.fileStoreURL + path } else { fullURL = a.abfURL + path } if len(params) > 0 { fullURL += "?" + params.Encode() } req, err := http.NewRequest(method, fullURL, body) if err != nil { return nil, err } req.Header.Set("Authorization", "Basic "+a.base64AuthStr) if method == http.MethodPost || method == http.MethodPut || method == http.MethodDelete { req.Header.Set("Content-Type", "application/json") } resp, err := a.client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode >= 400 { return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, resp.Status) } data, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } return data, nil } func (a *AbfJson) processResponse(data []byte) (map[string]interface{}, error) { var response map[string]interface{} err := json.Unmarshal(data, &response) if err != nil { return nil, fmt.Errorf("internal server error: it has returned non-json data") } message, ok := response["message"].(string) if ok && message != "Errors during build publishing!" && message != "Build is queued for publishing" { return nil, fmt.Errorf(message) } if errData, ok := response["error"].(string); ok { return nil, fmt.Errorf(errData) } if errData, ok := response["Error"].(string); ok { return nil, fmt.Errorf(errData) } return response, nil } func (a *AbfJson) GetArchitectures() (map[string]interface{}, error) { data, err := a.getURLContents("/api/v1/arches.json", nil, http.MethodGet, nil) if err != nil { return nil, err } return a.processResponse(data) } func (a *AbfJson) GetPlatforms(typ string) (map[string]interface{}, error) { params := url.Values{} if typ != "" { params["type"] = []string{typ} } data, err := a.getURLContents("/api/v1/platforms.json", params, http.MethodGet, nil) if err != nil { return nil, err } return a.processResponse(data) } func (a *AbfJson) GetPlatformByID(plID int) (map[string]interface{}, error) { path := fmt.Sprintf("/api/v1/platforms/%d.json", plID) data, err := a.getURLContents(path, nil, http.MethodGet, nil) if err != nil { return nil, err } return a.processResponse(data) } func (a *AbfJson) GetUserID(username string) (map[string]interface{}, error) { path := fmt.Sprintf("/api/v1/users/%s.json", username) data, err := a.getURLContents(path, nil, http.MethodGet, nil) if err != nil { return nil, err } return a.processResponse(data) } func (a *AbfJson) GetBuildPlatforms() (map[string]interface{}, error) { data, err := a.getURLContents("/api/v1/platforms/platforms_for_build.json", nil, http.MethodGet, nil) if err != nil { return nil, err } return a.processResponse(data) } func (a *AbfJson) GetRepositoryByID(repID int) (map[string]interface{}, error) { path := fmt.Sprintf("/api/v1/repositories/%d.json", repID) data, err := a.getURLContents(path, nil, http.MethodGet, nil) if err != nil { return nil, err } return a.processResponse(data) } func (a *AbfJson) GetBuildListByID(blID int) (map[string]interface{}, error) { path := fmt.Sprintf("/api/v1/build_lists/%d.json", blID) data, err := a.getURLContents(path, nil, http.MethodGet, nil) if err != nil { return nil, err } return a.processResponse(data) } func (a *AbfJson) GetListBuildLists(prjID int, filterQuery url.Values, page int) (map[string]interface{}, error) { filterQuery.Set("page", fmt.Sprintf("%d", page)) filterQuery.Set("per_page", "10") path := fmt.Sprintf("/api/v1/build_lists.json") data, err := a.getURLContents(path, filterQuery, http.MethodGet, nil) if err != nil { return nil, err } return a.processResponse(data) } func (a *AbfJson) GetProjectBuildLists(prjID int, filterQuery url.Values, page int) (map[string]interface{}, error) { filterQuery.Set("page", fmt.Sprintf("%d", page)) filterQuery.Set("per_page", "10") path := fmt.Sprintf("/api/v1/projects/%d/build_lists.json", prjID) data, err := a.getURLContents(path, filterQuery, http.MethodGet, nil) if err != nil { return nil, err } return a.processResponse(data) } func (a *AbfJson) GetProjectByID(pID int) (map[string]interface{}, error) { path := fmt.Sprintf("/api/v1/projects/%d.json", pID) data, err := a.getURLContents(path, nil, http.MethodGet, nil) if err != nil { return nil, err } return a.processResponse(data) } func (a *AbfJson) GetProjectIDByName(key [2]string) (map[string]interface{}, error) { params := url.Values{"name": {key[1]}, "owner": {key[0]}} data, err := a.getURLContents("/api/v1/projects/get_id.json", params, http.MethodGet, nil) if err != nil { return nil, err } return a.processResponse(data) } func (a *AbfJson) NewBuildTask(data map[string]interface{}) (map[string]interface{}, error) { jsonData, err := json.Marshal(data) if err != nil { return nil, err } dataBytes, err := a.getURLContents("/api/v1/build_lists.json", nil, http.MethodPost, bytes.NewBuffer(jsonData)) if err != nil { return nil, err } return a.processResponse(dataBytes) } func (a *AbfJson) Publish(taskID int) (map[string]interface{}, error) { path := fmt.Sprintf("/api/v1/build_lists/%d/publish.json", taskID) jsonData, err := json.Marshal(map[string]interface{}{"nothing": 1}) if err != nil { return nil, err } dataBytes, err := a.getURLContents(path, nil, http.MethodPut, bytes.NewBuffer(jsonData)) if err != nil { return nil, err } return a.processResponse(dataBytes) } func (a *AbfJson) NewPullRequest(data map[string]interface{}, pID int) (map[string]interface{}, error) { path := fmt.Sprintf("/api/v1/projects/%d/pull_requests.json", pID) jsonData, err := json.Marshal(data) if err != nil { return nil, err } dataBytes, err := a.getURLContents(path, nil, http.MethodPost, bytes.NewBuffer(jsonData)) if err != nil { return nil, err } return a.processResponse(dataBytes) } func (a *AbfJson) UpdateProject(data map[string]interface{}, pID int) (map[string]interface{}, error) { path := fmt.Sprintf("/api/v1/projects/%d.json", pID) jsonData, err := json.Marshal(data) if err != nil { return nil, err } dataBytes, err := a.getURLContents(path, nil, http.MethodPut, bytes.NewBuffer(jsonData)) if err != nil { return nil, err } return a.processResponse(dataBytes) } func (a *AbfJson) RemoveProjectFromRepo(data map[string]interface{}, repoID int) (map[string]interface{}, error) { path := fmt.Sprintf("/api/v1/repositories/%d/remove_project.json", repoID) jsonData, err := json.Marshal(data) if err != nil { return nil, err } dataBytes, err := a.getURLContents(path, nil, http.MethodDelete, bytes.NewBuffer(jsonData)) if err != nil { return nil, err } return a.processResponse(dataBytes) } func (a *AbfJson) ForkProject(data map[string]interface{}, projID int) (map[string]interface{}, error) { path := fmt.Sprintf("/api/v1/projects/%d/fork.json", projID) jsonData, err := json.Marshal(data) if err != nil { return nil, err } dataBytes, err := a.getURLContents(path, nil, http.MethodPost, bytes.NewBuffer(jsonData)) if err != nil { return nil, err } return a.processResponse(dataBytes) } func (a *AbfJson) AliasProject(data map[string]interface{}, projID int) (map[string]interface{}, error) { path := fmt.Sprintf("/api/v1/projects/%d/alias.json", projID) jsonData, err := json.Marshal(data) if err != nil { return nil, err } dataBytes, err := a.getURLContents(path, nil, http.MethodPost, bytes.NewBuffer(jsonData)) if err != nil { return nil, err } return a.processResponse(dataBytes) } func (a *AbfJson) DestroyProject(data map[string]interface{}, projID int) (map[string]interface{}, error) { path := fmt.Sprintf("/api/v1/projects/%d.json", projID) jsonData, err := json.Marshal(data) if err != nil { return nil, err } dataBytes, err := a.getURLContents(path, nil, http.MethodDelete, bytes.NewBuffer(jsonData)) if err != nil { return nil, err } return a.processResponse(dataBytes) } func (a *AbfJson) AddProjectToRepo(data map[string]interface{}, repoID int) (map[string]interface{}, error) { path := fmt.Sprintf("/api/v1/repositories/%d/add_project.json", repoID) jsonData, err := json.Marshal(data) if err != nil { return nil, err } dataBytes, err := a.getURLContents(path, nil, http.MethodPut, bytes.NewBuffer(jsonData)) if err != nil { return nil, err } return a.processResponse(dataBytes) } func (a *AbfJson) NewProject(data map[string]interface{}) (map[string]interface{}, error) { jsonData, err := json.Marshal(data) if err != nil { return nil, err } dataBytes, err := a.getURLContents("/api/v1/projects.json", nil, http.MethodPost, bytes.NewBuffer(jsonData)) if err != nil { return nil, err } return a.processResponse(dataBytes) } func (a *AbfJson) GetGitRefsList(projID int) (map[string]interface{}, error) { path := fmt.Sprintf("/api/v1/projects/%d/refs_list.json", projID) data, err := a.getURLContents(path, nil, http.MethodGet, nil) if err != nil { return nil, err } return a.processResponse(data) } func (a *AbfJson) GetUserByID(userID int) (map[string]interface{}, error) { path := fmt.Sprintf("/api/v1/users/%d.json", userID) data, err := a.getURLContents(path, nil, http.MethodGet, nil) if err != nil { return nil, err } return a.processResponse(data) } func (a *AbfJson) GetGroupByID(groupID int) (map[string]interface{}, error) { path := fmt.Sprintf("/api/v1/groups/%d.json", groupID) data, err := a.getURLContents(path, nil, http.MethodGet, nil) if err != nil { return nil, err } return a.processResponse(data) } func (a *AbfJson) GetSearchResults(searchType, query string) (map[string]interface{}, error) { params := url.Values{"type": {searchType}, "query": {query}, "per_page": {"100"}} data, err := a.getURLContents("/api/v1/search.json", params, http.MethodGet, nil) if err != nil { return nil, err } return a.processResponse(data) } func (a *AbfJson) GetList(listType string, page int) (map[string]interface{}, error) { params := url.Values{"page": {fmt.Sprintf("%d", page)}, "per_page": {"100"}} path := fmt.Sprintf("/api/v1/%s.json", listType) data, err := a.getURLContents(path, params, http.MethodGet, nil) if err != nil { return nil, err } return a.processResponse(data) } func (a *AbfJson) GetProjectsSingle(repoID, page int) (map[string]interface{}, error) { params := url.Values{"page": {fmt.Sprintf("%d", page)}, "per_page": {"100"}} path := fmt.Sprintf("/api/v1/repositories/%d/projects.json", repoID) data, err := a.getURLContents(path, params, http.MethodGet, nil) if err != nil { return nil, err } return a.processResponse(data) } func (a *AbfJson) ComputeSHA1(filePath string) (string, error) { file, err := os.Open(filePath) if err != nil { return "", err } defer file.Close() hasher := sha1.New() if _, err := io.Copy(hasher, file); err != nil { return "", err } return fmt.Sprintf("%x", hasher.Sum(nil)), nil } func (a *AbfJson) UploadFile(filePath string, silent bool) (string, error) { hash, err := a.ComputeSHA1(filePath) if err != nil { return "", err } a.log.Printf("File hash is %s", hash) res, err := a.GetFileInfoByHash(hash) if err != nil { return "", err } if len(res) > 0 && res[0]["sha1_hash"] == hash { newFn := filepath.Base(filePath) oldFn := res[0]["file_name"].(string) if oldFn != newFn && !silent { a.log.Printf("The name of the file in file-store is %s, but you are trying to upload file %s", oldFn, newFn) } return hash, nil } body := &bytes.Buffer{} writer := multipart.NewWriter(body) part, err := writer.CreateFormFile("file_store[file]", filepath.Base(filePath)) if err != nil { return "", err } file, err := os.Open(filePath) if err != nil { return "", err } defer file.Close() if _, err := io.Copy(part, file); err != nil { return "", err } if err := writer.Close(); err != nil { return "", err } size := body.Len() contentType := writer.FormDataContentType() if !silent { a.log.Printf("Uploading %s (%s)", filePath, a.bytesToHuman(int64(size), "%d%s", "basic")) } req, err := http.NewRequest(http.MethodPost, a.fileStoreURL+"/api/v1/upload", body) if err != nil { return "", err } req.Header.Set("Content-Type", contentType) req.Header.Set("Content-Length", fmt.Sprintf("%d", size)) req.Header.Set("Authorization", "Basic "+a.base64AuthStr) resp, err := a.client.Do(req) if err != nil { return "", err } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode > 299 { return "", fmt.Errorf("could not upload file. HTTP error %d %s", resp.StatusCode, resp.Status) } var output map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&output); err != nil { return "", err } shaHash, ok := output["sha1_hash"].(string) if !ok { return "", errors.New("invalid response from server") } return shaHash, nil } func (a *AbfJson) GetFileInfoByHash(shaHash string) ([]map[string]interface{}, error) { params := url.Values{"hash": {shaHash}} data, err := a.getURLContents("/api/v1/file_stores.json", params, http.MethodGet, nil) if err != nil { return nil, err } var response []map[string]interface{} if err := json.Unmarshal(data, &response); err != nil { return nil, err } return response, nil } func (a *AbfJson) FetchFile(shaHash, path string) error { URL := a.fileStoreURL + "/api/v1/file_stores/" + shaHash resp, err := a.client.Get(URL) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("failed to fetch file: HTTP error %d %s", resp.StatusCode, resp.Status) } out, err := os.Create(path) if err != nil { return err } defer out.Close() _, err = io.Copy(out, resp.Body) return err }