Help:Public API

From Svacer Wiki

Публичные REST запросы

Для работы с сервером через различные приложения реализованы публичные запросы, которые не будут меняться при обновлении самого сервера. Если будут происходить значимые изменения, то запрос будет помечаться как deprecated и создаваться новый такой же запрос с пометкой /api/some/request/v2.

Получение токена

Для большинства запросов требуется JWT-токен, чтобы его получить нужно пройти аутентификацию. Это можно сделать двумя способами:

  • Простой способ — POST запрос с basic auth на /api/login, который вернёт JWT-токен в body. Использование данного способа не желательно, так как он может меняться в будущем, а также не всегда правильно обрабатывает спец. символы и кириллицу в логине/пароле. Пример:
curl -X POST -u admin:admin http://svacer.example.com/api/login
  • Предпочтительный способ — POST запрос на /api/public/login с передачей данных в теле запроса, как описано ниже. Пример:
curl -X POST -d '{"login": "admin", "password": "admin"}' http://svacer.example.com/api/public/login

Полученный токен надо добавлять в header всех запросов: Authorization: Bearer <token>

Базовые публичные запросы

/api/public/login POST Аутентификация пользователя. Тело запроса имеет формат JSON и выглядит следующим образом:
 {
   "auth_type": "string",
   "server": "string"
   "login": "string",
   "password": "string"
 }
  • auth_type — тип аутентификации: при указании значения "ldap" — аутентификация через LDAP, во всех остальных случаях, или если поле не указано — по внутренней базе пользователей Svacer
  • server — имя LDAP-сервера, как он указан в конфигурационном файле LDAP (поле name) при запуске Svacer. Значение поля учитывается, только если "auth_type": "ldap", в иных случаях его можно не указывать
  • login — логин пользователя, обязательное поле
  • password — пароль пользователя, обязательное поле
/api/public/help GET Дополнительная информация по public REST API. Работает без авторизации
/api/health GET Проверка доступности сервера. Не требует авторизации. Возвращает статус 200 OK если сервер работает

Описание остальных запросов смотрите в спецификации OpenAPI на вашем сервере Svacer по адресу /api/public/swagger/

Также можете посмотреть на нашем демо-сервере: https://svacer-demo.ispras.ru/api/public/swagger/

Управление контейнерами

См. Help:Public API/management/containers

Экспорт, импорт и копирование разметки

См. Markup2

Примеры использования public REST API

Получение URL загруженного снимка

Этот пример работает только с python3 и не работает при аутентификации в Svacer через LDAP.

Скачайте и распакуйте get_link.zip

Запрос:

python get_link.py --url http://svacer.example.com:8080 --user admin --password admin --project bash

Ответ:

http://svacer.example.com:8080/mode/review/project/0fd645aa-8e70-4a4f-b68b-766c4f337bf2/branch/8925df5a-7a98-4f07-bc88-ee4ea5b43813/snapshot/e3367efa-a804-4c05-9a7a-a7cb052bef1d

Примеры использования фильтров в public api

Вспомогательные модули

В некоторых примерах API используется фильтр, с помощью которого можно ограничить вывод данных и получить более точные результаты. Для работы с этим фильтром используются следующие классы:

import requests
from requests import auth
from requests.models import HTTPBasicAuth
import json
from dataclasses import dataclass
import sys
import base64

def printerr(text):
    #print in stderr and exit
    print(f"{text}", file=sys.stderr, flush=True)
    exit()


@dataclass
class ProjectFilter:
    id: str
    name: str


class ProjectFilterEncoder(json.JSONEncoder):
    def default(self, o):
        return o.__dict__


@dataclass
class BranchFilter:
    id: str
    name: str


class BranchFilterEncoder(json.JSONEncoder):
    def default(self, o):
        return o.__dict__


@dataclass
class SnapshotFilter:
    id: str
    name: str


class SnapshotFilterEncoder(json.JSONEncoder):
    def default(self, o):
        return o.__dict__


@dataclass
class MarkerFilter:
    severity: str
    file: str
    checker: str
    review: str


class MarkerFilterEncoder(json.JSONEncoder):
    def default(self, o):
        return o.__dict__


@dataclass
class Filter:
    project: ProjectFilter
    branch: BranchFilter
    snapshot: SnapshotFilter
    marker: MarkerFilter


class FilterEncoder(json.JSONEncoder):
    def default(self, o):
        return o.__dict__


