~tribaal/+junk/landscape-client-14.12-0ubuntu0.10.04

« back to all changes in this revision

Viewing changes to landscape/configuration.py

  • Committer: Chris Glass
  • Date: 2014-12-15 06:54:28 UTC
  • Revision ID: chris.glass@canonical.com-20141215065428-e23g6yyvrsvyb656
Imported pristine tarball.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""Interactive configuration support for Landscape.
 
2
 
 
3
This module, and specifically L{LandscapeSetupScript}, implements the support
 
4
for the C{landscape-config} script.
 
5
"""
 
6
 
 
7
import base64
 
8
import time
 
9
import sys
 
10
import os
 
11
import getpass
 
12
import pwd
 
13
 
 
14
from StringIO import StringIO
 
15
 
 
16
from landscape.lib.tag import is_valid_tag
 
17
 
 
18
from landscape.sysvconfig import SysVConfig, ProcessError
 
19
from landscape.lib.amp import MethodCallError
 
20
from landscape.lib.twisted_util import gather_results
 
21
from landscape.lib.fetch import fetch, FetchError
 
22
from landscape.lib.bootstrap import BootstrapList, BootstrapDirectory
 
23
from landscape.reactor import LandscapeReactor
 
24
from landscape.broker.registration import InvalidCredentialsError
 
25
from landscape.broker.config import BrokerConfiguration
 
26
from landscape.broker.amp import RemoteBrokerConnector
 
27
 
 
28
 
 
29
class ConfigurationError(Exception):
 
30
    """Raised when required configuration values are missing."""
 
31
 
 
32
 
 
33
class ImportOptionError(ConfigurationError):
 
34
    """Raised when there are issues with handling the --import option."""
 
35
 
 
36
 
 
37
def print_text(text, end="\n", error=False):
 
38
    if error:
 
39
        stream = sys.stderr
 
40
    else:
 
41
        stream = sys.stdout
 
42
    stream.write(text + end)
 
43
    stream.flush()
 
44
 
 
45
 
 
46
def get_invalid_users(users):
 
47
    """
 
48
    Process a string with a list of comma separated usernames, this returns
 
49
    any usernames not known to the underlying user database.
 
50
    """
 
51
    if users is not None:
 
52
        user_list = [user.strip() for user in users.split(",")]
 
53
        if "ALL" in user_list:
 
54
            if len(user_list) > 1:
 
55
                raise ConfigurationError(
 
56
                    "Extra users specified with ALL users")
 
57
            user_list.remove("ALL")
 
58
        invalid_users = []
 
59
        for user in user_list:
 
60
            try:
 
61
                pwd.getpwnam(user)
 
62
            except KeyError:
 
63
                invalid_users.append(user)
 
64
        return invalid_users
 
65
 
 
66
 
 
67
class LandscapeSetupConfiguration(BrokerConfiguration):
 
68
 
 
69
    unsaved_options = ("no_start", "disable", "silent", "ok_no_register",
 
70
                       "import_from")
 
71
 
 
72
    def _load_external_options(self):
 
73
        """Handle the --import parameter.
 
74
 
 
75
        Imported options behave as if they were passed in the
 
76
        command line, with precedence being given to real command
 
77
        line options.
 
