abfapi/abfapi.go

629 lines
18 KiB
Go
Raw Permalink Normal View History

2024-12-09 00:44:37 +03:00
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
}
2024-12-13 23:00:48 +03:00
func NewAbfJson(abfURL, fileStoreURL, login, password string, logger *log.Logger) (*AbfJson, error) {
2024-12-09 00:44:37 +03:00
if !strings.HasPrefix(fileStoreURL, "http://") && !strings.HasPrefix(fileStoreURL, "https://") {
2024-12-13 23:00:48 +03:00
return nil, errors.New("file-store URL has to start with \"http(s)://\"")
2024-12-09 00:44:37 +03:00
}
cacheDir := filepath.Join(os.TempDir(), "abf_cache")
os.MkdirAll(cacheDir, 0755)
cache := diskcache.New(cacheDir)
transport := httpcache.NewTransport(cache)
client := &http.Client{
Transport: transport,
2024-12-13 22:52:11 +03:00
Timeout: 60 * time.Second, // Установите фиксированное значение таймаута или используйте другой способ установки
2024-12-09 00:44:37 +03:00
}
lpw := fmt.Sprintf("%s:%s", login, password)
encodedLpw := base64.StdEncoding.EncodeToString([]byte(lpw))
2024-12-13 23:00:48 +03:00
return &AbfJson{
2024-12-09 00:44:37 +03:00
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) {
2024-12-13 23:00:48 +03:00
var fullURL string
if strings.HasPrefix(path, "/api/v1/upload") {
fullURL = a.fileStoreURL + path
} else {
fullURL = a.abfURL + path
}
2024-12-09 00:44:37 +03:00
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{}
2024-12-13 23:00:48 +03:00
err := json.Unmarshal(data, &response)
if err != nil {
2024-12-09 00:44:37 +03:00
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) {
2024-12-13 23:00:48 +03:00
data, err := a.getURLContents("/api/v1/arches.json", nil, http.MethodGet, nil)
2024-12-09 00:44:37 +03:00
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}
}
2024-12-13 23:00:48 +03:00
data, err := a.getURLContents("/api/v1/platforms.json", params, http.MethodGet, nil)
2024-12-09 00:44:37 +03:00
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)
2024-12-13 23:00:48 +03:00
data, err := a.getURLContents(path, nil, http.MethodGet, nil)
2024-12-09 00:44:37 +03:00
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)
2024-12-13 23:00:48 +03:00
data, err := a.getURLContents(path, nil, http.MethodGet, nil)
2024-12-09 00:44:37 +03:00
if err != nil {
return nil, err
}
return a.processResponse(data)
}
func (a *AbfJson) GetBuildPlatforms() (map[string]interface{}, error) {
2024-12-13 23:00:48 +03:00
data, err := a.getURLContents("/api/v1/platforms/platforms_for_build.json", nil, http.MethodGet, nil)
2024-12-09 00:44:37 +03:00
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)
2024-12-13 23:00:48 +03:00
data, err := a.getURLContents(path, nil, http.MethodGet, nil)
2024-12-09 00:44:37 +03:00
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)
2024-12-13 23:00:48 +03:00
data, err := a.getURLContents(path, nil, http.MethodGet, nil)
2024-12-09 00:44:37 +03:00
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")
2024-12-13 23:00:48 +03:00
data, err := a.getURLContents(path, filterQuery, http.MethodGet, nil)
2024-12-09 00:44:37 +03:00
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)
2024-12-13 23:00:48 +03:00
data, err := a.getURLContents(path, filterQuery, http.MethodGet, nil)
2024-12-09 00:44:37 +03:00
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)
2024-12-13 23:00:48 +03:00
data, err := a.getURLContents(path, nil, http.MethodGet, nil)
2024-12-09 00:44:37 +03:00
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]}}
2024-12-13 23:00:48 +03:00
data, err := a.getURLContents("/api/v1/projects/get_id.json", params, http.MethodGet, nil)
2024-12-09 00:44:37 +03:00
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
}
2024-12-13 23:00:48 +03:00
dataBytes, err := a.getURLContents("/api/v1/build_lists.json", nil, http.MethodPost, bytes.NewBuffer(jsonData))
2024-12-09 00:44:37 +03:00
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
}
2024-12-13 23:00:48 +03:00
dataBytes, err := a.getURLContents(path, nil, http.MethodPut, bytes.NewBuffer(jsonData))
2024-12-09 00:44:37 +03:00
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
}
2024-12-13 23:00:48 +03:00
dataBytes, err := a.getURLContents(path, nil, http.MethodPost, bytes.NewBuffer(jsonData))
2024-12-09 00:44:37 +03:00
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
}
2024-12-13 23:00:48 +03:00
dataBytes, err := a.getURLContents(path, nil, http.MethodPut, bytes.NewBuffer(jsonData))
2024-12-09 00:44:37 +03:00
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
}
2024-12-13 23:00:48 +03:00
dataBytes, err := a.getURLContents(path, nil, http.MethodDelete, bytes.NewBuffer(jsonData))
2024-12-09 00:44:37 +03:00
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
}
2024-12-13 23:00:48 +03:00
dataBytes, err := a.getURLContents(path, nil, http.MethodPost, bytes.NewBuffer(jsonData))
2024-12-09 00:44:37 +03:00
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
}
2024-12-13 23:00:48 +03:00
dataBytes, err := a.getURLContents(path, nil, http.MethodPost, bytes.NewBuffer(jsonData))
2024-12-09 00:44:37 +03:00
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
}
2024-12-13 23:00:48 +03:00
dataBytes, err := a.getURLContents(path, nil, http.MethodDelete, bytes.NewBuffer(jsonData))
2024-12-09 00:44:37 +03:00
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
}
2024-12-13 23:00:48 +03:00
dataBytes, err := a.getURLContents(path, nil, http.MethodPut, bytes.NewBuffer(jsonData))
2024-12-09 00:44:37 +03:00
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
}
2024-12-13 23:00:48 +03:00
dataBytes, err := a.getURLContents("/api/v1/projects.json", nil, http.MethodPost, bytes.NewBuffer(jsonData))
2024-12-09 00:44:37 +03:00
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)
2024-12-13 23:00:48 +03:00
data, err := a.getURLContents(path, nil, http.MethodGet, nil)
2024-12-09 00:44:37 +03:00
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)
2024-12-13 23:00:48 +03:00
data, err := a.getURLContents(path, nil, http.MethodGet, nil)
2024-12-09 00:44:37 +03:00
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)
2024-12-13 23:00:48 +03:00
data, err := a.getURLContents(path, nil, http.MethodGet, nil)
2024-12-09 00:44:37 +03:00
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"}}
2024-12-13 23:00:48 +03:00
data, err := a.getURLContents("/api/v1/search.json", params, http.MethodGet, nil)
2024-12-09 00:44:37 +03:00
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)
2024-12-13 23:00:48 +03:00
data, err := a.getURLContents(path, params, http.MethodGet, nil)
2024-12-09 00:44:37 +03:00
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)
2024-12-13 23:00:48 +03:00
data, err := a.getURLContents(path, params, http.MethodGet, nil)
2024-12-09 00:44:37 +03:00
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)
2025-01-07 02:52:45 +03:00
part, err := writer.CreateFormFile("file_store[file]", filepath.Base(filePath))
2024-12-09 00:44:37 +03:00
if err != nil {
return "", err
}
2025-01-07 02:52:45 +03:00
file, err := os.Open(filePath)
2024-12-09 00:44:37 +03:00
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}}
2024-12-13 23:00:48 +03:00
data, err := a.getURLContents("/api/v1/file_stores.json", params, http.MethodGet, nil)
2024-12-09 00:44:37 +03:00
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
}