Statistics
| Branch: | Tag: | Revision:

root / lib / tools / prepare_node_join.py @ f712208d

History | View | Annotate | Download (8.9 kB)

1 d12b9f66 Michael Hanselmann
#
2 d12b9f66 Michael Hanselmann
#
3 d12b9f66 Michael Hanselmann
4 d12b9f66 Michael Hanselmann
# Copyright (C) 2012 Google Inc.
5 d12b9f66 Michael Hanselmann
#
6 d12b9f66 Michael Hanselmann
# This program is free software; you can redistribute it and/or modify
7 d12b9f66 Michael Hanselmann
# it under the terms of the GNU General Public License as published by
8 d12b9f66 Michael Hanselmann
# the Free Software Foundation; either version 2 of the License, or
9 d12b9f66 Michael Hanselmann
# (at your option) any later version.
10 d12b9f66 Michael Hanselmann
#
11 d12b9f66 Michael Hanselmann
# This program is distributed in the hope that it will be useful, but
12 d12b9f66 Michael Hanselmann
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 d12b9f66 Michael Hanselmann
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 d12b9f66 Michael Hanselmann
# General Public License for more details.
15 d12b9f66 Michael Hanselmann
#
16 d12b9f66 Michael Hanselmann
# You should have received a copy of the GNU General Public License
17 d12b9f66 Michael Hanselmann
# along with this program; if not, write to the Free Software
18 d12b9f66 Michael Hanselmann
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 d12b9f66 Michael Hanselmann
# 02110-1301, USA.
20 d12b9f66 Michael Hanselmann
21 d12b9f66 Michael Hanselmann
"""Script to prepare a node for joining a cluster.
22 d12b9f66 Michael Hanselmann

23 d12b9f66 Michael Hanselmann
"""
24 d12b9f66 Michael Hanselmann
25 d12b9f66 Michael Hanselmann
import os
26 d12b9f66 Michael Hanselmann
import os.path
27 d12b9f66 Michael Hanselmann
import optparse
28 d12b9f66 Michael Hanselmann
import sys
29 d12b9f66 Michael Hanselmann
import logging
30 d12b9f66 Michael Hanselmann
import errno
31 d12b9f66 Michael Hanselmann
import OpenSSL
32 d12b9f66 Michael Hanselmann
33 d12b9f66 Michael Hanselmann
from ganeti import cli
34 d12b9f66 Michael Hanselmann
from ganeti import constants
35 d12b9f66 Michael Hanselmann
from ganeti import errors
36 d12b9f66 Michael Hanselmann
from ganeti import pathutils
37 d12b9f66 Michael Hanselmann
from ganeti import utils
38 d12b9f66 Michael Hanselmann
from ganeti import serializer
39 d12b9f66 Michael Hanselmann
from ganeti import ht
40 d12b9f66 Michael Hanselmann
from ganeti import ssh
41 d12b9f66 Michael Hanselmann
from ganeti import ssconf
42 d12b9f66 Michael Hanselmann
43 d12b9f66 Michael Hanselmann
44 1facaf11 Michael Hanselmann
_SSH_KEY_LIST_ITEM = \
45 1facaf11 Michael Hanselmann
  ht.TAnd(ht.TIsLength(3),
46 1facaf11 Michael Hanselmann
          ht.TItems([
47 1facaf11 Michael Hanselmann
            ht.TElemOf(constants.SSHK_ALL),
48 1facaf11 Michael Hanselmann
            ht.Comment("public")(ht.TNonEmptyString),
49 1facaf11 Michael Hanselmann
            ht.Comment("private")(ht.TNonEmptyString),
50 1facaf11 Michael Hanselmann
          ]))
51 1facaf11 Michael Hanselmann
52 1facaf11 Michael Hanselmann
_SSH_KEY_LIST = ht.TListOf(_SSH_KEY_LIST_ITEM)
53 d12b9f66 Michael Hanselmann
54 d12b9f66 Michael Hanselmann
_DATA_CHECK = ht.TStrictDict(False, True, {
55 d12b9f66 Michael Hanselmann
  constants.SSHS_CLUSTER_NAME: ht.TNonEmptyString,
56 d12b9f66 Michael Hanselmann
  constants.SSHS_NODE_DAEMON_CERTIFICATE: ht.TNonEmptyString,
57 d12b9f66 Michael Hanselmann
  constants.SSHS_SSH_HOST_KEY: _SSH_KEY_LIST,
58 d12b9f66 Michael Hanselmann
  constants.SSHS_SSH_ROOT_KEY: _SSH_KEY_LIST,
59 d12b9f66 Michael Hanselmann
  })
