def test_values_not_all_iterables(self):
        # Make sure we don't confuse other sequence types, e.g. str
        with self.assertRaises(TypeError) as exception_context:
            PrefixList("zero")

        self.assertEqual(
            str(exception_context.exception),
            "Legal values should be provided via an iterable of strings, "
            "got 'zero'.")
Exemplo n.º 2
0
# * 'info': A temporary, frameless popup dialog that immediately updates the
#   object and is active only while the dialog is still over the invoking
#   control.
# * 'wizard': A wizard modal dialog box. A wizard contains a sequence of
#   pages, which can be accessed by clicking **Next** and **Back** buttons.
#   Changes to attribute values are applied only when the user clicks the
#   **Finish** button on the last page.
AKind = PrefixList(
    (
        "panel",
        "subpanel",
        "modal",
        "nonmodal",
        "livemodal",
        "live",
        "popup",
        "popover",
        "info",
        "wizard",
    ),
    default_value='live',
    desc="the kind of view window to create",
    cols=4,
)

# Apply changes handler:
OnApply = Callable(
    desc="the routine to call when modal changes are applied " "or reverted"
)

# Is the dialog window resizable?
Exemplo n.º 3
0
class Person(HasTraits):
    atr1 = PrefixList(('yes', 'no'))
    atr2 = PrefixList(['yes', 'no'])
Exemplo n.º 4
0
class Annotater(Component):

    color = ColorTrait((0.0, 0.0, 0.0, 0.2))
    style = PrefixList(["rectangular", "freehand"],
                       default_value="rectangular")
    annotation = Event

    traits_view = View(
        Group("<component>", id="component"),
        Group("<links>", id="links"),
        Group("color", "style", id="annotater", style="custom"),
    )

    # -------------------------------------------------------------------------
    # Mouse event handlers
    # -------------------------------------------------------------------------

    def _left_down_changed(self, event):
        event.handled = True
        self.window.mouse_owner = self
        self._cur_x, self._cur_y = event.x, event.y
        self._start_x, self._start_y = event.x, event.y

    def _left_up_changed(self, event):
        event.handled = True
        self.window.mouse_owner = None
        if self.xy_in_bounds(event):
            self.annotation = (
                min(self._start_x, event.x),
                min(self._start_y, event.y),
                abs(self._start_x - event.x),
                abs(self._start_y - event.y),
            )
        self._start_x = self._start_y = self._cur_x = self._cur_y = None
        self.redraw()

    def _mouse_move_changed(self, event):
        event.handled = True
        if self._start_x is not None:
            x = max(min(event.x, self.right - 1.0), self.x)
            y = max(min(event.y, self.top - 1.0), self.y)
            if (x != self._cur_x) or (y != self._cur_y):
                self._cur_x, self._cur_y = x, y
                self.redraw()

    # -------------------------------------------------------------------------
    # "Component" interface
    # -------------------------------------------------------------------------

    def _draw(self, gc):
        "Draw the contents of the control"
        if self._start_x is not None:
            with gc:
                gc.set_fill_color(self.color_)
                gc.begin_path()
                gc.rect(
                    min(self._start_x, self._cur_x),
                    min(self._start_y, self._cur_y),
                    abs(self._start_x - self._cur_x),
                    abs(self._start_y - self._cur_y),
                )
                gc.fill_path()
        return
Exemplo n.º 5
0
# -----------------------------------------------------------------------------

# Font trait:
font_trait = KivaFont(default_font_name)

# Bounds trait
bounds_trait = CList([0.0, 0.0])  # (w,h)
coordinate_trait = CList([0.0, 0.0])  # (x,y)

# Component minimum size trait
# PZW: Make these just floats, or maybe remove them altogether.
ComponentMinSize = Range(0.0, 99999.0)
ComponentMaxSize = ComponentMinSize(99999.0)

# Pointer shape trait:
Pointer = PrefixList(pointer_shapes, default_value="arrow")

