summaryrefslogtreecommitdiff
path: root/res/ari
diff options
context:
space:
mode:
authorMatt Jordan <mjordan@digium.com>2015-07-08 16:39:35 -0500
committerMatt Jordan <mjordan@digium.com>2015-07-16 20:37:58 -0500
commit8bcf6d2801d1b1ed7073ab560bdbe3d0047b1b2c (patch)
tree360290ba1f6389bf5e609f7754f1951aec77da5b /res/ari
parent00d858da87c77954778c6ea5dc559029d21e4bf5 (diff)
ARI: Add support for push configuration of dynamic object
This patch adds support for push configuration of dynamic, i.e., sorcery, objects in Asterisk. It adds three new REST API calls to the 'asterisk' resource: * GET /asterisk/{configClass}/{objectType}/{id}: retrieve the current object given its ID. This returns back a list of ConfigTuples, which define the fields and their present values that make up the object. * PUT /asterisk/{configClass}/{objectType}/{id}: create or update an object. A body may be passed with the request that contains fields to populate in the object. The same format as what is retrieved using the GET operation is used for the body, save that we specify that the list of fields to update are contained in the "fields" attribute. * DELETE /asterisk/{configClass}/{objectType}/{id}: remove a dynamic object from its backing storage. Note that the success/failure of these operations is somewhat configuration dependent, i.e., you must be using a sorcery wizard that supports the operation in question. If a sorcery wizard does not support the create or delete mechanisms, then the REST API call will fail with a 403 forbidden. ASTERISK-25238 #close Change-Id: I28cd5c7bf6f67f8e9e437ff097f8fd171d30ff5c
Diffstat (limited to 'res/ari')
-rw-r--r--res/ari/ari_model_validators.c54
-rw-r--r--res/ari/ari_model_validators.h21
-rw-r--r--res/ari/resource_asterisk.c254
-rw-r--r--res/ari/resource_asterisk.h64
4 files changed, 393 insertions, 0 deletions
diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c
index 2f54b8d17..667589601 100644
--- a/res/ari/ari_model_validators.c
+++ b/res/ari/ari_model_validators.c
@@ -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;
diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h
index 41b91791d..e122ded34 100644
--- a/res/ari/ari_model_validators.h
+++ b/res/ari/ari_model_validators.h
@@ -207,6 +207,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.
*
* Details of an Asterisk 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)
diff --git a/res/ari/resource_asterisk.c b/res/ari/resource_asterisk.c
index 1b9bf2120..4e6868bd2 100644
--- a/res/ari/resource_asterisk.c
+++ b/res/ari/resource_asterisk.c
@@ -36,8 +36,262 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#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)
diff --git a/res/ari/resource_asterisk.h b/res/ari/resource_asterisk.h
index 574d947e4..1afc09317 100644
--- a/res/ari/resource_asterisk.h
+++ b/res/ari/resource_asterisk.h
@@ -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 */