#!/usr/bin/env python

# Copyright 2009-2015 Jasper Poppe <jgpoppe@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
todo
====
improve documentation
add validation for YAML configuration files
improve security, run as a different user, daemon hardening
default pxelinux config file generator

future
======
RedHat kickstart support?
freeDOS support?
more?
"""

__author__ = 'Jasper Poppe <jgpoppe@gmail.com>'
__copyright__ = 'Copyright (c) 2009-2015 Jasper Poppe'
__credits__ = ''
__license__ = 'Apache License, Version 2.0'
__version__ = '2.0.0rc7'
__maintainer__ = 'Jasper Poppe'
__email__ = 'jgpoppe@gmail.com'
__status__ = 'production'

import argparse
import logging
import logging.config
import sys

from argparse import RawTextHelpFormatter

import settings
import parse
import utils


cfg = settings.parse_cfg()
try:
    logging.config.fileConfig(cfg['logging']['configuration'])
except IOError as err:
    sys.stderr.write(str(err) + ', do you have the right permissions?\n')
    sys.exit(1)
else:
    logger = logging.getLogger(cfg['logging']['logger'])

def argument_parser():
    """process the arguments"""
    parse_arg = parse.ParseArguments(cfg)

    parser = argparse.ArgumentParser(description='seedBank - Debian/Ubuntu '
        'netboot installations the way it is meant to be... (c) 2009-2015 '
        'Jasper Poppe <jgpoppe@gmail.com>', epilog='for more information '
        'visit: http://www.infrastructureanywhere.com',
        fromfile_prefix_chars='@')
    parser.add_argument('--version', action='version', version=__version__)
    subparsers = parser.add_subparsers(help='commands')

    parser_list = subparsers.add_parser('list',
        help='list resources like netboot images, seed files, Puppet '
        'manifests, configuration overrides, file overlays, pxelinux.cfg files'
        ', netboot images and ISOs', formatter_class=RawTextHelpFormatter)
    parser_list.add_argument('-a', '--all', action='store_true',
        help='list all resources')
    parser_list.add_argument('-n', '--netboots', action='store_true',
        help='list releases which are available for netboot\ninstallations'
        ', names starting with an asterisk are\nready to use\nnetboot images '
        'are managed by the "seedbank manage -n"\ncommand')
    parser_list.add_argument('-i', '--isos', action='store_true',
        help='list ISOs which are used for building (unattended)\ninstallation '
        'ISOs ISO names starting with an asterisk\nare ready to use\nISOs '
        'are managed by the "seedbank manage" command')
    parser_list.add_argument('-s', '--seeds',  action='store_true',
        help='list available seed files\nseed files are (partial) preseed ' 
        'files which are used\nfor providing answers to the installer\n'
        'default location: /etc/seedbank/seeds')
    parser_list.add_argument('-c', '--configs', action='store_true',
        help='list available configs\nconfigs are used for overriding the '
        'default configuration\nand providing default command line options '
        'for the\n"seedbank pxe" and "seedbank iso commands"\ndefault '
        'location: /etc/seedbank/configs')
    parser_list.add_argument('-o', '--overlays', action='store_true',
        help='list available overlays\nthe contents of an overlay directory '
        'will be copied over\nthe file system just before the end of an '
        'installation\ndefault location: /etc/seedbank/overlays')
    parser_list.add_argument('-p', '--puppet', action='store_true',
        help='list available Puppet manifests\nWhen you enable one or more '
        'Puppet manifests they will\nbe applied once by a stand alone Puppet '
        'instance,\ndirectly after the first boot of the machine\nwhich has '
        'been installed\ndefault location: /etc/seedbank/manifests')
    parser_list.add_argument('-P', '--pxe', action='store_true',
        help='list all "pxelinux.cfg" configs\npxelinux.cfg files are '
        'generated by the "seedbank pxe"\ncommand and are used to provide '
        'machine specific\ninformation to the installer after the machine '
        'PXE boots\nvia the network, the variables stored in those file\n'
        'comments are used by the seedBank daemon for\ngenerating the preseed '
        'file, those variables could also\nbe used in file overlay templates\n'
        'default location: /var/lib/tftpboot/pxelinux.cfg')
    parser_list.set_defaults(func=parse_arg.list)

    parser_shared = argparse.ArgumentParser(add_help=False)
    parser_shared.add_argument('-o', '--overlay',  default=None, help='file '
        'overlay which will be copied over the file system before the end of '
        'the installation')
    parser_shared.add_argument('-s', '--seed', help='override the default '
        'preseed file (the default preseed file has the name of the '
        'distribution, e.g: squeeze or precise)')
    parser_shared.add_argument('-a', '--additional', action='append',
        default=[], metavar='SEED', help='append additional seed files to the '
        'default seed file like disk recipes, repositories or other additional '
        '(custom) seeds')
    parser_shared.add_argument('fqdn', nargs='?', help='fully qualified domain '
        'name of the node to install')
    parser_shared.add_argument('-p', '--puppet', action='append',
        metavar='MANIFEST', default=[], help='choose one or more Puppet '
        'manifest(s) to apply after the installation')
    parser_shared.add_argument('-c', '--config', default=None, help='override '
        'template (pxe and seed) settings and set command line arguments,'
        'could also be used for creating machine profiles')

    parser_pxe = subparsers.add_parser('pxe', parents=[parser_shared],
        help='manage netboot installations, prepare a pxelinux.cfg '
        'file with all the settings required for a netboot installation')
    parser_pxe.add_argument('-r', '--release', help='release name (default: '
        'settings -> default_release -> pxe)')
    parser_pxe.add_argument('-m', '--macaddress',
        help='use a MAC address instead of a to hexidecimal converted IP '
        'address for the pxelinux.cfg configuration file name, the advantage '
        'of this is there will be no DNS lookups needed')
    parser_pxe.add_argument('-v', '--variables', nargs=2, action='append',
        metavar=('KEY', 'VALUE'),
        default=[], help='add one or more additional pxe variables which '
        'will be stored in the generated pxelinux.cfg file and could be used '
        'by seedBank templates in the overlay directory and the disable and '
        'enable hooks')
    parser_pxe.set_defaults(func=parse_arg.pxe)

    parser_iso = subparsers.add_parser('iso', parents=[parser_shared],
        help='build an (unattended) installation ISO')
    parser_iso.add_argument('-r', '--release', help='release name (default: '
        'settings -> default_release -> iso)')
    parser_iso.add_argument('-i', '--isofile', help='file name and location '
        'of the generated ISO (default: ./<fqdn>.iso)')
    parser_iso.add_argument('-v', '--variables', nargs=2, action='append',
        metavar=('KEY', 'VALUE'),
        default=[], help='add (or overrides) one or more seed and or overlay '
        'variables which could be used by seedBank templates in the overlay '
        'directory and preseed files')
    parser_iso.set_defaults(func=parse_arg.iso)

    parser_manage = subparsers.add_parser('manage', help='download and '
        'manage netboot images, syslinux files and ISOs')
    group = parser_manage.add_mutually_exclusive_group()
    group.add_argument('-s', '--syslinux', action='store_true',
        help='download the syslinux archive and extract the pxelinux.0, '
        'menu.c32 and vesamenu.c32 files and place those in the tftpboot '
        'directory. (those files are required for doing a PXE boot)')
    group.add_argument('-n', '--netboot', action='store', metavar='RELEASE',
        help='download and prepare a netboot image, when the release has been '
        'defined in the distributions -> firmware section Debian "non free" '
        'firmware files will be integrated into the netboot image, the '
        'contents of the netboot archive will be placed in the configured '
        'tftpboot path')
    group.add_argument('-i', '--iso', action='store', metavar='RELEASE',
        help='download ISO images which are used by the "seedbank iso command"')
    group.add_argument('-r', '--remove', action='store', metavar='RELEASE',
        help='remove an iso or netboot images from the tftpboot and seedBank '
        'archives directories')
    group.add_argument('-o', '--overlay', action='store_true',
        help='update or create <overlay>.permissions for all overlays, those '
        'files contain user, group and permissions which will be set by a '
        'dynamically generated script just before the end of an installation '
        'after the overlay got applied')
    group.set_defaults(func=parse_arg.manage)

    parser_daemon = subparsers.add_parser('daemon', help='seedBank daemon')
    parser_daemon.add_argument('-s', '--start', action='store_true',
        help='start the seedBank daemon which provides dynamically resources '
        'used by the installer, it also takes care of disabling the pxelinux '
        'configuration files after a successfull installation')
    parser_daemon.add_argument('-b', '--bottle', action='store_true',
        help='start the bottle')
    parser_daemon.set_defaults(func=parse_arg.daemon)

    args = parser.parse_args()
    logging.debug('given arguments: %s', args)

    if len(sys.argv) == 2:
        if sys.argv[1] == 'list':
            parser_list.print_help()
        if sys.argv[1] == 'pxe':
            parser_pxe.print_help()
        if sys.argv[1] == 'iso':
            parser_iso.print_help()
        elif sys.argv[1] == 'manage':
            parser_manage.print_help()
        elif sys.argv[1] == 'daemon':
            parser_daemon.print_help()
    else:
        args.func(args)

def main():
    """the main application"""
    try:
        argument_parser()
    except utils.FatalException:
        sys.exit(1)
    except Exception as err:
        logger.error(err)
        raise
        sys.exit(1)

if __name__ == '__main__':
    try:
        main()
    except SystemExit as err:
        sys.exit(err.code)
    except KeyboardInterrupt:
        sys.stderr.write('info: cancelled by user\n')
        sys.exit(1)
