Merge "ARI: Add support for push configuration of dynamic object"
This commit is contained in:
commit
a2bfd663a1
7 changed files with 826 additions and 2 deletions
7
CHANGES
7
CHANGES
|
@ -196,6 +196,13 @@ ARI
|
|||
can be also be retrieved. Individual modules can be loaded to Asterisk, as
|
||||
well as unloaded and reloaded.
|
||||
|
||||
* A new resource has been added to the 'asterisk' resource, 'config/dynamic'.
|
||||
This resource allows for push configuration of sorcery derived objects
|
||||
within Asterisk. The resource supports creation, retrieval, updating, and
|
||||
deletion. Sorcery derived objects that are manipulated by this resource
|
||||
must have a sorcery wizard that supports the desired operations.
|
||||
|
||||
|
||||
res_pjsip
|
||||
------------------
|
||||
* A new 'g726_non_standard' endpoint option has been added that, when set to
|
||||
|
|
|
@ -308,6 +308,60 @@ ari_validator ast_ari_validate_config_info_fn(void)
|
|||
return ast_ari_validate_config_info;
|
||||
}
|
||||
|
||||
int ast_ari_validate_config_tuple(struct ast_json *json)
|
||||
{
|
||||
int res = 1;
|
||||
struct ast_json_iter *iter;
|
||||
int has_attribute = 0;
|
||||
int has_value = 0;
|
||||
|
||||
for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
|
||||
if (strcmp("attribute", ast_json_object_iter_key(iter)) == 0) {
|
||||
int prop_is_valid;
|
||||
has_attribute = 1;
|
||||
prop_is_valid = ast_ari_validate_string(
|
||||
ast_json_object_iter_value(iter));
|
||||
if (!prop_is_valid) {
|
||||
ast_log(LOG_ERROR, "ARI ConfigTuple field attribute failed validation\n");
|
||||
res = 0;
|
||||
}
|
||||
} else
|
||||
if (strcmp("value", ast_json_object_iter_key(iter)) == 0) {
|
||||
int prop_is_valid;
|
||||
has_value = 1;
|
||||
prop_is_valid = ast_ari_validate_string(
|
||||
ast_json_object_iter_value(iter));
|
||||
if (!prop_is_valid) {
|
||||
ast_log(LOG_ERROR, "ARI ConfigTuple field value failed validation\n");
|
||||
res = 0;
|
||||
}
|
||||
} else
|
||||
{
|
||||
ast_log(LOG_ERROR,
|
||||
"ARI ConfigTuple has undocumented field %s\n",
|
||||
ast_json_object_iter_key(iter));
|
||||
res = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_attribute) {
|
||||
ast_log(LOG_ERROR, "ARI ConfigTuple missing required field attribute\n");
|
||||
res = 0;
|
||||
}
|
||||
|
||||
if (!has_value) {
|
||||
ast_log(LOG_ERROR, "ARI ConfigTuple missing required field value\n");
|
||||
res = 0;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
ari_validator ast_ari_validate_config_tuple_fn(void)
|
||||
{
|
||||
return ast_ari_validate_config_tuple;
|
||||
}
|
||||
|
||||
int ast_ari_validate_module(struct ast_json *json)
|
||||
{
|
||||
int res = 1;
|
||||
|
|
|
@ -206,6 +206,24 @@ int ast_ari_validate_config_info(struct ast_json *json);
|
|||
*/
|
||||
ari_validator ast_ari_validate_config_info_fn(void);
|
||||
|
||||
/*!
|
||||
* \brief Validator for ConfigTuple.
|
||||
*
|
||||
* A key/value pair that makes up part of a configuration object.
|
||||
*
|
||||
* \param json JSON object to validate.
|
||||
* \returns True (non-zero) if valid.
|
||||
* \returns False (zero) if invalid.
|
||||
*/
|
||||
int ast_ari_validate_config_tuple(struct ast_json *json);
|
||||
|
||||
/*!
|
||||
* \brief Function pointer to ast_ari_validate_config_tuple().
|
||||
*
|
||||
* See \ref ast_ari_model_validators.h for more details.
|
||||
*/
|
||||
ari_validator ast_ari_validate_config_tuple_fn(void);
|
||||
|
||||
/*!
|
||||
* \brief Validator for Module.
|
||||
*
|
||||
|
@ -1262,6 +1280,9 @@ ari_validator ast_ari_validate_application_fn(void);
|
|||
* - max_open_files: int
|
||||
* - name: string (required)
|
||||
* - setid: SetId (required)
|
||||
* ConfigTuple
|
||||
* - attribute: string (required)
|
||||
* - value: string (required)
|
||||
* Module
|
||||
* - description: string (required)
|
||||
* - name: string (required)
|
||||
|
|
|
@ -36,8 +36,262 @@ ASTERISK_REGISTER_FILE()
|
|||
#include "asterisk/module.h"
|
||||
#include "asterisk/paths.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/sorcery.h"
|
||||
#include "resource_asterisk.h"
|
||||
|
||||
static void return_sorcery_object(struct ast_sorcery *sorcery, void *sorcery_obj,
|
||||
struct ast_ari_response *response)
|
||||
{
|
||||
RAII_VAR(struct ast_json *, return_set, NULL, ast_json_unref);
|
||||
struct ast_variable *change_set;
|
||||
struct ast_variable *it_change_set;
|
||||
|
||||
return_set = ast_json_array_create();
|
||||
if (!return_set) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Note that we can't use the sorcery JSON change set directly,
|
||||
* as it will hand us back an Object (with fields), and we need
|
||||
* a more generic representation of whatever the API call asked
|
||||
* for, i.e., a list of tuples.
|
||||
*/
|
||||
change_set = ast_sorcery_objectset_create(sorcery, sorcery_obj);
|
||||
if (!change_set) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
return;
|
||||
}
|
||||
|
||||
for (it_change_set = change_set; it_change_set; it_change_set = it_change_set->next) {
|
||||
struct ast_json *tuple;
|
||||
|
||||
tuple = ast_json_pack("{s: s, s: s}",
|
||||
"attribute", it_change_set->name,
|
||||
"value", it_change_set->value);
|
||||
if (!tuple) {
|
||||
ast_variables_destroy(change_set);
|
||||
ast_ari_response_alloc_failed(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ast_json_array_append(return_set, tuple)) {
|
||||
ast_json_unref(tuple);
|
||||
ast_variables_destroy(change_set);
|
||||
ast_ari_response_alloc_failed(response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
ast_variables_destroy(change_set);
|
||||
|
||||
ast_ari_response_ok(response, ast_json_ref(return_set));
|
||||
}
|
||||
|
||||
void ast_ari_asterisk_get_object(struct ast_variable *headers,
|
||||
struct ast_ari_asterisk_get_object_args *args,
|
||||
struct ast_ari_response *response)
|
||||
{
|
||||
RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref);
|
||||
RAII_VAR(struct ast_sorcery_object_type *, object_type, NULL, ao2_cleanup);
|
||||
RAII_VAR(void *, sorcery_obj, NULL, ao2_cleanup);
|
||||
|
||||
|
||||
sorcery = ast_sorcery_retrieve_by_module_name(args->config_class);
|
||||
if (!sorcery) {
|
||||
ast_ari_response_error(
|
||||
response, 404, "Not Found",
|
||||
"configClass '%s' not found",
|
||||
args->config_class);
|
||||
return;
|
||||
}
|
||||
|
||||
object_type = ast_sorcery_get_object_type(sorcery, args->object_type);
|
||||
if (!object_type) {
|
||||
ast_ari_response_error(
|
||||
response, 404, "Not Found",
|
||||
"objectType '%s' not found",
|
||||
args->object_type);
|
||||
return;
|
||||
}
|
||||
|
||||
sorcery_obj = ast_sorcery_retrieve_by_id(sorcery, args->object_type, args->id);
|
||||
if (!sorcery_obj) {
|
||||
ast_ari_response_error(
|
||||
response, 404, "Not Found",
|
||||
"Object with id '%s' not found",
|
||||
args->id);
|
||||
return;
|
||||
}
|
||||
|
||||
return_sorcery_object(sorcery, sorcery_obj, response);
|
||||
}
|
||||
|
||||
void ast_ari_asterisk_update_object(struct ast_variable *headers, struct ast_ari_asterisk_update_object_args *args, struct ast_ari_response *response)
|
||||
{
|
||||
RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref);
|
||||
RAII_VAR(struct ast_sorcery_object_type *, object_type, NULL, ao2_cleanup);
|
||||
RAII_VAR(void *, sorcery_obj, NULL, ao2_cleanup);
|
||||
struct ast_json *fields;
|
||||
struct ast_variable *update_set = NULL;
|
||||
int created = 0;
|
||||
|
||||
sorcery = ast_sorcery_retrieve_by_module_name(args->config_class);
|
||||
if (!sorcery) {
|
||||
ast_ari_response_error(
|
||||
response, 404, "Not Found",
|
||||
"configClass '%s' not found",
|
||||
args->config_class);
|
||||
return;
|
||||
}
|
||||
|
||||
object_type = ast_sorcery_get_object_type(sorcery, args->object_type);
|
||||
if (!object_type) {
|
||||
ast_ari_response_error(
|
||||
response, 404, "Not Found",
|
||||
"objectType '%s' not found",
|
||||
args->object_type);
|
||||
return;
|
||||
}
|
||||
|
||||
sorcery_obj = ast_sorcery_retrieve_by_id(sorcery, args->object_type, args->id);
|
||||
if (!sorcery_obj) {
|
||||
ast_debug(5, "Sorcery object '%s' does not exist; creating it\n", args->id);
|
||||
sorcery_obj = ast_sorcery_alloc(sorcery, args->object_type, args->id);
|
||||
if (!sorcery_obj) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
return;
|
||||
}
|
||||
|
||||
created = 1;
|
||||
} else {
|
||||
void *copy;
|
||||
|
||||
copy = ast_sorcery_copy(sorcery, sorcery_obj);
|
||||
if (!copy) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
return;
|
||||
}
|
||||
|
||||
ao2_ref(sorcery_obj, -1);
|
||||
sorcery_obj = copy;
|
||||
}
|
||||
|
||||
fields = ast_json_object_get(args->fields, "fields");
|
||||
if (!fields && !created) {
|
||||
/* Whoops. We need data. */
|
||||
ast_ari_response_error(
|
||||
response, 400, "Bad request",
|
||||
"Fields must be provided to update object '%s'",
|
||||
args->id);
|
||||
return;
|
||||
} else if (fields) {
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < ast_json_array_size(fields); i++) {
|
||||
struct ast_variable *new_var;
|
||||
struct ast_json *json_value = ast_json_array_get(fields, i);
|
||||
|
||||
if (!json_value) {
|
||||
continue;
|
||||
}
|
||||
|
||||
new_var = ast_variable_new(
|
||||
ast_json_string_get(ast_json_object_get(json_value, "attribute")),
|
||||
ast_json_string_get(ast_json_object_get(json_value, "value")),
|
||||
"");
|
||||
if (!new_var) {
|
||||
ast_variables_destroy(update_set);
|
||||
ast_ari_response_alloc_failed(response);
|
||||
return;
|
||||
}
|
||||
ast_variable_list_append(&update_set, new_var);
|
||||
}
|
||||
}
|
||||
|
||||
/* APPLY! Note that a NULL update_set is fine (and necessary), as it
|
||||
* will force validation on a newly created object.
|
||||
*/
|
||||
if (ast_sorcery_objectset_apply(sorcery, sorcery_obj, update_set)) {
|
||||
ast_variables_destroy(update_set);
|
||||
ast_ari_response_error(
|
||||
response, 400, "Bad request",
|
||||
"%s of object '%s' failed field value validation",
|
||||
created ? "Creation" : "Update",
|
||||
args->id);
|
||||
return;
|
||||
}
|
||||
|
||||
ast_variables_destroy(update_set);
|
||||
|
||||
if (created) {
|
||||
if (ast_sorcery_create(sorcery, sorcery_obj)) {
|
||||
ast_ari_response_error(
|
||||
response, 403, "Forbidden",
|
||||
"Cannot create sorcery objects of type '%s'",
|
||||
args->object_type);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (ast_sorcery_update(sorcery, sorcery_obj)) {
|
||||
ast_ari_response_error(
|
||||
response, 403, "Forbidden",
|
||||
"Cannot update sorcery objects of type '%s'",
|
||||
args->object_type);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return_sorcery_object(sorcery, sorcery_obj, response);
|
||||
}
|
||||
|
||||
|
||||
void ast_ari_asterisk_delete_object(struct ast_variable *headers,
|
||||
struct ast_ari_asterisk_delete_object_args *args,
|
||||
struct ast_ari_response *response)
|
||||
{
|
||||
RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref);
|
||||
RAII_VAR(struct ast_sorcery_object_type *, object_type, NULL, ao2_cleanup);
|
||||
RAII_VAR(void *, sorcery_obj, NULL, ao2_cleanup);
|
||||
|
||||
sorcery = ast_sorcery_retrieve_by_module_name(args->config_class);
|
||||
if (!sorcery) {
|
||||
ast_ari_response_error(
|
||||
response, 404, "Not Found",
|
||||
"configClass '%s' not found",
|
||||
args->config_class);
|
||||
return;
|
||||
}
|
||||
|
||||
object_type = ast_sorcery_get_object_type(sorcery, args->object_type);
|
||||
if (!object_type) {
|
||||
ast_ari_response_error(
|
||||
response, 404, "Not Found",
|
||||
"objectType '%s' not found",
|
||||
args->object_type);
|
||||
return;
|
||||
}
|
||||
|
||||
sorcery_obj = ast_sorcery_retrieve_by_id(sorcery, args->object_type, args->id);
|
||||
if (!sorcery_obj) {
|
||||
ast_ari_response_error(
|
||||
response, 404, "Not Found",
|
||||
"Object with id '%s' not found",
|
||||
args->id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ast_sorcery_delete(sorcery, sorcery_obj)) {
|
||||
ast_ari_response_error(
|
||||
response, 403, "Forbidden",
|
||||
"Could not delete object with id '%s'",
|
||||
args->id);
|
||||
return;
|
||||
}
|
||||
|
||||
ast_ari_response_no_content(response);
|
||||
}
|
||||
|
||||
|
||||
void ast_ari_asterisk_get_info(struct ast_variable *headers,
|
||||
struct ast_ari_asterisk_get_info_args *args,
|
||||
struct ast_ari_response *response)
|
||||
|
|
|
@ -39,6 +39,70 @@
|
|||
|
||||
#include "asterisk/ari.h"
|
||||
|
||||
/*! Argument struct for ast_ari_asterisk_get_object() */
|
||||
struct ast_ari_asterisk_get_object_args {
|
||||
/*! The configuration class containing dynamic configuration objects. */
|
||||
const char *config_class;
|
||||
/*! The type of configuration object to retrieve. */
|
||||
const char *object_type;
|
||||
/*! The unique identifier of the object to retrieve. */
|
||||
const char *id;
|
||||
};
|
||||
/*!
|
||||
* \brief Retrieve a dynamic configuration object.
|
||||
*
|
||||
* \param headers HTTP headers
|
||||
* \param args Swagger parameters
|
||||
* \param[out] response HTTP response
|
||||
*/
|
||||
void ast_ari_asterisk_get_object(struct ast_variable *headers, struct ast_ari_asterisk_get_object_args *args, struct ast_ari_response *response);
|
||||
/*! Argument struct for ast_ari_asterisk_update_object() */
|
||||
struct ast_ari_asterisk_update_object_args {
|
||||
/*! The configuration class containing dynamic configuration objects. */
|
||||
const char *config_class;
|
||||
/*! The type of configuration object to create or update. */
|
||||
const char *object_type;
|
||||
/*! The unique identifier of the object to create or update. */
|
||||
const char *id;
|
||||
/*! The body object should have a value that is a list of ConfigTuples, which provide the fields to update. Ex. [ { "attribute": "directmedia", "value": "false" } ] */
|
||||
struct ast_json *fields;
|
||||
};
|
||||
/*!
|
||||
* \brief Body parsing function for /asterisk/config/dynamic/{configClass}/{objectType}/{id}.
|
||||
* \param body The JSON body from which to parse parameters.
|
||||
* \param[out] args The args structure to parse into.
|
||||
* \retval zero on success
|
||||
* \retval non-zero on failure
|
||||
*/
|
||||
int ast_ari_asterisk_update_object_parse_body(
|
||||
struct ast_json *body,
|
||||
struct ast_ari_asterisk_update_object_args *args);
|
||||
|
||||
/*!
|
||||
* \brief Create or update a dynamic configuration object.
|
||||
*
|
||||
* \param headers HTTP headers
|
||||
* \param args Swagger parameters
|
||||
* \param[out] response HTTP response
|
||||
*/
|
||||
void ast_ari_asterisk_update_object(struct ast_variable *headers, struct ast_ari_asterisk_update_object_args *args, struct ast_ari_response *response);
|
||||
/*! Argument struct for ast_ari_asterisk_delete_object() */
|
||||
struct ast_ari_asterisk_delete_object_args {
|
||||
/*! The configuration class containing dynamic configuration objects. */
|
||||
const char *config_class;
|
||||
/*! The type of configuration object to delete. */
|
||||
const char *object_type;
|
||||
/*! The unique identifier of the object to delete. */
|
||||
const char *id;
|
||||
};
|
||||
/*!
|
||||
* \brief Delete a dynamic configuration object.
|
||||
*
|
||||
* \param headers HTTP headers
|
||||
* \param args Swagger parameters
|
||||
* \param[out] response HTTP response
|
||||
*/
|
||||
void ast_ari_asterisk_delete_object(struct ast_variable *headers, struct ast_ari_asterisk_delete_object_args *args, struct ast_ari_response *response);
|
||||
/*! Argument struct for ast_ari_asterisk_get_info() */
|
||||
struct ast_ari_asterisk_get_info_args {
|
||||
/*! Array of Filter information returned */
|
||||
|
|
|
@ -52,6 +52,228 @@ ASTERISK_REGISTER_FILE()
|
|||
|
||||
#define MAX_VALS 128
|
||||
|
||||
/*!
|
||||
* \brief Parameter parsing callback for /asterisk/config/dynamic/{configClass}/{objectType}/{id}.
|
||||
* \param get_params GET parameters in the HTTP request.
|
||||
* \param path_vars Path variables extracted from the request.
|
||||
* \param headers HTTP headers.
|
||||
* \param[out] response Response to the HTTP request.
|
||||
*/
|
||||
static void ast_ari_asterisk_get_object_cb(
|
||||
struct ast_tcptls_session_instance *ser,
|
||||
struct ast_variable *get_params, struct ast_variable *path_vars,
|
||||
struct ast_variable *headers, struct ast_ari_response *response)
|
||||
{
|
||||
struct ast_ari_asterisk_get_object_args args = {};
|
||||
struct ast_variable *i;
|
||||
RAII_VAR(struct ast_json *, body, NULL, ast_json_unref);
|
||||
#if defined(AST_DEVMODE)
|
||||
int is_valid;
|
||||
int code;
|
||||
#endif /* AST_DEVMODE */
|
||||
|
||||
for (i = path_vars; i; i = i->next) {
|
||||
if (strcmp(i->name, "configClass") == 0) {
|
||||
args.config_class = (i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "objectType") == 0) {
|
||||
args.object_type = (i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "id") == 0) {
|
||||
args.id = (i->value);
|
||||
} else
|
||||
{}
|
||||
}
|
||||
ast_ari_asterisk_get_object(headers, &args, response);
|
||||
#if defined(AST_DEVMODE)
|
||||
code = response->response_code;
|
||||
|
||||
switch (code) {
|
||||
case 0: /* Implementation is still a stub, or the code wasn't set */
|
||||
is_valid = response->message == NULL;
|
||||
break;
|
||||
case 500: /* Internal Server Error */
|
||||
case 501: /* Not Implemented */
|
||||
case 404: /* {configClass|objectType|id} not found */
|
||||
is_valid = 1;
|
||||
break;
|
||||
default:
|
||||
if (200 <= code && code <= 299) {
|
||||
is_valid = ast_ari_validate_list(response->message,
|
||||
ast_ari_validate_config_tuple_fn());
|
||||
} else {
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /asterisk/config/dynamic/{configClass}/{objectType}/{id}\n", code);
|
||||
is_valid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_valid) {
|
||||
ast_log(LOG_ERROR, "Response validation failed for /asterisk/config/dynamic/{configClass}/{objectType}/{id}\n");
|
||||
ast_ari_response_error(response, 500,
|
||||
"Internal Server Error", "Response validation failed");
|
||||
}
|
||||
#endif /* AST_DEVMODE */
|
||||
|
||||
fin: __attribute__((unused))
|
||||
return;
|
||||
}
|
||||
int ast_ari_asterisk_update_object_parse_body(
|
||||
struct ast_json *body,
|
||||
struct ast_ari_asterisk_update_object_args *args)
|
||||
{
|
||||
/* Parse query parameters out of it */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Parameter parsing callback for /asterisk/config/dynamic/{configClass}/{objectType}/{id}.
|
||||
* \param get_params GET parameters in the HTTP request.
|
||||
* \param path_vars Path variables extracted from the request.
|
||||
* \param headers HTTP headers.
|
||||
* \param[out] response Response to the HTTP request.
|
||||
*/
|
||||
static void ast_ari_asterisk_update_object_cb(
|
||||
struct ast_tcptls_session_instance *ser,
|
||||
struct ast_variable *get_params, struct ast_variable *path_vars,
|
||||
struct ast_variable *headers, struct ast_ari_response *response)
|
||||
{
|
||||
struct ast_ari_asterisk_update_object_args args = {};
|
||||
struct ast_variable *i;
|
||||
RAII_VAR(struct ast_json *, body, NULL, ast_json_unref);
|
||||
#if defined(AST_DEVMODE)
|
||||
int is_valid;
|
||||
int code;
|
||||
#endif /* AST_DEVMODE */
|
||||
|
||||
for (i = path_vars; i; i = i->next) {
|
||||
if (strcmp(i->name, "configClass") == 0) {
|
||||
args.config_class = (i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "objectType") == 0) {
|
||||
args.object_type = (i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "id") == 0) {
|
||||
args.id = (i->value);
|
||||
} else
|
||||
{}
|
||||
}
|
||||
/* Look for a JSON request entity */
|
||||
body = ast_http_get_json(ser, headers);
|
||||
if (!body) {
|
||||
switch (errno) {
|
||||
case EFBIG:
|
||||
ast_ari_response_error(response, 413, "Request Entity Too Large", "Request body too large");
|
||||
goto fin;
|
||||
case ENOMEM:
|
||||
ast_ari_response_error(response, 500, "Internal Server Error", "Error processing request");
|
||||
goto fin;
|
||||
case EIO:
|
||||
ast_ari_response_error(response, 400, "Bad Request", "Error parsing request body");
|
||||
goto fin;
|
||||
}
|
||||
}
|
||||
args.fields = body;
|
||||
ast_ari_asterisk_update_object(headers, &args, response);
|
||||
#if defined(AST_DEVMODE)
|
||||
code = response->response_code;
|
||||
|
||||
switch (code) {
|
||||
case 0: /* Implementation is still a stub, or the code wasn't set */
|
||||
is_valid = response->message == NULL;
|
||||
break;
|
||||
case 500: /* Internal Server Error */
|
||||
case 501: /* Not Implemented */
|
||||
case 400: /* Bad request body */
|
||||
case 403: /* Could not create or update object */
|
||||
case 404: /* {configClass|objectType} not found */
|
||||
is_valid = 1;
|
||||
break;
|
||||
default:
|
||||
if (200 <= code && code <= 299) {
|
||||
is_valid = ast_ari_validate_list(response->message,
|
||||
ast_ari_validate_config_tuple_fn());
|
||||
} else {
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /asterisk/config/dynamic/{configClass}/{objectType}/{id}\n", code);
|
||||
is_valid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_valid) {
|
||||
ast_log(LOG_ERROR, "Response validation failed for /asterisk/config/dynamic/{configClass}/{objectType}/{id}\n");
|
||||
ast_ari_response_error(response, 500,
|
||||
"Internal Server Error", "Response validation failed");
|
||||
}
|
||||
#endif /* AST_DEVMODE */
|
||||
|
||||
fin: __attribute__((unused))
|
||||
return;
|
||||
}
|
||||
/*!
|
||||
* \brief Parameter parsing callback for /asterisk/config/dynamic/{configClass}/{objectType}/{id}.
|
||||
* \param get_params GET parameters in the HTTP request.
|
||||
* \param path_vars Path variables extracted from the request.
|
||||
* \param headers HTTP headers.
|
||||
* \param[out] response Response to the HTTP request.
|
||||
*/
|
||||
static void ast_ari_asterisk_delete_object_cb(
|
||||
struct ast_tcptls_session_instance *ser,
|
||||
struct ast_variable *get_params, struct ast_variable *path_vars,
|
||||
struct ast_variable *headers, struct ast_ari_response *response)
|
||||
{
|
||||
struct ast_ari_asterisk_delete_object_args args = {};
|
||||
struct ast_variable *i;
|
||||
RAII_VAR(struct ast_json *, body, NULL, ast_json_unref);
|
||||
#if defined(AST_DEVMODE)
|
||||
int is_valid;
|
||||
int code;
|
||||
#endif /* AST_DEVMODE */
|
||||
|
||||
for (i = path_vars; i; i = i->next) {
|
||||
if (strcmp(i->name, "configClass") == 0) {
|
||||
args.config_class = (i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "objectType") == 0) {
|
||||
args.object_type = (i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "id") == 0) {
|
||||
args.id = (i->value);
|
||||
} else
|
||||
{}
|
||||
}
|
||||
ast_ari_asterisk_delete_object(headers, &args, response);
|
||||
#if defined(AST_DEVMODE)
|
||||
code = response->response_code;
|
||||
|
||||
switch (code) {
|
||||
case 0: /* Implementation is still a stub, or the code wasn't set */
|
||||
is_valid = response->message == NULL;
|
||||
break;
|
||||
case 500: /* Internal Server Error */
|
||||
case 501: /* Not Implemented */
|
||||
case 403: /* Could not delete object */
|
||||
case 404: /* {configClass|objectType|id} not found */
|
||||
is_valid = 1;
|
||||
break;
|
||||
default:
|
||||
if (200 <= code && code <= 299) {
|
||||
is_valid = ast_ari_validate_void(
|
||||
response->message);
|
||||
} else {
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /asterisk/config/dynamic/{configClass}/{objectType}/{id}\n", code);
|
||||
is_valid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_valid) {
|
||||
ast_log(LOG_ERROR, "Response validation failed for /asterisk/config/dynamic/{configClass}/{objectType}/{id}\n");
|
||||
ast_ari_response_error(response, 500,
|
||||
"Internal Server Error", "Response validation failed");
|
||||
}
|
||||
#endif /* AST_DEVMODE */
|
||||
|
||||
fin: __attribute__((unused))
|
||||
return;
|
||||
}
|
||||
int ast_ari_asterisk_get_info_parse_body(
|
||||
struct ast_json *body,
|
||||
struct ast_ari_asterisk_get_info_args *args)
|
||||
|
@ -689,6 +911,52 @@ fin: __attribute__((unused))
|
|||
return;
|
||||
}
|
||||
|
||||
/*! \brief REST handler for /api-docs/asterisk.{format} */
|
||||
static struct stasis_rest_handlers asterisk_config_dynamic_configClass_objectType_id = {
|
||||
.path_segment = "id",
|
||||
.is_wildcard = 1,
|
||||
.callbacks = {
|
||||
[AST_HTTP_GET] = ast_ari_asterisk_get_object_cb,
|
||||
[AST_HTTP_PUT] = ast_ari_asterisk_update_object_cb,
|
||||
[AST_HTTP_DELETE] = ast_ari_asterisk_delete_object_cb,
|
||||
},
|
||||
.num_children = 0,
|
||||
.children = { }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/asterisk.{format} */
|
||||
static struct stasis_rest_handlers asterisk_config_dynamic_configClass_objectType = {
|
||||
.path_segment = "objectType",
|
||||
.is_wildcard = 1,
|
||||
.callbacks = {
|
||||
},
|
||||
.num_children = 1,
|
||||
.children = { &asterisk_config_dynamic_configClass_objectType_id, }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/asterisk.{format} */
|
||||
static struct stasis_rest_handlers asterisk_config_dynamic_configClass = {
|
||||
.path_segment = "configClass",
|
||||
.is_wildcard = 1,
|
||||
.callbacks = {
|
||||
},
|
||||
.num_children = 1,
|
||||
.children = { &asterisk_config_dynamic_configClass_objectType, }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/asterisk.{format} */
|
||||
static struct stasis_rest_handlers asterisk_config_dynamic = {
|
||||
.path_segment = "dynamic",
|
||||
.callbacks = {
|
||||
},
|
||||
.num_children = 1,
|
||||
.children = { &asterisk_config_dynamic_configClass, }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/asterisk.{format} */
|
||||
static struct stasis_rest_handlers asterisk_config = {
|
||||
.path_segment = "config",
|
||||
.callbacks = {
|
||||
},
|
||||
.num_children = 1,
|
||||
.children = { &asterisk_config_dynamic, }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/asterisk.{format} */
|
||||
static struct stasis_rest_handlers asterisk_info = {
|
||||
.path_segment = "info",
|
||||
|
@ -735,8 +1003,8 @@ static struct stasis_rest_handlers asterisk = {
|
|||
.path_segment = "asterisk",
|
||||
.callbacks = {
|
||||
},
|
||||
.num_children = 3,
|
||||
.children = { &asterisk_info,&asterisk_modules,&asterisk_variable, }
|
||||
.num_children = 4,
|
||||
.children = { &asterisk_config,&asterisk_info,&asterisk_modules,&asterisk_variable, }
|
||||
};
|
||||
|
||||
static int load_module(void)
|
||||
|
|
|
@ -7,6 +7,146 @@
|
|||
"basePath": "http://localhost:8088/ari",
|
||||
"resourcePath": "/api-docs/asterisk.{format}",
|
||||
"apis": [
|
||||
{
|
||||
"path": "/asterisk/config/dynamic/{configClass}/{objectType}/{id}",
|
||||
"description": "Asterisk dynamic configuration",
|
||||
"operations": [
|
||||
{
|
||||
"httpMethod": "GET",
|
||||
"summary": "Retrieve a dynamic configuration object.",
|
||||
"nickname": "getObject",
|
||||
"responseClass": "List[ConfigTuple]",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "configClass",
|
||||
"description": "The configuration class containing dynamic configuration objects.",
|
||||
"paramType": "path",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string"
|
||||
},
|
||||
{
|
||||
"name": "objectType",
|
||||
"description": "The type of configuration object to retrieve.",
|
||||
"paramType": "path",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string"
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"description": "The unique identifier of the object to retrieve.",
|
||||
"paramType": "path",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string"
|
||||
}
|
||||
],
|
||||
"errorResponses": [
|
||||
{
|
||||
"code": 404,
|
||||
"reason": "{configClass|objectType|id} not found"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"httpMethod": "PUT",
|
||||
"summary": "Create or update a dynamic configuration object.",
|
||||
"nickname": "updateObject",
|
||||
"responseClass": "List[ConfigTuple]",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "configClass",
|
||||
"description": "The configuration class containing dynamic configuration objects.",
|
||||
"paramType": "path",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string"
|
||||
},
|
||||
{
|
||||
"name": "objectType",
|
||||
"description": "The type of configuration object to create or update.",
|
||||
"paramType": "path",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string"
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"description": "The unique identifier of the object to create or update.",
|
||||
"paramType": "path",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string"
|
||||
},
|
||||
{
|
||||
"name": "fields",
|
||||
"description": "The body object should have a value that is a list of ConfigTuples, which provide the fields to update. Ex. [ { \"attribute\": \"directmedia\", \"value\": \"false\" } ]",
|
||||
"paramType": "body",
|
||||
"required": false,
|
||||
"dataType": "containers",
|
||||
"allowMultiple": false
|
||||
}
|
||||
],
|
||||
"errorResponses": [
|
||||
{
|
||||
"code": 400,
|
||||
"reason": "Bad request body"
|
||||
},
|
||||
{
|
||||
"code": 403,
|
||||
"reason": "Could not create or update object"
|
||||
},
|
||||
{
|
||||
"code": 404,
|
||||
"reason": "{configClass|objectType} not found"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"httpMethod": "DELETE",
|
||||
"summary": "Delete a dynamic configuration object.",
|
||||
"nickname": "deleteObject",
|
||||
"responseClass": "void",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "configClass",
|
||||
"description": "The configuration class containing dynamic configuration objects.",
|
||||
"paramType": "path",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string"
|
||||
},
|
||||
{
|
||||
"name": "objectType",
|
||||
"description": "The type of configuration object to delete.",
|
||||
"paramType": "path",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string"
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"description": "The unique identifier of the object to delete.",
|
||||
"paramType": "path",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string"
|
||||
}
|
||||
],
|
||||
"errorResponses": [
|
||||
{
|
||||
"code": 403,
|
||||
"reason": "Could not delete object"
|
||||
},
|
||||
{
|
||||
"code": 404,
|
||||
"reason": "{configClass|objectType|id} not found"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/asterisk/info",
|
||||
"description": "Asterisk system information (similar to core show settings)",
|
||||
|
@ -403,6 +543,22 @@
|
|||
"description": "The value of the variable requested"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ConfigTuple": {
|
||||
"id": "ConfigTuple",
|
||||
"description": "A key/value pair that makes up part of a configuration object.",
|
||||
"properties": {
|
||||
"attribute": {
|
||||
"required": true,
|
||||
"type": "string",
|
||||
"description": "A configuration object attribute."
|
||||
},
|
||||
"value": {
|
||||
"required": true,
|
||||
"type": "string",
|
||||
"description": "The value for the attribute."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue