Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
1a83ffa
Allow user to specify "data_files" attribute when creating an action …
Kami Aug 3, 2015
5208f22
Add tests for get_pack_resource_file_abs_path function.
Kami Aug 3, 2015
d08e2af
Return written file paths so we can cleanup on exception.
Kami Aug 3, 2015
03ff7cc
Dispatch an internal trigger for each written action data file.
Kami Aug 3, 2015
961352d
Remove unused method - we allow users to override runner parameters a…
Kami Jul 27, 2015
86fba6d
When writing pack data file create requested pack sub-directory if it…
Kami Aug 3, 2015
96867c6
Update __all__.
Kami Aug 3, 2015
82ed2e4
Don't throw if data_files is not provided.
Kami Aug 3, 2015
eb1a454
Fix potential security issue in get_entry_point_abs_path and
Kami Aug 3, 2015
cb2b818
Don't throw if directory doesn't exist, this check should be performe…
Kami Aug 3, 2015
64049f0
Throw inside the controller if the pack directory doesn't exist.
Kami Aug 3, 2015
135daf2
Update affected tests, fix typo.
Kami Aug 3, 2015
94f36cc
Add new packs views controller which allows users to retrieve content…
Kami Aug 3, 2015
2e3568d
Set packs_base_paths for tests to the fixtures directory.
Kami Aug 3, 2015
ce5c762
Also allow user to pass "exclude_fields" argument to the getter metho…
Kami Aug 3, 2015
7910db5
Update chanhelog.
Kami Aug 3, 2015
ce7936e
Update affected tetts so they don't violate entry_point constraint.
Kami Aug 3, 2015
dee947f
Merge branch 'master' into create_action_include_data_files
Kami Aug 4, 2015
389e539
Add get_file_list utility functions.
Kami Aug 4, 2015
05fe089
Store a list of files inside a pack on the PackDB model.
Kami Aug 4, 2015
e4253d0
Add tests for get_file_list function.
Kami Aug 4, 2015
e3b9e5f
Add new get_pack_file_abs_path function which is a superset of
Kami Aug 4, 2015
7924904
Update pack files view endpoint - allow user to retrieve a list of fi…
Kami Aug 4, 2015
1937a41
Return 404 if user passes an invalid number of arguments aka requests…
Kami Aug 4, 2015
08c271b
Also allow user to include files when updating an action.
Kami Aug 5, 2015
292f40a
Don't allow users to create actions in system packs using the API.
Kami Aug 5, 2015
b8abf25
Remove now unnecessary try / except - exception is now translated to …
Kami Aug 5, 2015
16f52a1
Merge branch 'master' into create_action_include_data_files
Kami Aug 5, 2015
8843418
Refactor _handle_data_files, also dispatch trigger when updating an a…
Kami Aug 5, 2015
751ec35
Only return 404 if TypeError refers to the controller method and not …
Kami Aug 5, 2015
d47b2b4
Add valid pack.yaml to packs inside the fixtures directory so they ca…
Kami Aug 5, 2015
5ffb8b2
Register all the packs inside the fixtures directory in the
Kami Aug 5, 2015
80e4719
Add some tests for including files on action create.
Kami Aug 5, 2015
349fcc5
Make sure we store relative paths.
Kami Aug 5, 2015
f4f11ec
Also verify file has been created.
Kami Aug 5, 2015
962f33b
Fix broken test, only register packs with test_actions controller tes…
Kami Aug 5, 2015
8db076a
Merge branch 'master' into create_action_include_data_files
Kami Aug 14, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ in development
* Fix bug in triggers emitted on key value pair changes and sensor spawn/exit. When
dispatching those triggers, the reference used didn't contain the pack names
which meant it was invalid and lookups in the rules engine would fail. (bug-fix)
* Allow user to include files which are written on disk inside the action create API payload.
(new feature)
* Allow user to retrieve content of a file inside a pack by using the new
``/packs/views/files/`` API endpoint. (new feature)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we shouldn't advertise it beyond using it in the UI yet?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's a big deal putting it in changelog (we usually write a release announcement blog post where we more prominently announce new features so as long we don't put it there we are fine), but if you think otherwise I'm also fine with removing it.


0.12.1 - July 31, 2015
----------------------
Expand Down
10 changes: 6 additions & 4 deletions st2actions/tests/unit/test_runner_container_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ def test_get_entry_point_absolute_path(self):
service = RunnerContainerService()
orig_path = cfg.CONF.content.system_packs_base_path
cfg.CONF.content.system_packs_base_path = '/tests/packs'
acutal_path = service.get_entry_point_abs_path(pack='foo', entry_point='/foo/bar.py')
self.assertEqual(acutal_path, '/foo/bar.py', 'Entry point path doesn\'t match.')
acutal_path = service.get_entry_point_abs_path(pack='foo',
entry_point='/tests/packs/foo/bar.py')
self.assertEqual(acutal_path, '/tests/packs/foo/bar.py', 'Entry point path doesn\'t match.')
cfg.CONF.content.system_packs_base_path = orig_path

def test_get_entry_point_absolute_path_empty(self):
Expand Down Expand Up @@ -86,7 +87,8 @@ def test_get_action_libs_abs_path(self):
self.assertEqual(acutal_path, expected_path, 'Action libs path doesn\'t match.')

# entry point absolute.
acutal_path = service.get_action_libs_abs_path(pack='foo', entry_point='/tmp/foo.py')
expected_path = os.path.join('/tmp', ACTION_LIBS_DIR)
acutal_path = service.get_action_libs_abs_path(pack='foo',
entry_point='/tests/packs/foo/tmp/foo.py')
expected_path = os.path.join('/tests/packs/foo/tmp', ACTION_LIBS_DIR)
self.assertEqual(acutal_path, expected_path, 'Action libs path doesn\'t match.')
cfg.CONF.content.system_packs_base_path = orig_path
25 changes: 9 additions & 16 deletions st2api/st2api/controllers/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ def _get_one_by_name_or_id(self, name_or_id, exclude_fields=None):

from_model_kwargs = self._get_from_model_kwargs_for_request(request=pecan.request)
result = self.model.from_model(instance, **from_model_kwargs)
LOG.debug('GET %s with name_or_odid=%s, client_result=%s', pecan.request.path, id, result)
LOG.debug('GET %s with name_or_id=%s, client_result=%s', pecan.request.path, id, result)

return result

Expand Down Expand Up @@ -235,11 +235,11 @@ def get_one(self, ref_or_id):
def get_all(self, **kwargs):
return self._get_all(**kwargs)

def _get_one(self, ref_or_id):
def _get_one(self, ref_or_id, exclude_fields=None):
LOG.info('GET %s with ref_or_id=%s', pecan.request.path, ref_or_id)

try:
instance = self._get_by_ref_or_id(ref_or_id=ref_or_id)
instance = self._get_by_ref_or_id(ref_or_id=ref_or_id, exclude_fields=exclude_fields)
except Exception as e:
LOG.exception(e.message)
pecan.abort(http_client.NOT_FOUND, e.message)
Expand Down Expand Up @@ -268,7 +268,7 @@ def _get_all(self, **kwargs):

return result

def _get_by_ref_or_id(self, ref_or_id):
def _get_by_ref_or_id(self, ref_or_id, exclude_fields=None):
"""
Retrieve resource object by an id of a reference.

Expand All @@ -283,31 +283,24 @@ def _get_by_ref_or_id(self, ref_or_id):
is_reference = False

if is_reference:
resource_db = self._get_by_ref(resource_ref=ref_or_id)
resource_db = self._get_by_ref(resource_ref=ref_or_id, exclude_fields=exclude_fields)
else:
resource_db = self._get_by_id(resource_id=ref_or_id)
resource_db = self._get_by_id(resource_id=ref_or_id, exclude_fields=exclude_fields)

if not resource_db:
msg = 'Resource with a reference or id "%s" not found' % (ref_or_id)
raise StackStormDBObjectNotFoundError(msg)

return resource_db

def _get_by_id(self, resource_id):
try:
resource_db = self.access.get_by_id(resource_id)
except Exception:
resource_db = None

return resource_db

def _get_by_ref(self, resource_ref):
def _get_by_ref(self, resource_ref, exclude_fields=None):
try:
ref = ResourceReference.from_string_reference(ref=resource_ref)
except Exception:
return None

resource_db = self.access.query(name=ref.name, pack=ref.pack).first()
resource_db = self.access.query(name=ref.name, pack=ref.pack,
exclude_fields=exclude_fields).first()
return resource_db

def _get_filters(self, **kwargs):
Expand Down
137 changes: 132 additions & 5 deletions st2api/st2api/controllers/v1/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from mongoengine import ValidationError
import os
import os.path

from pecan import abort
import six
from pecan import abort
from mongoengine import ValidationError

# TODO: Encapsulate mongoengine errors in our persistence layer. Exceptions
# that bubble up to this layer should be core Python exceptions or
Expand All @@ -26,11 +28,19 @@
from st2api.controllers.v1.actionviews import ActionViewsController
from st2common import log as logging
from st2common.constants.pack import DEFAULT_PACK_NAME
from st2common.constants.triggers import ACTION_FILE_WRITTEN_TRIGGER
from st2common.exceptions.apivalidation import ValueValidationException
from st2common.models.api.base import jsexpose
from st2common.persistence.action import Action
from st2common.models.api.action import ActionAPI
from st2common.models.api.action import ActionCreateAPI
from st2common.persistence.pack import Pack
from st2common.validators.api.misc import validate_not_part_of_system_pack
from st2common.content.utils import get_pack_base_path
from st2common.content.utils import get_pack_resource_file_abs_path
from st2common.content.utils import get_relative_path_to_pack
from st2common.transport.reactor import TriggerDispatcher
from st2common.util.system_info import get_host_info
import st2common.validators.api.action as action_validator

http_client = six.moves.http_client
Expand Down Expand Up @@ -58,7 +68,11 @@ class ActionsController(resource.ContentPackResourceController):

include_reference = True

@jsexpose(body_cls=ActionAPI, status_code=http_client.CREATED)
def __init__(self, *args, **kwargs):
super(ActionsController, self).__init__(*args, **kwargs)
self._trigger_dispatcher = TriggerDispatcher(LOG)

@jsexpose(body_cls=ActionCreateAPI, status_code=http_client.CREATED)
def post(self, action):
"""
Create a new action.
Expand All @@ -73,19 +87,32 @@ def post(self, action):
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)

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)

extra = {'action_db': 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

@jsexpose(arg_types=[str], body_cls=ActionAPI)
@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)
action_id = action_db.id
Expand All @@ -97,6 +124,13 @@ def put(self, action_ref_or_id, action):
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)
action_db.id = action_id
Expand All @@ -106,6 +140,12 @@ def put(self, action_ref_or_id, 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)

Expand Down Expand Up @@ -143,3 +183,90 @@ def delete(self, action_ref_or_id):
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)
4 changes: 4 additions & 0 deletions st2api/st2api/controllers/v1/packs.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

from st2common.models.api.base import jsexpose
from st2api.controllers.resource import ResourceController
from st2api.controllers.v1.packviews import PackViewsController
from st2common.models.api.pack import PackAPI
from st2common.persistence.pack import Pack

Expand All @@ -35,6 +36,9 @@ class PacksController(ResourceController):
'sort': ['ref']
}

# Nested controllers
views = PackViewsController()

@jsexpose(arg_types=[str])
def get_one(self, name_or_id):
return self._get_one_by_name_or_id(name_or_id=name_or_id)
Loading