def CreateFilterParam(filter: Filter):
    jsoned = FilterEncoder().encode(filter)
    b64ed = base64.b64encode(jsoned.encode("utf-8"))
    return b64ed.decode("utf-8")

Принцип использования классов следующий:

  1. Создается экземпляр нужного класса XXXXFilter
  2. Создается base64 строка на основе объекта пункта 1, которая в дальнейшем передается в url запросе. Для этого используется метод CreateFilterParam()

Например. Для создания фильтра, ограничивающего множество проектов выражением darpa*, можно использовать следующие строчки кода:

f: Filter = Filter(ProjectFilter(id="", name="darp*"), None, None, None)
param = CreateFilterParam(f)

Как можно заметить из определения фильтра, для проекта, ветки и снимка - можно указать как имя так и ID. Имя можно указать с использованием регулярного выражения. ID необходимо указать полностью.

Точка входа api/public/login

Данный класс может быть использован для соединения с сервером Svacer. В данном модуле демонстрируется взаимодействие с точкой входа /api/public/login.

import requests

class SvacerClient:
    def __init__(self, host, user, password):
        """
        Initialize the Svacer client        
        """
        self.host = host
        self.user = user
        self.password = password
        self.token = None
        self.auth_header = None
        self.session = requests.Session()  # Use session for connection pooling
        self.set_auth_token()
    
    def set_auth_token(self):
        """
        Get authorization token using /api/public/login and setup in session
        """
        try:
            response = requests.post(
                url=f"{self.host}/api/public/login",
                json={
                    "login":self.user,
                    "password":self.password,
                },
                verify=False
            )
            response.raise_for_status()  # Raise an exception for bad status codes            
            body = response.json()
            self.token = body["token"]
            self.auth_header = {'Authorization': f'Bearer {self.token}'}
            
            # Set the authorization header in the session
            self.session.headers.update(self.auth_header)
            
            return self.token
            
        except requests.exceptions.RequestException as e:
            raise Exception(f"Failed to get authentication token: {e}")
        except KeyError as e:
            raise Exception(f"Token not found in response: {e}")
    
    def post(self, endpoint, data=None, json=None, **kwargs):
        """
        Make a POST request to the API

        """
        if self.auth_header is None:
            raise Exception("No authentication token available. Call get_auth_token() first.")
        
        url = f"{self.host}{endpoint}"
        
        try:
            response = self.session.post(
                url, 
                data=data, 
                json=json, 
                verify=False,
                **kwargs
            )
            response.raise_for_status()
            return response
            
        except requests.exceptions.RequestException as e:
            raise Exception(f"POST request failed: {e}")
    
    def get(self, endpoint, params=None, **kwargs):
        """
        Make a GET request to the API
  
        """
        if self.auth_header is None:
            raise Exception("No authentication token available. Call get_auth_token() first.")
        
        url = f"{self.host}{endpoint}"
        
        try:
            response = self.session.get(
                url, 
                params=params, 
                verify=False,
                **kwargs
            )
            response.raise_for_status()
            return response
            
        except requests.exceptions.RequestException as e:
            raise Exception(f"GET request failed: {e}")

Точка входа /api/public/projects

В данном примере выводятся в формате json список проектов, имя которых соответствует регулярному выражению darpa*

from filters import *
from svacer import *

cl = SvacerClient(host="http://swarm-mgr:10142",user="admin",password="admin")
f: Filter = Filter(ProjectFilter(id="", name="darp*"), None, None, None)
param = CreateFilterParam(f)
resp=cl.get(f"/api/public/projects?filters={param}")
print(f"Response: {resp.json()}")

Результат может быть следующим

[
    {
        "project":
        {
            "id": "019abb61-8cd3-7437-957d-aed0c87e603a",
            "name": "darpa-clone",
            "time": "2025-11-25T14:18:43.79526Z",
            "created_by": "admin",
            "created_by_id": "00000000-0000-0000-0000-000000000000"
        },
        "branches":
        [
            {
                "id": "019abb61-83b1-7d80-8d23-ce849387bd6e",
                "name": "master",
                "time": "2025-11-25T14:18:41.457887Z",
                "created_by": "admin",
                "created_by_id": "00000000-0000-0000-0000-000000000000"
            }
        ]
    },
    {
        "project":
        {
            "id": "e6abca1b-23a0-4107-9120-4d8d3596030b",
            "name": "darpa",
            "time": "2020-04-03T04:54:13.754864Z",
            "created_by": "importer",
            "created_by_id": null
        },
        "branches":
        [
            {
                "id": "ab179fad-c96a-498d-8407-2a3c82719385",
                "name": "master",
                "time": "2020-04-03T04:54:13.754864Z",
                "created_by": "importer",
                "created_by_id": null
            }
        ]
    }
]

