Skip to content
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
5fb78d2
st2 API and CLI command added for actions/workflows clone operation
ashwini-orchestral Aug 27, 2021
411f38f
Clone api for actions/workflows- adding openapi.yaml file
ashwini-orchestral Aug 27, 2021
89a94a4
Modifications done as per code review comments
ashwini-orchestral Sep 14, 2021
ba17481
Adding RBAC unit tests for clone action API
ashwini-orchestral Sep 14, 2021
0dedd4a
Refactoring clone action function and unit tests
ashwini-orchestral Sep 15, 2021
4ca98b6
Merge branch 'master' into clone_action_api
ashwini-orchestral Sep 15, 2021
89b71c9
Merge branch 'master' into clone_action_api
ashwini-orchestral Sep 28, 2021
5a1f1a8
Refactored clone_actions function to create workflows directory if do…
ashwini-orchestral Sep 29, 2021
ef43f5b
Added new unit test and refactored existing unit tests related to clo…
ashwini-orchestral Sep 29, 2021
63395cc
Refactored action clone API method to reuse existing create and delet…
ashwini-orchestral Sep 29, 2021
d344448
Minor fixes in existing unit tests
ashwini-orchestral Sep 29, 2021
0eb5dce
Minor fix in unit test related to clone response error code
ashwini-orchestral Sep 29, 2021
d5003a4
Update st2client/st2client/commands/action.py to deduplicate `self.ma…
ashwini-orchestral Oct 4, 2021
9750ebc
Merge branch 'master' into clone_action_api
ashwini-orchestral Oct 4, 2021
0ff59a1
Move local generic body class to public GenericRequestParam
m4dcoder Oct 5, 2021
d32b1d0
For options (i.e. `remove_files` flag) using GenericRequestParam clas…
mahesh-orch Oct 8, 2021
b9484c7
Refactoring unit tests regarding cleanup partially cloned actions and…
mahesh-orch Oct 8, 2021
7a55c94
Added functions related to backup destination action files in case of…
mahesh-orch Oct 8, 2021
57eb5fe
Added unit tests for newly added function for restoring destination a…
mahesh-orch Oct 8, 2021
83591f3
Deleted st2api/tests/base.py as it was temporarily added here for RBA…
mahesh-orch Oct 8, 2021
3c0f50e
Deleted test_actions_rbac.py file as these clone action RBAC unit tes…
mahesh-orch Oct 8, 2021
67adadc
Merge branch 'master' into clone_action_api
mahesh-orch Oct 8, 2021
b1815a2
Created new function `_restore_action` to reuse the common code and d…
mahesh-orch Oct 11, 2021
f795daf
Adding check for `actions` directory in pack to create it if doesn't …
mahesh-orch Oct 11, 2021
305d50e
Added unit tests for exceptions for `temp_backup_action_files` and `r…
mahesh-orch Oct 11, 2021
6a250b8
Refactored test_packs.py to fix failing CI unit test chunk 1
mahesh-orch Oct 11, 2021
d41453a
Minor fix in test_packs.py, removed extra variable name `SOURCE_WORKF…
mahesh-orch Oct 11, 2021
0ea4b9a
Refactored unit test `test_clone_overwrite_exception_destination_reco…
mahesh-orch Oct 13, 2021
6d736c1
Minor grammar fix in comment in `test_actions.py`
mahesh-orch Oct 13, 2021
76bebb8
Refactoring unit test `test_clone_overwrite` to use ACTION_16["parame…
mahesh-orch Oct 13, 2021
34326d5
Minor change to add comment line in unit test in `test_actions.py`
mahesh-orch Oct 13, 2021
ab45818
Added comment for source workflow path required for related unit tests
mahesh-orch Oct 14, 2021
7085db1
Fix related to entry_point directories creatinon in `/tmp/<uuid>` rel…
mahesh-orch Oct 14, 2021
d9d27ae
Minor fix in clone api function for removing cloned action database e…
mahesh-orch Oct 14, 2021
02bac39
Refactoring unit tests for newly updated clone api function for remov…
mahesh-orch Oct 14, 2021
44fb50e
Deleted unnecessary file __init__.py
mahesh-orch Oct 19, 2021
a009f8c
Fixing failing unit tests with `FileNotFoundError`. Creating empty fo…
mahesh-orch Oct 19, 2021
5a8f693
Fixing black/flake error - removed blank line
mahesh-orch Oct 19, 2021
d9a43b3
Fixing black/flake check - added Apache license header and copyright …
mahesh-orch Oct 19, 2021
f27900a
Simplified logic of creating entry point folder in /tmp/<uuid> directory
mahesh-orch Oct 19, 2021
64db892
Merge branch 'master' into clone_action_api
mahesh-orch Oct 19, 2021
c125cd1
Merge branch 'master' into clone_action_api
mahesh-orch Oct 26, 2021
97251ae
Merge branch 'master' into clone_action_api
mahesh-orch Oct 27, 2021
91e4c2d
Minor fix for running CIs to fix timeout error in ci/circleci: packages
mahesh-orch Oct 27, 2021
9ad36c4
Minor fix in comment for creation actions directory under packs.py
mahesh-orch Oct 27, 2021
b2cef24
Adding newly added functions in __all__ list
mahesh-orch Oct 28, 2021
91029eb
Adding newly added functions in __all__ list in misc.py
mahesh-orch Oct 28, 2021
f9ec53e
Merge branch 'master' into clone_action_api
m4dcoder Dec 1, 2021
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
160 changes: 160 additions & 0 deletions st2api/st2api/controllers/v1/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import os.path
import stat
import errno
import uuid

import six
from mongoengine import ValidationError
Expand All @@ -31,18 +32,26 @@
from st2common.constants.triggers import ACTION_FILE_WRITTEN_TRIGGER
from st2common.exceptions.action import InvalidActionParameterException
from st2common.exceptions.apivalidation import ValueValidationException
from st2common.exceptions.rbac import ResourceAccessDeniedError
from st2common.persistence.action import Action
from st2common.models.api.action import ActionAPI
from st2common.persistence.pack import Pack
from st2common.rbac.types import PermissionType
from st2common.rbac.backends import get_rbac_backend
from st2common.router import abort
from st2common.router import GenericRequestParam
from st2common.router import Response
from st2common.validators.api.misc import validate_not_part_of_system_pack
from st2common.validators.api.misc import validate_not_part_of_system_pack_by_name
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_file
from st2common.services.packs import delete_action_files_from_pack
from st2common.services.packs import clone_action_files
from st2common.services.packs import clone_action_db
from st2common.services.packs import temp_backup_action_files
from st2common.services.packs import remove_temp_action_files
from st2common.services.packs import restore_temp_action_files
from st2common.transport.reactor import TriggerDispatcher
from st2common.util.system_info import get_host_info
import st2common.validators.api.action as action_validator
Expand Down Expand Up @@ -281,6 +290,146 @@ def delete(self, options, ref_or_id, requester_user):
LOG.audit("Action deleted. Action.id=%s" % (action_db.id), extra=extra)
return Response(status=http_client.NO_CONTENT)

def clone(self, dest_data, ref_or_id, requester_user):
"""
Clone an action from source pack to destination pack.
Handles requests:
POST /actions/{ref_or_id}/clone
"""

source_action_db = self._get_by_ref_or_id(ref_or_id=ref_or_id)
if not source_action_db:
msg = "The requested source for cloning operation doesn't exists"
abort(http_client.BAD_REQUEST, six.text_type(msg))

extra = {"action_db": source_action_db}
LOG.audit(
"Source action found. Action.id=%s" % (source_action_db.id), extra=extra
)

try:
permission_type = PermissionType.ACTION_VIEW
rbac_utils = get_rbac_backend().get_utils_class()
rbac_utils.assert_user_has_resource_db_permission(
user_db=requester_user,
resource_db=source_action_db,
permission_type=permission_type,
)
except ResourceAccessDeniedError as e:
abort(http_client.UNAUTHORIZED, six.text_type(e))

cloned_dest_action_db = clone_action_db(
source_action_db=source_action_db,
dest_pack=dest_data.dest_pack,
dest_action=dest_data.dest_action,
)

cloned_action_api = ActionAPI.from_model(cloned_dest_action_db)

try:
permission_type = PermissionType.ACTION_CREATE
rbac_utils.assert_user_has_resource_api_permission(
user_db=requester_user,
resource_api=cloned_action_api,
permission_type=permission_type,
)
except ResourceAccessDeniedError as e:
abort(http_client.UNAUTHORIZED, six.text_type(e))

dest_pack_base_path = get_pack_base_path(pack_name=dest_data.dest_pack)

if not os.path.isdir(dest_pack_base_path):
msg = "Destination pack '%s' doesn't exist" % (dest_data.dest_pack)
abort(http_client.BAD_REQUEST, six.text_type(msg))

dest_pack_base_path = get_pack_base_path(pack_name=dest_data.dest_pack)
dest_ref = ".".join([dest_data.dest_pack, dest_data.dest_action])
dest_action_db = self._get_by_ref(resource_ref=dest_ref)

try:
validate_not_part_of_system_pack_by_name(dest_data.dest_pack)
except ValueValidationException as e:
abort(http_client.BAD_REQUEST, six.text_type(e))

if dest_action_db:
if not dest_data.overwrite:
msg = "The requested destination action already exists"
abort(http_client.BAD_REQUEST, six.text_type(msg))

try:
permission_type = PermissionType.ACTION_DELETE
rbac_utils.assert_user_has_resource_db_permission(
user_db=requester_user,
resource_db=dest_action_db,
permission_type=permission_type,
)
options = GenericRequestParam(remove_files=True)
dest_metadata_file = dest_action_db["metadata_file"]
dest_entry_point = dest_action_db["entry_point"]
temp_sub_dir = str(uuid.uuid4())
temp_backup_action_files(
dest_pack_base_path,
dest_metadata_file,
dest_entry_point,
temp_sub_dir,
)
self.delete(options, dest_ref, requester_user)
except ResourceAccessDeniedError as e:
abort(http_client.UNAUTHORIZED, six.text_type(e))
except Exception as e:
LOG.debug(
"Exception encountered during deleting existing destination action. "
"Exception was: %s",
e,
)
abort(http_client.INTERNAL_SERVER_ERROR, six.text_type(e))

try:
post_response = self.post(cloned_action_api, requester_user)
if post_response.status_code != http_client.CREATED:
raise Exception("Could not add cloned action to database.")
cloned_dest_action_db["id"] = post_response.json["id"]
clone_action_files(
source_action_db=source_action_db,
dest_action_db=cloned_dest_action_db,
dest_pack_base_path=dest_pack_base_path,
)
extra = {"cloned_acion_db": cloned_dest_action_db}
LOG.audit(
"Action cloned. Action.id=%s" % (cloned_dest_action_db.id), extra=extra
)
if dest_action_db:
remove_temp_action_files(temp_sub_dir)
return post_response
except PermissionError as e:
LOG.error("No permission to clone the action. Exception was %s", e)
delete_action_files_from_pack(
pack_name=cloned_dest_action_db["pack"],
entry_point=cloned_dest_action_db["entry_point"],
metadata_file=cloned_dest_action_db["metadata_file"],
)
if post_response.status_code == http_client.CREATED:
Action.delete(cloned_dest_action_db)
if dest_action_db:
self._restore_action(dest_action_db, dest_pack_base_path, temp_sub_dir)
abort(http_client.FORBIDDEN, six.text_type(e))
except Exception as e:
LOG.error(
"Exception encountered during cloning action. Exception was %s",
e,
)
delete_action_files_from_pack(
pack_name=cloned_dest_action_db["pack"],
entry_point=cloned_dest_action_db["entry_point"],
metadata_file=cloned_dest_action_db["metadata_file"],
)
if post_response.status_code == http_client.CREATED:
Action.delete(cloned_dest_action_db)
if dest_action_db:
self._restore_action(dest_action_db, dest_pack_base_path, temp_sub_dir)

abort(http_client.INTERNAL_SERVER_ERROR, six.text_type(e))

def _handle_data_files(self, pack_ref, data_files):
"""
Method for handling action data files.
Expand Down Expand Up @@ -392,5 +541,16 @@ def _dispatch_trigger_for_written_data_files(self, action_db, written_data_files
}
self._trigger_dispatcher.dispatch(trigger=trigger, payload=payload)

def _restore_action(self, action_db, pack_base_path, temp_sub_dir):
restore_temp_action_files(
pack_base_path,
action_db["metadata_file"],
action_db["entry_point"],
temp_sub_dir,
)
action_db.id = None
Action.add_or_update(action_db)
remove_temp_action_files(temp_sub_dir)


actions_controller = ActionsController()
Loading