# Cursor style trait:
cursor_style_trait = PrefixMap(cursor_styles, default_value="default")

spacing_trait = Range(0, 63, value=4)
padding_trait = Range(0, 63, value=4)
margin_trait = Range(0, 63)
border_size_trait = Range(0, 8, editor=border_size_editor)

# Time interval trait:
TimeInterval = Union(None, Range(0.0, 3600.0))

# Stretch traits:
Stretch = Range(0.0, 1.0, value=1.0)
NoStretch = Stretch(0.0)
Exemplo n.º 6
0
    List,
    PrefixList,
    Range,
    Str,
    TraitError,
    TraitType,
)

from .helper import SequenceTypes

# -------------------------------------------------------------------------
#  Trait definitions:
# -------------------------------------------------------------------------

# Orientation trait:
Orientation = PrefixList(("vertical", "horizontal"))

# Styles for user interface elements:
EditorStyle = style_trait = PrefixList(
    ("simple", "custom", "text", "readonly"), cols=4)

# Group layout trait:
Layout = PrefixList(("normal", "split", "tabbed", "flow", "fold"))

# Trait for the default object being edited:
AnObject = Expression("object")

# The default dock style to use:
DockStyle = dock_style_trait = Enum(
    "fixed",
    "horizontal",
Exemplo n.º 7
0
class Shell(HasRequiredTraits):
    host = Str(value="", required=True)
    port = Range(value=22, low=1, high=65534)
    username = Str(value=getuser(), required=False)
    password = Str(value="", required=False)
    private_key_path = File(value=os.path.expanduser("~/.ssh/id_rsa"))
    original_host = Str(value="", required=False)
    inventory_file = File(required=False, default=None)
    # FIXME - needs more drivers... -> https://exscript.readthedocs.io/en/latest/Exscript.protocols.drivers.html
    driver = PrefixList(
        value="generic", values=["generic", "shell", "junos", "ios"], required=False
    )
    termtype = PrefixList(
        value="dumb", values=["dumb", "xterm", "vt100"], required=False
    )
    protocol = PrefixList(value="ssh", values=["ssh"], required=False)
    stdout = PrefixList(value=None, values=[None, sys.stdout], required=False)
    stderr = PrefixList(value=sys.stderr, values=[None, sys.stderr], required=False)
    banner_timeout = Range(value=20, low=1, high=30, required=False)
    connect_timeout = Range(value=10, low=1, high=30, required=False)
    prompt_timeout = Range(value=10, low=1, high=65535, required=False)
    prompt_list = List(Str, required=False)
    default_prompt_list = List(re.Pattern, required=False)
    account = Any(value=None, required=True)
    account_list = List(Exscript.account.Account, required=False)
    json_logfile = File(value="/dev/null", required=False)
    jh = Any(value=None)
    encoding = PrefixList(value="utf-8", values=["latin-1", "utf-8"], required=False)
    interact = Bool(value=False, values=[True, False])
    downgrade_ssh_crypto = Bool(value=False, values=[True, False])
    ssh_attempt_number = Range(value=1, low=1, high=3, required=False)
    conn = Any(required=False)
    debug = Range(value=0, low=0, high=5, required=False)
    allow_invalid_command = Bool(value=True, values=[True, False], required=False)
    MAX_SSH_ATTEMPT = Int(3)

    def __init__(self, **traits):
        super().__init__(**traits)

        self.original_host = copy.copy(self.host)
        assert self.host != ""
        assert isinstance(self.port, int)
        assert len(self.account_list) == 0
        if isinstance(self.account, Account):
            self.append_account(self.account)
        else:
            raise ValueError("Account must be included in the Shell() call")

        # Ensure this was NOT called with username
        if traits.get("username", False) is not False:
            raise ValueError("Shell() calls with username are not supported")

        # Ensure this was NOT called with password
        if traits.get("password", False) is not False:
            raise ValueError("Shell() calls with password are not supported")

        # Check whether host matches an ip address in the inventory...
        # Overwrite self.host with resolved IP Address...
        resolved_host = self.search_inventory_for_host_address(self.host)
        logger.debug(
            "Shell(host='%s') resolved host to '%s'"
            % (self.original_host, resolved_host)
        )
        self.host = resolved_host

        self.conn = self.do_ssh_login(debug=self.debug)
        self.allow_invalid_command = True

        # Always store the original prompt(s) so we can fallback to them later
        self.default_prompt_list = self.conn.get_prompt()

        # Populate the initial prompt list...
        if self.json_logfile != "/dev/null":
            self.open_json_log()
            self.json_log_entry(cmd="ssh2", action="login", timeout=False)

        if self.interact is True:
            self.interactive_session()

    def __repr__(self):
        return """<Shell: %s>""" % self.host

    def interactive_session(self):
        """Basic method to run an interactive session
        TODO - write timestamped interactive session to disk...
        """

        finished = False
        while finished is not True:
            cmd = input("{0}# ".format(self.original_host))
            for line in cmd.splitlines():
                self.execute(line, consume=False, timeout=10)
                for line in self.conn.response.splitlines():
                    print(line.strip())

    def search_inventory_for_host_address(self, hostname=None):
        """Use an ansible-style inventory file to map the input hostname to an IPv4 or IPv6 address"""

        logger.debug("Checking inventory for hostname='%s'" % hostname)
        # Search inventory first...
        for line in self.iter_inventory_lines():
            # Try to resolve the address as an ansible hostfile...
            mm = re.search(
                r"^\s*({0})\s.*?(ansible_host)\s*=\s*(\S+)".format(hostname.lower()),
                line.lower(),
            )
            if mm is not None:
                host_ip = mm.group(3)
                tmp = stdlib_ip_factory(host_ip)  # This will raise a ValueError
                logger.debug(
                    "Found inventory match for '%s' -> %s" % (hostname, host_ip)
                )
                return host_ip

        else:
            logger.debug("No inventory match for '%s'" % hostname)

        # If this resolves to a valid ipv4 string, return the ipv4 string
        valid_ipv4 = sock.getaddrinfo(hostname, None, sock.AF_INET)[0][4][0]
        logger.debug(valid_ipv4)
        try:
            if stdlib_ip_factory(valid_ipv4):
                return valid_ipv4
        except ValueError:
            logger.debug("No ipv4 DNS record for '%s'" % hostname)
            # Fall through to the next step
            pass

        # If this resolves to a valid ipv6 string, return the ipv6 string
        valid_ipv6 = sock.getaddrinfo(hostname, None, sock.AF_INET6)[0][4][0]
        logger.debug(valid_ipv6)
        try:
            if stdlib_ip_factory(valid_ipv6):
                logger.debug("No ipv6 DNS record for '%s'" % hostname)
                return valid_ipv6
        except ValueError:
            # Fall through to the next step
            pass

        # Raise a non-fatal error...
        logger.warning(
            "Could not resolve or match hostname='%s' in the inventory file: %s"
            % (hostname, self.inventory_file)
        )
        return None

    def iter_inventory_lines(self):

        explicit_inventory_filepath = os.path.expanduser(self.inventory_file)

        # Handle the default invntory value...
        if self.inventory_file.strip() == "":
            return []
        if os.path.isfile(explicit_inventory_filepath):
            with open(explicit_inventory_filepath, "r", encoding="utf=8") as fh:
                for line in fh.read().splitlines():
                    yield line.lower()
        else:
            raise OSError("Cannot find inventory file named '%s'" % self.inventory_file)

    def do_ssh_login(
        self,
        connect_timeout=10,
        debug=0,
    ):
        assert isinstance(connect_timeout, int)
        assert isinstance(debug, int)
        assert len(self.account_list) > 0

        # FIXME - clean up PrivateKey here...
        private_key = PrivateKey(keytype="rsa").from_file(self.private_key_path)

        self.downgrade_ssh_crypto = False
        if self.protocol == "ssh":

            for self.ssh_attempt_number in [1, 2, 3]:

                assert self.ssh_attempt_number <= self.MAX_SSH_ATTEMPT

                # You have to change allowed ciphers / key exchange options
                # **before** the connection
                #     -> https://stackoverflow.com/a/31303321/667301
                if self.downgrade_ssh_crypto is True:
                    paramiko.Transport._preferred_ciphers = (
                        "aes128-cbc",
                        "3des-cbc",
                    )
                    paramiko.Transport._preferred_kex = (
                        "diffie-hellman-group-exchange-sha1",
                        "diffie-hellman-group14-sha1",
                        "diffie-hellman-group1-sha1",
                    )

                conn = SSH2(driver=self.driver)

                # Save default values...
                DEFAULT_CONNECT_TIMEOUT = conn.get_connect_timeout()
                DEFAULT_PROMPT_LIST = conn.get_prompt()
                DEFAULT_PROMPT_TIMEOUT = conn.get_timeout()

                # FIXME - Exscript should be handling this but the pypi pkg doesn't
                #

                conn.set_connect_timeout(connect_timeout)
                try:
                    conn.connect(hostname=self.host, port=self.port)
                    break

                except sock.timeout as ee:
                    self.downgrade_ssh_crypto = True
                    if self.ssh_attempt_number == self.MAX_SSH_ATTEMPT:
                        error = "Timeout connecting to TCP port {1} on host:{0}".format(
                            self.host, self.port
                        )
                        logger.critical(error)
                        raise OSError(error)
                    else:
                        assert self.ssh_attempt_number < self.MAX_SSH_ATTEMPT
                        time.sleep(0.5)

                except SSHException as ee:
                    self.downgrade_ssh_crypto = True
                    if self.ssh_attempt_number == self.MAX_SSH_ATTEMPT:
                        error = (
                            "Connection to host:{0} on TCP port {1} was reset".format(
                                self.host, self.port
                            )
                        )
                        logger.critical(error)
                        raise OSError(error)
                    else:
                        assert self.ssh_attempt_number < self.MAX_SSH_ATTEMPT
                        time.sleep(0.5)

            login_success = False
            for account in self.account_list:
                conn.login(account)
                try:
                    assert isinstance(conn, SSH2)  # This succeeds if logged in...
                    login_success = True
                    break
                except AssertionError as aa:
                    # login with account failed...
                    continue

            assert login_success is True
            if login_success is True:
                self.password = account.password
            else:
                raise ValueError("Login to host='%s' failed" % self.host)

            conn.set_connect_timeout(DEFAULT_CONNECT_TIMEOUT)

            return conn

        else:
            raise ValueError("FATAL: proto='%s' isn't a valid protocol" % proto)

    ## TODO - we should use a true try / except here...
    def open_json_log(self):
        if self.json_logfile == "/dev/null":
            return None
        else:
            self.jh = open(os.path.expanduser(self.json_logfile), "w", encoding="utf-8")
            atexit.register(self.close_json_log)
            return True

    ## TODO - we should use a true try / except here...
    def close_json_log(self):
        if self.json_logfile == "/dev/null":
            return None
        else:
            self.jh.flush()
            self.jh.close()
            return True

    def json_log_entry(self, cmd=None, action=None, result=None, timeout=False):
        if self.json_logfile == "/dev/null":
            return None
        assert isinstance(cmd, str)
        assert isinstance(action, str)
        assert action in set(["login", "execute", "send", "expect", "output"])
        assert isinstance(result, str) or (result is None)
        assert isinstance(timeout, bool)
        # Pretty json output... or reference json docs
        #     https://stackoverflow.com/a/12944035/667301
        self.jh.write(
            json.dumps(
                {
                    "time": str(arrow.now()),
                    "cmd": cmd,
                    "host": self.host,
                    "action": action,
                    "result": result,
                    "timeout": timeout,
                },
                indent=4,
                sort_keys=True,
            )
            + ","
            + os.linesep
        )

    def append_account(self, account):
        # From the exscript docs...
        #     key = PrivateKey.from_file('~/.ssh/id_rsa', 'my_key_password')
        self.account_list.append(account)

    def _extend_prompt(self, prompt_list=()):
        retval = list()
        for ii in prompt_list:
            if isinstance(ii, str):
                compiled = re.compile(ii)
                retval.append(compiled)

            elif isinstance(ii, re.Pattern):
                retval.append(ii)

            else:
                raise ValueError("Cannot process prompt:'%s'" % ii)

        for ii in self.conn.get_prompt():
            retval.append(ii)

        self.conn.set_prompt(retval)

    def tfsm(self, template=None, input_str=None):
        """Run the textfsm template against input_str"""
        assert isinstance(template, str)
        assert isinstance(input_str, str)
        if os.path.isfile(os.path.expanduser(str(template))):
            # open the textfsm template from disk...
            fh = open(template, "r")
        else:
            # build a fake filehandle around textfsm template string
            fh = StringIO(template)
        fsm = TextFSM(fh)
        header = fsm.header
        values = fsm.ParseText(input_str)
        assert values != [], "Could not match any values with the template."

        ## Pack the extracted values into a list of dicts, using keys from
        ##   the header file
        retval = list()

        # Values is a nested list of captured information
        for ii in (values, values[0]):
            if not isinstance(ii, list):
                continue
            for row in ii:
                try:
                    # Require the row to be a list
                    assert isinstance(row, list)
                    # Require row to be exactly as long as the header list
                    assert len(row) == len(header)
                    row_dict = {}
                    for idx, value in enumerate(row):
                        row_dict[header[idx]] = value
                    retval.append(row_dict)
                except AssertionError:
                    break
            if len(retval) > 0:
                return retval
            else:
                raise ValueError("Cannot parse the textfsm template")

    def execute(
        self, cmd="", prompt_list=(), timeout=0, template=None, debug=0, consume=True
    ):
        assert isinstance(cmd, str)
        if cmd.strip() == "":
            assert len(cmd.splitlines()) == 0
        else:
            assert len(cmd.splitlines()) == 1
        assert isinstance(timeout, int)
        assert (template is None) or isinstance(template, str)
        assert isinstance(debug, int)

        cmd = cmd.strip()

        if debug > 0:
            logger.debug("Calling execute(cmd='%s', timeout=%s)" % (cmd, timeout))

        if len(prompt_list) > 0:
            self._extend_prompt(prompt_list)

        normal_timeout = self.conn.get_timeout()
        if timeout > 0:
            self.conn.set_timeout(timeout)

        # Handle prompt_list...
        self.set_custom_prompts(prompt_list)

        self.json_log_entry(cmd=cmd, action="execute", timeout=False)

        # Handle sudo command...
        if cmd.strip()[0:4] == "sudo":
            pre_sudo_prompts = self.conn.get_prompt()
            # FIXME I removed re.compile from the sudo prompt. Example prompt:
            #    [sudo] password for mpenning:
            sudo_prompt = re.compile(r"[\r\n].+?:")
            prompts_w_sudo = copy.deepcopy(pre_sudo_prompts)
            prompts_w_sudo.insert(0, sudo_prompt)
            self.conn.set_prompt(prompts_w_sudo)

            # Sending sudo cmd here...
            self.conn.send(cmd + os.linesep)
            prompt_idx, re_match_object = self.conn.expect_prompt()
            # idx==0 is a sudo password prompt...
            if prompt_idx == 0:
                self.conn.set_prompt(self.default_prompt_list)
                self.conn.send(self.password + os.linesep)
                prompt_idx, re_match_object = self.conn.expect_prompt()
                assert isinstance(prompt_idx, int)

            else:
                raise ValueError("Cannot complete 'execute(cmd='%s')" % cmd)
            self.conn.set_prompt(pre_sudo_prompts)

        # Handle non-sudo execute()...
        else:
            try:
                self.conn.execute(cmd)

            except ConnectionResetError as dd:
                error = "SSH session with {0} was reset while running cmd='{1}'".format(
                    self.host, cmd
                )
                raise ConnectionResetError(error)

            except InvalidCommandException:

                if self.allow_invalid_command is False:
                    error = "cmd='%s' is an invalid command" % cmd
                    logger.critical(error)
                    raise InvalidCommandException(error)

        # Reset the prompt list at the end of the command...
        if len(prompt_list) > 0:
            self.reset_prompt()

        # Reset the timeout at the end of the command...
        if self.conn.get_timeout() != normal_timeout:
            self.conn.set_timeout(normal_timeout)

        # save the raw response...
        cmd_output = self.conn.response

        self.json_log_entry(cmd=cmd, action="output", result=cmd_output, timeout=False)
        ## TextFSM
        ## If template is specified, parse the response into a list of dicts...
        if isinstance(template, str):
            return self.tfsm(template, cmd_output)
        else:
            return cmd_output

    def send(self, cmd="", debug=0):
        assert isinstance(cmd, str)
        assert len(cmd.splitlines()) == 1
        assert isinstance(debug, int)

        self.json_log_entry(cmd=cmd, action="send", result=None, timeout=False)

        self.conn.send(cmd)

    def expect(self, prompt_list=(), timeout=0, debug=0):
        """Expect prompts, including those in prompt_list"""
        assert isinstance(debug, int)
        if debug > 0:
            pass

        normal_timeout = self.conn.get_timeout()
        if timeout > 0:
            self.conn.set_timeout(timeout)

        normal_prompts = self.conn.get_prompt()

        # Handle prompt_list...
        self.set_custom_prompts(prompt_list)

        prompt_idx, re_match_object = self.conn.expect_prompt()

        self.conn.set_timeout(normal_timeout)
        # Reset the prompt list at the end of the command...
        if len(prompt_list) > 0:
            self.reset_prompt()

        self.json_log_entry(cmd=cmd, action="expect", result=None, timeout=False)
        return prompt_idx, re_match_object

    def set_timeout(self, timeout=0):
        """Set the command timeout"""
        if isinstance(timeout, int) and timeout > 0:
            return self.conn.set_timeout(timeout)

    def set_custom_prompts(self, prompt_list=()):
        """Wrapper around set_prompt()"""
        return self.set_prompt(prompt_list)

    def reset_prompt(self):
        """Reset all prompts to default"""
        return self.conn.set_prompt(self.default_prompt_list)

    def set_prompt(self, prompt_list=()):
        """Extend the expected prompts with prompts in prompt_list"""
        normal_prompts = self.conn.get_prompt()
        custom_prompts = copy.copy(normal_prompts)
        if (isinstance(prompt_list, tuple) or isinstance(prompt_list, list)) and len(
            prompt_list
        ) > 0:
            prompt_list.reverse()
            for prompt in prompt_list:
                if isinstance(prompt, str):
                    custom_prompts.insert(0, re.compile(prompt))

                elif isinstance(prompt, re.Pattern):
                    custom_prompts.insert(0, prompt)

                else:
                    raise ValueError("Cannot process prompt='%s'" % prompt)
            self.conn.set_prompt(custom_prompts)

        return normal_prompts, custom_prompts

    def close(self, force=True, timeout=0, debug=0):
        self.conn.close(force=force)

    @property
    def response(self):
        return self.conn.response
 class A(HasTraits):
     foo = PrefixList(["zero", "one", "two"], default_value="one")
 def test_values_is_empty(self):
     # it doesn't make sense to use a PrefixList with an empty list, so make
     # sure we raise a ValueError
     with self.assertRaises(ValueError):
         PrefixList([])
Exemplo n.º 10
0
 def test_values_not_sequence(self):
     # Defining values with this signature is not supported
     with self.assertRaises(TypeError):
         PrefixList("zero", "one", "two")
Exemplo n.º 11
0
 class A(HasTraits):
     foo = PrefixList(("abc1", "abc2"))