Beispiel #1
0
class ActionsController(resource.ContentPackResourceController):
    """
        Implements the RESTful web endpoint that handles
        the lifecycle of Actions in the system.
    """
    views = ActionViewsController()

    model = ActionAPI
    access = Action
    supported_filters = {'name': 'name', 'pack': 'pack'}

    query_options = {'sort': ['pack', 'name']}

    include_reference = True

    def __init__(self, *args, **kwargs):
        super(ActionsController, self).__init__(*args, **kwargs)
        self._trigger_dispatcher = TriggerDispatcher(LOG)

    @request_user_has_permission(permission_type=PermissionType.ACTION_LIST)
    @jsexpose()
    def get_all(self, **kwargs):
        return super(ActionsController, self)._get_all(**kwargs)

    @request_user_has_resource_db_permission(
        permission_type=PermissionType.ACTION_VIEW)
    @jsexpose(arg_types=[str])
    def get_one(self, ref_or_id):
        return super(ActionsController, self)._get_one(ref_or_id)

    @jsexpose(body_cls=ActionCreateAPI, status_code=http_client.CREATED)
    @request_user_has_resource_api_permission(
        permission_type=PermissionType.ACTION_CREATE)
    def post(self, action):
        """
            Create a new action.

            Handles requests:
                POST /actions/
        """

        try:
            # Perform validation
            validate_not_part_of_system_pack(action)
            action_validator.validate_action(action)
        except (ValidationError, ValueError, ValueValidationException,
                InvalidActionParameterException) as e:
            LOG.exception('Unable to create action data=%s', action)
            abort(http_client.BAD_REQUEST, str(e))
            return

        # Write pack data files to disk (if any are provided)
        data_files = getattr(action, 'data_files', [])
        written_data_files = []
        if data_files:
            written_data_files = self._handle_data_files(pack_name=action.pack,
                                                         data_files=data_files)

        action_model = ActionAPI.to_model(action)

        LOG.debug('/actions/ POST verified ActionAPI object=%s', action)
        action_db = Action.add_or_update(action_model)
        LOG.debug('/actions/ POST saved ActionDB object=%s', action_db)

        # Dispatch an internal trigger for each written data file. This way user
        # automate comitting this files to git using StackStorm rule
        if written_data_files:
            self._dispatch_trigger_for_written_data_files(
                action_db=action_db, written_data_files=written_data_files)

        extra = {'acion_db': action_db}
        LOG.audit('Action created. Action.id=%s' % (action_db.id), extra=extra)
        action_api = ActionAPI.from_model(action_db)

        return action_api

    @request_user_has_resource_db_permission(
        permission_type=PermissionType.ACTION_MODIFY)
    @jsexpose(arg_types=[str], body_cls=ActionCreateAPI)
    def put(self, action_ref_or_id, action):
        action_db = self._get_by_ref_or_id(ref_or_id=action_ref_or_id)

        # Assert permissions
        action_id = action_db.id

        if not getattr(action, 'pack', None):
            action.pack = action_db.pack

        # Perform validation
        validate_not_part_of_system_pack(action)
        action_validator.validate_action(action)

        # Write pack data files to disk (if any are provided)
        data_files = getattr(action, 'data_files', [])
        written_data_files = []
        if data_files:
            written_data_files = self._handle_data_files(pack_name=action.pack,
                                                         data_files=data_files)

        try:
            action_db = ActionAPI.to_model(action)
            LOG.debug('/actions/ PUT incoming action: %s', action_db)
            action_db.id = action_id
            action_db = Action.add_or_update(action_db)
            LOG.debug('/actions/ PUT after add_or_update: %s', action_db)
        except (ValidationError, ValueError) as e:
            LOG.exception('Unable to update action data=%s', action)
            abort(http_client.BAD_REQUEST, str(e))
            return

        # Dispatch an internal trigger for each written data file. This way user
        # automate comitting this files to git using StackStorm rule
        if written_data_files:
            self._dispatch_trigger_for_written_data_files(
                action_db=action_db, written_data_files=written_data_files)

        action_api = ActionAPI.from_model(action_db)
        LOG.debug('PUT /actions/ client_result=%s', action_api)

        return action_api

    @request_user_has_resource_db_permission(
        permission_type=PermissionType.ACTION_DELETE)
    @jsexpose(arg_types=[str], status_code=http_client.NO_CONTENT)
    def delete(self, action_ref_or_id):
        """
            Delete an action.

            Handles requests:
                POST /actions/1?_method=delete
                DELETE /actions/1
                DELETE /actions/mypack.myaction
        """
        action_db = self._get_by_ref_or_id(ref_or_id=action_ref_or_id)
        action_id = action_db.id

        try:
            validate_not_part_of_system_pack(action_db)
        except ValueValidationException as e:
            abort(http_client.BAD_REQUEST, str(e))

        LOG.debug('DELETE /actions/ lookup with ref_or_id=%s found object: %s',
                  action_ref_or_id, action_db)

        try:
            Action.delete(action_db)
        except Exception as e:
            LOG.error(
                'Database delete encountered exception during delete of id="%s". '
                'Exception was %s', action_id, e)
            abort(http_client.INTERNAL_SERVER_ERROR, str(e))
            return

        extra = {'action_db': action_db}
        LOG.audit('Action deleted. Action.id=%s' % (action_db.id), extra=extra)
        return None

    def _handle_data_files(self, pack_name, data_files):
        """
        Method for handling action data files.

        This method performs two tasks:

        1. Writes files to disk
        2. Updates affected PackDB model
        """
        # Write files to disk
        written_file_paths = self._write_data_files_to_disk(
            pack_name=pack_name, data_files=data_files)

        # Update affected PackDB model (update a list of files)
        # Update PackDB
        self._update_pack_model(pack_name=pack_name,
                                data_files=data_files,
                                written_file_paths=written_file_paths)

        return written_file_paths

    def _write_data_files_to_disk(self, pack_name, data_files):
        """
        Write files to disk.
        """
        written_file_paths = []

        for data_file in data_files:
            file_path = data_file['file_path']
            content = data_file['content']

            file_path = get_pack_resource_file_abs_path(pack_name=pack_name,
                                                        resource_type='action',
                                                        file_path=file_path)

            LOG.debug('Writing data file "%s" to "%s"' %
                      (str(data_file), file_path))
            self._write_data_file(pack_name=pack_name,
                                  file_path=file_path,
                                  content=content)
            written_file_paths.append(file_path)

        return written_file_paths

    def _update_pack_model(self, pack_name, data_files, written_file_paths):
        """
        Update PackDB models (update files list).
        """
        file_paths = [
        ]  # A list of paths relative to the pack directory for new files
        for file_path in written_file_paths:
            file_path = get_relative_path_to_pack(pack_name=pack_name,
                                                  file_path=file_path)
            file_paths.append(file_path)

        pack_db = Pack.get_by_ref(pack_name)
        pack_db.files = set(pack_db.files)
        pack_db.files.update(set(file_paths))
        pack_db.files = list(pack_db.files)
        pack_db = Pack.add_or_update(pack_db)

        return pack_db

    def _write_data_file(self, pack_name, file_path, content):
        """
        Write data file on disk.
        """
        # Throw if pack directory doesn't exist
        pack_base_path = get_pack_base_path(pack_name=pack_name)
        if not os.path.isdir(pack_base_path):
            raise ValueError('Directory for pack "%s" doesn\'t exist' %
                             (pack_name))

        # Create pack sub-directory tree if it doesn't exist
        directory = os.path.dirname(file_path)

        if not os.path.isdir(directory):
            os.makedirs(directory)

        with open(file_path, 'w') as fp:
            fp.write(content)

    def _dispatch_trigger_for_written_data_files(self, action_db,
                                                 written_data_files):
        trigger = ACTION_FILE_WRITTEN_TRIGGER['name']
        host_info = get_host_info()

        for file_path in written_data_files:
            payload = {
                'ref': action_db.ref,
                'file_path': file_path,
                'host_info': host_info
            }
            self._trigger_dispatcher.dispatch(trigger=trigger, payload=payload)