60 d12b9f66 Michael Hanselmann
61 d12b9f66 Michael Hanselmann
62 d12b9f66 Michael Hanselmann
class JoinError(errors.GenericError):
63 d12b9f66 Michael Hanselmann
  """Local class for reporting errors.
64 d12b9f66 Michael Hanselmann

65 d12b9f66 Michael Hanselmann
  """
66 d12b9f66 Michael Hanselmann
67 d12b9f66 Michael Hanselmann
68 d12b9f66 Michael Hanselmann
def ParseOptions():
69 d12b9f66 Michael Hanselmann
  """Parses the options passed to the program.
70 d12b9f66 Michael Hanselmann

71 d12b9f66 Michael Hanselmann
  @return: Options and arguments
72 d12b9f66 Michael Hanselmann

73 d12b9f66 Michael Hanselmann
  """
74 d12b9f66 Michael Hanselmann
  program = os.path.basename(sys.argv[0])
75 d12b9f66 Michael Hanselmann
76 d12b9f66 Michael Hanselmann
  parser = optparse.OptionParser(usage="%prog [--dry-run]",
77 d12b9f66 Michael Hanselmann
                                 prog=program)
78 d12b9f66 Michael Hanselmann
  parser.add_option(cli.DEBUG_OPT)
79 d12b9f66 Michael Hanselmann
  parser.add_option(cli.VERBOSE_OPT)
80 d12b9f66 Michael Hanselmann
  parser.add_option(cli.DRY_RUN_OPT)
81 d12b9f66 Michael Hanselmann
82 d12b9f66 Michael Hanselmann
  (opts, args) = parser.parse_args()
83 d12b9f66 Michael Hanselmann
84 d12b9f66 Michael Hanselmann
  return VerifyOptions(parser, opts, args)
85 d12b9f66 Michael Hanselmann
86 d12b9f66 Michael Hanselmann
87 d12b9f66 Michael Hanselmann
def VerifyOptions(parser, opts, args):
88 d12b9f66 Michael Hanselmann
  """Verifies options and arguments for correctness.
89 d12b9f66 Michael Hanselmann

90 d12b9f66 Michael Hanselmann
  """
91 d12b9f66 Michael Hanselmann
  if args:
92 d12b9f66 Michael Hanselmann
    parser.error("No arguments are expected")
93 d12b9f66 Michael Hanselmann
94 d12b9f66 Michael Hanselmann
  return opts
95 d12b9f66 Michael Hanselmann
96 d12b9f66 Michael Hanselmann
97 d12b9f66 Michael Hanselmann
def SetupLogging(opts):
98 d12b9f66 Michael Hanselmann
  """Configures the logging module.
99 d12b9f66 Michael Hanselmann

100 d12b9f66 Michael Hanselmann
  """
101 d12b9f66 Michael Hanselmann
  formatter = logging.Formatter("%(asctime)s: %(message)s")
102 d12b9f66 Michael Hanselmann
103 d12b9f66 Michael Hanselmann
  stderr_handler = logging.StreamHandler()
104 d12b9f66 Michael Hanselmann
  stderr_handler.setFormatter(formatter)
105 d12b9f66 Michael Hanselmann
  if opts.debug:
106 d12b9f66 Michael Hanselmann
    stderr_handler.setLevel(logging.NOTSET)
107 d12b9f66 Michael Hanselmann
  elif opts.verbose:
108 d12b9f66 Michael Hanselmann
    stderr_handler.setLevel(logging.INFO)
109 d12b9f66 Michael Hanselmann
  else:
110 d12b9f66 Michael Hanselmann
    stderr_handler.setLevel(logging.WARNING)
111 d12b9f66 Michael Hanselmann
112 d12b9f66 Michael Hanselmann
  root_logger = logging.getLogger("")
113 d12b9f66 Michael Hanselmann
  root_logger.setLevel(logging.NOTSET)
114 d12b9f66 Michael Hanselmann
  root_logger.addHandler(stderr_handler)