Точка входа /api/public/projects/{project_id}/branch/{branch_id}/snapshots

С помощью данной точки входа можно получить список снимков, указанной ветки. Пример кода, для получения снимков с именем "ver. 2.7.0*"

from filters import *
from svacer import *
import json

def get_snapshots(pr_id, br_id):
    cl = SvacerClient(host="http://swarm-mgr:10142",user="admin",password="admin")
    f: Filter = Filter(None,None,SnapshotFilter(id="",name="ver. 2.7.0*"), None)
    param = CreateFilterParam(f)
    resp=cl.get(f"/api/public/projects/{pr_id}/branch/{br_id}/snapshots?filters={param}")
    fmt = json.dumps(resp.json())
    print(f"Response: {fmt}")


get_snapshots("e6abca1b-23a0-4107-9120-4d8d3596030b","ab179fad-c96a-498d-8407-2a3c82719385")

Возможный вывод команды:

[
    {
        "id": "4242b43a-da44-44ca-9b28-770295b98493",
        "name": " ver. 2.7.1 200419 05:55",
        "time": "2020-04-03T04:56:49.006218Z",
        "link": "http://swarm-mgr.home:10142/mode/review/project/e6abca1b-23a0-4107-9120-4d8d3596030b/branch/ab179fad-c96a-498d-8407-2a3c82719385/snapshot/4242b43a-da44-44ca-9b28-770295b98493",
        "created_by": "importer",
        "created_by_id": "00000000-0000-0000-0000-000000000002",
        "import_time": "2019-04-20T04:26:52Z",
        "details":
        {
            "branch": "master",
            "branchIDHint": "4d1f1413-c638-4a8b-aa4d-d9fccd8df3e9",
            "buildObject": "cf43335308de8b7b3ae03735a7f738a6e9246f52",
            "dxrIncluded": true,
            "host":
            {
                "hostname": "seroshki",
                "user": "sergey"
            },
            "id": "4242b43a-da44-44ca-9b28-770295b98493",
            "importTime": "2019-04-20T04:26:52Z",
            "migration-data":
            {
                "DXR": "ce03cd6d1bd54eac92e227d4184356a90b64b7c1",
                "SOURCE_TREE": "0949114563f48d5a2df6e94816b7b1095b6c6540",
                "date": "2019-04-20T04:26:52Z",
                "description": " ver. 2.7.1 200419 05:55",
                "id": "2af625b5faad4083be881135ad1daf6b304e4c99",
                "owner": "7a831745d52244db2a4f1692f0c473e61f0f0157",
                "parent_id": "0d491fde062e2795a4b38758488a47bf8481d03c",
                "sourceData": "cf43335308de8b7b3ae03735a7f738a6e9246f52",
                "svres": "2af625b5faad4083be881135ad1daf6b304e4c99.svres.gzip",
                "type": "snapshot",
                "user": "jenkins"
            },
            "project": "darpa",
            "projectIDHint": "354c9c94-7b57-4427-8ce0-c41f1e59e6c2",
            "snapshot": " ver. 2.7.1 200419 05:55",
            "sourceIncluded": true
        }
    },
    {
        "id": "4f706c3a-fdac-475e-bb0a-8bdd753879ca",
        "name": " ver. 2.7.1 130419 07:39",
        "time": "2020-04-03T04:58:53.618464Z",
        "link": "http://swarm-mgr.home:10142/mode/review/project/e6abca1b-23a0-4107-9120-4d8d3596030b/branch/ab179fad-c96a-498d-8407-2a3c82719385/snapshot/4f706c3a-fdac-475e-bb0a-8bdd753879ca",
        "created_by": "importer",
        "created_by_id": "00000000-0000-0000-0000-000000000002",
        "import_time": "2019-04-13T04:33:46Z",
        "details":
        {
            "branch": "master",
            "branchIDHint": "c472aab7-d53f-496c-9d3d-069c0901d9bb",
            "buildObject": "cf43335308de8b7b3ae03735a7f738a6e9246f52",
            "dxrIncluded": false,
            "host":
            {
                "hostname": "seroshki",
                "user": "sergey"
            },
            "id": "4f706c3a-fdac-475e-bb0a-8bdd753879ca",
            "importTime": "2019-04-13T04:33:46Z",
            "migration-data":
            {
                "DXR": "ce03cd6d1bd54eac92e227d4184356a90b64b7c1",
                "SOURCE_TREE": "0949114563f48d5a2df6e94816b7b1095b6c6540",
                "date": "2019-04-13T04:33:46Z",
                "description": " ver. 2.7.1 130419 07:39",
                "id": "0d491fde062e2795a4b38758488a47bf8481d03c",
                "owner": "7a831745d52244db2a4f1692f0c473e61f0f0157",
                "parent_id": "edca1583aa41489d37ae9fb2c348ad066ecd3ab3",
                "sourceData": "cf43335308de8b7b3ae03735a7f738a6e9246f52",
                "svres": "0d491fde062e2795a4b38758488a47bf8481d03c.svres.gzip",
                "type": "snapshot",
                "user": "jenkins"
            },
            "project": "darpa",
            "projectIDHint": "cf032d99-2efe-4ee0-8db8-ee85ff17b296",
            "snapshot": " ver. 2.7.1 130419 07:39",
            "sourceIncluded": false
        }
    }
 ]

