~landscape/landscape-client/landscape-client-12.04.3-0ubuntu0.11.10

« back to all changes in this revision

Viewing changes to landscape/configuration.py

  • Committer: Andreas Hasenack
  • Date: 2012-04-10 14:41:19 UTC
  • mfrom: (1.1.24)
  • Revision ID: andreas@canonical.com-20120410144119-raro24wbg3f4ir8b
Applied 12.04.3 changes via bzr merge-upstream

Show diffs side-by-side

added added

removed removed

Lines of Context:
4
4
for the C{landscape-config} script.
5
5
"""
6
6
 
 
7
import json
7
8
import base64
8
9
import time
9
10
import sys
18
19
from landscape.sysvconfig import SysVConfig, ProcessError
19
20
from landscape.lib.amp import MethodCallError
20
21
from landscape.lib.twisted_util import gather_results
21
 
from landscape.lib.fetch import fetch, FetchError
 
22
from landscape.lib.fetch import fetch, FetchError, HTTPCodeError
22
23
from landscape.reactor import TwistedReactor
23
24
from landscape.broker.registration import InvalidCredentialsError
24
25
from landscape.broker.config import BrokerConfiguration
68
69
    unsaved_options = ("no_start", "disable", "silent", "ok_no_register",
69
70
                       "import_from")
70
71
 
71
 
    def __init__(self, fetch_import_url):
 
72
    def __init__(self):
72
73
        super(LandscapeSetupConfiguration, self).__init__()
73
 
        self._fetch_import_url = fetch_import_url
74
74
 
75
75
    def _load_external_options(self):
76
76
        """Handle the --import parameter.
89
89
                        os.environ["http_proxy"] = self.http_proxy
90
90
                    if self.https_proxy:
91
91
                        os.environ["https_proxy"] = self.https_proxy
92
 
                    content = self._fetch_import_url(self.import_from)
 
92
                    content = self.fetch_import_url(self.import_from)
93
93
                    parser.readfp(StringIO(content))
94
94
                elif not os.path.isfile(self.import_from):
