コード例 #1
0
ファイル: ravshello.py プロジェクト: filiy/ravshello
def apply_config_file(filepath):
    """Update cfgFile dict w/yaml config file, ignoring missing files."""
    try:
        with open(filepath) as f:
            cfg.cfgFile.update(yaml.load(f, Loader))
    except IOError as e:
        if e.strerror == 'No such file or directory':
            pass
        else:
            print(c.yellow("Ignoring config file '{}'; IOError: {}".format(
                filepath, e.strerror)),
                  file=stderr)
    except TypeError as e:
        if e.message == "'NoneType' object is not iterable":
            # This means it's an empty config file
            pass
        else:
            raise
    except:
        print(c.red("Fatal error parsing config file '{}'\n".format(filepath)),
              file=stderr)
        raise
コード例 #2
0
ファイル: rav-notify.py プロジェクト: h4ckl4bm3/ravshello
def main(argparseOptions):
    
    global rOpt, appnamePrefix, rClient
    rOpt = argparseOptions
    c.enableColor = rOpt.enableColor
    runningApps = []
    timestamps = []
    
    try:
        with open(os.devnull, 'w') as devnull:
            subprocess.check_call(['notify-send', '--version'], stdout=devnull)
    except:
        print(c.RED("Unable to launch notify-send command!",
                    "We cannot notify you of events without libnotify installed"))
        sys.exit(2)
    
    try:
        with open(os.path.expanduser(rOpt.configFile)) as f:
            cfg = yaml.safe_load(f)
    except:
        print(c.yellow(
            "Note: unable to read configFile '{}'; using defaults"
            .format(rOpt.configFile)))
        nick = user = passwd = messg = events = None
    else:
        nick   = cfg.get('nickname', None)
        user   = cfg.get('ravelloUser', None)
        passwd = cfg.get('ravelloPass', None)
        messg  = cfg.get('unableToLoginAdditionalMsg', None)
        events = cfg.get('eventsOfInterest', None)
    
    if rOpt.kerberos:
        appnamePrefix = 'k:' + rOpt.kerberos + '__'
    elif nick:
        appnamePrefix = 'k:' + nick + '__'
    else:
        appnamePrefix = ''
    
    lackingCreds = False
    
    if not rOpt.ravelloUser:
        if user:
            rOpt.ravelloUser = user
        elif sys.stdout.isatty():
            rOpt.ravelloUser = get_username(c.CYAN("Enter Ravello username: "******"Enter Ravello passphrase: "))
        else:
            lackingCreds = True
    
    if lackingCreds:
        cmd = [
            'notify-send', '--urgency', 'critical',
            "rav-notify missing Ravello credentials!",
            "You must either populate ~/.ravshello/ravshello.conf, run " +
            "rav-notify with -u & -p options, or run rav-notify from a " +
            "terminal so it can prompt you for user/pass.",
            ]
        subprocess.check_call(cmd)
        sys.exit(3)
    
    rClient = ravello_sdk.RavelloClient()
    try:
        # Try to log in.
        rClient.login(rOpt.ravelloUser, rOpt.ravelloPass)
    except:
        if sys.stdout.isatty():
            print(c.RED("Logging in to Ravello failed!"))
            print("\nRe-check your username and password.")
            if messg: print(messg)
        else:
            cmd = [
                'notify-send', '--urgency', 'critical',
                "rav-notify failed to log in to Ravello!",
                "Re-check your username and password.",
                ]
            subprocess.check_call(cmd)
        sys.exit(5)
    
    cmd = [
        'notify-send', '--urgency', 'low',
        "rav-notify monitoring Ravello events",
        "Any events of interest (app timeouts or deletions, vms being " +
        "started or stopped) will trigger further notifications",
        ]
    subprocess.check_call(cmd)
    
    if events:
        eventsOfInterest = events
    else:
        eventsOfInterest = [
            'APP_TIMEOUT_AUTO_STOPPING',
            'APP_TIMEOUT_AUTO_STOPPED',
            'APPLICATION_TIMER_RESET',
            'APPLICATION_DELETED',
            'VM_STOPPED',
            'VM_STARTED',
            'VM_SNAPSHOTTING_AFTER_STOP',
            'VM_FINISHED_SNAPSHOTTING',
            ]
    
    debug("Event triggers:\n{}\n".format("\n".join(eventsOfInterest)))
    
    urgency = {
        'INFO': "low",
        'WARN': "normal",
        'ERROR': "critical",
        }
    
    # Build a list of app ids we should pay attention to.
    myAppIds = update_myAppIds()
    
    for appId in myAppIds:
        app = rClient.get_application(appId, aspect='properties')
        try:
            # Grab expiration time for all of my deployed apps.
            expirationTime = app['deployment']['expirationTime']
        except:
            continue
        else:
            a = {
                'id': appId,
                'name': app['name'].replace(appnamePrefix, ''),
                'expirationTime': sanitize_timestamp(expirationTime),
                }
            runningApps.append(a)
    
    # Run forever-loop to watch for notifications or expiring apps.
    while 1:
        
        # Run check to see if any apps are about to expire.
        act_on_imminent_app_expiration(runningApps)
        
        myEvents = []
        # Set lower bound to 5 minutes ago, upper bound to right now.
        # Unusual manipulation present because Ravello expects timestamps to
        # include thousandths of a sec, but not as floating-point.
        start = time.time() - (5*60 + rOpt.refreshInterval)
        start = int("{:.3f}".format(start).replace('.', ''))
        end = int("{:.3f}".format(time.time()).replace('.', ''))
        query = {
            'dateRange': {
                'startTime': start,
                'endTime': end,
                },
            }
        try:
            # Perform our search.
            results = rClient.search_notifications(query)
        except ravello_sdk.RavelloError as e:
            if e.args[0] == 'request timeout':
                # Timeout, so try one more time.
                results = rClient.search_notifications(query)
        try:
            # Results are returned in reverse-chronological order.
            for event in reversed(results['notification']):
                try:
                    # Only deal with events we have not seen before that relate
                    # to one of myAppIds.
                    if (any(appId == event['appId'] for appId in myAppIds) and 
                            event['eventTimeStamp'] not in timestamps):
                        myEvents.append(event)
                except:
                    pass
        except:
            pass
        
        # Iterate over events relevant to my apps.
        for event in myEvents:
            
            if any(etype in event['eventType'] for etype in eventsOfInterest):
                # Get application data if event of interest.
                try:
                    app = rClient.get_application(
                        event['appId'], aspect='properties')
                except KeyError:
                    # Will fail if event is not about an app, i.e.: on user login.
                    continue
            else:
                continue
            
            # Add unique timestamp for this event to our list, to prevent acting
            # on it in a subsequent loop.
            timestamps.append(event['eventTimeStamp'])
            
            try:
                appName = app['name'].replace(appnamePrefix, '')
            except TypeError:
                # Will fail if app was deleted.
                appName = ''
            
            if event['eventType'] == 'APPLICATION_TIMER_RESET':
                try:
                    # Grab expiration time if app is deployed.
                    expirationTime = app['deployment']['expirationTime']
                except:
                    # (app isn't deployed)
                    pass
                else:
                    expirationTime = sanitize_timestamp(expirationTime)
                    for a in runningApps:
                        # Try to find the app by id in our existing list.
                        if a['id'] == app['id']:
                            # Update the app's expirationTime timestamp.
                            a['expirationTime'] = expirationTime
                            break
                    else:
                        # If the appId for the APPLICATION_TIMER_RESET event isn't
                        # present in our runningApps list, we need to add it.
                        a = {
                            'id': app['id'],
                            'name': appName,
                            'expirationTime': expirationTime,
                            }
                        runningApps.append(a)
            else:
                # Event type is anything but APPLICATION_TIMER_RESET.
                tstamp = datetime.fromtimestamp(
                    sanitize_timestamp(timestamps[-1])
                    ).strftime("%H:%M:%S")
                if appName:
                    appName = " ({})".format(appName)
                msg = event['eventProperties'][0]['value'].replace(appnamePrefix, '')
                cmd = [
                    'notify-send',
                    '--urgency',
                    urgency[event['notificationLevel']],
                    "{}{}".format(event['eventType'], appName),
                    "[{}] {}".format(tstamp, msg),
                    ]
                subprocess.check_call(cmd)
        
        if rOpt.enableDebug and sys.stdout.isatty():
            i = rOpt.refreshInterval
            while i >= 0:
                print(c.REVERSE("{}".format(i)), end='')
                sys.stdout.flush()
                time.sleep(1)
                print('\033[2K', end='')
                i -= 1
            print()
        else:
            time.sleep(rOpt.refreshInterval)

        myAppIds = update_myAppIds(myAppIds)