Точка входа /api/public/projects/{project_id}/branch/{branch_id}/snapshots/{snapshot_id}/fullmarkers

Данная точка входа служит для получения информации о получении информации о маркерах указанного снимка. Информация о маркере может включать в себя:

  • трассу ошибки (параметр traces, возможные значения true,false)
  • информацию о чекере (параметр checker_info, возможные значения true,false)
  • историю разметки (параметр review_history, возможные значения true,false)
  • история комментария (параметр comment_history, возможные значения true,false)
  • идентификатор пользовательского фильтра (параметр custom_filter)
  • фильтр на параметры маркера

Пример кода, для получения всего множества информации о маркерах в некотором снимке, размеченных со статусом Conf*:

def get_markers (pr_id, br_id, snap_id):
    cl = SvacerClient(host="http://swarm-mgr:10142",user="admin",password="admin")
    f: Filter = Filter(None, None, None, MarkerFilter(severity="*",
                       file="*", checker="*", review="Conf*"))
    param = CreateFilterParam(f)
    resp=cl.get(f"/api/public/projects/{pr_id}/branch/{br_id}/snapshots/{snap_id}/fullmarkers?traces=true&checker_info=true&review_history=true&comment_history=true&filters={param}")
    fmt = json.dumps(resp.json())
    print(f"Response: {fmt}")

get_markers("e6abca1b-23a0-4107-9120-4d8d3596030b","ab179fad-c96a-498d-8407-2a3c82719385","250a4ff1-bfc8-439a-834d-18b62a545d8d")  

Возможный вывод:

