noipy/main.py

Summary

Maintainability
B
5 hrs
Test Coverage
A
91%
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# noipy.noipy
# Copyright (c) 2013 Pablo O Vieira (povieira)
# See README.rst and LICENSE for details.

from __future__ import print_function

import argparse
import getpass
import re
import sys

from noipy import utils


from noipy import dnsupdater
from noipy import authinfo
from noipy import __version__


EXECUTION_RESULT_OK = 0
EXECUTION_RESULT_NOK = 1

URL_RE = re.compile(
    r'^https?://'  # http:// or https://
    r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+'
    r'(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|'  # domain...
    r'localhost|'  # localhost...
    r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|'  # ...or ipv4
    r'\[?[A-F0-9]*:[A-F0-9:]+\]?)'  # ...or ipv6
    r'(?::\d+)?'  # optional port
    r'(?:/?|[/?]\S+)$',
    re.IGNORECASE,
)


def execute_update(args):
    """Execute the update based on command line args and returns a dictionary
    with 'execution result, ''response code', 'response info' and
    'process friendly message'.
    """

    provider_class = getattr(dnsupdater, dnsupdater.AVAILABLE_PLUGINS.get(args.provider))
    updater_options = {}
    process_message = None
    auth = None

    if args.store:  # --store argument
        if provider_class.auth_type == 'T':
            user_arg = args.usertoken or utils.read_input('Paste your auth token: ')
            auth = authinfo.ApiAuth(usertoken=user_arg)
        else:
            user_arg = args.usertoken or utils.read_input('Type your username: ')
            pass_arg = args.password or getpass.getpass('Type your password: ')
            auth = authinfo.ApiAuth(user_arg, pass_arg)

        authinfo.store(auth, args.provider, args.config)
        exec_result = EXECUTION_RESULT_OK
        if not args.hostname:
            update_ddns = False
            process_message = 'Auth info stored.'
        else:
            update_ddns = True

    # informations arguments
    elif args.usertoken and args.hostname:
        if provider_class.auth_type == 'T':
            auth = authinfo.ApiAuth(args.usertoken)
        else:
            auth = authinfo.ApiAuth(args.usertoken, args.password)
        update_ddns = True
        exec_result = EXECUTION_RESULT_OK
    elif args.hostname:
        if authinfo.exists(args.provider, args.config):
            auth = authinfo.load(args.provider, args.config)
            update_ddns = True
            exec_result = EXECUTION_RESULT_OK
        else:
            update_ddns = False
            exec_result = EXECUTION_RESULT_NOK
            process_message = 'No stored auth information found for provider: "{}"'.format(args.provider)
    else:  # no arguments
        update_ddns = False
        exec_result = EXECUTION_RESULT_NOK
        process_message = (
            'Warning: The hostname to be updated must be '
            'provided.\nUsertoken and password can be either '
            'provided via command line or stored with --store '
            'option.\nExecute noipy --help for more details.'
        )

    if update_ddns and args.provider == 'generic':
        if args.url:
            if not URL_RE.match(args.url):
                process_message = 'Malformed URL.'
                exec_result = EXECUTION_RESULT_NOK
                update_ddns = False
            else:
                updater_options['url'] = args.url
        else:
            process_message = 'Must use --url if --provider is "generic" (default)'
            exec_result = EXECUTION_RESULT_NOK
            update_ddns = False

    response_code = None
    response_text = None
    if update_ddns:
        ip_address = args.ip if args.ip else utils.get_ip()
        if not ip_address:
            process_message = 'Unable to get IP address. Check connection.'
            exec_result = EXECUTION_RESULT_NOK
        elif ip_address == utils.get_dns_ip(args.hostname):
            process_message = 'No update required.'
        else:
            updater = provider_class(auth, args.hostname, updater_options)
            print(
                'Updating hostname "{}" with IP address {} '
                '[provider: "{}"]...'.format(args.hostname, ip_address, args.provider)
            )
            response_code, response_text = updater.update_dns(ip_address)
            process_message = updater.status_message

    proc_result = {
        'exec_result': exec_result,
        'response_code': response_code,
        'response_text': response_text,
        'process_message': process_message,
    }

    return proc_result


def create_parser():
    parser = argparse.ArgumentParser(description='Update DDNS IP address on selected provider.')
    parser.add_argument('-u', '--usertoken', help='provider username or token')
    parser.add_argument('-p', '--password', help='provider password when apply')
    parser.add_argument('-n', '--hostname', help='provider hostname to be updated')
    parser.add_argument(
        '--provider',
        help='DDNS provider plugin (default: {})'.format(dnsupdater.DEFAULT_PLUGIN),
        choices=dnsupdater.AVAILABLE_PLUGINS.keys(),
        default=dnsupdater.DEFAULT_PLUGIN,
    )
    parser.add_argument('--url', help='custom DDNS server address')
    parser.add_argument(
        '--store',
        help='store DDNS authentication information and update the hostname if it is provided',
        action='store_true',
    )
    parser.add_argument(
        '-c',
        '--config',
        help='noipy config directory (default: {})'.format(authinfo.DEFAULT_CONFIG_DIR),
        default=authinfo.DEFAULT_CONFIG_DIR,
    )
    parser.add_argument(
        'ip',
        metavar='IP_ADDRESS',
        nargs='?',
        help='New host IP address. If not provided, current external IP address will be used.',
    )

    return parser


def main():
    print('== noipy DDNS updater tool v{} =='.format(__version__))
    parser = create_parser()
    args = parser.parse_args()

    result = execute_update(args)
    print(result.get('process_message'))
    if result.get('exec_result') != EXECUTION_RESULT_OK:
        parser.format_usage()

    sys.exit(result.get('exec_result'))


if __name__ == '__main__':
    main()