95
95
                    raise ImportOptionError("File %s doesn't exist." %
109
109
            options.update(self._command_line_options)
110
110
            self._command_line_options = options
111
111
 
 
112
    def fetch_import_url(self, url):
 
113
        """Handle fetching of URLs passed to --url."""
 
114
 
 
115
        print_text("Fetching configuration from %s..." % url)
 
116
        error_message = None
 
117
        try:
 
118
            content = fetch(url)
 
119
        except FetchError, error:
 
120
            error_message = str(error)
 
121
        if error_message is not None:
 
122
            raise ImportOptionError(
 
123
                "Couldn't download configuration from %s: %s" %
 
124
                (url, error_message))
 
125
        return content
 
126
 
112
127
    def make_parser(self):
113
128
        """
114
129
        Specialize the parser, adding configure-specific options.
378
393
            if self._get_invalid_tags(self.config.tags):
379
394
                self.show_help("Tag names may only contain alphanumeric "
380
395
                              "characters.")
381
 
                self.config.tags = None # Reset for the next prompt
 
396
                self.config.tags = None  # Reset for the next prompt
382
397
            else:
383
398
                break
384
399
 
409
424
 
410
425
 
411
426
def setup_init_script_and_start_client():
 
427
    "Configure the init script to start the client on boot."
412
428
    # XXX This function is misnamed; it doesn't start the client.
413
429
    sysvconfig = SysVConfig()
414
430
    sysvconfig.set_start_on_boot(True)
415
431
 
416
432
 
417
433
def stop_client_and_disable_init_script():
 
434
    """
 
435
    Stop landscape-client and change configuration to prevent starting
 
436
    landscape-client on boot.
 
437
    """
418
438
    sysvconfig = SysVConfig()
419
439
    sysvconfig.stop_landscape()
420
440
    sysvconfig.set_start_on_boot(False)
421
441
 
422
442
 
 
443
def setup_http_proxy(config):
 
444
    """
 
445
    If a http_proxy and a https_proxy value are not set then copy the values,
 
446
    if any, from the environment variables L{http_proxy} and L{https_proxy}.
 
447
    """
 
448
    if config.http_proxy is None and os.environ.get("http_proxy"):
 
449
        config.http_proxy = os.environ["http_proxy"]
 
450
    if config.https_proxy is None and os.environ.get("https_proxy"):
 
451
        config.https_proxy = os.environ["https_proxy"]
 
452
 
 
453
 
 
454
def check_account_name_and_password(config):
 
455
    """
 
456
    Ensure that silent configurations which plan to start landscape-client are
 
457
    either configured for OTP or have both an account_name and computer title.
 
458
    """
 
459
    if config.silent and not config.no_start:
 
460
        if not (config.get("otp") or config.provisioning_otp or
 
461
                (config.get("account_name") and config.get("computer_title"))):
 
462
            raise ConfigurationError("An account name and computer title are "
 
463
                                     "required.")
 
464
 
 
465
 
 
466
def check_script_users(config):
 
467
    """
 
468
    If the configuration allows for script execution ensure that the configured
 
469
    users are valid for that purpose.
 
470
    """
 
471
    if config.get("script_users"):
 
472
        invalid_users = get_invalid_users(config.get("script_users"))
 
473
        if invalid_users:
 
474
            raise ConfigurationError("Unknown system users: %s" %
 
475
                                     ", ".join(invalid_users))
 
476
        if not config.include_manager_plugins:
 
477
            config.include_manager_plugins = "ScriptExecution"
 
478
 
 
479
 
 
480
def decode_base64_ssl_public_certificate(config):
 
481
    """
 
482
    Decode base64 encoded SSL certificate and push that back into place in the
 
483
    config object.
 
484
    """
 
485
    # WARNING: ssl_public_certificate is misnamed, it's not the key of the
 
486
    # certificate, but the actual certificate itself.
 
487
    if config.ssl_public_key and config.ssl_public_key.startswith("base64:"):
 
488
        decoded_cert = base64.decodestring(config.ssl_public_key[7:])
 
489
        config.ssl_public_key = store_public_key_data(
 
490
            config, decoded_cert)
 
491
 
 
492
 
 
493
def fetch_base64_ssl_public_certificate(hostname, on_info=print_text,
 
494
    on_error=print_text):
 
495
    """
 
496
    Fetch base64 encoded SSL CA certificate from the discovered landscape
 
497
    server and return that decoded info.
 
498
    """
 
499
    on_info("Fetching CA certificate from %s if available..." % hostname)
 
500
    content = ""
 
501
    encoded_cert = ""
 
502
    ca_url = "http://%s/get-ca-cert" % hostname
 
503
    try:
 
504
        content = fetch(ca_url, insecure=True)
 
505
    except HTTPCodeError, error:
 
506
        on_error("Unable to fetch CA certificate from discovered server %s: "
 
507
                 "Server does not support client auto-registation." % hostname)
 
508
        return encoded_cert
 
509
    except FetchError, error:
 
510
        on_error("Unable to fetch CA certificate from %s: %s"
 
511
                % (hostname, str(error)))
 
512
        return encoded_cert
 
513
 
 
514
    if content:
 
515
        ca_dict = json.loads(content)
 
516
        try:
 
517
            if ca_dict["custom_ca_cert"].startswith("base64:"):
 
518
                encoded_cert = ca_dict["custom_ca_cert"]
 
519
            else:
 
520
                on_error("Auto-registration URL %s returns invalid CA JSON: "
 
521
                         "%s." % (ca_url, ca_dict))
 
522
        except KeyError:
 
523
            # No custom CA certificate needed to talk with this server
 
524
            on_info("No custom CA certificate available for %s." % hostname)
 
525
    else:
 
526
        on_error("Unable to fetch CA certificate from discovered server "
 
527
                 "%s.  Proceding without custom CA certificate."
 
528
                % hostname)
 
529
    return encoded_cert
 
530
 
 
531
 
423
532
def setup(config):
 
533
    """
 
534
    Perform steps to ensure that landscape-client is correctly configured
 
535
    before we attempt to register it with a landscape server.
 
536
 
 
537
    If we are not configured to be silent then interrogate the user to provide
 
538
    necessary details for registration.
 
539
    """
424
540
    sysvconfig = SysVConfig()
425
541
    if not config.no_start:
426
542
        if config.silent:
434
550
            else:
435
551
                sys.exit("Aborting Landscape configuration")
436
552
 
437
 
    if config.http_proxy is None and os.environ.get("http_proxy"):
438
 
        config.http_proxy = os.environ["http_proxy"]
439
 
    if config.https_proxy is None and os.environ.get("https_proxy"):
440
 
        config.https_proxy = os.environ["https_proxy"]
441
 
 
 
553
    setup_http_proxy(config)
 
554
    check_account_name_and_password(config)
442
555
    if config.silent:
443
 
        if not config.get("otp") and (not config.get("account_name") or not
444
 
                                          config.get("computer_title")):
445
 
            raise ConfigurationError("An account name and computer title are "
446
 
                                     "required.")
447
 
        if config.get("script_users"):
448
 
            invalid_users = get_invalid_users(config.get("script_users"))
449
 
            if invalid_users:
450
 
                raise ConfigurationError("Unknown system users: %s" %
451
 
                                         ", ".join(invalid_users))
452
 
            if not config.include_manager_plugins:
453
 
                config.include_manager_plugins = "ScriptExecution"
 
556
        check_script_users(config)
454
557
    else:
455
558
        script = LandscapeSetupScript(config)
456
559
        script.run()
457
 
 
458
 
    # WARNING: ssl_public_key is misnamed, it's not the key of the certificate,
459
 
    # but the actual certificate itself.
460
 
    if config.ssl_public_key and config.ssl_public_key.startswith("base64:"):
461
 
        decoded_cert = base64.decodestring(config.ssl_public_key[7:])
462
 
        config.ssl_public_key = store_public_key_data(
463
 
            config, decoded_cert)
464
 
 
 
560
    decode_base64_ssl_public_certificate(config)
465
561
    config.write()
466
562
    # Restart the client to ensure that it's using the new configuration.
467
563
    if not config.no_start and not config.otp:
497
593
    return key_filename
498
594
 
499
595
 
500
 
def register(config, reactor=None):
 
596
def register(config, on_message=print_text, on_error=sys.exit, reactor=None):
501
597
    """Instruct the Landscape Broker to register the client.
502
598
 
503
599
    The broker will be instructed to reload its configuration and then to
510
606
    reactor = TwistedReactor()
511
607
    exit_with_error = []
512
608
 
513
 
    # XXX: many of these reactor.stop() calls should also specify a non-0 exit
514
 
    # code, unless ok-no-register is passed.
515
 
 
516
 
    def stop():
 
609
    def stop(error=None):
 
610
        if not config.ok_no_register and error is not None:
 
611
            exit_with_error.append(error)
517
612
        connector.disconnect()
518
613
        reactor.stop()
519
614
 
520
615
    def failure():
521
 
        print_text("Invalid account name or "
 
616
        on_message("Invalid account name or "
522
617
                   "registration password.", error=True)
523
 
        stop()
 
618
        stop(2)
524
619
 
525
620
    def success():
526
 
        print_text("System successfully registered.")
 
621
        on_message("System successfully registered.")
527
622
        stop()
528
623
 
529
624
    def exchange_failure():
530
 
        print_text("We were unable to contact the server. "
 
625
        on_message("We were unable to contact the server. "
531
626
                   "Your internet connection may be down. "
532
627
                   "The landscape client will continue to try and contact "
533
628
                   "the server periodically.",
534
629
                   error=True)
535
 
        stop()
 
630
        stop(2)
536
631
 
537
632
    def handle_registration_errors(failure):
538
633
        # We'll get invalid credentials through the signal.
540
635
        connector.disconnect()
541
636
 
542
637
    def catch_all(failure):
543
 
        # We catch SecurityError here too, because on some DBUS configurations
544
 
        # if you try to connect to a dbus name that doesn't have a listener,
545
 
        # it'll try auto-starting the service, but then the StartServiceByName
546
 
        # call can raise a SecurityError.
547
 
        print_text(failure.getTraceback(), error=True)
548
 
        print_text("Unknown error occurred.", error=True)
549
 
        stop()
 
638
        on_message(failure.getTraceback(), error=True)
 
639
        on_message("Unknown error occurred.", error=True)
 
640
        stop(2)
550
641
 
551
 
    print_text("Please wait... ", "")
 
642
    on_message("Please wait... ", "")
552
643
 
553
644
    time.sleep(2)
554
645
 
566
657
        return results.addErrback(catch_all)
567
658
 
568
659
    def got_error(failure):
569
 
        print_text("There was an error communicating with the Landscape "
570
 
                   "client.", error=True)
571
 
        print_text("This machine will be registered with the provided "
 
660
        on_message("There was an error communicating with the Landscape"
 
661
                   " client.", error=True)
 
662
        on_message("This machine will be registered with the provided "
572
663
                   "details when the client runs.", error=True)
573
 
        if not config.ok_no_register:
574
 
            exit_with_error.append(2)
575
 
        stop()
 
664
        stop(2)
576
665
 
577
666
    connector = RemoteBrokerConnector(reactor, config)
578
667
    result = connector.connect(max_retries=0, quiet=True)
582
671
    reactor.run()
583
672
 
584
673
    if exit_with_error:
585
 
        sys.exit(exit_with_error[0])
 
674
        on_error(exit_with_error[0])
586
675
 
587
676
    return result
588
677
 
589
678
 
590
 
def fetch_import_url(url):
591
 
    """Handle fetching of URLs passed to --url.
592
 
 
593
 
    This is done out of LandscapeSetupConfiguration since it has to deal
594
 
    with interaction with the user and downloading of files.
595
 
    """
596
 
 
597
 
    print_text("Fetching configuration from %s..." % url)
598
 
    error_message = None
599
 
    try:
600
 
        content = fetch(url)
601
 
    except FetchError, error:
602
 
        error_message = str(error)
603
 
    if error_message is not None:
604
 
        raise ImportOptionError(
605
 
            "Couldn't download configuration from %s: %s" %
606
 
            (url, error_message))
607
 
    return content
608
 
 
609
 
 
610
679
def main(args):
611
 
    config = LandscapeSetupConfiguration(fetch_import_url)
 
680
    config = LandscapeSetupConfiguration()
612
681
    if args in (["-h"], ["--help"]):
613
682
        # We let landscape-config --help to be run as normal user
614
683
        config.load(args)
622
691
        print_text(str(error), error=True)
623
692
        sys.exit(1)
624
693
 
625
 
 
626
694
    # Disable startup on boot and stop the client, if one is running.
627
695
    if config.disable:
628
696
        stop_client_and_disable_init_script()