115 d12b9f66 Michael Hanselmann
116 d12b9f66 Michael Hanselmann
117 d12b9f66 Michael Hanselmann
def _VerifyCertificate(cert, _noded_cert_file=pathutils.NODED_CERT_FILE):
118 d12b9f66 Michael Hanselmann
  """Verifies a certificate against the local node daemon certificate.
119 d12b9f66 Michael Hanselmann

120 d12b9f66 Michael Hanselmann
  @type cert: string
121 d12b9f66 Michael Hanselmann
  @param cert: Certificate in PEM format (no key)
122 d12b9f66 Michael Hanselmann

123 d12b9f66 Michael Hanselmann
  """
124 d12b9f66 Michael Hanselmann
  try:
125 d12b9f66 Michael Hanselmann
    OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, cert)
126 d12b9f66 Michael Hanselmann
  except OpenSSL.crypto.Error, err:
127 d12b9f66 Michael Hanselmann
    pass
128 d12b9f66 Michael Hanselmann
  else:
129 d12b9f66 Michael Hanselmann
    raise JoinError("No private key may be given")
130 d12b9f66 Michael Hanselmann
131 d12b9f66 Michael Hanselmann
  try:
132 d12b9f66 Michael Hanselmann
    cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
133 d12b9f66 Michael Hanselmann
  except Exception, err:
134 d12b9f66 Michael Hanselmann
    raise errors.X509CertError("(stdin)",
135 d12b9f66 Michael Hanselmann
                               "Unable to load certificate: %s" % err)
136 d12b9f66 Michael Hanselmann
137 d12b9f66 Michael Hanselmann
  try:
138 d12b9f66 Michael Hanselmann
    noded_pem = utils.ReadFile(_noded_cert_file)
139 d12b9f66 Michael Hanselmann
  except EnvironmentError, err:
140 d12b9f66 Michael Hanselmann
    if err.errno != errno.ENOENT:
141 d12b9f66 Michael Hanselmann
      raise
142 d12b9f66 Michael Hanselmann
143 d12b9f66 Michael Hanselmann
    logging.debug("Local node certificate was not found (file %s)",
144 d12b9f66 Michael Hanselmann
                  _noded_cert_file)
145 d12b9f66 Michael Hanselmann
    return
146 d12b9f66 Michael Hanselmann
147 d12b9f66 Michael Hanselmann
  try:
148 d12b9f66 Michael Hanselmann
    key = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, noded_pem)
149 d12b9f66 Michael Hanselmann
  except Exception, err:
150 d12b9f66 Michael Hanselmann
    raise errors.X509CertError(_noded_cert_file,
151 d12b9f66 Michael Hanselmann
                               "Unable to load private key: %s" % err)
152 d12b9f66 Michael Hanselmann
153 d12b9f66 Michael Hanselmann
  ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
154 d12b9f66 Michael Hanselmann
  ctx.use_privatekey(key)
155 d12b9f66 Michael Hanselmann
  ctx.use_certificate(cert)
156 d12b9f66 Michael Hanselmann
  try:
157 d12b9f66 Michael Hanselmann
    ctx.check_privatekey()
158 d12b9f66 Michael Hanselmann
  except OpenSSL.SSL.Error:
159 d12b9f66 Michael Hanselmann
    raise JoinError("Given cluster certificate does not match local key")
160 d12b9f66 Michael Hanselmann
161 d12b9f66 Michael Hanselmann
162 d12b9f66 Michael Hanselmann
def VerifyCertificate(data, _verify_fn=_VerifyCertificate):
163 d12b9f66 Michael Hanselmann
  """Verifies cluster certificate.
164 d12b9f66 Michael Hanselmann

165 d12b9f66 Michael Hanselmann
  @type data: dict
166 d12b9f66 Michael Hanselmann

167 d12b9f66 Michael Hanselmann
  """
168 d12b9f66 Michael Hanselmann
  cert = data.get(constants.SSHS_NODE_DAEMON_CERTIFICATE)
169 d12b9f66 Michael Hanselmann
  if cert:
170 d12b9f66 Michael Hanselmann
    _verify_fn(cert)