[
  {
    "id": "ab877a02-03ac-41ac-8b87-46f7e76791ba",
    "file": "/mnt/scratch/sources/darpa-source/challenges/Divelogger2/lib/string.cc",
    "function": "_ZN6StringaSERKS_",
    "line": 179,
    "locID": 1006,
    "lang": "CXX",
    "tool": "SvEng",
    "warnClass": "ASSIGN_NO_CHECK_FOR_THIS",
    "mtid": "SvEng.LA.4",
    "msg": "Method 'operator=' doesn't check its argument with 'this' pointer.",
    "details": "d96c832835f414a119ea8c7575a8926378fd7cf6",
    "flags": 0,
    "traces": [
      {
        "role": "declaration",
        "locations": [
          {
            "file": "/mnt/scratch/sources/darpa-source/challenges/Divelogger2/lib/string.cc",
            "line": 179,
            "col": 0,
            "spec": false,
            "info": "declaration"
          }
        ]
      }
    ],
    "checker_severity": "Normal",
    "checker_reliability": "High",
    "checker_origin": "builtin",
    "review": {
      "id": "65796",
      "status": "Confirmed",
      "severity": "Major",
      "action": "Fix required",
      "createdBy": "admin",
      "created_by_id": "00000000-0000-0000-0000-000000000000",
      "time": "2025-11-25T15:04:25.216816Z",
      "createdFrom": "ab877a02-03ac-41ac-8b87-46f7e76791ba"
    },
    "review_history": [
      {
        "id": "65796",
        "status": "Confirmed",
        "severity": "Major",
        "action": "Fix required",
        "createdBy": "admin",
        "created_by_id": "00000000-0000-0000-0000-000000000000",
        "time": "2025-11-25T15:04:25.216816Z",
        "createdFrom": "ab877a02-03ac-41ac-8b87-46f7e76791ba",
        "createdFromContext": {
          "projectID": "e6abca1b-23a0-4107-9120-4d8d3596030b",
          "branchID": "ab179fad-c96a-498d-8407-2a3c82719385",
          "snapshotID": "250a4ff1-bfc8-439a-834d-18b62a545d8d",
          "markerID": "ab877a02-03ac-41ac-8b87-46f7e76791ba",
          "projectName": "darpa",
          "branchName": "master",
          "snapshotName": " ver. 2.7.1 300319 07:08",
          "created_by": "importer",
          "created_by_id": "00000000-0000-0000-0000-000000000002",
          "create_ts": "2020-04-03T04:56:40.524836Z"
        }
      },
      {
        "id": "65795",
        "status": "Confirmed",
        "severity": "Major",
        "action": "Undecided",
        "createdBy": "admin",
        "created_by_id": "00000000-0000-0000-0000-000000000000",
        "time": "2025-11-25T15:04:21.683963Z",
        "createdFrom": "ab877a02-03ac-41ac-8b87-46f7e76791ba",
        "createdFromContext": {
          "projectID": "e6abca1b-23a0-4107-9120-4d8d3596030b",
          "branchID": "ab179fad-c96a-498d-8407-2a3c82719385",
          "snapshotID": "250a4ff1-bfc8-439a-834d-18b62a545d8d",
          "markerID": "ab877a02-03ac-41ac-8b87-46f7e76791ba",
          "projectName": "darpa",
          "branchName": "master",
          "snapshotName": " ver. 2.7.1 300319 07:08",
          "created_by": "importer",
          "created_by_id": "00000000-0000-0000-0000-000000000002",
          "create_ts": "2020-04-03T04:56:40.524836Z"
        }
      },
      {
        "id": "63495",
        "status": "Confirmed",
        "severity": "Unspecified",
        "action": "Undecided",
        "createdBy": "reviewer",
        "created_by_id": "077fdd75-b382-4008-97c4-ec6ef8732ccd",
        "time": "2022-04-05T11:47:47.026161Z",
        "createdFrom": "a94f92f4-4e3e-4071-9f10-aa8e4389dd0a",
        "createdFromContext": {
          "projectID": "e6abca1b-23a0-4107-9120-4d8d3596030b",
          "branchID": "ab179fad-c96a-498d-8407-2a3c82719385",
          "snapshotID": "4242b43a-da44-44ca-9b28-770295b98493",
          "markerID": "a94f92f4-4e3e-4071-9f10-aa8e4389dd0a",
          "projectName": "darpa",
          "branchName": "master",
          "snapshotName": " ver. 2.7.1 200419 05:55",
          "created_by": "importer",
          "created_by_id": "00000000-0000-0000-0000-000000000002",
          "create_ts": "2020-04-03T04:56:49.006218Z"
        },
        "reviewer_attrs": ["review_master"]
      }
    ],
    "comments": [
      {
        "id": "e3287eb2-9e64-4780-a1c0-22ed9393a226",
        "text": "aaaaaaaaa",
        "type": "txt",
        "ref": "ab877a02-03ac-41ac-8b87-46f7e76791ba",
        "createdBy": "admin",
        "created_by_id": "",
        "invariant": "",
        "createTs": "2025-11-25T15:04:11.358138Z",
        "updateTs": "2025-11-25T15:04:17.220568Z",
        "history": [
          {
            "id": "019abb8b-4244-7931-81f7-a7d1a4512de0",
            "comment_id": "e3287eb2-9e64-4780-a1c0-22ed9393a226",
            "comment_ref": "ab877a02-03ac-41ac-8b87-46f7e76791ba",
            "text": "aaaa",
            "created_by": "admin",
            "created_by_id": "00000000-0000-0000-0000-000000000000",
            "create_ts": "2025-11-25T15:04:11.358138Z"
          }
        ]
      }
    ],
    "invariant": "1pQ3T5bq6AMYEYJqzFsW+Q"
  }
]

Следует заметить, что данный метод может требовать достаточно большого времени выполнения для проектов с большим количеством предупреждений.