We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Pack reinstall fails if config.yaml is missing
Related to: https://github.com/StackStorm/st2contrib/pull/229

Reproduce:
Here is an empty pack which has no `config.yaml` file in it: https://github.com/armab/st2-pack-bug
#### 1. Pack install (with no `config.yaml`)

``` sh
# st2 run packs.install packs=st2-pack-bug repo_url=armab/st2-pack-bug
......................
id: 5586c6190a84b40a4a08a164
action.ref: packs.install
status: succeeded
start_timestamp: 2015-06-21T14:11:37.259169Z
end_timestamp: 2015-06-21T14:12:22.686647Z
+--------------------------+-----------+--------------------------+-------------------------+----------------------------+
| id                       | status    | task                     | action                  | start_timestamp            |
+--------------------------+-----------+--------------------------+-------------------------+----------------------------+
| 5586c61a0a84b40a402bdb88 | succeeded | download                 | packs.download          | Sun, 21 Jun 2015 14:11:38  |
|                          |           |                          |                         | UTC                        |
| 5586c62a0a84b40a402bdb8a | succeeded | virtualenv_prerun        | packs.virtualenv_prerun | Sun, 21 Jun 2015 14:11:54  |
|                          |           |                          |                         | UTC                        |
| 5586c62c0a84b40a402bdb8c | succeeded | setup_virtualenv         | packs.setup_virtualenv  | Sun, 21 Jun 2015 14:11:56  |
|                          |           |                          |                         | UTC                        |
| 5586c6370a84b40a402bdb8e | succeeded | reload                   | packs.load              | Sun, 21 Jun 2015 14:12:07  |
|                          |           |                          |                         | UTC                        |
| 5586c6410a84b40a402bdb90 | succeeded | restart-sensor-container | packs.restart_component | Sun, 21 Jun 2015 14:12:17  |
|                          |           |                          |                         | UTC                        |
+--------------------------+-----------+--------------------------+-------------------------+----------------------------+

```
#### 2. Pack reinstall (if pack already exists)

``` sh
# st2 run packs.install packs=st2-pack-bug repo_url=armab/st2-pack-bug
........
id: 5586c6700a84b40a4a08a166
action.ref: packs.install
status: failed
error: st2.actions.python.DownloadGitRepoAction: DEBUG    Removing existing pack st2-pack-bug in /opt/stackstorm/packs/st2-pack-bug to replace.
Traceback (most recent call last):
  File "/usr/lib/python2.7/dist-packages/st2actions/runners/python_action_wrapper.py", line 116, in <module>
    obj.run()
  File "/usr/lib/python2.7/dist-packages/st2actions/runners/python_action_wrapper.py", line 61, in run
    output = action.run(**self._parameters)
  File "/opt/stackstorm/packs/packs/actions/pack_mgmt/download.py", line 65, in run
    result = self._move_packs(abs_repo_base, packs, pack_abs_local_path, self._subtree)
  File "/opt/stackstorm/packs/packs/actions/pack_mgmt/download.py", line 102, in _move_packs
    shutil.move(old_config_file, new_config_file)
  File "/usr/lib/python2.7/shutil.py", line 302, in move
    copy2(src, real_dst)
  File "/usr/lib/python2.7/shutil.py", line 130, in copy2
    copyfile(src, dst)
  File "/usr/lib/python2.7/shutil.py", line 82, in copyfile
    with open(src, 'rb') as fsrc:
IOError: [Errno 2] No such file or directory: u'/opt/stackstorm/packs/st2-pack-bug/config.yaml'

traceback: None
failed_on: download
start_timestamp: 2015-06-21T14:13:04.269175Z
end_timestamp: 2015-06-21T14:13:19.928860Z
+--------------------------+--------+----------+----------------+----------------------------+
| id                       | status | task     | action         | start_timestamp            |
+--------------------------+--------+----------+----------------+----------------------------+
| 5586c6700a84b40a3f312d06 | failed | download | packs.download | Sun, 21 Jun 2015 14:13:04  |
|                          |        |          |                | UTC                        |
+--------------------------+--------+----------+----------------+----------------------------+
```

---