78
        """
 
79
        if self.import_from:
 
80
            parser = None
 
81
 
 
82
            try:
 
83
                if "://" in self.import_from:
 
84
                    # If it's from a URL, download it now.
 
85
                    if self.http_proxy:
 
86
                        os.environ["http_proxy"] = self.http_proxy
 
87
                    if self.https_proxy:
 
88
                        os.environ["https_proxy"] = self.https_proxy
 
89
                    content = self.fetch_import_url(self.import_from)
 
90
                    parser = self._get_config_object(
 
91
                        alternative_config=StringIO(content))
 
92
                elif not os.path.isfile(self.import_from):
 
93
                    raise ImportOptionError("File %s doesn't exist." %
 
94
                                            self.import_from)
 
95
                else:
 
96
                    try:
 
97
                        parser = self._get_config_object(
 
98
                            alternative_config=self.import_from)
 
99
                    except:
 
100
                        raise ImportOptionError(
 
101
                            "Couldn't read configuration from %s." %
 
102
                            self.import_from)
 
103
            except Exception, error:
 
104
                raise ImportOptionError(str(error))
 
105
 
 
106
            # But real command line options have precedence.
 
107
            options = None
 
108
            if parser and self.config_section in parser:
 
109
                options = parser[self.config_section]
 
110
            if not options:
 
111
                raise ImportOptionError("Nothing to import at %s." %
 
112
                                        self.import_from)
 
113
            options.update(self._command_line_options)
 
114
            self._command_line_options = options
 
115
 
 
116
    def fetch_import_url(self, url):
 
117
        """Handle fetching of URLs passed to --url."""
 
118
 
 
119
        print_text("Fetching configuration from %s..." % url)
 
120
        error_message = None
 
121
        try:
 
122
            content = fetch(url)
 
123
        except FetchError, error:
 
124
            error_message = str(error)
 
125
        if error_message is not None:
 
126
            raise ImportOptionError(
 
127
                "Couldn't download configuration from %s: %s" %
 
128
                (url, error_message))
 
129
        return content
 
130
 
 
131
    def make_parser(self):
 
132
        """
 
133
        Specialize the parser, adding configure-specific options.
 
134
        """
 
135
        parser = super(LandscapeSetupConfiguration, self).make_parser()
 
136
 
 
137
        parser.add_option("--import", dest="import_from",
 
138
                          metavar="FILENAME_OR_URL",
 
139
                          help="Filename or URL to import configuration from. "
 
140
                               "Imported options behave as if they were "
 
141
                               "passed in the command line, with precedence "
 
142
                               "being given to real command line options.")
 
143
        parser.add_option("--script-users", metavar="USERS",
 
144
                          help="A comma-separated list of users to allow "
 
145
                               "scripts to run.  To allow scripts to be run "
 
146
                               "by any user, enter: ALL")
 
147
        parser.add_option("--include-manager-plugins", metavar="PLUGINS",
 
148
                          default="",
 
149
                          help="A comma-separated list of manager plugins to "
 
150
                               "load.")
 
151
        parser.add_option("-n", "--no-start", action="store_true",
 
152
                          help="Don't start the client automatically.")
 
153
        parser.add_option("--ok-no-register", action="store_true",
 
154
                          help="Return exit code 0 instead of 2 if the client "
 
155
                          "can't be registered.")
 
156
        parser.add_option("--silent", action="store_true", default=False,
 
157
                          help="Run without manual interaction.")
 
158
        parser.add_option("--disable", action="store_true", default=False,
 
159
                          help="Stop running clients and disable start at "
 
160
                               "boot.")
 
161
        parser.add_option("--init", action="store_true", default=False,
 
162
                          help="Set up the client directories structure "
 
163
                               "and exit.")
 
164
        return parser
 
165
 
 
166
 
 
167
class LandscapeSetupScript(object):
 
168
    """
 
169
    An interactive procedure which manages the prompting and temporary storage
 
170
    of configuration parameters.
 
171
 
 
172
    Various attributes on this object will be set on C{config} after L{run} is
 
173
    called.
 
174
 
 
175
    @ivar config: The L{BrokerConfiguration} object to read and set values from
 
176
        and to.
 
177
    """
 
178
 
 
179
    def __init__(self, config):
 
180
        self.config = config
 
181
 
 
182
    def show_help(self, text):
 
183
        lines = text.strip().splitlines()
 
184
        print_text("\n" + "".join([line.strip() + "\n" for line in lines]))
 
185
 
 
186
    def prompt_get_input(self, msg, required):
 
187
        """Prompt the user on the terminal for a value
 
188
 
 
189
        @param msg: Message to prompt user with
 
190
        @param required: True if value must be entered
 
191
        """
 
192
        while True:
 
193
            value = raw_input(msg)
 
194
            if value:
 
195
                return value
 
196
            elif not required:
 
197
                break
 
198
            self.show_help("This option is required to configure Landscape.")
 
199
 
 
200
    def prompt(self, option, msg, required=False):
 
201
        """Prompt the user on the terminal for a value.
 
202
 
 
203
        @param option: The attribute of C{self.config} that contains the
 