Beispiel #2
0
class ActionsController(resource.ContentPackResourceController):
    """
        Implements the RESTful web endpoint that handles
        the lifecycle of Actions in the system.
    """
    views = ActionViewsController()

    model = ActionAPI
    access = Action
    supported_filters = {'name': 'name', 'pack': 'pack'}

    query_options = {'sort': ['pack', 'name']}

    include_reference = True

    @staticmethod
    def _validate_action_parameters(action, runnertype_db):
        # check if action parameters conflict with those from the supplied runner_type.
        conflicts = [
            p for p in action.parameters.keys()
            if p in runnertype_db.runner_parameters
        ]
        if len(conflicts) > 0:
            msg = 'Parameters %s conflict with those inherited from runner_type : %s' % \
                  (str(conflicts), action.runner_type)
            LOG.error(msg)
            abort(http_client.CONFLICT, msg)

    @jsexpose(body_cls=ActionAPI, status_code=http_client.CREATED)
    def post(self, action):
        """
            Create a new action.

            Handles requests:
                POST /actions/
        """
        if not hasattr(action, 'enabled'):
            LOG.debug(
                'POST /actions/ incoming action data has enabled field unset. '
                'Defaulting enabled to True.')
            setattr(action, 'enabled', True)
        else:
            action.enabled = bool(action.enabled)

        if not hasattr(action, 'pack'):
            setattr(action, 'pack', DEFAULT_PACK_NAME)

        try:
            action_validator.validate_action(action)
        except ValueValidationException as e:
            abort(http_client.BAD_REQUEST, str(e))
            return

        # ActionsController._validate_action_parameters(action, runnertype_db)
        action_model = ActionAPI.to_model(action)

        LOG.debug('/actions/ POST verified ActionAPI object=%s', action)
        try:
            action_db = Action.add_or_update(action_model)
        except StackStormDBObjectConflictError as e:
            # If an existing DB object conflicts with new object then raise error.
            LOG.warn(
                '/actions/ POST unable to save ActionDB object "%s" due to uniqueness '
                'conflict. %s', action_model, str(e))
            abort(http_client.CONFLICT,
                  str(e),
                  body={'conflict-id': e.conflict_id})
            return
        except Exception as e:
            LOG.exception(
                '/actions/ POST unable to save ActionDB object "%s". %s',
                action_model, e)
            abort(http_client.INTERNAL_SERVER_ERROR, str(e))
            return

        LOG.debug('/actions/ POST saved ActionDB object=%s', action_db)

        extra = {'action_db': action_db}
        LOG.audit('Action created. Action.id=%s' % (action_db.id), extra=extra)
        action_api = ActionAPI.from_model(action_db)

        return action_api

    @jsexpose(arg_types=[str], body_cls=ActionAPI)
    def put(self, action_ref_or_id, action):
        try:
            action_db = self._get_by_ref_or_id(ref_or_id=action_ref_or_id)
        except Exception as e:
            LOG.exception(e.message)
            abort(http_client.NOT_FOUND, e.message)
            return

        action_id = action_db.id

        try:
            validate_not_part_of_system_pack(action_db)
        except ValueValidationException as e:
            abort(http_client.BAD_REQUEST, str(e))

        if not getattr(action, 'pack', None):
            action.pack = action_db.pack

        try:
            action_validator.validate_action(action)
        except ValueValidationException as e:
            abort(http_client.BAD_REQUEST, str(e))
            return

        try:
            action_db = ActionAPI.to_model(action)
            action_db.id = action_id
            action_db = Action.add_or_update(action_db)
        except (ValidationError, ValueError) as e:
            LOG.exception('Unable to update action data=%s', action)
            abort(http_client.BAD_REQUEST, str(e))
            return

        action_api = ActionAPI.from_model(action_db)
        LOG.debug('PUT /actions/ client_result=%s', action_api)

        return action_api

    @jsexpose(arg_types=[str], status_code=http_client.NO_CONTENT)
    def delete(self, action_ref_or_id):
        """
            Delete an action.

            Handles requests:
                POST /actions/1?_method=delete
                DELETE /actions/1
                DELETE /actions/mypack.myaction
        """
        try:
            action_db = self._get_by_ref_or_id(ref_or_id=action_ref_or_id)
        except Exception as e:
            LOG.exception(e.message)
            abort(http_client.NOT_FOUND, e.message)
            return

        action_id = action_db.id

        try:
            validate_not_part_of_system_pack(action_db)
        except ValueValidationException as e:
            abort(http_client.BAD_REQUEST, str(e))

        LOG.debug('DELETE /actions/ lookup with ref_or_id=%s found object: %s',
                  action_ref_or_id, action_db)

        try:
            Action.delete(action_db)
        except Exception as e:
            LOG.error(
                'Database delete encountered exception during delete of id="%s". '
                'Exception was %s', action_id, e)
            abort(http_client.INTERNAL_SERVER_ERROR, str(e))
            return

        extra = {'action_db': action_db}
        LOG.audit('Action deleted. Action.id=%s' % (action_db.id), extra=extra)
        return None