{"action":"created","comment":{"url":"https://api.github.com/repos/lago-project/lago/pulls/comments/103163088","pull_request_review_id":23940029,"id":103163088,"diff_hunk":"@@ -0,0 +1,254 @@\n+# coding=utf-8\n+import functools\n+import os\n+from os import path\n+import logging\n+import shutil\n+\n+import log_utils\n+import utils\n+import lockfile\n+\n+LOGGER = logging.getLogger(__name__)\n+LogTask = functools.partial(log_utils.LogTask, logger=LOGGER)\n+log_task = functools.partial(log_utils.log_task, logger=LOGGER)\n+\"\"\"\n+Hooks\n+======\n+Run scripts before or after a Lago command.\n+The script will be run on the host which runs Lago.\n+\"\"\"\n+\n+\n+def with_hooks(func):\n+    \"\"\"\n+        Decorate a callable to run with hooks.\n+        If without hooks==True, don't run the hooks, just the callable.\n+\n+        Args:\n+            func(callable): callable to decorate\n+\n+        Returns:\n+            The value returned by calling to func\n+\n+    \"\"\"\n+\n+    @functools.wraps(func)\n+    def wrap(prefix, without_hooks=False, *args, **kwargs):\n+        kwargs['prefix'] = prefix\n+\n+        if without_hooks:\n+            LOGGER.debug('without_hooks=True, skipping hooks')\n+            return func(*args, **kwargs)\n+\n+        cmd = func.__name__\n+        if cmd.startswith('do_'):\n+            cmd = cmd[3:]\n+\n+        hooks = Hooks(prefix.paths.hooks())\n+        hooks.run_pre_hooks(cmd)\n+        result = func(*args, **kwargs)\n+        hooks.run_post_hooks(cmd)\n+\n+        return result\n+\n+    return wrap\n+\n+\n+def copy_hooks_to_prefix(config, dir):\n+    \"\"\"\n+        Copy hooks into a prefix.\n+        All the hooks will be copied to $LAGO_PREFIX_PATH/hooks.\n+        Symlinks will be created between each hook and the matching\n+         stage and command\n+        that were specified in the config.\n+\n+        For example, the following config:\n+\n+            \"hooks\": {\n+                \"start\": {\n+                    \"pre\": [\n+                        \"$LAGO_INITFILE_PATH/a.py\"\n+                    ],\n+                    \"post\": [\n+                        \"$LAGO_INITFILE_PATH/b.sh\"\n+                    ]\n+                },\n+                \"stop\": {\n+                    \"pre\": [\n+                        \"$LAGO_INITFILE_PATH/c.sh\"\n+                    ],\n+                \"post\": [\n+                    \"$LAGO_INITFILE_PATH/d.sh\"\n+                    ]\n+                }\n+            }\n+\n+        will end up as the following directory structure:\n+\n+            └── $LAGO_PREFIX_PATH\n+                ├── hooks\n+                │   ├── scripts\n+                │   │   ├── a.py\n+                │   │   ├── b.sh\n+                │   │   ├── c.sh\n+                │   │   └── d.sh\n+                │   ├── start\n+                │   │   ├── post\n+                │   │   │   └── b.sh -> /home/gbenhaim/tmp/fc24/.lago/default\n+                                                        /hooks/scripts/b.sh\n+                │   │   └── pre\n+                │   │       └── a.py -> .lago/default/hooks/scripts/a.py\n+                │   └── stop\n+                │       ├── post\n+                │       │   └── d.sh -> /home/gbenhaim/tmp/fc24/.lago/default\n+                                                        /hooks/scripts/d.sh\n+                │       └── pre\n+                │           └── c.sh -> /home/gbenhaim/tmp/fc24/.lago/default\n+                                                        /hooks/scripts/c.sh\n+\n+\n+        Args:\n+            config(dict): A dict which contains path to hooks categorized by\n+                command and stage\n+            dir(str): A path to the ne\n+        Returns:\n+            None\n+    \"\"\"\n+    with LogTask('Copying Hooks'):\n+        scripts_dir = path.join(dir, 'scripts')\n+        os.mkdir(dir)\n+        os.mkdir(scripts_dir)\n+\n+        for cmd, stages in config.viewitems():\n+            cmd_dir = path.join(dir, cmd)\n+            os.mkdir(cmd_dir)\n+            for stage, hooks in stages.viewitems():\n+                stage_dir = path.join(cmd_dir, stage)\n+                os.mkdir(stage_dir)\n+                for hook in hooks:\n+                    hook_src_path = path.expandvars(hook)\n+                    hook_name = path.basename(hook_src_path)\n+                    hook_dst_path = path.join(scripts_dir, hook_name)\n+\n+                    try:\n+                        shutil.copy(hook_src_path, hook_dst_path)\n+                        os.symlink(\n+                            hook_dst_path, path.join(stage_dir, hook_name)\n+                        )\n+                    except IOError as e:\n+                        raise utils.LagoUserException(e)\n+\n+\n+class Hooks(object):\n+\n+    PRE_CMD = 'pre'\n+    POST_CMD = 'post'\n+\n+    def __init__(self, path):\n+        \"\"\"\n+        Args:\n+            path(list of str): path to the hook dir inside the prefix\n+\n+        Returns:\n+            None\n+        \"\"\"\n+        self._path = path\n+\n+    def run_pre_hooks(self, cmd):\n+        \"\"\"\n+        Run the pre hooks of cmd\n+        Args:\n+            cmd(str): Name of the command\n+\n+        Returns:\n+            None\n+        \"\"\"\n+        self._run(cmd, Hooks.PRE_CMD)\n+\n+    def run_post_hooks(self, cmd):\n+        \"\"\"\n+        Run the post hooks of cmd\n+        Args:\n+            cmd(str): Name of the command\n+\n+        Returns:\n+            None\n+        \"\"\"\n+        self._run(cmd, Hooks.POST_CMD)\n+\n+    def _run(self, cmd, stage):\n+        \"\"\"\n+        Run the [ pre | post ] hooks of cmd\n+        Note that the directory of cmd will be locked by this function in\n+        order to avoid circular call, for example:\n+\n+        a.sh = lago stop\n+        b.sh = lago start\n+\n+        a.sh is post hook of start\n+        b.sh is post hook of stop\n+\n+        start -> a.sh -> stop -> b.sh -> start (in this step the hook\n+        directory of start is locked, so start will be called without\n+        its hooks)\n+\n+        Args:\n+            cmd(str): Name of the command\n+            stage(str): The stage of the hook\n+\n+        Returns:\n+            None\n+        \"\"\"\n+        LOGGER.debug('hook called for {}-{}'.format(stage, cmd))\n+        cmd_dir = path.join(self._path, cmd)\n+        hook_dir = path.join(self._path, cmd, stage)\n+\n+        if not path.isdir(hook_dir):\n+            LOGGER.debug('{} directory not found'.format(hook_dir))\n+            return\n+\n+        _, _, hooks = os.walk(hook_dir).next()\n+\n+        if not hooks:\n+            LOGGER.debug('No hooks were found for command: {}'.format(cmd))\n+            return\n+\n+        # Avoid Recursion\n+        try:\n+            with utils.DirLockWithTimeout(cmd_dir):\n+                self._run_hooks(\n+                    sorted([path.join(hook_dir, hook) for hook in hooks])","path":"lago/hooks.py","position":221,"original_position":221,"commit_id":"6f7c05a9786e9a7a7cdb306e1477bd7d26a0af02","original_commit_id":"6f7c05a9786e9a7a7cdb306e1477bd7d26a0af02","user":{"login":"gbenhaim","id":17479229,"avatar_url":"https://avatars.githubusercontent.com/u/17479229?v=3","gravatar_id":"","url":"https://api.github.com/users/gbenhaim","html_url":"https://github.com/gbenhaim","followers_url":"https://api.github.com/users/gbenhaim/followers","following_url":"https://api.github.com/users/gbenhaim/following{/other_user}","gists_url":"https://api.github.com/users/gbenhaim/gists{/gist_id}","starred_url":"https://api.github.com/users/gbenhaim/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gbenhaim/subscriptions","organizations_url":"https://api.github.com/users/gbenhaim/orgs","repos_url":"https://api.github.com/users/gbenhaim/repos","events_url":"https://api.github.com/users/gbenhaim/events{/privacy}","received_events_url":"https://api.github.com/users/gbenhaim/received_events","type":"User","site_admin":false},"body":"I want to preserve the order in which the hooks were mentioned in the init file, but now I realize that I didn't prefix them with an index. will fix.","created_at":"2017-02-27T09:20:36Z","updated_at":"2017-02-27T09:20:36Z","html_url":"https://github.com/lago-project/lago/pull/456#discussion_r103163088","pull_request_url":"https://api.github.com/repos/lago-project/lago/pulls/456","_links":{"self":{"href":"https://api.github.com/repos/lago-project/lago/pulls/comments/103163088"},"html":{"href":"https://github.com/lago-project/lago/pull/456#discussion_r103163088"},"pull_request":{"href":"https://api.github.com/repos/lago-project/lago/pulls/456"}}},"pull_request":{"url":"https://api.github.com/repos/lago-project/lago/pulls/456","id":108013908,"html_url":"https://github.com/lago-project/lago/pull/456","diff_url":"https://github.com/lago-project/lago/pull/456.diff","patch_url":"https://github.com/lago-project/lago/pull/456.patch","issue_url":"https://api.github.com/repos/lago-project/lago/issues/456","number":456,"state":"open","locked":false,"title":"[WIP] Adding hook support","user":{"login":"gbenhaim","id":17479229,"avatar_url":"https://avatars.githubusercontent.com/u/17479229?v=3","gravatar_id":"","url":"https://api.github.com/users/gbenhaim","html_url":"https://github.com/gbenhaim","followers_url":"https://api.github.com/users/gbenhaim/followers","following_url":"https://api.github.com/users/gbenhaim/following{/other_user}","gists_url":"https://api.github.com/users/gbenhaim/gists{/gist_id}","starred_url":"https://api.github.com/users/gbenhaim/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gbenhaim/subscriptions","organizations_url":"https://api.github.com/users/gbenhaim/orgs","repos_url":"https://api.github.com/users/gbenhaim/repos","events_url":"https://api.github.com/users/gbenhaim/events{/privacy}","received_events_url":"https://api.github.com/users/gbenhaim/received_events","type":"User","site_admin":false},"body":"1. The hooks are defined in the init file and\r\n   can be any type of script (shell, python...).\r\n\r\n2. The hooks will be run on the host which runs Lago.\r\n\r\n3. Hooks will be only supported for functions that were\r\n   decorated with \"hooks.with_hooks\" (for now just start / stop)\r\n\r\nSigned-off-by: gbenhaim <galbh2@gmail.com>","created_at":"2017-02-26T17:44:05Z","updated_at":"2017-02-27T09:20:36Z","closed_at":null,"merged_at":null,"merge_commit_sha":"33ff92afc2cef6ace916ae6d2dc5ab85b97d637b","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/lago-project/lago/pulls/456/commits","review_comments_url":"https://api.github.com/repos/lago-project/lago/pulls/456/comments","review_comment_url":"https://api.github.com/repos/lago-project/lago/pulls/comments{/number}","comments_url":"https://api.github.com/repos/lago-project/lago/issues/456/comments","statuses_url":"https://api.github.com/repos/lago-project/lago/statuses/6f7c05a9786e9a7a7cdb306e1477bd7d26a0af02","head":{"label":"lago-project:adding_hooks","ref":"adding_hooks","sha":"6f7c05a9786e9a7a7cdb306e1477bd7d26a0af02","user":{"login":"lago-project","id":16708442,"avatar_url":"https://avatars.githubusercontent.com/u/16708442?v=3","gravatar_id":"","url":"https://api.github.com/users/lago-project","html_url":"https://github.com/lago-project","followers_url":"https://api.github.com/users/lago-project/followers","following_url":"https://api.github.com/users/lago-project/following{/other_user}","gists_url":"https://api.github.com/users/lago-project/gists{/gist_id}","starred_url":"https://api.github.com/users/lago-project/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lago-project/subscriptions","organizations_url":"https://api.github.com/users/lago-project/orgs","repos_url":"https://api.github.com/users/lago-project/repos","events_url":"https://api.github.com/users/lago-project/events{/privacy}","received_events_url":"https://api.github.com/users/lago-project/received_events","type":"Organization","site_admin":false},"repo":{"id":49959573,"name":"lago","full_name":"lago-project/lago","owner":{"login":"lago-project","id":16708442,"avatar_url":"https://avatars.githubusercontent.com/u/16708442?v=3","gravatar_id":"","url":"https://api.github.com/users/lago-project","html_url":"https://github.com/lago-project","followers_url":"https://api.github.com/users/lago-project/followers","following_url":"https://api.github.com/users/lago-project/following{/other_user}","gists_url":"https://api.github.com/users/lago-project/gists{/gist_id}","starred_url":"https://api.github.com/users/lago-project/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lago-project/subscriptions","organizations_url":"https://api.github.com/users/lago-project/orgs","repos_url":"https://api.github.com/users/lago-project/repos","events_url":"https://api.github.com/users/lago-project/events{/privacy}","received_events_url":"https://api.github.com/users/lago-project/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/lago-project/lago","description":"Ad-hoc virtual testing environment framework","fork":false,"url":"https://api.github.com/repos/lago-project/lago","forks_url":"https://api.github.com/repos/lago-project/lago/forks","keys_url":"https://api.github.com/repos/lago-project/lago/keys{/key_id}","collaborators_url":"https://api.github.com/repos/lago-project/lago/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/lago-project/lago/teams","hooks_url":"https://api.github.com/repos/lago-project/lago/hooks","issue_events_url":"https://api.github.com/repos/lago-project/lago/issues/events{/number}","events_url":"https://api.github.com/repos/lago-project/lago/events","assignees_url":"https://api.github.com/repos/lago-project/lago/assignees{/user}","branches_url":"https://api.github.com/repos/lago-project/lago/branches{/branch}","tags_url":"https://api.github.com/repos/lago-project/lago/tags","blobs_url":"https://api.github.com/repos/lago-project/lago/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/lago-project/lago/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/lago-project/lago/git/refs{/sha}","trees_url":"https://api.github.com/repos/lago-project/lago/git/trees{/sha}","statuses_url":"https://api.github.com/repos/lago-project/lago/statuses/{sha}","languages_url":"https://api.github.com/repos/lago-project/lago/languages","stargazers_url":"https://api.github.com/repos/lago-project/lago/stargazers","contributors_url":"https://api.github.com/repos/lago-project/lago/contributors","subscribers_url":"https://api.github.com/repos/lago-project/lago/subscribers","subscription_url":"https://api.github.com/repos/lago-project/lago/subscription","commits_url":"https://api.github.com/repos/lago-project/lago/commits{/sha}","git_commits_url":"https://api.github.com/repos/lago-project/lago/git/commits{/sha}","comments_url":"https://api.github.com/repos/lago-project/lago/comments{/number}","issue_comment_url":"https://api.github.com/repos/lago-project/lago/issues/comments{/number}","contents_url":"https://api.github.com/repos/lago-project/lago/contents/{+path}","compare_url":"https://api.github.com/repos/lago-project/lago/compare/{base}...{head}","merges_url":"https://api.github.com/repos/lago-project/lago/merges","archive_url":"https://api.github.com/repos/lago-project/lago/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/lago-project/lago/downloads","issues_url":"https://api.github.com/repos/lago-project/lago/issues{/number}","pulls_url":"https://api.github.com/repos/lago-project/lago/pulls{/number}","milestones_url":"https://api.github.com/repos/lago-project/lago/milestones{/number}","notifications_url":"https://api.github.com/repos/lago-project/lago/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/lago-project/lago/labels{/name}","releases_url":"https://api.github.com/repos/lago-project/lago/releases{/id}","deployments_url":"https://api.github.com/repos/lago-project/lago/deployments","created_at":"2016-01-19T14:53:46Z","updated_at":"2017-02-08T23:20:30Z","pushed_at":"2017-02-26T19:18:01Z","git_url":"git://github.com/lago-project/lago.git","ssh_url":"git@github.com:lago-project/lago.git","clone_url":"https://github.com/lago-project/lago.git","svn_url":"https://github.com/lago-project/lago","homepage":"http://lago.readthedocs.org","size":38021,"stargazers_count":21,"watchers_count":21,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":32,"mirror_url":null,"open_issues_count":91,"forks":32,"open_issues":91,"watchers":21,"default_branch":"master"}},"base":{"label":"lago-project:master","ref":"master","sha":"74b30c3f840b436f1425598f7a6c034ef6f82bce","user":{"login":"lago-project","id":16708442,"avatar_url":"https://avatars.githubusercontent.com/u/16708442?v=3","gravatar_id":"","url":"https://api.github.com/users/lago-project","html_url":"https://github.com/lago-project","followers_url":"https://api.github.com/users/lago-project/followers","following_url":"https://api.github.com/users/lago-project/following{/other_user}","gists_url":"https://api.github.com/users/lago-project/gists{/gist_id}","starred_url":"https://api.github.com/users/lago-project/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lago-project/subscriptions","organizations_url":"https://api.github.com/users/lago-project/orgs","repos_url":"https://api.github.com/users/lago-project/repos","events_url":"https://api.github.com/users/lago-project/events{/privacy}","received_events_url":"https://api.github.com/users/lago-project/received_events","type":"Organization","site_admin":false},"repo":{"id":49959573,"name":"lago","full_name":"lago-project/lago","owner":{"login":"lago-project","id":16708442,"avatar_url":"https://avatars.githubusercontent.com/u/16708442?v=3","gravatar_id":"","url":"https://api.github.com/users/lago-project","html_url":"https://github.com/lago-project","followers_url":"https://api.github.com/users/lago-project/followers","following_url":"https://api.github.com/users/lago-project/following{/other_user}","gists_url":"https://api.github.com/users/lago-project/gists{/gist_id}","starred_url":"https://api.github.com/users/lago-project/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lago-project/subscriptions","organizations_url":"https://api.github.com/users/lago-project/orgs","repos_url":"https://api.github.com/users/lago-project/repos","events_url":"https://api.github.com/users/lago-project/events{/privacy}","received_events_url":"https://api.github.com/users/lago-project/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/lago-project/lago","description":"Ad-hoc virtual testing environment framework","fork":false,"url":"https://api.github.com/repos/lago-project/lago","forks_url":"https://api.github.com/repos/lago-project/lago/forks","keys_url":"https://api.github.com/repos/lago-project/lago/keys{/key_id}","collaborators_url":"https://api.github.com/repos/lago-project/lago/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/lago-project/lago/teams","hooks_url":"https://api.github.com/repos/lago-project/lago/hooks","issue_events_url":"https://api.github.com/repos/lago-project/lago/issues/events{/number}","events_url":"https://api.github.com/repos/lago-project/lago/events","assignees_url":"https://api.github.com/repos/lago-project/lago/assignees{/user}","branches_url":"https://api.github.com/repos/lago-project/lago/branches{/branch}","tags_url":"https://api.github.com/repos/lago-project/lago/tags","blobs_url":"https://api.github.com/repos/lago-project/lago/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/lago-project/lago/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/lago-project/lago/git/refs{/sha}","trees_url":"https://api.github.com/repos/lago-project/lago/git/trees{/sha}","statuses_url":"https://api.github.com/repos/lago-project/lago/statuses/{sha}","languages_url":"https://api.github.com/repos/lago-project/lago/languages","stargazers_url":"https://api.github.com/repos/lago-project/lago/stargazers","contributors_url":"https://api.github.com/repos/lago-project/lago/contributors","subscribers_url":"https://api.github.com/repos/lago-project/lago/subscribers","subscription_url":"https://api.github.com/repos/lago-project/lago/subscription","commits_url":"https://api.github.com/repos/lago-project/lago/commits{/sha}","git_commits_url":"https://api.github.com/repos/lago-project/lago/git/commits{/sha}","comments_url":"https://api.github.com/repos/lago-project/lago/comments{/number}","issue_comment_url":"https://api.github.com/repos/lago-project/lago/issues/comments{/number}","contents_url":"https://api.github.com/repos/lago-project/lago/contents/{+path}","compare_url":"https://api.github.com/repos/lago-project/lago/compare/{base}...{head}","merges_url":"https://api.github.com/repos/lago-project/lago/merges","archive_url":"https://api.github.com/repos/lago-project/lago/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/lago-project/lago/downloads","issues_url":"https://api.github.com/repos/lago-project/lago/issues{/number}","pulls_url":"https://api.github.com/repos/lago-project/lago/pulls{/number}","milestones_url":"https://api.github.com/repos/lago-project/lago/milestones{/number}","notifications_url":"https://api.github.com/repos/lago-project/lago/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/lago-project/lago/labels{/name}","releases_url":"https://api.github.com/repos/lago-project/lago/releases{/id}","deployments_url":"https://api.github.com/repos/lago-project/lago/deployments","created_at":"2016-01-19T14:53:46Z","updated_at":"2017-02-08T23:20:30Z","pushed_at":"2017-02-26T19:18:01Z","git_url":"git://github.com/lago-project/lago.git","ssh_url":"git@github.com:lago-project/lago.git","clone_url":"https://github.com/lago-project/lago.git","svn_url":"https://github.com/lago-project/lago","homepage":"http://lago.readthedocs.org","size":38021,"stargazers_count":21,"watchers_count":21,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":32,"mirror_url":null,"open_issues_count":91,"forks":32,"open_issues":91,"watchers":21,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/lago-project/lago/pulls/456"},"html":{"href":"https://github.com/lago-project/lago/pull/456"},"issue":{"href":"https://api.github.com/repos/lago-project/lago/issues/456"},"comments":{"href":"https://api.github.com/repos/lago-project/lago/issues/456/comments"},"review_comments":{"href":"https://api.github.com/repos/lago-project/lago/pulls/456/comments"},"review_comment":{"href":"https://api.github.com/repos/lago-project/lago/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/lago-project/lago/pulls/456/commits"},"statuses":{"href":"https://api.github.com/repos/lago-project/lago/statuses/6f7c05a9786e9a7a7cdb306e1477bd7d26a0af02"}}}}
{"actor":{"display_login":"gbenhaim"}}