Compare commits

...

8 Commits

Author SHA1 Message Date
5c45425235 fix(openapi): rename ImageListParams.globs to names
/image/list is served by ls, which reads cfg["names"]
(src/cmd/cli_ls.cpp:597). Nothing reads "globs", so the documented
filter was accepted and silently ignored.

Signed-off-by: Stepan Rabotkin <epicstyt@gmail.com>
2026-09-02 20:28:16 +03:00
dd9b9f926d fix(openapi): drop the leftover format and description on two OSD schemas
The 200 schemas of /osd/delete and /osd/modify are objects but carried
"format": "uint64" and "description": "New OSD number" at schema level,
copied from /osd/alloc where the response really is a number.

Signed-off-by: Stepan Rabotkin <epicstyt@gmail.com>
2026-09-02 19:57:33 +03:00
806ebf5bcd fix(openapi): describe every response
Added description to the 30 responses that lacked it: the 200 of every
operation and the inline 412 of /osd/delete and /data/delete. OpenAPI 3.0
requires it.

Signed-off-by: Stepan Rabotkin <epicstyt@gmail.com>
2026-09-02 19:57:33 +03:00
5f4e394eb0 fix(openapi): drop the Swagger 2.0 consumes and produces leftovers
Removed 48 occurrences. OpenAPI 3.0 has no such fields and the media
types are already declared in requestBody.content and responses.*.content.

Signed-off-by: Stepan Rabotkin <epicstyt@gmail.com>
2026-09-02 19:57:33 +03:00
50e36a04df fix(openapi): add the required info.title field
OpenAPI 3.0 requires info.title. Reuses the text of info.description.

Signed-off-by: Stepan Rabotkin <epicstyt@gmail.com>
2026-09-02 19:57:03 +03:00
9fc2781798 fix(openapi): require image, not the undefined names, in /image/flatten
The schema required "names", which it does not define. The handler reads
cfg["image"] (src/cmd/cli_flatten.cpp:141).

Signed-off-by: Stepan Rabotkin <epicstyt@gmail.com>
2026-09-02 19:57:03 +03:00
26c6251888 fix(cli): create an http context when serving plain HTTP
http_ctx was assigned only in the TLS branch, so on plain HTTP it stayed
NULL and http_init() segfaulted on the first accepted connection.

Signed-off-by: Stepan Rabotkin <epicstyt@gmail.com>
2026-09-02 19:43:19 +03:00
145942fc3c fix(openapi): give /osd/modify and /data/fix unique operationIds
Duplicate operationIds are forbidden outright by the OpenAPI
specification: the field must be unique among all operations in the
document. Two endpoints violated it by carrying the id of their
neighbouring delete operation, most likely copy-paste leftovers:
/osd/modify was osdDelete and /data/fix was dataDelete.

Rename them to osdModify and dataFix. Client generators derive method
names from operationId, so besides restoring validity this stops the
duplicates from silently colliding or being renamed by the generator.

Signed-off-by: Stepan Rabotkin <epicstyt@gmail.com>
2026-09-02 19:39:30 +03:00
2 changed files with 48 additions and 88 deletions

View File

