vFW and vDNS support added to azure-plugin
[multicloud/azure.git] / azure / aria / aria-extension-cloudify / src / aria / aria / orchestrator / execution_plugin / ssh / operations.py
1 # Licensed to the Apache Software Foundation (ASF) 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 """
17 Utilities for running commands remotely over SSH.
18 """
19
20 import os
21 import random
22 import string
23 import tempfile
24 import StringIO
25
26 import fabric.api
27 import fabric.context_managers
28 import fabric.contrib.files
29
30 from .. import constants
31 from .. import exceptions
32 from .. import common
33 from .. import ctx_proxy
34 from . import tunnel
35
36
37 _PROXY_CLIENT_PATH = ctx_proxy.client.__file__
38 if _PROXY_CLIENT_PATH.endswith('.pyc'):
39     _PROXY_CLIENT_PATH = _PROXY_CLIENT_PATH[:-1]
40
41
42 def run_commands(ctx, commands, fabric_env, use_sudo, hide_output, **_):
43     """Runs the provider 'commands' in sequence
44
45     :param commands: a list of commands to run
46     :param fabric_env: fabric configuration
47     """
48     with fabric.api.settings(_hide_output(ctx, groups=hide_output),
49                              **_fabric_env(ctx, fabric_env, warn_only=True)):
50         for command in commands:
51             ctx.logger.info('Running command: {0}'.format(command))
52             run = fabric.api.sudo if use_sudo else fabric.api.run
53             result = run(command)
54             if result.failed:
55                 raise exceptions.ProcessException(
56                     command=result.command,
57                     exit_code=result.return_code,
58                     stdout=result.stdout,
59                     stderr=result.stderr)
60
61
62 def run_script(ctx, script_path, fabric_env, process, use_sudo, hide_output, **kwargs):
63     process = process or {}
64     paths = _Paths(base_dir=process.get('base_dir', constants.DEFAULT_BASE_DIR),
65                    local_script_path=common.download_script(ctx, script_path))
66     with fabric.api.settings(_hide_output(ctx, groups=hide_output),
67                              **_fabric_env(ctx, fabric_env, warn_only=False)):
68         # the remote host must have the ctx before running any fabric scripts
69         if not fabric.contrib.files.exists(paths.remote_ctx_path):
70             # there may be race conditions with other operations that
71             # may be running in parallel, so we pass -p to make sure
72             # we get 0 exit code if the directory already exists
73             fabric.api.run('mkdir -p {0} && mkdir -p {1}'.format(paths.remote_scripts_dir,
74                                                                  paths.remote_work_dir))
75             # this file has to be present before using ctx
76             fabric.api.put(_PROXY_CLIENT_PATH, paths.remote_ctx_path)
77         process = common.create_process_config(
78             script_path=paths.remote_script_path,
79             process=process,
80             operation_kwargs=kwargs,
81             quote_json_env_vars=True)
82         fabric.api.put(paths.local_script_path, paths.remote_script_path)
83         with ctx_proxy.server.CtxProxy(ctx, _patch_ctx) as proxy:
84             local_port = proxy.port
85             with fabric.context_managers.cd(process.get('cwd', paths.remote_work_dir)):  # pylint: disable=not-context-manager
86                 with tunnel.remote(ctx, local_port=local_port) as remote_port:
87                     local_socket_url = proxy.socket_url
88                     remote_socket_url = local_socket_url.replace(str(local_port), str(remote_port))
89                     env_script = _write_environment_script_file(
90                         process=process,
91                         paths=paths,
92                         local_socket_url=local_socket_url,
93                         remote_socket_url=remote_socket_url)
94                     fabric.api.put(env_script, paths.remote_env_script_path)
95                     try:
96                         command = 'source {0} && {1}'.format(paths.remote_env_script_path,
97                                                              process['command'])
98                         run = fabric.api.sudo if use_sudo else fabric.api.run
99                         run(command)
100                     except exceptions.TaskException:
101                         return common.check_error(ctx, reraise=True)
102             return common.check_error(ctx)
103
104
105 def _patch_ctx(ctx):
106     common.patch_ctx(ctx)
107     original_download_resource = ctx.download_resource
108     original_download_resource_and_render = ctx.download_resource_and_render
109
110     def _download_resource(func, destination, **kwargs):
111         handle, temp_local_path = tempfile.mkstemp()
112         os.close(handle)
113         try:
114             func(destination=temp_local_path, **kwargs)
115             return fabric.api.put(temp_local_path, destination)
116         finally:
117             os.remove(temp_local_path)
118
119     def download_resource(destination, path=None):
120         _download_resource(
121             func=original_download_resource,
122             destination=destination,
123             path=path)
124     ctx.download_resource = download_resource
125
126     def download_resource_and_render(destination, path=None, variables=None):
127         _download_resource(
128             func=original_download_resource_and_render,
129             destination=destination,
130             path=path,
131             variables=variables)
132     ctx.download_resource_and_render = download_resource_and_render
133
134
135 def _hide_output(ctx, groups):
136     """ Hides Fabric's output for every 'entity' in `groups` """
137     groups = set(groups or [])
138     if not groups.issubset(constants.VALID_FABRIC_GROUPS):
139         ctx.task.abort('`hide_output` must be a subset of {0} (Provided: {1})'
140                        .format(', '.join(constants.VALID_FABRIC_GROUPS), ', '.join(groups)))
141     return fabric.api.hide(*groups)
142
143
144 def _fabric_env(ctx, fabric_env, warn_only):
145     """Prepares fabric environment variables configuration"""
146     ctx.logger.debug('Preparing fabric environment...')
147     env = constants.FABRIC_ENV_DEFAULTS.copy()
148     env.update(fabric_env or {})
149     env.setdefault('warn_only', warn_only)
150     # validations
151     if (not env.get('host_string')) and (ctx.task) and (ctx.task.actor) and (ctx.task.actor.host):
152         env['host_string'] = ctx.task.actor.host.host_address
153     if not env.get('host_string'):
154         ctx.task.abort('`host_string` not supplied and ip cannot be deduced automatically')
155     if not (env.get('password') or env.get('key_filename') or env.get('key')):
156         ctx.task.abort(
157             'Access credentials not supplied '
158             '(you must supply at least one of `key_filename`, `key` or `password`)')
159     if not env.get('user'):
160         ctx.task.abort('`user` not supplied')
161     ctx.logger.debug('Environment prepared successfully')
162     return env
163
164
165 def _write_environment_script_file(process, paths, local_socket_url, remote_socket_url):
166     env_script = StringIO.StringIO()
167     env = process['env']
168     env['PATH'] = '{0}:$PATH'.format(paths.remote_ctx_dir)
169     env['PYTHONPATH'] = '{0}:$PYTHONPATH'.format(paths.remote_ctx_dir)
170     env_script.write('chmod +x {0}\n'.format(paths.remote_script_path))
171     env_script.write('chmod +x {0}\n'.format(paths.remote_ctx_path))
172     env.update({
173         ctx_proxy.client.CTX_SOCKET_URL: remote_socket_url,
174         'LOCAL_{0}'.format(ctx_proxy.client.CTX_SOCKET_URL): local_socket_url
175     })
176     for key, value in env.iteritems():
177         env_script.write('export {0}={1}\n'.format(key, value))
178     return env_script
179
180
181 class _Paths(object):
182
183     def __init__(self, base_dir, local_script_path):
184         self.local_script_path = local_script_path
185         self.remote_ctx_dir = base_dir
186         self.base_script_path = os.path.basename(self.local_script_path)
187         self.remote_ctx_path = '{0}/ctx'.format(self.remote_ctx_dir)
188         self.remote_scripts_dir = '{0}/scripts'.format(self.remote_ctx_dir)
189         self.remote_work_dir = '{0}/work'.format(self.remote_ctx_dir)
190         random_suffix = ''.join(random.choice(string.ascii_lowercase + string.digits)
191                                 for _ in range(8))
192         remote_path_suffix = '{0}-{1}'.format(self.base_script_path, random_suffix)
193         self.remote_env_script_path = '{0}/env-{1}'.format(self.remote_scripts_dir,
194                                                            remote_path_suffix)
195         self.remote_script_path = '{0}/{1}'.format(self.remote_scripts_dir, remote_path_suffix)