204
            default and which the value will be assigned to.
 
205
        @param msg: The message to prompt the user with (via C{raw_input}).
 
206
        @param required: If True, the user will be required to enter a value
 
207
            before continuing.
 
208
        """
 
209
        default = getattr(self.config, option, None)
 
210
        if default:
 
211
            msg += " [%s]: " % default
 
212
        else:
 
213
            msg += ": "
 
214
        required = required and not (bool(default))
 
215
        result = self.prompt_get_input(msg, required)
 
216
        if result:
 
217
            setattr(self.config, option, result)
 
218
 
 
219
    def password_prompt(self, option, msg, required=False):
 
220
        """Prompt the user on the terminal for a password and mask the value.
 
221
 
 
222
        This also prompts the user twice and errors if both values don't match.
 
223
 
 
224
        @param option: The attribute of C{self.config} that contains the
 
225
            default and which the value will be assigned to.
 
226
        @param msg: The message to prompt the user with (via C{raw_input}).
 
227
        @param required: If True, the user will be required to enter a value
 
228
            before continuing.
 
229
        """
 
230
        default = getattr(self.config, option, None)
 
231
        msg += ": "
 
232
        while True:
 
233
            value = getpass.getpass(msg)
 
234
            if value:
 
235
                value2 = getpass.getpass("Please confirm: ")
 
236
            if value:
 
237
                if value != value2:
 
238
                    self.show_help("Keys must match.")
 
239
                else:
 
240
                    setattr(self.config, option, value)
 
241
                    break
 
242
            elif default or not required:
 
243
                break
 
244
            else:
 
245
                self.show_help("This option is required to configure "
 
246
                               "Landscape.")
 
247
 
 
248
    def prompt_yes_no(self, message, default=True):
 
249
        if default:
 
250
            default_msg = " [Y/n]"
 
251
        else:
 
252
            default_msg = " [y/N]"
 
253
        while True:
 
254
            value = raw_input(message + default_msg).lower()
 
255
            if value:
 
256
                if value.startswith("n"):
 
257
                    return False
 
258
                if value.startswith("y"):
 
259
                    return True
 
260
                self.show_help("Invalid input.")
 
261
            else:
 
262
                return default
 
263
 
 
264
    def query_computer_title(self):
 
265
        if "computer_title" in self.config.get_command_line_options():
 
266
            return
 
267
 
 
268
        self.show_help(
 
269
            """
 
270
            The computer title you provide will be used to represent this
 
271
            computer in the Landscape user interface. It's important to use
 
272
            a title that will allow the system to be easily recognized when
 
273
            it appears on the pending computers page.
 
274
            """)
 
275
 
 
276
        self.prompt("computer_title", "This computer's title", True)
 
277
 
 
278
    def query_account_name(self):
 
279
        if "account_name" in self.config.get_command_line_options():
 
280
            return
 
281
 
 
282
        self.show_help(
 
283
            """
 
284
            You must now specify the name of the Landscape account you
 
285
            want to register this computer with. Your account name is shown
 
286
            under 'Account name' at https://landscape.canonical.com .
 
287
            """)
 
288
 
 
289
        self.prompt("account_name", "Account name", True)
 
290
 
 
291
    def query_registration_key(self):
 
292
        command_line_options = self.config.get_command_line_options()
 
293
        if "registration_key" in command_line_options:
 
294
            return
 
295
 
 
296
        self.show_help(
 
297
            """
 
298
            A registration key may be associated with your Landscape
 
299
            account to prevent unauthorized registration attempts.  This
 
300
            is not your personal login password.  It is optional, and unless
 
301
            explicitly set on the server, it may be skipped here.
 
302
 
 
303
            If you don't remember the registration key you can find it
 
304
            at https://landscape.canonical.com/account/%s
 
305
            """ % self.config.account_name)
 
306
 
 
307
        self.password_prompt("registration_key",
 
308
                             "Account registration key")
 
309
 
 
310
    def query_proxies(self):
 
311
        options = self.config.get_command_line_options()
 
312
        if "http_proxy" in options and "https_proxy" in options:
 
313
            return
 
314
 
 
315
        self.show_help(
 
316
            """
 