As I understand the solution is or to make `config.yaml` as a requirement or to fix that reinstall problem.
№2 seems more logical, since config is not needed for all packs and it's used only by python action runners (what could be improved too btw).


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `contrib/packs/actions/pack_mgmt/download.py`
Content:
```
1 # Licensed to the StackStorm, Inc ('StackStorm') under one or more
2 # contributor license agreements.  See the NOTICE file distributed with
3 # this work for additional information regarding copyright ownership.
4 # The ASF licenses this file to You under the Apache License, Version 2.0
5 # (the "License"); you may not use this file except in compliance with
6 # the License.  You may obtain a copy of the License at
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 
16 import os
17 import shutil
18 import hashlib
19 import re
20 import json
21 
22 import six
23 from git.repo import Repo
24 from lockfile import LockFile
25 
26 from st2actions.runners.pythonrunner import Action
27 
28 ALL_PACKS = '*'
29 PACK_REPO_ROOT = 'packs'
30 MANIFEST_FILE = 'pack.yaml'
31 CONFIG_FILE = 'config.yaml'
32 GITINFO_FILE = '.gitinfo'
33 PACK_RESERVE_CHARACTER = '.'
34 
35 STACKSTORM_CONTRIB_REPOS = [
36     'st2contrib',
37     'st2incubator'
38 ]
39 
40 
41 class DownloadGitRepoAction(Action):
42     def __init__(self, config=None):
43         super(DownloadGitRepoAction, self).__init__(config=config)
44         self._subtree = None
45         self._repo_url = None
46 
47     def run(self, packs, repo_url, abs_repo_base, verifyssl=True, branch='master', subtree=False):
48         self._subtree = self._eval_subtree(repo_url, subtree)
49         self._repo_url = self._eval_repo_url(repo_url)
50 
51         repo_name = self._repo_url[self._repo_url.rfind('/') + 1: self._repo_url.rfind('.')]
52         lock_name = hashlib.md5(repo_name).hexdigest() + '.lock'
53 
54         with LockFile('/tmp/%s' % (lock_name)):
55             abs_local_path = self._clone_repo(repo_url=self._repo_url, verifyssl=verifyssl,
56                                               branch=branch)
57             try:
58                 if self._subtree:
59                     # st2-contrib repo has a top-level packs folder that actually contains the
60                     pack_abs_local_path = os.path.join(abs_local_path, PACK_REPO_ROOT)
61                 else:
62                     pack_abs_local_path = abs_local_path
63 
64                 self._tag_pack(pack_abs_local_path, packs, self._subtree)
65                 result = self._move_packs(abs_repo_base, packs, pack_abs_local_path, self._subtree)
66             finally:
67                 self._cleanup_repo(abs_local_path)
68         return self._validate_result(result=result, packs=packs, repo_url=repo_url)
69 
70     @staticmethod
71     def _clone_repo(repo_url, verifyssl=True, branch='master'):
72         user_home = os.path.expanduser('~')
73         # Assuming git url is of form git@github.com:user/git-repo.git
74         repo_name = repo_url[repo_url.rfind('/') + 1: repo_url.rfind('.')]
75         abs_local_path = os.path.join(user_home, repo_name)
76 
77         # Disable SSL cert checking if explictly asked
78         if not verifyssl:
79             os.environ['GIT_SSL_NO_VERIFY'] = 'true'
80         # Shallow clone the repo to avoid getting all the metadata. We only need HEAD of a
81         # specific branch so save some download time.
82         Repo.clone_from(repo_url, abs_local_path, branch=branch, depth=1)
83         return abs_local_path
84 
85     def _move_packs(self, abs_repo_base, packs, abs_local_path, subtree):
86         result = {}
87         # all_packs should be removed as a pack with that name is not expected to be found.
88         if ALL_PACKS in packs:
89             packs = os.listdir(abs_local_path)
90         for pack in packs:
91             abs_pack_temp_location = os.path.join(abs_local_path, pack) if subtree else abs_local_path
92             desired, message = DownloadGitRepoAction._is_desired_pack(abs_pack_temp_location, pack)
93             if desired:
94                 to = abs_repo_base
95                 dest_pack_path = os.path.join(abs_repo_base, pack)
96                 if os.path.exists(dest_pack_path):
97                     self.logger.debug('Removing existing pack %s in %s to replace.', pack,
98                                       dest_pack_path)
99                     # Ensure to preserve any existing configuration
100                     old_config_file = os.path.join(dest_pack_path, CONFIG_FILE)
101                     new_config_file = os.path.join(abs_pack_temp_location, CONFIG_FILE)
102                     shutil.move(old_config_file, new_config_file)
103                     shutil.rmtree(dest_pack_path)
104                 self.logger.debug('Moving pack from %s to %s.', abs_pack_temp_location, to)
105                 shutil.move(abs_pack_temp_location, to)
106                 message = 'Success.'
107             elif message:
108                 message = 'Failure : %s' % message
109             result[pack] = (desired, message)
110         return result
111 
112     @staticmethod
113     def _is_desired_pack(abs_pack_path, pack_name):
114         # path has to exists.
115         if not os.path.exists(abs_pack_path):
116             return (False, 'Pack "%s" not found or it\'s missing a "pack.yaml" file.' %
117                     (pack_name))
118         # Must be a dir.
119         if not os.path.isdir(abs_pack_path):
120             return (False, '%s is not a expected directory structure.' % (pack_name))
121         # should not include reserve characters
122         if PACK_RESERVE_CHARACTER in pack_name:
123             return (False, 'Pack name "%s" contains reserve character "%s"' %
124                     (pack_name, PACK_RESERVE_CHARACTER))
125         # must contain a manifest file. Empty file is ok for now.
126         if not os.path.isfile(os.path.join(abs_pack_path, MANIFEST_FILE)):
127             return (False, 'Pack is missing a manifest file (%s).' % (MANIFEST_FILE))
128         return (True, '')
129 
130     @staticmethod
131     def _cleanup_repo(abs_local_path):
132         # basic lock checking etc?
133         if os.path.isdir(abs_local_path):
134             shutil.rmtree(abs_local_path)
135 
136     @staticmethod
137     def _validate_result(result, packs, repo_url):
138         atleast_one_success = False
139         sanitized_result = {}
140         for k, v in six.iteritems(result):
141             atleast_one_success |= v[0]
142             sanitized_result[k] = v[1]
143 
144         if not atleast_one_success:
145             message_list = []
146             message_list.append('No packs were downloaded from repository "%s".\n' % (repo_url))
147             message_list.append('Errors:')
148 
149             for pack, value in result.items():
150                 success, error = value
151 
152                 if success:
153                     continue
154 
155                 message_list.append(' - %s: %s' % (pack, error))
156 
157             message = '\n'.join(message_list)
158             raise Exception(message)
159 
160         return sanitized_result
161 
162     @staticmethod
163     def _eval_subtree(repo_url, subtree):
164         match = False
165         for stackstorm_repo_name in STACKSTORM_CONTRIB_REPOS:
166             if stackstorm_repo_name in repo_url:
167                 match = True
168                 break
169 
170         return subtree | match
171 
172     @staticmethod
173     def _eval_repo_url(repo_url):
174         """Allow passing short GitHub style URLs"""
175         has_git_extension = repo_url.endswith('.git')
176         if len(repo_url.split('/')) == 2 and "git@" not in repo_url:
177             url = "https://github.com/{}".format(repo_url)
178         else:
179             url = repo_url
180         return url if has_git_extension else "{}.git".format(url)
181 
182     @staticmethod
183     def _tag_pack(pack_dir, packs, subtree):
184         """Add git information to pack directory for retrieval later"""
185         for pack in packs:
186             repo = Repo(pack_dir)
187             payload = {
188                 "branch": repo.active_branch.name,
189                 "ref": repo.head.commit.hexsha
190             }
191 
192             if subtree:
193                 info_file = os.path.join(pack_dir, pack, GITINFO_FILE)
194             else:
195                 info_file = os.path.join(pack_dir, GITINFO_FILE)
196 
197             try:
198                 gitinfo = open(info_file, "w")
199                 gitinfo.write(json.dumps(payload))
200             finally:
201                 gitinfo.close()
202
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/contrib/packs/actions/pack_mgmt/download.py b/contrib/packs/actions/pack_mgmt/download.py
--- a/contrib/packs/actions/pack_mgmt/download.py
+++ b/contrib/packs/actions/pack_mgmt/download.py
@@ -96,11 +96,16 @@
                 if os.path.exists(dest_pack_path):
                     self.logger.debug('Removing existing pack %s in %s to replace.', pack,
                                       dest_pack_path)
+
                     # Ensure to preserve any existing configuration
                     old_config_file = os.path.join(dest_pack_path, CONFIG_FILE)
                     new_config_file = os.path.join(abs_pack_temp_location, CONFIG_FILE)
-                    shutil.move(old_config_file, new_config_file)
+
+                    if os.path.isfile(old_config_file):
+                        shutil.move(old_config_file, new_config_file)
+
                     shutil.rmtree(dest_pack_path)
+
                 self.logger.debug('Moving pack from %s to %s.', abs_pack_temp_location, to)
                 shutil.move(abs_pack_temp_location, to)
                 message = 'Success.'

{"golden_diff": "diff --git a/contrib/packs/actions/pack_mgmt/download.py b/contrib/packs/actions/pack_mgmt/download.py\n--- a/contrib/packs/actions/pack_mgmt/download.py\n+++ b/contrib/packs/actions/pack_mgmt/download.py\n@@ -96,11 +96,16 @@\n                 if os.path.exists(dest_pack_path):\n                     self.logger.debug('Removing existing pack %s in %s to replace.', pack,\n                                       dest_pack_path)\n+\n                     # Ensure to preserve any existing configuration\n                     old_config_file = os.path.join(dest_pack_path, CONFIG_FILE)\n                     new_config_file = os.path.join(abs_pack_temp_location, CONFIG_FILE)\n-                    shutil.move(old_config_file, new_config_file)\n+\n+                    if os.path.isfile(old_config_file):\n+                        shutil.move(old_config_file, new_config_file)\n+\n                     shutil.rmtree(dest_pack_path)\n+\n                 self.logger.debug('Moving pack from %s to %s.', abs_pack_temp_location, to)\n                 shutil.move(abs_pack_temp_location, to)\n                 message = 'Success.'\n", "issue": "Pack reinstall fails if config.yaml is missing\nRelated to: https://github.com/StackStorm/st2contrib/pull/229\n\nReproduce:\nHere is an empty pack which has no `config.yaml` file in it: https://github.com/armab/st2-pack-bug\n#### 1. Pack install (with no `config.yaml`)\n\n``` sh\n# st2 run packs.install packs=st2-pack-bug repo_url=armab/st2-pack-bug\n......................\nid: 5586c6190a84b40a4a08a164\naction.ref: packs.install\nstatus: succeeded\nstart_timestamp: 2015-06-21T14:11:37.259169Z\nend_timestamp: 2015-06-21T14:12:22.686647Z\n+--------------------------+-----------+--------------------------+-------------------------+----------------------------+\n| id                       | status    | task                     | action                  | start_timestamp            |\n+--------------------------+-----------+--------------------------+-------------------------+----------------------------+\n| 5586c61a0a84b40a402bdb88 | succeeded | download                 | packs.download          | Sun, 21 Jun 2015 14:11:38  |\n|                          |           |                          |                         | UTC                        |\n| 5586c62a0a84b40a402bdb8a | succeeded | virtualenv_prerun        | packs.virtualenv_prerun | Sun, 21 Jun 2015 14:11:54  |\n|                          |           |                          |                         | UTC                        |\n| 5586c62c0a84b40a402bdb8c | succeeded | setup_virtualenv         | packs.setup_virtualenv  | Sun, 21 Jun 2015 14:11:56  |\n|                          |           |                          |                         | UTC                        |\n| 5586c6370a84b40a402bdb8e | succeeded | reload                   | packs.load              | Sun, 21 Jun 2015 14:12:07  |\n|                          |           |                          |                         | UTC                        |\n| 5586c6410a84b40a402bdb90 | succeeded | restart-sensor-container | packs.restart_component | Sun, 21 Jun 2015 14:12:17  |\n|                          |           |                          |                         | UTC                        |\n+--------------------------+-----------+--------------------------+-------------------------+----------------------------+\n\n```\n#### 2. Pack reinstall (if pack already exists)\n\n``` sh\n# st2 run packs.install packs=st2-pack-bug repo_url=armab/st2-pack-bug\n........\nid: 5586c6700a84b40a4a08a166\naction.ref: packs.install\nstatus: failed\nerror: st2.actions.python.DownloadGitRepoAction: DEBUG    Removing existing pack st2-pack-bug in /opt/stackstorm/packs/st2-pack-bug to replace.\nTraceback (most recent call last):\n  File \"/usr/lib/python2.7/dist-packages/st2actions/runners/python_action_wrapper.py\", line 116, in <module>\n    obj.run()\n  File \"/usr/lib/python2.7/dist-packages/st2actions/runners/python_action_wrapper.py\", line 61, in run\n    output = action.run(**self._parameters)\n  File \"/opt/stackstorm/packs/packs/actions/pack_mgmt/download.py\", line 65, in run\n    result = self._move_packs(abs_repo_base, packs, pack_abs_local_path, self._subtree)\n  File \"/opt/stackstorm/packs/packs/actions/pack_mgmt/download.py\", line 102, in _move_packs\n    shutil.move(old_config_file, new_config_file)\n  File \"/usr/lib/python2.7/shutil.py\", line 302, in move\n    copy2(src, real_dst)\n  File \"/usr/lib/python2.7/shutil.py\", line 130, in copy2\n    copyfile(src, dst)\n  File \"/usr/lib/python2.7/shutil.py\", line 82, in copyfile\n    with open(src, 'rb') as fsrc:\nIOError: [Errno 2] No such file or directory: u'/opt/stackstorm/packs/st2-pack-bug/config.yaml'\n\ntraceback: None\nfailed_on: download\nstart_timestamp: 2015-06-21T14:13:04.269175Z\nend_timestamp: 2015-06-21T14:13:19.928860Z\n+--------------------------+--------+----------+----------------+----------------------------+\n| id                       | status | task     | action         | start_timestamp            |\n+--------------------------+--------+----------+----------------+----------------------------+\n| 5586c6700a84b40a3f312d06 | failed | download | packs.download | Sun, 21 Jun 2015 14:13:04  |\n|                          |        |          |                | UTC                        |\n+--------------------------+--------+----------+----------------+----------------------------+\n```\n\n---\n\nAs I understand the solution is or to make `config.yaml` as a requirement or to fix that reinstall problem.\n\u21162 seems more logical, since config is not needed for all packs and it's used only by python action runners (what could be improved too btw).\n\n", "before_files": [{"content": "# Licensed to the StackStorm, Inc ('StackStorm') under one or more\n# contributor license agreements.  See the NOTICE file distributed with\n# this work for additional information regarding copyright ownership.\n# The ASF licenses this file to You under the Apache License, Version 2.0\n# (the \"License\"); you may not use this file except in compliance with\n# the License.  You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport shutil\nimport hashlib\nimport re\nimport json\n\nimport six\nfrom git.repo import Repo\nfrom lockfile import LockFile\n\nfrom st2actions.runners.pythonrunner import Action\n\nALL_PACKS = '*'\nPACK_REPO_ROOT = 'packs'\nMANIFEST_FILE = 'pack.yaml'\nCONFIG_FILE = 'config.yaml'\nGITINFO_FILE = '.gitinfo'\nPACK_RESERVE_CHARACTER = '.'\n\nSTACKSTORM_CONTRIB_REPOS = [\n    'st2contrib',\n    'st2incubator'\n]\n\n\nclass DownloadGitRepoAction(Action):\n    def __init__(self, config=None):\n        super(DownloadGitRepoAction, self).__init__(config=config)\n        self._subtree = None\n        self._repo_url = None\n\n    def run(self, packs, repo_url, abs_repo_base, verifyssl=True, branch='master', subtree=False):\n        self._subtree = self._eval_subtree(repo_url, subtree)\n        self._repo_url = self._eval_repo_url(repo_url)\n\n        repo_name = self._repo_url[self._repo_url.rfind('/') + 1: self._repo_url.rfind('.')]\n        lock_name = hashlib.md5(repo_name).hexdigest() + '.lock'\n\n        with LockFile('/tmp/%s' % (lock_name)):\n            abs_local_path = self._clone_repo(repo_url=self._repo_url, verifyssl=verifyssl,\n                                              branch=branch)\n            try:\n                if self._subtree:\n                    # st2-contrib repo has a top-level packs folder that actually contains the\n                    pack_abs_local_path = os.path.join(abs_local_path, PACK_REPO_ROOT)\n                else:\n                    pack_abs_local_path = abs_local_path\n\n                self._tag_pack(pack_abs_local_path, packs, self._subtree)\n                result = self._move_packs(abs_repo_base, packs, pack_abs_local_path, self._subtree)\n            finally:\n                self._cleanup_repo(abs_local_path)\n        return self._validate_result(result=result, packs=packs, repo_url=repo_url)\n\n    @staticmethod\n    def _clone_repo(repo_url, verifyssl=True, branch='master'):\n        user_home = os.path.expanduser('~')\n        # Assuming git url is of form git@github.com:user/git-repo.git\n        repo_name = repo_url[repo_url.rfind('/') + 1: repo_url.rfind('.')]\n        abs_local_path = os.path.join(user_home, repo_name)\n\n        # Disable SSL cert checking if explictly asked\n        if not verifyssl:\n            os.environ['GIT_SSL_NO_VERIFY'] = 'true'\n        # Shallow clone the repo to avoid getting all the metadata. We only need HEAD of a\n        # specific branch so save some download time.\n        Repo.clone_from(repo_url, abs_local_path, branch=branch, depth=1)\n        return abs_local_path\n\n    def _move_packs(self, abs_repo_base, packs, abs_local_path, subtree):\n        result = {}\n        # all_packs should be removed as a pack with that name is not expected to be found.\n        if ALL_PACKS in packs:\n            packs = os.listdir(abs_local_path)\n        for pack in packs:\n            abs_pack_temp_location = os.path.join(abs_local_path, pack) if subtree else abs_local_path\n            desired, message = DownloadGitRepoAction._is_desired_pack(abs_pack_temp_location, pack)\n            if desired:\n                to = abs_repo_base\n                dest_pack_path = os.path.join(abs_repo_base, pack)\n                if os.path.exists(dest_pack_path):\n                    self.logger.debug('Removing existing pack %s in %s to replace.', pack,\n                                      dest_pack_path)\n                    # Ensure to preserve any existing configuration\n                    old_config_file = os.path.join(dest_pack_path, CONFIG_FILE)\n                    new_config_file = os.path.join(abs_pack_temp_location, CONFIG_FILE)\n                    shutil.move(old_config_file, new_config_file)\n                    shutil.rmtree(dest_pack_path)\n                self.logger.debug('Moving pack from %s to %s.', abs_pack_temp_location, to)\n                shutil.move(abs_pack_temp_location, to)\n                message = 'Success.'\n            elif message:\n                message = 'Failure : %s' % message\n            result[pack] = (desired, message)\n        return result\n\n    @staticmethod\n    def _is_desired_pack(abs_pack_path, pack_name):\n        # path has to exists.\n        if not os.path.exists(abs_pack_path):\n            return (False, 'Pack \"%s\" not found or it\\'s missing a \"pack.yaml\" file.' %\n                    (pack_name))\n        # Must be a dir.\n        if not os.path.isdir(abs_pack_path):\n            return (False, '%s is not a expected directory structure.' % (pack_name))\n        # should not include reserve characters\n        if PACK_RESERVE_CHARACTER in pack_name:\n            return (False, 'Pack name \"%s\" contains reserve character \"%s\"' %\n                    (pack_name, PACK_RESERVE_CHARACTER))\n        # must contain a manifest file. Empty file is ok for now.\n        if not os.path.isfile(os.path.join(abs_pack_path, MANIFEST_FILE)):\n            return (False, 'Pack is missing a manifest file (%s).' % (MANIFEST_FILE))\n        return (True, '')\n\n    @staticmethod\n    def _cleanup_repo(abs_local_path):\n        # basic lock checking etc?\n        if os.path.isdir(abs_local_path):\n            shutil.rmtree(abs_local_path)\n\n    @staticmethod\n    def _validate_result(result, packs, repo_url):\n        atleast_one_success = False\n        sanitized_result = {}\n        for k, v in six.iteritems(result):\n            atleast_one_success |= v[0]\n            sanitized_result[k] = v[1]\n\n        if not atleast_one_success:\n            message_list = []\n            message_list.append('No packs were downloaded from repository \"%s\".\\n' % (repo_url))\n            message_list.append('Errors:')\n\n            for pack, value in result.items():\n                success, error = value\n\n                if success:\n                    continue\n\n                message_list.append(' - %s: %s' % (pack, error))\n\n            message = '\\n'.join(message_list)\n            raise Exception(message)\n\n        return sanitized_result\n\n    @staticmethod\n    def _eval_subtree(repo_url, subtree):\n        match = False\n        for stackstorm_repo_name in STACKSTORM_CONTRIB_REPOS:\n            if stackstorm_repo_name in repo_url:\n                match = True\n                break\n\n        return subtree | match\n\n    @staticmethod\n    def _eval_repo_url(repo_url):\n        \"\"\"Allow passing short GitHub style URLs\"\"\"\n        has_git_extension = repo_url.endswith('.git')\n        if len(repo_url.split('/')) == 2 and \"git@\" not in repo_url:\n            url = \"https://github.com/{}\".format(repo_url)\n        else:\n            url = repo_url\n        return url if has_git_extension else \"{}.git\".format(url)\n\n    @staticmethod\n    def _tag_pack(pack_dir, packs, subtree):\n        \"\"\"Add git information to pack directory for retrieval later\"\"\"\n        for pack in packs:\n            repo = Repo(pack_dir)\n            payload = {\n                \"branch\": repo.active_branch.name,\n                \"ref\": repo.head.commit.hexsha\n            }\n\n            if subtree:\n                info_file = os.path.join(pack_dir, pack, GITINFO_FILE)\n            else:\n                info_file = os.path.join(pack_dir, GITINFO_FILE)\n\n            try:\n                gitinfo = open(info_file, \"w\")\n                gitinfo.write(json.dumps(payload))\n            finally:\n                gitinfo.close()\n", "path": "contrib/packs/actions/pack_mgmt/download.py"}], "after_files": [{"content": "# Licensed to the StackStorm, Inc ('StackStorm') under one or more\n# contributor license agreements.  See the NOTICE file distributed with\n# this work for additional information regarding copyright ownership.\n# The ASF licenses this file to You under the Apache License, Version 2.0\n# (the \"License\"); you may not use this file except in compliance with\n# the License.  You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport shutil\nimport hashlib\nimport re\nimport json\n\nimport six\nfrom git.repo import Repo\nfrom lockfile import LockFile\n\nfrom st2actions.runners.pythonrunner import Action\n\nALL_PACKS = '*'\nPACK_REPO_ROOT = 'packs'\nMANIFEST_FILE = 'pack.yaml'\nCONFIG_FILE = 'config.yaml'\nGITINFO_FILE = '.gitinfo'\nPACK_RESERVE_CHARACTER = '.'\n\nSTACKSTORM_CONTRIB_REPOS = [\n    'st2contrib',\n    'st2incubator'\n]\n\n\nclass DownloadGitRepoAction(Action):\n    def __init__(self, config=None):\n        super(DownloadGitRepoAction, self).__init__(config=config)\n        self._subtree = None\n        self._repo_url = None\n\n    def run(self, packs, repo_url, abs_repo_base, verifyssl=True, branch='master', subtree=False):\n        self._subtree = self._eval_subtree(repo_url, subtree)\n        self._repo_url = self._eval_repo_url(repo_url)\n\n        repo_name = self._repo_url[self._repo_url.rfind('/') + 1: self._repo_url.rfind('.')]\n        lock_name = hashlib.md5(repo_name).hexdigest() + '.lock'\n\n        with LockFile('/tmp/%s' % (lock_name)):\n            abs_local_path = self._clone_repo(repo_url=self._repo_url, verifyssl=verifyssl,\n                                              branch=branch)\n            try:\n                if self._subtree:\n                    # st2-contrib repo has a top-level packs folder that actually contains the\n                    pack_abs_local_path = os.path.join(abs_local_path, PACK_REPO_ROOT)\n                else:\n                    pack_abs_local_path = abs_local_path\n\n                self._tag_pack(pack_abs_local_path, packs, self._subtree)\n                result = self._move_packs(abs_repo_base, packs, pack_abs_local_path, self._subtree)\n            finally:\n                self._cleanup_repo(abs_local_path)\n        return self._validate_result(result=result, packs=packs, repo_url=repo_url)\n\n    @staticmethod\n    def _clone_repo(repo_url, verifyssl=True, branch='master'):\n        user_home = os.path.expanduser('~')\n        # Assuming git url is of form git@github.com:user/git-repo.git\n        repo_name = repo_url[repo_url.rfind('/') + 1: repo_url.rfind('.')]\n        abs_local_path = os.path.join(user_home, repo_name)\n\n        # Disable SSL cert checking if explictly asked\n        if not verifyssl:\n            os.environ['GIT_SSL_NO_VERIFY'] = 'true'\n        # Shallow clone the repo to avoid getting all the metadata. We only need HEAD of a\n        # specific branch so save some download time.\n        Repo.clone_from(repo_url, abs_local_path, branch=branch, depth=1)\n        return abs_local_path\n\n    def _move_packs(self, abs_repo_base, packs, abs_local_path, subtree):\n        result = {}\n        # all_packs should be removed as a pack with that name is not expected to be found.\n        if ALL_PACKS in packs:\n            packs = os.listdir(abs_local_path)\n        for pack in packs:\n            abs_pack_temp_location = os.path.join(abs_local_path, pack) if subtree else abs_local_path\n            desired, message = DownloadGitRepoAction._is_desired_pack(abs_pack_temp_location, pack)\n            if desired:\n                to = abs_repo_base\n                dest_pack_path = os.path.join(abs_repo_base, pack)\n                if os.path.exists(dest_pack_path):\n                    self.logger.debug('Removing existing pack %s in %s to replace.', pack,\n                                      dest_pack_path)\n\n                    # Ensure to preserve any existing configuration\n                    old_config_file = os.path.join(dest_pack_path, CONFIG_FILE)\n                    new_config_file = os.path.join(abs_pack_temp_location, CONFIG_FILE)\n\n                    if os.path.isfile(old_config_file):\n                        shutil.move(old_config_file, new_config_file)\n\n                    shutil.rmtree(dest_pack_path)\n\n                self.logger.debug('Moving pack from %s to %s.', abs_pack_temp_location, to)\n                shutil.move(abs_pack_temp_location, to)\n                message = 'Success.'\n            elif message:\n                message = 'Failure : %s' % message\n            result[pack] = (desired, message)\n        return result\n\n    @staticmethod\n    def _is_desired_pack(abs_pack_path, pack_name):\n        # path has to exists.\n        if not os.path.exists(abs_pack_path):\n            return (False, 'Pack \"%s\" not found or it\\'s missing a \"pack.yaml\" file.' %\n                    (pack_name))\n        # Must be a dir.\n        if not os.path.isdir(abs_pack_path):\n            return (False, '%s is not a expected directory structure.' % (pack_name))\n        # should not include reserve characters\n        if PACK_RESERVE_CHARACTER in pack_name:\n            return (False, 'Pack name \"%s\" contains reserve character \"%s\"' %\n                    (pack_name, PACK_RESERVE_CHARACTER))\n        # must contain a manifest file. Empty file is ok for now.\n        if not os.path.isfile(os.path.join(abs_pack_path, MANIFEST_FILE)):\n            return (False, 'Pack is missing a manifest file (%s).' % (MANIFEST_FILE))\n        return (True, '')\n\n    @staticmethod\n    def _cleanup_repo(abs_local_path):\n        # basic lock checking etc?\n        if os.path.isdir(abs_local_path):\n            shutil.rmtree(abs_local_path)\n\n    @staticmethod\n    def _validate_result(result, packs, repo_url):\n        atleast_one_success = False\n        sanitized_result = {}\n        for k, v in six.iteritems(result):\n            atleast_one_success |= v[0]\n            sanitized_result[k] = v[1]\n\n        if not atleast_one_success:\n            message_list = []\n            message_list.append('No packs were downloaded from repository \"%s\".\\n' % (repo_url))\n            message_list.append('Errors:')\n\n            for pack, value in result.items():\n                success, error = value\n\n                if success:\n                    continue\n\n                message_list.append(' - %s: %s' % (pack, error))\n\n            message = '\\n'.join(message_list)\n            raise Exception(message)\n\n        return sanitized_result\n\n    @staticmethod\n    def _eval_subtree(repo_url, subtree):\n        match = False\n        for stackstorm_repo_name in STACKSTORM_CONTRIB_REPOS:\n            if stackstorm_repo_name in repo_url:\n                match = True\n                break\n\n        return subtree | match\n\n    @staticmethod\n    def _eval_repo_url(repo_url):\n        \"\"\"Allow passing short GitHub style URLs\"\"\"\n        has_git_extension = repo_url.endswith('.git')\n        if len(repo_url.split('/')) == 2 and \"git@\" not in repo_url:\n            url = \"https://github.com/{}\".format(repo_url)\n        else:\n            url = repo_url\n        return url if has_git_extension else \"{}.git\".format(url)\n\n    @staticmethod\n    def _tag_pack(pack_dir, packs, subtree):\n        \"\"\"Add git information to pack directory for retrieval later\"\"\"\n        for pack in packs:\n            repo = Repo(pack_dir)\n            payload = {\n                \"branch\": repo.active_branch.name,\n                \"ref\": repo.head.commit.hexsha\n            }\n\n            if subtree:\n                info_file = os.path.join(pack_dir, pack, GITINFO_FILE)\n            else:\n                info_file = os.path.join(pack_dir, GITINFO_FILE)\n\n            try:\n                gitinfo = open(info_file, \"w\")\n                gitinfo.write(json.dumps(payload))\n            finally:\n                gitinfo.close()\n", "path": "contrib/packs/actions/pack_mgmt/download.py"}]}