@@ -151,6 +151,17 @@ struct cli_serve_t
}
}
}
else
{
std::string error;
http_ctx = http_context_init(parent->epmgr->tfd, "", "", "", false, error);
if (error != "")
{
result = (cli_result_t){ .err = EINVAL, .text = error };
state = 100;
return;
}
}
if (use_perms && client_ca == "")
{
result = (cli_result_t){ .err = EINVAL, .text = "use_perms requires client_ca" };

View File

@@ -1,6 +1,7 @@
{
"openapi": "3.0.0",
"info": {
"title": "Vitastor control API",
"description": "Vitastor control API",
"version": "3.0.2",
"license": {
@@ -11,10 +12,9 @@
"/status": { "get": {
"summary": "Show server status",
"operationId": "status",
"produces": [ "application/json" ],
"parameters": [],
"responses": {
"200": { "content": { "application/json": { "schema": {
"200": { "description": "Cluster status", "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"pool_count": { "type": "integer", "format": "uint64", "description": "Total number of pools" },
@@ -60,7 +60,6 @@
"/image/list": { "get": {
"summary": "List images",
"operationId": "imageListGet",
"produces": [ "application/json" ],
"parameters": [ {
"name": "params",
"in": "query",
@@ -69,23 +68,19 @@
"explode": true
} ],
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ImageList" } } } }
"200": { "description": "List of images", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ImageList" } } } }
}
}, "post": {
"summary": "List images",
"operationId": "imageListPost",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ImageListParams" } } } },
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ImageList" } } } }
"200": { "description": "List of images", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ImageList" } } } }
}
} },
"/image/create": { "post": {
"summary": "Create an image, snapshot, or a clone",
"operationId": "imageCreate",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": {
"type": "object",
"required": [ "image" ],
@@ -113,7 +108,7 @@
}
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Image" } } } },
"200": { "description": "The created image, snapshot or clone", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Image" } } } },
"400": { "$ref": "#/components/responses/Invalid" },
"403": { "$ref": "#/components/responses/Forbidden" },
"409": { "$ref": "#/components/responses/UpdateConflict" },
@@ -123,8 +118,6 @@
"/image/modify": { "post": {
"summary": "Modify an image",
"operationId": "imageModify",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": {
"type": "object",
"required": [ "image" ],
@@ -154,7 +147,7 @@
}
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Image" } } } },
"200": { "description": "The modified image", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Image" } } } },
"400": { "$ref": "#/components/responses/Invalid" },
"403": { "$ref": "#/components/responses/Forbidden" },
"409": { "$ref": "#/components/responses/UpdateConflict" },
@@ -164,8 +157,6 @@
"/image/delete": { "post": {
"summary": "Delete image(s)",
"operationId": "imageDelete",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": {
"oneOf": [ {
"type": "object",
@@ -195,7 +186,7 @@
} ]
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": {
"200": { "description": "Deleted and rebased images", "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"deleted_ids": { "type": "array", "items": { "type": "integer", "format": "uint64" }, "description": "Full inode ID(s) of deleted images" },
@@ -214,17 +205,15 @@
"/image/flatten": { "post": {
"summary": "Flatten an image",
"operationId": "imageFlatten",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": {
"type": "object",
"required": [ "names" ],
"required": [ "image" ],
"properties": {
"image": { "type": "string", "description": "Image name to flatten" }
}
} } } },
"responses": {
"200": { "content": { "text/plain": { "schema": { "type": "string", "description": "Empty response" } } } },
"200": { "description": "Image flattened", "content": { "text/plain": { "schema": { "type": "string", "description": "Empty response" } } } },
"400": { "$ref": "#/components/responses/Invalid" },
"403": { "$ref": "#/components/responses/Forbidden" },
"409": { "$ref": "#/components/responses/UpdateConflict" },
@@ -234,7 +223,6 @@
"/osd/list": { "get": {
"summary": "List OSDs",
"operationId": "osdListGet",
"produces": [ "application/json" ],
"parameters": [ {
"name": "params",
"in": "query",
@@ -243,24 +231,21 @@
"explode": true
} ],
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OsdList" } } } }
"200": { "description": "List of OSDs", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OsdList" } } } }
}
}, "post": {
"summary": "List OSDs",
"operationId": "osdListPost",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OsdListParams" } } } },
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OsdList" } } } }
"200": { "description": "List of OSDs", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OsdList" } } } }
}
} },
"/osd/alloc": { "post": {
"summary": "Allocate a new OSD number",
"operationId": "osdAlloc",
"produces": [ "application/json" ],
"responses": {
"200": { "content": { "application/json": { "schema": {
"200": { "description": "The allocated OSD number", "content": { "application/json": { "schema": {
"type": "integer",
"format": "uint64",
"description": "New OSD number"
@@ -272,8 +257,6 @@
"/osd/delete": { "post": {
"summary": "Delete stopped OSD(s) from etcd",
"operationId": "osdDelete",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": {
"type": "object",
"properties": {
@@ -283,7 +266,7 @@
}
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": {
"200": { "description": "Deleted OSD numbers", "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"deleted_osds": {
@@ -291,13 +274,11 @@
"items": { "type": "integer", "format": "uint64" },
"description": "Deleted OSD numbers"
}
},
"format": "uint64",
"description": "New OSD number"
}
} } } },
"400": { "$ref": "#/components/responses/Invalid" },
"409": { "$ref": "#/components/responses/UpdateConflict" },
"412": { "content": { "application/json": { "schema": {
"412": { "description": "Precondition failed: removing these OSD(s) would affect some pools", "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"pool_effects": {
@@ -321,9 +302,7 @@
} },
"/osd/modify": { "post": {
"summary": "Modify OSD reweight, tags or noout flag",
"operationId": "osdDelete",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"operationId": "osdModify",
"requestBody": { "content": { "application/json": { "schema": {
"type": "object",
"properties": {
@@ -334,15 +313,13 @@
}
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": {
"200": { "description": "OSD settings after modification", "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"reweight": { "type": "number", "minimum": 0, "maximum": 1, "description": "OSD weight" },
"tags": { "type": "array", "items": { "type": "string" }, "description": "OSD tags" },
"noout": { "type": "boolean", "description": "noout flag" }
},
"format": "uint64",
"description": "New OSD number"
}
} } } },
"400": { "$ref": "#/components/responses/Invalid" },
"409": { "$ref": "#/components/responses/UpdateConflict" },
@@ -352,7 +329,6 @@
"/pool/list": { "get": {
"summary": "List pools",
"operationId": "poolListGet",
"produces": [ "application/json" ],
"parameters": [ {
"name": "params",
"in": "query",
@@ -361,23 +337,19 @@
"explode": true
} ],
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PoolList" } } } }
"200": { "description": "List of pools", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PoolList" } } } }
}
}, "post": {
"summary": "List pools",
"operationId": "poolListPost",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PoolListParams" } } } },
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PoolList" } } } }
"200": { "description": "List of pools", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PoolList" } } } }
}
} },
"/pool/create": { "post": {
"summary": "Create a pool",
"operationId": "poolCreate",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": {
"allOf": [ {
"$ref": "#/components/schemas/PoolConfig"
@@ -387,7 +359,7 @@
} } ]
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": {
"200": { "description": "The created pool", "content": { "application/json": { "schema": {
"$ref": "#/components/schemas/PoolConfig"
} } } },
"400": { "$ref": "#/components/responses/Invalid" },
@@ -398,8 +370,6 @@
"/pool/modify": { "post": {
"summary": "Modify a pool",
"operationId": "poolModify",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": {
"allOf": [ {
"$ref": "#/components/schemas/PoolConfig"
@@ -410,7 +380,7 @@
} } ]
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": {
"200": { "description": "The modified pool", "content": { "application/json": { "schema": {
"$ref": "#/components/schemas/PoolConfig"
} } } },
"400": { "$ref": "#/components/responses/Invalid" },
@@ -421,8 +391,6 @@
"/pool/delete": { "post": {
"summary": "Delete a pool",
"operationId": "poolDelete",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": {
"type": "object",
"properties": {
@@ -432,7 +400,7 @@
}
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": {
"200": { "description": "Pool deleted", "content": { "application/json": { "schema": {
"type": "object",
"properties": { "ok": { "type": "boolean" } }
} } } },
@@ -444,7 +412,6 @@
"/user/list": { "get": {
"summary": "List users",
"operationId": "userListGet",
"produces": [ "application/json" ],
"parameters": [ {
"name": "params",
"in": "query",
@@ -453,26 +420,22 @@
"explode": true
} ],
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserList" } } } }
"200": { "description": "List of users", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserList" } } } }
}
}, "post": {
"summary": "List users",
"operationId": "userListPost",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserListParams" } } } },
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserList" } } } }
"200": { "description": "List of users", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserList" } } } }
}
} },
"/user/modify": { "post": {
"summary": "Modify a user",
"operationId": "userModify",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/User" } } } },
"responses": {
"200": { "content": { "application/json": { "schema": {
"200": { "description": "The modified user", "content": { "application/json": { "schema": {
"$ref": "#/components/schemas/User"
} } } },
"400": { "$ref": "#/components/responses/Invalid" },
@@ -483,8 +446,6 @@
"/user/delete": { "post": {
"summary": "Delete a user",
"operationId": "userDelete",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": {
"type": "object",
"properties": {
@@ -492,7 +453,7 @@
}
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": {
"200": { "description": "User deleted", "content": { "application/json": { "schema": {
"type": "object",
"properties": { "ok": { "type": "boolean" } }
} } } },
@@ -504,7 +465,6 @@
"/pg/list": { "get": {
"summary": "List PGs",
"operationId": "pgListGet",
"produces": [ "application/json" ],
"parameters": [ {
"name": "params",
"in": "query",
@@ -513,23 +473,19 @@
"explode": true
} ],
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PgList" } } } }
"200": { "description": "List of PGs", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PgList" } } } }
}
}, "post": {
"summary": "List PGs",
"operationId": "pgListPost",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PgListParams" } } } },
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PgList" } } } }
"200": { "description": "List of PGs", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PgList" } } } }
}
} },
"/data/delete": { "post": {
"summary": "Remove inode data without changing metadata",
"operationId": "dataDelete",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": {
"type": "object",
"properties": {
@@ -540,14 +496,14 @@
}
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": {
"200": { "description": "Number of removed objects", "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"removed_objects": { "type": "integer", "format": "uint64", "description": "Removed object count" }
}
} } } },
"400": { "$ref": "#/components/responses/Invalid" },
"412": { "content": { "application/json": { "schema": {
"412": { "description": "Precondition failed: some data could not be removed", "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"total_objects": { "type": "integer", "format": "uint64", "description": "Total object count" },
@@ -570,7 +526,6 @@
"/data/describe": { "get": {
"summary": "Describe unclean object locations in the cluster",
"operationId": "dataDescribeGet",
"produces": [ "application/json" ],
"parameters": [ {
"name": "params",
"in": "query",
@@ -579,23 +534,19 @@
"explode": true
} ],
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DataDescribe" } } } }
"200": { "description": "Unclean object locations", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DataDescribe" } } } }
}
}, "post": {
"summary": "Describe unclean object locations in the cluster",
"operationId": "dataDescribePost",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DataDescribeParams" } } } },
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DataDescribe" } } } }
"200": { "description": "Unclean object locations", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DataDescribe" } } } }
}
} },
"/data/fix": { "post": {
"summary": "Fix inconsistent objects in the cluster by deleting some copies",
"operationId": "dataDelete",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"operationId": "dataFix",
"requestBody": { "content": { "application/json": { "schema": {
"type": "object",
"properties": {
@@ -619,7 +570,7 @@
}
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": {
"200": { "description": "The removed object copy", "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"inode": { "type": "integer", "format": "uint64", "description": "Full inode ID with pool" },
@@ -634,8 +585,6 @@
"/data/merge": { "post": {
"summary": "Merge layer data without changing metadata",
"operationId": "dataMerge",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": {
"type": "object",
"properties": {
@@ -648,7 +597,7 @@
}
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": {
"200": { "description": "The merged layer range", "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"from": { "type": "string", "description": "First merged parent layer" },
@@ -779,7 +728,7 @@
"ImageListParams": {
"type": "object",
"properties": {
"globs": { "type": "array", "items": { "type": "string" }, "description": "Glob patterns or exact name matches" },
"names": { "type": "array", "items": { "type": "string" }, "description": "Glob patterns or exact name matches" },
"exact": { "type": "boolean", "description": "Treat globs as exact name matches, not as patterns" },
"pool_id": { "type": "integer", "format": "uint64", "description": "Pool ID to list" },
"pool_name": { "type": "string", "description": "Pool name to list" },