317
            The Landscape client communicates with the server over HTTP and
 
318
            HTTPS.  If your network requires you to use a proxy to access HTTP
 
319
            and/or HTTPS web sites, please provide the address of these
 
320
            proxies now.  If you don't use a proxy, leave these fields empty.
 
321
            """)
 
322
 
 
323
        if not "http_proxy" in options:
 
324
            self.prompt("http_proxy", "HTTP proxy URL")
 
325
        if not "https_proxy" in options:
 
326
            self.prompt("https_proxy", "HTTPS proxy URL")
 
327
 
 
328
    def query_script_plugin(self):
 
329
        options = self.config.get_command_line_options()
 
330
        if "include_manager_plugins" in options and "script_users" in options:
 
331
            invalid_users = get_invalid_users(options["script_users"])
 
332
            if invalid_users:
 
333
                raise ConfigurationError("Unknown system users: %s" %
 
334
                                         ", ".join(invalid_users))
 
335
            return
 
336
        self.show_help(
 
337
            """
 
338
            Landscape has a feature which enables administrators to run
 
339
            arbitrary scripts on machines under their control. By default this
 
340
            feature is disabled in the client, disallowing any arbitrary script
 
341
            execution. If enabled, the set of users that scripts may run as is
 
342
            also configurable.
 
343
            """)
 
344
        msg = "Enable script execution?"
 
345
        included_plugins = [
 
346
            p.strip() for p in self.config.include_manager_plugins.split(",")]
 
347
        if included_plugins == [""]:
 
348
            included_plugins = []
 
349
        default = "ScriptExecution" in included_plugins
 
350
        if self.prompt_yes_no(msg, default=default):
 
351
            if "ScriptExecution" not in included_plugins:
 
352
                included_plugins.append("ScriptExecution")
 
353
            self.show_help(
 
354
                """
 
355
                By default, scripts are restricted to the 'landscape' and
 
356
                'nobody' users. Please enter a comma-delimited list of users
 
357
                that scripts will be restricted to. To allow scripts to be run
 
358
                by any user, enter "ALL".
 
359
                """)
 
360
            while True:
 
361
                self.prompt("script_users", "Script users")
 
362
                invalid_users = get_invalid_users(
 
363
                    self.config.script_users)
 
364
                if not invalid_users:
 
365
                    break
 
366
                else:
 
367
                    self.show_help("Unknown system users: %s" %
 
368
                                   ",".join(invalid_users))
 
369
                    self.config.script_users = None
 
370
        else:
 
371
            if "ScriptExecution" in included_plugins:
 
372
                included_plugins.remove("ScriptExecution")
 
373
        self.config.include_manager_plugins = ", ".join(included_plugins)
 
374
 
 
375
    def query_access_group(self):
 
376
        """Query access group from the user."""
 
377
        options = self.config.get_command_line_options()
 
378
        if "access_group" in options:
 
379
            return  # an access group is already provided, don't ask for one
 
380
 
 
381
        self.show_help("You may provide an access group for this computer "
 
382
                       "e.g. webservers.")
 
383
        self.prompt("access_group", "Access group", False)
 
384
 
 
385
    def _get_invalid_tags(self, tagnames):
 
386
        """
 
387
        Splits a string on , and checks the validity of each tag, returns any
 
388
        invalid tags.
 
389
        """
 
390
        invalid_tags = []
 
391
        if tagnames:
 
392
            tags = [tag.strip() for tag in tagnames.split(",")]
 
393
            invalid_tags = [tag for tag in tags if not is_valid_tag(tag)]
 
394
        return invalid_tags
 
395
 
 
396
    def query_tags(self):
 
397
        """Query tags from the user."""
 
398
        options = self.config.get_command_line_options()
 
399
        if "tags" in options:
 
400
            invalid_tags = self._get_invalid_tags(options["tags"])
 
401
            if invalid_tags:
 
402
                raise ConfigurationError("Invalid tags: %s" %
 
403
                                         ", ".join(invalid_tags))
 
404
            return
 
405
 
 
406
        self.show_help("You may provide tags for this computer e.g. "
 
407
                       "server,precise.")
 
408
        while True:
 
409
            self.prompt("tags", "Tags", False)
 
410
            if self._get_invalid_tags(self.config.tags):
 
411
                self.show_help("Tag names may only contain alphanumeric "
 
412
                              "characters.")
 
413
                self.config.tags = None  # Reset for the next prompt
 
414
            else:
 
415
                break
 
416
 
 
417
    def show_header(self):
 
418
        self.show_help(
 
419
            """
 