171 d12b9f66 Michael Hanselmann
172 d12b9f66 Michael Hanselmann
173 d12b9f66 Michael Hanselmann
def _VerifyClusterName(name, _ss_cluster_name_file=None):
174 d12b9f66 Michael Hanselmann
  """Verifies cluster name against a local cluster name.
175 d12b9f66 Michael Hanselmann

176 d12b9f66 Michael Hanselmann
  @type name: string
177 d12b9f66 Michael Hanselmann
  @param name: Cluster name
178 d12b9f66 Michael Hanselmann

179 d12b9f66 Michael Hanselmann
  """
180 d12b9f66 Michael Hanselmann
  if _ss_cluster_name_file is None:
181 d12b9f66 Michael Hanselmann
    _ss_cluster_name_file = \
182 d12b9f66 Michael Hanselmann
      ssconf.SimpleStore().KeyToFilename(constants.SS_CLUSTER_NAME)
183 d12b9f66 Michael Hanselmann
184 d12b9f66 Michael Hanselmann
  try:
185 d12b9f66 Michael Hanselmann
    local_name = utils.ReadOneLineFile(_ss_cluster_name_file)
186 d12b9f66 Michael Hanselmann
  except EnvironmentError, err:
187 d12b9f66 Michael Hanselmann
    if err.errno != errno.ENOENT:
188 d12b9f66 Michael Hanselmann
      raise
189 d12b9f66 Michael Hanselmann
190 d12b9f66 Michael Hanselmann
    logging.debug("Local cluster name was not found (file %s)",
191 d12b9f66 Michael Hanselmann
                  _ss_cluster_name_file)
192 d12b9f66 Michael Hanselmann
  else:
193 d12b9f66 Michael Hanselmann
    if name != local_name:
194 d12b9f66 Michael Hanselmann
      raise JoinError("Current cluster name is '%s'" % local_name)
195 d12b9f66 Michael Hanselmann
196 d12b9f66 Michael Hanselmann
197 d12b9f66 Michael Hanselmann
def VerifyClusterName(data, _verify_fn=_VerifyClusterName):
198 d12b9f66 Michael Hanselmann
  """Verifies cluster name.
199 d12b9f66 Michael Hanselmann

200 d12b9f66 Michael Hanselmann
  @type data: dict
201 d12b9f66 Michael Hanselmann

202 d12b9f66 Michael Hanselmann
  """
203 d12b9f66 Michael Hanselmann
  name = data.get(constants.SSHS_CLUSTER_NAME)
204 d12b9f66 Michael Hanselmann
  if name:
205 d12b9f66 Michael Hanselmann
    _verify_fn(name)
206 d12b9f66 Michael Hanselmann
  else:
207 d12b9f66 Michael Hanselmann
    raise JoinError("Cluster name must be specified")
208 d12b9f66 Michael Hanselmann
209 d12b9f66 Michael Hanselmann
210 d12b9f66 Michael Hanselmann
def _UpdateKeyFiles(keys, dry_run, keyfiles):
211 d12b9f66 Michael Hanselmann
  """Updates SSH key files.
212 d12b9f66 Michael Hanselmann

213 d12b9f66 Michael Hanselmann
  @type keys: sequence of tuple; (string, string, string)
214 d12b9f66 Michael Hanselmann
  @param keys: Keys to write, tuples consist of key type
215 d12b9f66 Michael Hanselmann
    (L{constants.SSHK_ALL}), public and private key
216 d12b9f66 Michael Hanselmann
  @type dry_run: boolean
217 d12b9f66 Michael Hanselmann
  @param dry_run: Whether to perform a dry run
218 d12b9f66 Michael Hanselmann
  @type keyfiles: dict; (string as key, tuple with (string, string) as values)
219 d12b9f66 Michael Hanselmann
  @param keyfiles: Mapping from key types (L{constants.SSHK_ALL}) to file
220 d12b9f66 Michael Hanselmann
    names; value tuples consist of public key filename and private key filename
221 d12b9f66 Michael Hanselmann

222 d12b9f66 Michael Hanselmann
  """
223 d12b9f66 Michael Hanselmann
  assert set(keyfiles) == constants.SSHK_ALL
224 d12b9f66 Michael Hanselmann
225 340ae7da Michael Hanselmann
  for (kind, private_key, public_key) in keys:
226 340ae7da Michael Hanselmann
    (private_file, public_file) = keyfiles[kind]
227 d12b9f66 Michael Hanselmann
228 d12b9f66 Michael Hanselmann
    logging.debug("Writing %s ...", private_file)