コード例 #3
0
ファイル: ravshello.py プロジェクト: filiy/ravshello
def main():
    """Parse cmdline args, configure prefs, login, and start captive UI."""
    # Setup parser
    description = ("Interface with Ravello Systems to create & manage apps "
                   "hosted around the world")
    epilog = (
        "ENVIRONMENT VARIABLES:\n"
        "  Various printing commands in {} make use of the RAVSH_EDITOR variable\n"
        "  if it is present, falling back to the EDITOR variable. If that's empty, the\n"
        "  fall-back process is to use: gvim, vim, and finally less.\n\n"
        "VERSION:\n"
        "  {}\n"
        "  Report bugs/RFEs/feedback at https://github.com/ryran/ravshello/issues"
        .format(cfg.prog, cfg.version))
    p = argparse.ArgumentParser(
        prog=cfg.prog,
        description=description,
        add_help=False,
        epilog=epilog,
        formatter_class=lambda prog: CustomFormatter(prog))

    # Setup groups for help page:
    grpU = p.add_argument_group('UNIVERSAL OPTIONS')
    grpA = p.add_argument_group(
        'ADMINISTRATIVE FEATURES',
        description="Require that Ravello account user has admin rights")

    # Universal opts:
    grpU.add_argument('-h',
                      '--help',
                      dest='showHelp',
                      action='store_true',
                      help="Show this help message and exit")
    grpU.add_argument(
        '-u',
        '--user',
        dest='ravelloUser',
        metavar='USER',
        default='',
        help=("Explicitly specify Ravello username or profile name from {} "
              "config file (will automatically prompt for passphrase if none "
              "is present in cfgfile)".format(cfg.defaultUserCfgFile)))
    grpU.add_argument(
        '-p',
        '--passwd',
        dest='ravelloPass',
        metavar='PASSWD',
        default='',
        help=("Explicitly specify a Ravello user password on the command-line "
              "(unsafe on multi-user system)"))
    grpU_0 = grpU.add_mutually_exclusive_group()
    grpU_0.add_argument(
        '-k',
        '--nick',
        dest='nick',
        help=("Explicitly specify a nickname to use for app-filtering "
              "(nickname is normally determined from the system user name "
              "and is used to hide applications that don't start with "
              "'k:NICK__'; any apps created will also have that tag prefixed "
              "to their name; see also 'nickname' and 'appnameNickPrefix' "
              "config directives in /usr/share/{}/config.yaml)".format(
                  cfg.prog)))
    grpU_0.add_argument('--prompt-nick',
                        dest='promptNickname',
                        action='store_true',
                        help="Prompt for nickname to use for app-filtering")
    grpU.add_argument(
        '--never-prompt-creds',
        dest='neverPromptCreds',
        action='store_true',
        help=(
            "Never prompt for Ravello user or pass credentials if missing; "
            "instead exit with standard 'Logging in to Ravello failed' message "
            "and set exit code '5' (note that using this will override an "
            "explicit 'neverPromptCreds=false' setting from a config file)"))
    grpU.add_argument('-n',
                      '--nocolor',
                      dest='enableColor',
                      action='store_false',
                      help="Disable all color terminal enhancements")
    grpU.add_argument('--cfgdir',
                      dest='userCfgDir',
                      metavar='CFGDIR',
                      default=cfg.defaultUserCfgDir,
                      help=("Explicitly specify path to user config directory "
                            "(default: '{}')".format(cfg.defaultUserCfgDir)))
    grpU.add_argument(
        '--cfgfile',
        dest='cfgFileName',
        metavar='CFGFILE',
        default=cfg.defaultUserCfgFile,
        help=
        ("Explicitly specify basename of optional per-user yaml config file "
         "containing login credentials & other settings (default: '{default}'); "
         "note that this will be created in CFGDIR; also note that this "
         "file will be read AFTER /usr/share/{prog}/config.yaml & "
         "/etc/{prog}/config.yaml".format(default=cfg.defaultUserCfgFile,
                                          prog=cfg.prog)))
    grpU.add_argument(
        '--clearprefs',
        dest='clearPreferences',
        action='store_true',
        help="Delete prefs.bin in per-user CFGDIR before starting")
    grpU.add_argument('-q',
                      '--quiet',
                      dest='enableVerbose',
                      action='store_false',
                      help="Hide verbose messages during startup")
    grpU.add_argument(
        '-Q',
        '--more-quiet',
        dest='printWelcome',
        action='store_false',
        help=(
            "A superset of the --quiet option; also hides various non-verbose "
            "welcome messages during startup"))
    grpU.add_argument(
        '-d',
        '--debug',
        dest='enableDebugging',
        action='store_true',
        help=("Turn on debugging features to help troubleshoot a problem "
              "(critically, this disables some ConfigShell exception-handling "
              "so that errors in commands will cause {} to exit)".format(
                  cfg.prog)))
    grpU.add_argument(
        '-D',
        '--directsdk',
        action='store_true',
        help=(
            "Replaces the standard {} interface with a shell that provides "
            "direct access to the Ravello SDK (note that this shell respects "
            "the --stdin & --scripts options, as well as any cmdline args)".
            format(cfg.prog)))
    grpU.add_argument('-V', '--version', action='version', version=cfg.version)

    # Admin-only opts:
    grpA.add_argument('-a',
                      '--admin',
                      dest='enableAdminFuncs',
                      action='store_true',
                      help="Enable admin functionality")
    grpA.add_argument(
        '-A',
        '--allapps',
        dest='showAllApps',
        action='store_true',
        help=("Show all applications, including ones not associated with your "
              "user (automatically triggers --admin option)"))
    grpA_0 = grpA.add_mutually_exclusive_group()
    grpA_0.add_argument(
        '-0',
        '--stdin',
        dest='useStdin',
        action='store_true',
        help=("Enable reading newline-delimited commands from stdin "
              "(these commands will be executed instead of entering the "
              "interactive shell -- automatic exit after last cmd)"))
    grpA_0.add_argument(
        '-s',
        '--script',
        dest='scriptFile',
        metavar='FILE',
        help=("Specify a script file containing newline-delimited "
              "commands (these commands will be executed instead of entering "
              "the interactive shell -- automatic exit after last cmd)"))
    grpA.add_argument(
        'cmdlineArgs',
        metavar='COMMANDS',
        nargs=argparse.REMAINDER,
        help=("If any additional cmdline args are present, each shell word "
              "will be treated as a separate command and they will all be "
              "executed prior to entering the interactive shell (ensure "
              "each cmd is quoted to protect from shell expansion!)"))

    # Build out options namespace
    cfg.opts = rOpt = p.parse_args()

    # Halp-quit
    if rOpt.showHelp:
        p.print_help()
        exit()

    # Trigger -q if -Q was called
    if not rOpt.printWelcome:
        rOpt.enableVerbose = False

    # Setup color/verbosity
    c.enableColor = rOpt.enableColor
    c.enableVerbose = rOpt.enableVerbose
    c.enableDebug = rOpt.enableDebugging

    # Trigger -a if -A was called
    if rOpt.showAllApps:
        rOpt.enableAdminFuncs = True

    if not rOpt.enableAdminFuncs:
        if rOpt.cmdlineArgs or rOpt.scriptFile or rOpt.useStdin:
            print(c.red(
                "Sorry! Only admins are allowed to use {} non-interactively".
                format(cfg.prog)),
                  file=stderr)
            exit(1)
        if rOpt.directsdk:
            print(c.red(
                "Sorry! Only admins are allowed to use the direct SDK shell!"),
                  file=stderr)
            exit(1)

    # Print warnings about incompatible options
    if rOpt.useStdin and rOpt.cmdlineArgs:
        print(
            c.yellow("Ignoring cmdline-args because -0/--stdin was requested"),
            file=stderr)
    elif rOpt.scriptFile and rOpt.cmdlineArgs:
        print(c.yellow(
            "Ignoring cmdline-args because -s/--script was requested"),
              file=stderr)

    # Expand userCfgDir in case of tildes; set to default if missing specified dir
    if os.path.isdir(os.path.expanduser(rOpt.userCfgDir)):
        rOpt.userCfgDir = os.path.expanduser(rOpt.userCfgDir)
    else:
        rOpt.userCfgDir = os.path.expanduser(cfg.defaultUserCfgDir)

    # Read package config file
    apply_config_file('/usr/share/{}/config.yaml'.format(cfg.prog))
    # Read system config file
    apply_config_file('/etc/{}/config.yaml'.format(cfg.prog))
    # Read user config file
    apply_config_file(os.path.join(rOpt.userCfgDir, rOpt.cfgFileName))
    # Do some checking of cfgfile options
    if cfg.cfgFile:
        # Handle include files
        includes = cfg.cfgFile.get('includes', [])
        if isinstance(includes, list):
            # Handle glob-syntax
            L = []
            for filepath in includes:
                L.extend(glob(os.path.expanduser(filepath)))
            for filepath in L:
                apply_config_file(filepath)
        else:
            print(c.yellow(
                "Error: Ignoring configFile `includes` directive because it's not a list\n"
                "  See /usr/share/{}/config.yaml for example".format(
                    cfg.prog)),
                  file=stderr)
        # Validate pre-run commands
        preRunCommands = cfg.cfgFile.get('preRunCommands', [])
        if not isinstance(preRunCommands, list):
            print(c.yellow(
                "Error: Ignoring configFile `preRunCommands` directive because it's not a list\n"
                "  See /usr/share/{}/config.yaml for example".format(
                    cfg.prog)),
                  file=stderr)
            del cfg.cfgFile['preRunCommands']
        # Validate neverPromptCreds
        neverPromptCreds = cfg.cfgFile.get('neverPromptCreds', False)
        if isinstance(neverPromptCreds, bool):
            if neverPromptCreds:
                rOpt.neverPromptCreds = True
        else:
            print(c.yellow(
                "Error: Ignoring configFile `neverPromptCreds` directive because it's not a boolean\n"
                "  See /usr/share/{}/config.yaml for example".format(
                    cfg.prog)),
                  file=stderr)

    # Set sshKeyFile var to none if missing
    cfg.cfgFile['sshKeyFile'] = cfg.cfgFile.get('sshKeyFile', None)

    if rOpt.printWelcome:
        print(c.BOLD("Welcome to {}!".format(cfg.prog)), file=stderr)

    # Liftoff
    # 1.) Establish a local user name to use in ravshello
    #     This name is arbitrary and has nothing to do with Ravello login creds
    #     It is used:
    #       - To construct names for new apps
    #       - To restrict which apps can be seen
    #       - To determine if admin functionality is unlockable (assuming -a or -A)
    cfg.user = auth_local.authorize_user()

    # 2.) Use ravello_sdk.RavelloClient() object to log in to Ravello
    cfg.rClient = auth_ravello.login()
    cfg.rCache = ravello_cache.RavelloCache(cfg.rClient)

    # 3.) Launch main configShell user interface
    #     It will read options and objects from the cfg module
    user_interface.main()