420
            This script will interactively set up the Landscape client. It will
 
421
            ask you a few questions about this computer and your Landscape
 
422
            account, and will submit that information to the Landscape server.
 
423
            After this computer is registered it will need to be approved by an
 
424
            account administrator on the pending computers page.
 
425
 
 
426
            Please see https://landscape.canonical.com for more information.
 
427
            """)
 
428
 
 
429
    def run(self):
 
430
        """Kick off the interactive process which prompts the user for data.
 
431
 
 
432
        Data will be saved to C{self.config}.
 
433
        """
 
434
        self.show_header()
 
435
        self.query_computer_title()
 
436
        self.query_account_name()
 
437
        self.query_registration_key()
 
438
        self.query_proxies()
 
439
        self.query_script_plugin()
 
440
        self.query_access_group()
 
441
        self.query_tags()
 
442
 
 
443
 
 
444
def setup_init_script_and_start_client():
 
445
    "Configure the init script to start the client on boot."
 
446
    # XXX This function is misnamed; it doesn't start the client.
 
447
    sysvconfig = SysVConfig()
 
448
    sysvconfig.set_start_on_boot(True)
 
449
 
 
450
 
 
451
def stop_client_and_disable_init_script():
 
452
    """
 
453
    Stop landscape-client and change configuration to prevent starting
 
454
    landscape-client on boot.
 
455
    """
 
456
    sysvconfig = SysVConfig()
 
457
    sysvconfig.stop_landscape()
 
458
    sysvconfig.set_start_on_boot(False)
 
459
 
 
460
 
 
461
def setup_http_proxy(config):
 
462
    """
 
463
    If a http_proxy and a https_proxy value are not set then copy the values,
 
464
    if any, from the environment variables L{http_proxy} and L{https_proxy}.
 
465
    """
 
466
    if config.http_proxy is None and os.environ.get("http_proxy"):
 
467
        config.http_proxy = os.environ["http_proxy"]
 
468
    if config.https_proxy is None and os.environ.get("https_proxy"):
 
469
        config.https_proxy = os.environ["https_proxy"]
 
470
 
 
471
 
 
472
def check_account_name_and_password(config):
 
473
    """
 
474
    Ensure that silent configurations which plan to start landscape-client are
 
475
    have both an account_name and computer title.
 
476
    """
 
477
    if config.silent and not config.no_start:
 
478
        if not (config.get("account_name") and config.get("computer_title")):
 
479
            raise ConfigurationError("An account name and computer title are "
 
480
                                     "required.")
 
481
 
 
482
 
 
483
def check_script_users(config):
 
484
    """
 
485
    If the configuration allows for script execution ensure that the configured
 
486
    users are valid for that purpose.
 
487
    """
 
488
    if config.get("script_users"):
 
489
        invalid_users = get_invalid_users(config.get("script_users"))
 
490
        if invalid_users:
 
491
            raise ConfigurationError("Unknown system users: %s" %
 
492
                                     ", ".join(invalid_users))
 
493
        if not config.include_manager_plugins:
 
494
            config.include_manager_plugins = "ScriptExecution"
 
495
 
 
496
 
 
497
def decode_base64_ssl_public_certificate(config):
 
498
    """
 
499
    Decode base64 encoded SSL certificate and push that back into place in the
 
500
    config object.
 
501
    """
 
502
    # WARNING: ssl_public_certificate is misnamed, it's not the key of the
 
503
    # certificate, but the actual certificate itself.
 
504
    if config.ssl_public_key and config.ssl_public_key.startswith("base64:"):
 
505
        decoded_cert = base64.decodestring(config.ssl_public_key[7:])
 
506
        config.ssl_public_key = store_public_key_data(
 
507
            config, decoded_cert)
 
508
 
 
509
 
 
510
def setup(config):
 
511
    """
 