229 d12b9f66 Michael Hanselmann
    utils.WriteFile(private_file, data=private_key, mode=0600,
230 d12b9f66 Michael Hanselmann
                    backup=True, dry_run=dry_run)
231 d12b9f66 Michael Hanselmann
232 340ae7da Michael Hanselmann
    logging.debug("Writing %s ...", public_file)
233 340ae7da Michael Hanselmann
    utils.WriteFile(public_file, data=public_key, mode=0644,
234 340ae7da Michael Hanselmann
                    backup=True, dry_run=dry_run)
235 340ae7da Michael Hanselmann
236 d12b9f66 Michael Hanselmann
237 d12b9f66 Michael Hanselmann
def UpdateSshDaemon(data, dry_run, _runcmd_fn=utils.RunCmd,
238 d12b9f66 Michael Hanselmann
                    _keyfiles=None):
239 d12b9f66 Michael Hanselmann
  """Updates SSH daemon's keys.
240 d12b9f66 Michael Hanselmann

241 d12b9f66 Michael Hanselmann
  Unless C{dry_run} is set, the daemon is restarted at the end.
242 d12b9f66 Michael Hanselmann

243 d12b9f66 Michael Hanselmann
  @type data: dict
244 d12b9f66 Michael Hanselmann
  @param data: Input data
245 d12b9f66 Michael Hanselmann
  @type dry_run: boolean
246 d12b9f66 Michael Hanselmann
  @param dry_run: Whether to perform a dry run
247 d12b9f66 Michael Hanselmann

248 d12b9f66 Michael Hanselmann
  """
249 d12b9f66 Michael Hanselmann
  keys = data.get(constants.SSHS_SSH_HOST_KEY)
250 d12b9f66 Michael Hanselmann
  if not keys:
251 d12b9f66 Michael Hanselmann
    return
252 d12b9f66 Michael Hanselmann
253 d12b9f66 Michael Hanselmann
  if _keyfiles is None:
254 ebae9e37 Michael Hanselmann
    _keyfiles = constants.SSH_DAEMON_KEYFILES
255 d12b9f66 Michael Hanselmann
256 d12b9f66 Michael Hanselmann
  logging.info("Updating SSH daemon key files")
257 d12b9f66 Michael Hanselmann
  _UpdateKeyFiles(keys, dry_run, _keyfiles)
258 d12b9f66 Michael Hanselmann
259 d12b9f66 Michael Hanselmann
  if dry_run:
260 d12b9f66 Michael Hanselmann
    logging.info("This is a dry run, not restarting SSH daemon")
261 d12b9f66 Michael Hanselmann
  else:
262 d12b9f66 Michael Hanselmann
    result = _runcmd_fn([pathutils.DAEMON_UTIL, "reload-ssh-keys"],
263 d12b9f66 Michael Hanselmann
                        interactive=True)
264 d12b9f66 Michael Hanselmann
    if result.failed:
265 d12b9f66 Michael Hanselmann
      raise JoinError("Could not reload SSH keys, command '%s'"
266 d12b9f66 Michael Hanselmann
                      " had exitcode %s and error %s" %
267 d12b9f66 Michael Hanselmann
                       (result.cmd, result.exit_code, result.output))
268 d12b9f66 Michael Hanselmann
269 d12b9f66 Michael Hanselmann
270 d12b9f66 Michael Hanselmann
def UpdateSshRoot(data, dry_run, _homedir_fn=None):
271 d12b9f66 Michael Hanselmann
  """Updates root's SSH keys.
272 d12b9f66 Michael Hanselmann

273 d12b9f66 Michael Hanselmann
  Root's C{authorized_keys} file is also updated with new public keys.
274 d12b9f66 Michael Hanselmann

275 d12b9f66 Michael Hanselmann
  @type data: dict
276 d12b9f66 Michael Hanselmann
  @param data: Input data
277 d12b9f66 Michael Hanselmann
  @type dry_run: boolean
278 d12b9f66 Michael Hanselmann
  @param dry_run: Whether to perform a dry run
279 d12b9f66 Michael Hanselmann

280 d12b9f66 Michael Hanselmann
  """
281 d12b9f66 Michael Hanselmann
  keys = data.get(constants.SSHS_SSH_ROOT_KEY)
282 d12b9f66 Michael Hanselmann
  if not keys:
283 d12b9f66 Michael Hanselmann
    return
284 d12b9f66 Michael Hanselmann
285 f712208d Michael Hanselmann
  (auth_keys_file, keyfiles) = \
286 f712208d Michael Hanselmann
    ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=True,
287 f712208d Michael Hanselmann
                        _homedir_fn=_homedir_fn)
288 f712208d Michael Hanselmann
289 f712208d Michael Hanselmann
  _UpdateKeyFiles(keys, dry_run, keyfiles)
290 d12b9f66 Michael Hanselmann
291 d12b9f66 Michael Hanselmann
  if dry_run:
292 d12b9f66 Michael Hanselmann
    logging.info("This is a dry run, not modifying %s", auth_keys_file)
293 d12b9f66 Michael Hanselmann
  else:
294 910ef222 Michael Hanselmann
    for (_, _, public_key) in keys:
295 910ef222 Michael Hanselmann
      utils.AddAuthorizedKey(auth_keys_file, public_key)
296 d12b9f66 Michael Hanselmann
297 d12b9f66 Michael Hanselmann
298 d12b9f66 Michael Hanselmann
def LoadData(raw):
299 d12b9f66 Michael Hanselmann
  """Parses and verifies input data.
300 d12b9f66 Michael Hanselmann

301 d12b9f66 Michael Hanselmann
  @rtype: dict
302 d12b9f66 Michael Hanselmann

303 d12b9f66 Michael Hanselmann
  """
304 d12b9f66 Michael Hanselmann
  try:
305 d12b9f66 Michael Hanselmann
    data = serializer.LoadJson(raw)
306 d12b9f66 Michael Hanselmann
  except Exception, err:
307 d12b9f66 Michael Hanselmann
    raise errors.ParseError("Can't parse input data: %s" % err)
308 d12b9f66 Michael Hanselmann
309 d12b9f66 Michael Hanselmann
  if not _DATA_CHECK(data):
310 d12b9f66 Michael Hanselmann
    raise errors.ParseError("Input data does not match expected format: %s" %
311 d12b9f66 Michael Hanselmann
                            _DATA_CHECK)
312 d12b9f66 Michael Hanselmann
313 d12b9f66 Michael Hanselmann
  return data
314 d12b9f66 Michael Hanselmann
315 d12b9f66 Michael Hanselmann
316 d12b9f66 Michael Hanselmann
def Main():
317 d12b9f66 Michael Hanselmann
  """Main routine.
318 d12b9f66 Michael Hanselmann

319 d12b9f66 Michael Hanselmann
  """
320 d12b9f66 Michael Hanselmann
  opts = ParseOptions()
321 d12b9f66 Michael Hanselmann
322 d12b9f66 Michael Hanselmann
  SetupLogging(opts)
323 d12b9f66 Michael Hanselmann
324 d12b9f66 Michael Hanselmann
  try:
325 d12b9f66 Michael Hanselmann
    data = LoadData(sys.stdin.read())
326 d12b9f66 Michael Hanselmann
327 d12b9f66 Michael Hanselmann
    # Check if input data is correct
328 d12b9f66 Michael Hanselmann
    VerifyClusterName(data)
329 d12b9f66 Michael Hanselmann
    VerifyCertificate(data)
330 d12b9f66 Michael Hanselmann
331 d12b9f66 Michael Hanselmann
    # Update SSH files
332 d12b9f66 Michael Hanselmann
    UpdateSshDaemon(data, opts.dry_run)
333 d12b9f66 Michael Hanselmann
    UpdateSshRoot(data, opts.dry_run)
334 d12b9f66 Michael Hanselmann
335 d12b9f66 Michael Hanselmann
    logging.info("Setup finished successfully")
336 d12b9f66 Michael Hanselmann
  except Exception, err: # pylint: disable=W0703
337 d12b9f66 Michael Hanselmann
    logging.debug("Caught unhandled exception", exc_info=True)
338 d12b9f66 Michael Hanselmann
339 d12b9f66 Michael Hanselmann
    (retcode, message) = cli.FormatError(err)
340 d12b9f66 Michael Hanselmann
    logging.error(message)
341 d12b9f66 Michael Hanselmann
342 d12b9f66 Michael Hanselmann
    return retcode
343 d12b9f66 Michael Hanselmann
  else:
344 d12b9f66 Michael Hanselmann
    return constants.EXIT_SUCCESS