512
    Perform steps to ensure that landscape-client is correctly configured
 
513
    before we attempt to register it with a landscape server.
 
514
 
 
515
    If we are not configured to be silent then interrogate the user to provide
 
516
    necessary details for registration.
 
517
    """
 
518
    bootstrap_tree(config)
 
519
 
 
520
    sysvconfig = SysVConfig()
 
521
    if not config.no_start:
 
522
        if config.silent:
 
523
            setup_init_script_and_start_client()
 
524
        elif not sysvconfig.is_configured_to_run():
 
525
            answer = raw_input("\nThe Landscape client must be started "
 
526
                               "on boot to operate correctly.\n\n"
 
527
                               "Start Landscape client on boot? (Y/n): ")
 
528
            if not answer.upper().startswith("N"):
 
529
                setup_init_script_and_start_client()
 
530
            else:
 
531
                sys.exit("Aborting Landscape configuration")
 
532
 
 
533
    setup_http_proxy(config)
 
534
    check_account_name_and_password(config)
 
535
    if config.silent:
 
536
        check_script_users(config)
 
537
    else:
 
538
        script = LandscapeSetupScript(config)
 
539
        script.run()
 
540
    decode_base64_ssl_public_certificate(config)
 
541
    config.write()
 
542
    # Restart the client to ensure that it's using the new configuration.
 
543
    if not config.no_start:
 
544
        try:
 
545
            sysvconfig.restart_landscape()
 
546
        except ProcessError:
 
547
            print_text("Couldn't restart the Landscape client.", error=True)
 
548
            print_text("This machine will be registered with the provided "
 
549
                       "details when the client runs.", error=True)
 
550
            exit_code = 2
 
551
            if config.ok_no_register:
 
552
                exit_code = 0
 
553
            sys.exit(exit_code)
 
554
 
 
555
 
 
556
def bootstrap_tree(config):
 
557
    """Create the client directories tree."""
 
558
    bootstrap_list = [
 
559
        BootstrapDirectory("$data_path", "landscape", "root", 0755),
 
560
        BootstrapDirectory("$annotations_path", "landscape", "landscape",
 
561
                           0755)]
 
562
    BootstrapList(bootstrap_list).bootstrap(
 
563
        data_path=config.data_path, annotations_path=config.annotations_path)
 
564
 
 
565
 
 
566
def store_public_key_data(config, certificate_data):
 
567
    """
 
568
    Write out the data from the SSL certificate provided to us, either from a
 
569
    bootstrap.conf file, or from EC2-style user-data.
 
570
 
 
571
    @param config:  The L{BrokerConfiguration} object in use.
 
572
    @param certificate_data: a string of data that represents the contents of
 
573
    the file to be written.
 
574
    @return the L{BrokerConfiguration} object that was passed in, updated to
 
575
    reflect the path of the ssl_public_key file.
 
576
    """
 
577
    key_filename = os.path.join(
 
578
        config.data_path,
 
579
        os.path.basename(config.get_config_filename() + ".ssl_public_key"))
 
580
    print_text("Writing SSL CA certificate to %s..." % key_filename)
 
581
    key_file = open(key_filename, "w")
 
582
    key_file.write(certificate_data)
 
583
    key_file.close()
 
584
    return key_filename
 
585
 
 
586
 
 
587
def register(config, on_message=print_text, on_error=sys.exit, reactor=None,
 
588
             max_retries=14):
 
589
    """Instruct the Landscape Broker to register the client.
 
590
 
 
591
    The broker will be instructed to reload its configuration and then to
 
592
    attempt a registration.
 
593
 
 
594
    @param reactor: The reactor to use.  Please only pass reactor when you
 
595
        have totally mangled everything with mocker.  Otherwise bad things
 
596
        will happen.
 
597
    @param max_retries: The number of times to retry connecting to the
 
598
        landscape client service.  The delay between retries is calculated
 
599
        by Twisted and increases geometrically.  The default of 14 results in
 
600
        a total wait time of about 70 seconds.
 
601
 
 
602
        initialDelay = 0.05
 
603
        factor =  1.62
 
604
        maxDelay = 30
 
605
        max_retries = 14
 
606
 
 
607
        0.05 * (1 - 1.62 ** 14) / (1 - 1.62) = 69 seconds
 
608
   """
 
609
    if reactor is None:
 
610
        reactor = LandscapeReactor()
 
611
    exit_with_error = []
 
612
 
 
613
    def stop(errors):
 
614
        if not config.ok_no_register:
 
615
            for error in errors:
 
616
                if error is not None:
 
617
                    exit_with_error.append(error)
 
618
        connector.disconnect()
 
619
        reactor.stop()
 
620
 
 
621
    def failure():
 
622
        on_message("Invalid account name or "
 
623
                   "registration key.", error=True)
 
624
        return 2
 
625
 
 
626
    def success():
 
627
        on_message("System successfully registered.")
 
628
 
 
629
    def exchange_failure():
 
630
        on_message("We were unable to contact the server. "
 
631
                   "Your internet connection may be down. "
 
632
                   "The landscape client will continue to try and contact "
 
633
                   "the server periodically.",
 
634
                   error=True)
 
635
        return 2
 
636
 
 
637
    def handle_registration_errors(failure):
 
638
        # We'll get invalid credentials through the signal.
 
639
        failure.trap(InvalidCredentialsError, MethodCallError)
 
640
        connector.disconnect()
 
641
 
 
642
    def catch_all(failure):
 
643
        on_message(failure.getTraceback(), error=True)
 
644
        on_message("Unknown error occurred.", error=True)
 
645
        return [2]
 
646
 
 
647
    on_message("Please wait... ", "")
 
648
 
 
649
    time.sleep(2)
 
650
 
 
651
    def got_connection(remote):
 
652
        handlers = {"registration-done": success,
 
653
                    "registration-failed": failure,
 
654
                    "exchange-failed": exchange_failure}
 
655
        deferreds = [
 
656
            remote.call_on_event(handlers),
 
657
            remote.register().addErrback(handle_registration_errors)]
 
658
        # We consume errors here to ignore errors after the first one.
 
659
        # catch_all will be called for the very first deferred that fails.
 
660
        results = gather_results(deferreds, consume_errors=True)
 
661
        results.addErrback(catch_all)
 
662
        results.addCallback(stop)
 
663
 
 
664
    def got_error(failure):
 
665
        on_message("There was an error communicating with the Landscape"
 
666
                   " client.", error=True)
 
667
        on_message("This machine will be registered with the provided "
 
668
                   "details when the client runs.", error=True)
 
669
        stop([2])
 
670
 
 
671
    connector = RemoteBrokerConnector(reactor, config)
 
672
    result = connector.connect(max_retries=max_retries, quiet=True)
 
673
    result.addCallback(got_connection)
 
674
    result.addErrback(got_error)
 
675
 
 
676
    reactor.run()
 
677
 
 
678
    if exit_with_error:
 
679
        on_error(exit_with_error[0])
 
680
 
 
681
    return result
 
682
 
 
683
 
 
684
def main(args):
 
685
    config = LandscapeSetupConfiguration()
 
686
    try:
 
687
        config.load(args)
 
688
    except ImportOptionError, error:
 
689
        print_text(str(error), error=True)
 
690
        sys.exit(1)
 
691
 
 
692
    if os.getuid() != 0:
 
693
        sys.exit("landscape-config must be run as root.")
 
694
 
 
695
    if config.init:
 
696
        bootstrap_tree(config)
 
697
        sys.exit(0)
 
698
 
 
699
    # Disable startup on boot and stop the client, if one is running.
 
700
    if config.disable:
 
701
        stop_client_and_disable_init_script()
 
702
        return
 
703
 
 
704
    # Setup client configuration.
 
705
    try:
 
706
        setup(config)
 
707
    except Exception, e:
 
708
        print_text(str(e))
 
709
        sys.exit("Aborting Landscape configuration")
 
710
 
 
711
    # Attempt to register the client.
 
712
    if config.silent:
 
713
        register(config)
 
714
    else:
 
715
        answer = raw_input("\nRequest a new registration for "
 
716
                           "this computer now? (Y/n): ")
 
717
        if not answer.upper().startswith("N"):
 
718
            register(config)