Skip to content

Evengsdk

EvengClient

Source code in evengsdk/client.py
class EvengClient:
    def __init__(
        self,
        host: str = None,
        protocol: str = "http",
        log_file: str = None,
        log_level: str = "INFO",
        port: int = None,
        disable_insecure_warnings: bool = False,
        ssl_verify: bool = True,
    ):
        self.host = host
        self.protocol = protocol
        self.session = None
        self.log_level = log_level
        self.log_file = log_file
        self.html5 = -1
        self.api = None
        self.port = port
        self.ssl_verify = ssl_verify
        self.user = None

        # Create Logger and set Set log level
        self.log = logging.getLogger("eveng-client")
        self.set_log_level(log_level)
        if log_file:
            self.log.addHandler(logging.FileHandler(log_file))
        else:
            self.log.addHandler(logging.NullHandler())

        # Disable insecure warnings
        if disable_insecure_warnings:
            self.disable_insecure_warnings()

    def disable_insecure_warnings(self):
        # disable insecure warnings
        requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

    @property
    def url_prefix(self):
        """Return full url prefix for EVE-NG API."""
        if self.port:
            return f"{self.protocol}://{self.host}:{self.port}/api"
        return f"{self.protocol}://{self.host}/api"

    def set_log_level(self, log_level: str) -> None:
        """Set log level for logger. Defaults to INFO if no level passed in or
        if an invalid level is passed in.

        :param log_level: Log level to use for logger, defaults to 'INFO'
        :type log_level: str, optional
        """
        LOG_LEVELS = ("NOTSET", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL")
        if log_level.upper() not in LOG_LEVELS:
            log_level = "INFO"
        self.log.setLevel(getattr(logging, log_level))

    def login(self, username: str, password: str, *args, **kwargs) -> None:
        """Initiate login to EVE-NG host. Accepts args and kwargs for
        request.Session object, except "data" key.

        :param username: EVE-NG login username, defaults to ''
        :type username: str, optional
        :param password: EVE-NG login password, defaults to ''
        :type password: str, optional
        :raises EvengLoginError: raises for unsuccessful logins
        """
        login_endpoint = self.url_prefix + "/auth/login"
        authdata = {"username": username, "password": password, "html5": self.html5}

        self.log.debug("creating session")
        if not self.session:
            self.session = requests.Session()
            self.session.verify = self.ssl_verify

        # set default session header
        self.session.headers = {
            "Accept": "application/json",
            "Content-Type": "application/json",
        }

        _ = kwargs.pop("data", None)  # avoids duplicate `data` key for Session
        r = self.session.post(
            login_endpoint, data=json.dumps(authdata), *args, **kwargs
        )
        if r.ok:
            try:
                "logged in" in r.json()
                self.username = username
                self.api = EvengApi(self)  # create API wrapper object
            except json.decoder.JSONDecodeError:
                self.log.error("Error logging in: {}".format(r.text))
                raise EvengLoginError("Error logging in: {}".format(r.text))
        else:
            self.session = {}
            raise EvengLoginError("Error logging in: {}".format(r.text))

    def logout(self):
        try:
            self.session.get(self.url_prefix + "/auth/logout")
        finally:
            self.session = None

    def _make_request(
        self, method: str, url: str, use_prefix: bool = True, *args, **kwargs
    ) -> dict:
        """Craft request to EVE-NG API

        :param method: request method
        :type method: str
        :param url: full url or endpoint for request
        :type url: str
        :raises ValueError: if no session exists
        :raises ValueError: if response object does not contain valid JSON data
        :return: Response dictionary from EVE-NG API
        :rtype: dict
        """
        if not self.session:
            raise ValueError("No valid session exist")

        if use_prefix and self.url_prefix not in url:
            url = self.url_prefix + url

        req = requests.Request(method, url, *args, **kwargs)
        prepped_req = self.session.prepare_request(req)

        r = self.session.send(prepped_req)
        if r.ok:
            try:
                return r.json()
            except json.JSONDecodeError:
                return r
        self.log.error("Error: {}".format(r.text))

        # EVE-NG API returns HTTP error code and message in JSON response
        if hasattr(r, "json"):
            try:
                err_code = r.json().get("code")
            except json.JSONDecodeError:
                err_code = r.text
            try:
                err_msg = r.json().get("message")
            except json.JSONDecodeError:
                err_msg = r.text
            raise EvengHTTPError("Error: {} {}".format(err_code, err_msg))

        # Other HTTP errors for which we don't have a JSON response
        r.raise_for_status()

    def post(self, url: str, *args, **kwargs):
        return self._make_request("POST", url, *args, **kwargs)

    def get(self, url: str, *args, **kwargs):
        return self._make_request("GET", url, *args, **kwargs)

    def put(self, url: str, *args, **kwargs):
        return self._make_request("PUT", url, *args, **kwargs)

    def patch(self, url: str, *args, **kwargs):
        return self._make_request("PATCH", url, *args, **kwargs)

    def delete(self, url: str, *args, **kwargs):
        return self._make_request("DELETE", url, *args, **kwargs)

url_prefix property readonly

Return full url prefix for EVE-NG API.

login(self, username, password, *args, **kwargs)

Initiate login to EVE-NG host. Accepts args and kwargs for request.Session object, except "data" key.

Parameters:

Name Type Description Default
username str

EVE-NG login username, defaults to ''

required
password str

EVE-NG login password, defaults to ''

required

Exceptions:

Type Description
EvengLoginError

raises for unsuccessful logins

Source code in evengsdk/client.py
def login(self, username: str, password: str, *args, **kwargs) -> None:
    """Initiate login to EVE-NG host. Accepts args and kwargs for
    request.Session object, except "data" key.

    :param username: EVE-NG login username, defaults to ''
    :type username: str, optional
    :param password: EVE-NG login password, defaults to ''
    :type password: str, optional
    :raises EvengLoginError: raises for unsuccessful logins
    """
    login_endpoint = self.url_prefix + "/auth/login"
    authdata = {"username": username, "password": password, "html5": self.html5}

    self.log.debug("creating session")
    if not self.session:
        self.session = requests.Session()
        self.session.verify = self.ssl_verify

    # set default session header
    self.session.headers = {
        "Accept": "application/json",
        "Content-Type": "application/json",
    }

    _ = kwargs.pop("data", None)  # avoids duplicate `data` key for Session
    r = self.session.post(
        login_endpoint, data=json.dumps(authdata), *args, **kwargs
    )
    if r.ok:
        try:
            "logged in" in r.json()
            self.username = username
            self.api = EvengApi(self)  # create API wrapper object
        except json.decoder.JSONDecodeError:
            self.log.error("Error logging in: {}".format(r.text))
            raise EvengLoginError("Error logging in: {}".format(r.text))
    else:
        self.session = {}
        raise EvengLoginError("Error logging in: {}".format(r.text))

set_log_level(self, log_level)

Set log level for logger. Defaults to INFO if no level passed in or if an invalid level is passed in.

Parameters:

Name Type Description Default
log_level str

Log level to use for logger, defaults to 'INFO'

required
Source code in evengsdk/client.py
def set_log_level(self, log_level: str) -> None:
    """Set log level for logger. Defaults to INFO if no level passed in or
    if an invalid level is passed in.

    :param log_level: Log level to use for logger, defaults to 'INFO'
    :type log_level: str, optional
    """
    LOG_LEVELS = ("NOTSET", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL")
    if log_level.upper() not in LOG_LEVELS:
        log_level = "INFO"
    self.log.setLevel(getattr(logging, log_level))

EvengApi

Source code in evengsdk/api.py
class EvengApi:
    def __init__(self, client):
        """EVE-NG API wrapper object

        :param client: the EvengClient object for managing REST calls
        :type client: evengsdk.client.EvengClient
        :param timeout: connection timeout in seconds, defaults to 30
        :type timeout: int, optional
        """
        self.client = client
        self.log = client.log
        self.version = None
        self.supports_multi_tenants = False
        self.is_community = True

        status = self.get_server_status()
        self.version = status["data"]["version"]

        if self.version and "pro" in self.version.lower():
            self.is_community = False

    def __repr__(self):
        return "{}({})".format(self.__class__.__name__, self.client.session)

    def get_server_status(self) -> Dict:
        """Returns EVE-NG server status"""
        return self.client.get("/status")

    def list_node_templates(self) -> Dict:
        """List available node templates from EVE-NG"""
        return self.client.get("/list/templates/")

    def node_template_detail(self, node_type: str) -> Dict:
        """List details for single node template all available
        images for the selected template will be included in the
        output.

        :param node_type: Node type name to retrieve details for
        :type node_type: str
        :return: response dict
        :rtype: str
        """
        return self.client.get(f"/list/templates/{node_type}")

    def list_users(self) -> Dict:
        """Return list of EVE-NG users"""
        return self.client.get("/users/")

    def list_user_roles(self) -> Dict:
        """Return user roles"""
        return self.client.get("/list/roles")

    def get_user(self, username: str) -> Dict:
        """get user details. Returns empty dictionary if the user does
        not exist.

        :param username: username to retrieve details for
        :type username: str
        """
        return self.client.get(f"/users/{username}")

    def add_user(
        self,
        username: str,
        password: str,
        role: str = "user",
        name: str = "",
        email: str = "",
        expiration: str = "-1",
    ) -> Dict:
        """Add a new user in EVE-NG host

        :param username: a unique alphanumeric string used to login
        :type username: str
        :param password:  the user password used to login
        :type password: str
        :param role: user roles, defaults to 'user'.
                    choices are ['user', 'admin']
        :type role: str, optional
        :param name: user's full name, defaults to ''
        :type name: str, optional
        :param email: the email address of the user, defaults to ''
        :type email: str, optional
        :param expiration: date until the user is valid (UNIX timestamp)
                or -1 if never expires, defaults to '-1'
        :type expiration: str, optional
        """
        return self.client.post(
            "/users",
            data=json.dumps(
                {
                    "username": username,
                    "name": name,
                    "email": email,
                    "password": password,
                    "role": role,
                    "expiration": expiration,
                }
            ),
        )

    def edit_user(self, username: str, data: dict = None) -> Dict:
        """Edit user details

        :param username: the user name for user to update
        :type username: str
        :param data: payload for user details to update, defaults to None
        :type data: dict, optional
        :raises ValueError: when data value is missing
        """
        if not data:
            raise ValueError("data field is required.")

        url = self.client.url_prefix + f"/users/{username}"
        existing_user = self.get_user(username)

        updated_user = {}
        if existing_user:
            updated_user = copy.deepcopy(existing_user)
            updated_user.update(data)
            return self.client.put(url, data=json.dumps(updated_user))

    def delete_user(self, username: str) -> Dict:
        return self.client.delete(f"/users/{username}")

    def list_networks(self) -> Dict:
        """List network types"""
        return self.client.get("/list/networks")

    def list_folders(self) -> Dict:
        """List all folders, including the labs contained within each"""
        return self.client.get("/folders/")

    def get_folder(self, folder: str) -> Dict:
        """Return details for given folder. folders contain lab files.

        :param folder: path to folder on server. ex. my_lab_folder
        :type folder: str
        """
        return self.client.get(f"/folders/{folder}")

    def normalize_path(self, path: str) -> str:
        if not path.startswith("/"):
            path = (
                "/" + path if self.is_community else f"/{self.client.username}/{path}"
            )
        path = Path(path).resolve()

        # Add extension if needed
        path = path.with_suffix(".unl")

        # make parts of the path url safe
        quoted_parts = [str(quote_plus(x)) for x in path.parts[1:]]

        # rejoin the path and return string
        new_path = Path("/").joinpath(*quoted_parts).as_posix()
        return str(new_path)

    def get_lab(self, path: str) -> Dict:
        """Return details for a single lab

        :param path: path to lab file(including parent folder)
        :type path: str
        """
        url = "/labs" + self.normalize_path(path)
        return self.client.get(url)

    def export_lab(
        self, path: str, filename: str = None
    ) -> Tuple[bool, Optional[BinaryIO]]:
        """Export and download a lab as a .unl file

        :param path: the path of the lab (include parent folder)
        :type path: str
        :param filename: filename to save the export.
                        defaults to 'lab_export.zip'
        :type filename: str, optional
        :return: tuple of (success, file)
        """
        lab_filepath = Path(path)

        payload = {"0": str(lab_filepath), "path": ""}
        resp = self.client.post("/export", data=json.dumps(payload))
        zip_file_endpoint = resp.get("data", "")
        zip_filename = zip_file_endpoint.split("/")[-1]

        if resp:
            client = self.client
            download_url = f"{client.protocol}://{client.host}{zip_file_endpoint}"
            r = self.client.get(download_url, use_prefix=False)

            with open(filename or zip_filename, "wb") as handle:
                handle.write(r.content)
            return (True, zip_filename)
        return (False, None)

    def import_lab(self, path: str, folder: str = "/") -> bool:
        """Import a lab from a .unl file

        :param path: the source .zip file path of the lab
        :type path: str
        :param folder: the destination folder(s) for the lab as a path string
                        defaults to '/'
        :type folder: str, optional
        :return: True if import was successful, False otherwise
        """
        if not Path(path).exists():
            raise FileNotFoundError(f"{path} does not exist.")

        # retrieve the current cookies and reset the client custom headers
        cookies = self.client.session.cookies.get_dict()
        headers = {
            "Accept": "*/*",
            "Cookie": "; ".join(f"{k}={v}" for k, v in cookies.items()),
        }
        self.client.session.headers = headers

        # upload the file
        files = {"file": open(path, "rb")}
        return self.client.post("/import", data={"path": folder}, files=files)

    def list_lab_networks(self, path: str) -> Dict:
        """Get all networks configured in a lab

        :param path: path to lab file (include parent folder)
        :type path: str
        """
        normalized_path = self.normalize_path(path)
        url = f"/labs{normalized_path}/networks"
        return self.client.get(url)

    def get_lab_network(self, path: str, net_id: int) -> Dict:
        """Retrieve details for a single network in a lab

        :param path: path to lab file (include parent folder)
        :type path: str
        :param net_id: unique id for the lab network
        :type net_id: int
        """
        normalized_path = self.normalize_path(path)
        url = "/labs" + f"{normalized_path}/networks/{net_id}"
        return self.client.get(url)

    def get_lab_network_by_name(self, path: str, name: str) -> Dict:
        """retrieve details for a single network using the
        lab name

        :param path: path to lab file (include parent folder)
        :type path: str
        :param name: name of the network
        :type name: str
        """
        resp = self.list_lab_networks(path)
        if networks := resp.get("data"):
            return next((v for _, v in networks.items() if v["name"] == name), None)
        return

    def list_lab_links(self, path: str) -> Dict:
        """Get all remote endpoint for both ethernet and serial interfaces

        :param path: path to lab file (include parent folder)
        :type path: str
        """
        url = "/labs" + f"{self.normalize_path(path)}/links"
        return self.client.get(url)

    def list_nodes(self, path: str) -> Dict:
        """List all nodes in the lab

        :param path: path to lab file (include parent folder)
        :type path: str
        """
        url = "/labs" + f"{self.normalize_path(path)}/nodes"
        return self.client.get(url)

    def get_node(self, path: str, node_id: str) -> Dict:
        """Retrieve single node from lab by ID

        :param path: path to lab file (include parent folder)
        :type path: str
        :param node_id: node ID to retrieve
        :type node_id: str
        """
        url = "/labs" + f"{self.normalize_path(path)}/nodes/{node_id}"
        return self.client.get(url)

    def delete_node(self, path: str, node_id: str) -> Dict:
        """Delete a node from the lab

        :param path: path to lab file (include parent folder)
        :type path: str
        :param node_id: node ID to delete
        :type node_id: str
        """
        url = "/labs" + f"{self.normalize_path(path)}/nodes/{node_id}"
        return self.client.delete(url)

    def get_node_by_name(self, path: str, name: str) -> Dict:
        """Retrieve single node from lab by name

        :param path: path to lab file (include parent folder)
        :type path: str
        :param name: node name
        :type name: str
        """
        r = self.list_nodes(path)
        node_data = r["data"]
        if node_data:
            return next((v for _, v in node_data.items() if v["name"] == name), None)
        return

    def get_node_configs(self, path: str, configset: str = "default") -> Dict:
        """Return information about node configs

        :param path: path to lab file (include parent folder)
        :type path: str
        :param configset: name of the configset to retrieve configs for (pro version)
        :type configset: str, optional
        """
        url = "/labs" + f"{self.normalize_path(path)}/configs"
        if not self.is_community:
            return self.client.post(url, data=json.dumps({"cfsid": configset}))
        return self.client.get(url)

    def get_node_config_by_id(
        self, path: str, node_id: int, configset: str = "default"
    ) -> Dict:
        """Return configuration information about a specific node given
        the configuration ID

        :param path: path to lab file (include parent folder)
        :type path: str
        :param node_id: ID for node to retrieve configuration for
        :type node_id: int
        :param configset: name of the configset to retrieve configs for (pro version)
        :type configset: str, optional
        """
        url = "/labs" + f"{self.normalize_path(path)}/configs/{node_id}"
        if not self.is_community:
            return self.client.post(url, data=json.dumps({"cfsid": configset}))
        return self.client.get(url)

    def upload_node_config(
        self, path: str, node_id: str, config: str, configset: str = "default"
    ) -> Dict:
        """Upload node's startup config.

        :param path: path to lab file (include parent folder)
        :type path: str
        :param node_id: node ID to upload config for
        :type node_id: str
        :param config: configuration string
        :type config: str
        :param enable: enable the node config after upload, defaults to False
        :type enable: bool, optional
        """
        url = "/labs" + f"{self.normalize_path(path)}/configs/{node_id}"
        payload = {"id": node_id, "data": config}
        if not self.is_community:
            payload["cfsid"] = configset
        return self.client.put(url, data=json.dumps(payload))

    def enable_node_config(self, path: str, node_id: str) -> Dict:
        """Enable a node's startup config

        :param path: path to lab file (include parent folder)
        :type path: str
        :param node_id: node ID to enable config for
        :type node_id: str
        """
        url = "/labs" + f"{self.normalize_path(path)}/nodes/{node_id}"
        return self.client.put(url, data=json.dumps({"id": node_id, "config": 1}))

    def find_node_interface(
        self,
        path: str,
        node_id: str,
        interface_name: str,
        media: Literal["ethernet", "serial"] = "ethernet",
    ) -> Dict:
        r = self.get_node_interfaces(path, node_id)
        interface_list = r["data"].get(media, [])
        return next(
            (
                (idx, interface)
                for idx, interface in enumerate(interface_list)
                if interface["name"] == interface_name
            ),
            None,
        )

    def connect_node(
        self,
        path: str,
        src: str = "",
        src_port: str = "",
        dst: str = "",
        dst_port: str = "",
        dst_type: str = "network",
        media: str = "",
    ) -> Dict:
        """Connect node to a network or node

        :param path: path to lab file (include parent folder)
        :type path: str
        :param src: source device name, defaults to ""
        :type src: str, optional
        :param src_port: source port name, defaults to ""
        :type src_port: str, optional
        :param dst: destination device name, defaults to ""
        :type dst: str, optional
        :param dst_port: destination port, defaults to ""
        :type dst_port: str, optional
        :param dst_type: destination type, defaults to "network".
                        choices are ["node", "network"]
        :type dst_type: str, optional
        :param media: port media type, defaults to ""
        :type media: str, optional
        """
        dest_types = ["network", "node"]
        if dst_type not in dest_types:
            raise ValueError(f"destination type not in allowed types: {dest_types}")

        # normalize lab path
        normpath = self.normalize_path(path)

        # Connect node to either cloud (network) or node
        if dst_type == "network":
            self.log.debug(f"{path}: Connecting node {src} to cloud {dst}")
            return self.connect_node_to_cloud(normpath, src, src_port, dst, media=media)
        else:
            self.log.debug(f"{path}: Connecting node {src} to node {dst}")
            return self.connect_node_to_node(
                normpath, src, src_port, dst, dst_port, media=media
            )

    def connect_p2p_interface(
        self, path: str, node_id: str, interface: Tuple, net_id: str
    ) -> Dict:
        """Connect node interface to a network

        :param path: path to lab file (include parent folder)
        :type path: str
        :param node_id: Node ID
        :type node_id: str
        :param interface: [description]
        :type interface: Tuple
        :param net_id: [description]
        :type net_id: str
        """
        url = "/labs" f"{self.normalize_path(path)}/nodes/{node_id}/interfaces"

        # connect interfaces
        interface_id = interface[0]
        payload = {interface_id: str(net_id)}
        self.client.put(url, data=json.dumps(payload))

        # set visibility for bridge to "0" to hide bridge in the GUI
        return self.edit_lab_network(path, net_id, data={"visibility": "0"})

    def connect_node_to_cloud(
        self,
        path: str,
        src: str,
        src_label: str,
        dst: str,
        media: Literal["ethernet", "serial"] = "ethernet",
    ) -> Dict:
        """Connect node to a cloud"""
        normpath = self.normalize_path(path)
        node = self.get_node_by_name(path, src)
        if node is None:
            raise ValueError(f"node {src} not found or invalid")
        node_id = node.get("id")

        net = self.get_lab_network_by_name(path, dst)
        if net is None:
            raise ValueError(f"network {dst} not found or invalid")
        net_id = net.get("id")

        node_interface = self.find_node_interface(path, node_id, src_label, media)
        if not node_interface:
            raise ValueError(f"{src_label} invalid or missing for " f"{src}")
        interface = node_interface[0]

        url = f"/labs{normpath}/nodes/{node_id}/interfaces"
        return self.client.put(url, data=json.dumps({interface: f"{net_id}"}))

    def connect_node_to_node(
        self,
        path: str,
        src: str,
        src_label: str,
        dst: str,
        dst_label: str,
        media: Literal["ethernet", "serial"] = "ethernet",
    ) -> bool:
        """Connect node to another node

        :param path: path to lab file (include parent folder)
        :type path: str
        :param src: source device name
        :type src: str
        :param src_label: source port name
        :type src_label: str
        :param dst: destination device name
        :type dst: str
        :param dst_label: destination port name
        :type dst_label: str
        :param media: port media type, defaults to "ethernet"
        :type media: str, optional
        :return: True if successful
        :rtype: bool
        """

        self.client.log.debug(
            f"connecting node {src} to node {dst}"
            f" on interfaces {src_label} <-> {dst_label}"
            f" in lab {path.replace(' ', '_')}"
        )
        # find nodes using node names
        s_node_dict = self.get_node_by_name(path, src)
        d_node_dict = self.get_node_by_name(path, dst)

        # Validate that we found the hosts to connect
        s_node_id = s_node_dict.get("id")
        d_node_id = d_node_dict.get("id")
        if not all((s_node_id, d_node_id)):
            raise ValueError("host(s) not found or invalid")

        # find the p2p interfaces on each of the nodes
        src_int = self.find_node_interface(path, s_node_id, src_label, media)
        if not src_int:
            raise ValueError(f"{src_label} invalid or missing for " f"{src}")
        dst_int = self.find_node_interface(path, d_node_id, dst_label, media)
        if not dst_int:
            raise ValueError(f"{dst_label} invalid or missing for " f"{dst}")

        # create the bridge for the p2p interfaces
        self.client.log.debug(
            f"creating bridge for p2p link: node{s_node_id} <-> node{d_node_id}"
        )
        net_resp = self.add_lab_network(path, network_type="bridge", visibility="1")
        net_id = net_resp.get("data", {}).get("id")
        self.client.log.debug(f"created bridge ID: {net_id}")

        if not net_id:
            raise ValueError("Failed to create bridge")

        # connect the p2p interfaces to the bridge
        self.client.log.debug(f"connecting node{s_node_id} -> net:{net_id}")
        r1 = self.connect_p2p_interface(path, s_node_id, src_int, net_id)
        self.client.log.debug(f"connecting node{d_node_id} -> net:{net_id}")
        r2 = self.connect_p2p_interface(path, d_node_id, dst_int, net_id)
        return r1["status"] == "success" and r2["status"] == "success"

    def _update_nodes(
        self, path: str, action: Literal["stop", "start", "wipe"]
    ) -> Dict:
        resp = self.list_nodes(path)
        action_method = getattr(self, f"{action}_node")
        if resp["data"]:
            results = []
            for node_id, _ in resp["data"].items():
                res = action_method(path, node_id)
                results.append(res)
            return self._extract_recursive_statuses(results)
        return resp

    def start_all_nodes(self, path: str) -> Dict:
        """Start one or all nodes configured in a lab

        :param path: path to lab file (including parent folder)
        :type path: str
        """
        if self.is_community:
            url = f"/labs{self.normalize_path(path)}/nodes/start"
            return self.client.get(url)
        return self._update_nodes(path, "start")

    def stop_all_nodes(self, path: str) -> Dict:
        """Stop one or all nodes configured in a lab

        :param path: [description]
        :type path: str
        """
        if self.is_community:
            url = f"/labs{self.normalize_path(path)}/nodes/stop"
            return self.client.get(url)
        return self._update_nodes(path, "stop")

    def start_node(self, path: str, node_id: str) -> Dict:
        """Start single node in a lab

        :param path: [description]
        :type path: str
        :param node_id: [description]
        :type node_id: str
        """
        url = "/labs" + self.normalize_path(path) + f"/nodes/{node_id}/start"
        return self.client.get(url)

    def stop_node(self, path: str, node_id: str) -> Dict:
        """Stop single node in a lab

        :param path: [description]
        :type path: str
        :param node_id: [description]
        :type node_id: str
        """
        url = "/labs" + self.normalize_path(path) + f"/nodes/{node_id}/stop"
        if not self.is_community:
            url += "/stopmode=3"
        return self.client.get(url)

    def _extract_recursive_statuses(self, results):
        success = all(r["status"] == "success" for r in results)
        messages = [r["message"] for r in results]
        return {
            "status": "success" if success else "error",
            "data": results,
            "message": messages,
        }

    def wipe_all_nodes(self, path: str) -> Dict:
        """Wipe one or all nodes configured in a lab. Wiping deletes
        all user config, included startup-config, VLANs, and so on. The
        next start will rebuild node from selected image.

        :param path: [description]
        :type path: [type]
        :return: str
        """
        if self.is_community:
            url = f"/labs{self.normalize_path(path)}/nodes/wipe"
            return self.client.get(url)
        return self._update_nodes(path, "wipe")

    def wipe_node(self, path: str, node_id: int) -> Dict:
        """Wipe single node configured in a lab. Wiping deletes
        all user config, included startup-config, VLANs, and so on. The
        next start will rebuild node from selected image.

        :param path: [description]
        :type path: [type]
        :param node_id: [description]
        :type node_id: [type]
        """
        url = "/labs" + self.normalize_path(path) + f"/nodes/{node_id}/wipe"
        return self.client.get(url)

    def export_all_nodes(self, path: str) -> Dict:
        """Export one or all nodes configured in a lab.
        Exporting means saving the startup-config into
        the lab file.

        :param path: [description]
        :type path: str
        """
        url = "/labs" + self.normalize_path(path) + "/nodes/export"
        return self.client.put(url)

    def export_node(self, path: str, node_id: int) -> Dict:
        """Export node configuration. Exporting means
        saving the startup-config into the lab file.

        :param path: lab path
        :type path: str
        :param node_id: node ID for to export config from
        :type node_id: int
        """
        url = "/labs" + f"{self.normalize_path(path)}/nodes/{node_id}/export"
        return self.client.put(url)

    def get_node_interfaces(self, path: str, node_id: int) -> Dict:
        """Get configured interfaces from a node.

        :param path: lab path
        :type path: str
        :param node_id: node id in lab
        :type node_id: int
        """
        url = "/labs" + self.normalize_path(path) + f"/nodes/{node_id}/interfaces"
        return self.client.get(url)

    def get_lab_topology(self, path: str) -> Dict:
        """Get the lab topology

        :param path: [description]
        :type path: str
        """
        url = "/labs" + self.normalize_path(path) + "/topology"
        return self.client.get(url)

    def get_lab_pictures(self, path: str) -> Dict:
        """Get one or all pictures configured in a lab

        :param path: [description]
        :type path: str
        """
        url = f"/labs{self.normalize_path(path)}/pictures"
        return self.client.get(url)

    def get_lab_picture_details(self, path: str, picture_id: int) -> Dict:
        """Retrieve single picture

        :param path: [description]
        :type path: str
        :param picture_id: [description]
        :type picture_id: int
        """
        url = "/labs" + self.normalize_path(path) + f"/pictures/{picture_id}"
        return self.client.get(url)

    def node_exists(self, path, nodename) -> bool:
        node = self.get_node_by_name(path, nodename)
        return node.get("name") == nodename.lower() if node is not None else False

    def create_lab(
        self,
        name: str,
        path: str = "/",
        author: str = "",
        body: str = "",
        version: str = "1.0",
        description: str = "",
        scripttimeout: int = 600,
        lock: int = 0,
        tenant: str = "",
    ) -> Dict:
        """Create a new lab

        :param name: name of the lab
        :type name: str
        :param path: path to lab file on EVE-NG server, defaults to "/"
        :type path: str, optional
        :param author: lab author, defaults to ""
        :type author: str, optional
        :param body: long description for lab, defaults to ""
        :type body: str, optional
        :param version: lab version. Defaults to "1.0"
        :type version: str, optional
        :param description: short description, defaults to ""
        :type description: str, optional
        :param scripttimeout: value in seconds used for the
                “Configuration Export” and “Boot from exported configs”
                operations, defaults to 600
        :type scripttimeout: int, optional
        :param lock: set lab as as readonly. Defaults to 0
        :type lock: int, optional
        :param tenant: tenant (username) to create lab for, defaults to ""
        :type tenant: str, optional
        """
        data = {
            "path": path,
            "name": name,
            "version": version,
            "author": author,
            "description": description,
            "body": body,
            "lock": lock,
            "scripttimeout": scripttimeout,
        }

        url = "/labs"
        if self.supports_multi_tenants:
            existing_user = self.get_user(tenant)
            if existing_user:
                url += "{tenant}/"
        return self.client.post(url, data=json.dumps(data))

    def edit_lab(self, path: str, param: dict) -> Dict:
        """Edit an existing lab. The request can set only one single
        parameter. Optional parameter can be reverted to the default
        setting an empty string “”.

        :param path: [description]
        :type path: str
        :param param: [description]
        :type param: dict
        """
        valid_params = (
            "name",
            "version",
            "author",
            "description",
            "body",
            "lock",
            "scripttimeout",
        )
        if len(param) > 1:
            raise ValueError(
                "API allows updating a single paramater per request. "
                f"received {len(param)}."
            )
        for key, _ in param.items():
            if key not in valid_params:
                raise ValueError(f"{key} is an invalid or unsupported paramater")

        url = "/labs" + f"{self.normalize_path(path)}"
        return self.client.put(url, data=json.dumps(param))

    def close_lab(self) -> Dict:
        """Close the current lab."""
        url = "/labs/close"
        return self.client.delete(url)

    def delete_lab(self, path: str) -> Dict:
        """Delete an existing lab

        :param path: [description]
        :type path: str
        """
        url = "/labs" + self.normalize_path(path)
        return self.client.delete(url)

    def lock_lab(self, path: str) -> Dict:
        """Lock lab to prevent edits"""
        url = "/labs" + f"{self.normalize_path(path)}/Lock"
        return self.client.put(url)

    def unlock_lab(self, path: str) -> Dict:
        """Unlock lab to allow edits"""
        url = "/labs" + f"{self.normalize_path(path)}/Unlock"
        return self.client.put(url)

    def _get_network_types(self):
        network_types = self.list_networks()
        return [key for key, _ in network_types["data"].items()]

    @property
    def network_types(self):
        return self._get_network_types()

    def edit_lab_network(self, path: str, net_id: int, data: Dict = None) -> Dict:
        """Edit lab network

        :param path: [description]
        :type path: str
        :param net_id: [description]
        :type net_id: int
        :param data: [description], defaults to None
        :type data: [type], optional
        """
        if not data:
            raise ValueError("data field is required.")
        url = "/labs" + self.normalize_path(path) + f"/networks/{net_id}"
        return self.client.put(url, data=json.dumps(data))

    def add_lab_network(
        self,
        path: str,
        network_type: str = "",
        visibility: int = 0,
        name: str = "",
        left: int = randint(30, 70),
        top: int = randint(30, 70),
    ) -> Dict:
        """Add new network to lab

        :param path: [description]
        :type path: str
        :param network_type: [description], defaults to ""
        :type network_type: str, optional
        :param visibility: [description], defaults to 0
        :type visibility: int, optional
        :param name: network name (i.e. Core Network), default is
                    NetX (X = network_id), defaults to ""
        :type name: str, optional
        :param left: margin from left, in percentage (i.e. 35%),
                    default is a random value between 30% and 70%,
                    defaults to randint(30, 70)
        :type left: int, optional
        :param top:  margin from left, in percentage (i.e. 35%),
                    default is a random value between 30% and 70%,
                    defaults to randint(30, 70)
        :type top: int, optional
        """
        existing_network = self.get_lab_network_by_name(path, name)
        if existing_network:
            raise ValueError(f"Network already exists: `{name}` in lab {path}")

        additional_network_types = ("internal", "private", "nat")
        if self.is_community:
            if any(
                network_type.startswith(prefix) for prefix in additional_network_types
            ):
                raise ValueError(
                    f"Community edition does not support network type: `{network_type}`"
                )
        if network_type not in self.network_types:
            raise ValueError(
                f"invalid network type: {network_type} \
                not member of set {self.network_types}"
            )

        data = {
            "left": left,
            "name": name,
            "top": top,
            "type": network_type,
            "visibility": visibility,
        }
        url = "/labs" + self.normalize_path(path) + "/networks"
        return self.client.post(url, data=json.dumps(data))

    def delete_lab_network(self, path: str, net_id: int) -> Dict:
        url = f"/labs{self.normalize_path(path)}/networks/{net_id}"
        return self.client.delete(url)

    def add_node(
        self,
        path: str,
        template: str,
        delay: int = 0,
        name: str = "",
        node_type: str = "qemu",
        top: int = randint(30, 70),
        left: int = randint(30, 70),
        console: str = "telnet",
        config: str = "Unconfigured",
        ethernet: int = None,
        serial: int = None,
        image: str = None,
        icon: str = None,
        ram: int = None,
        cpu: int = None,
        nvram: int = None,
        idlepc: str = None,
        slot: str = "",
    ) -> Dict:
        """Create node and add to lab


        :param path: [description]
        :type path: str
        :param delay: seconds to wait before starting the node,
                    default is 0, defaults to 0
        :type delay: int, optional
        :param name: node name (i.e. “Core1”), default is
                    NodeX (X = node_id), defaults to ""
        :type name: str, optional
        :param node_type: node type, defaults to "qemu". value ccan be
                        one of ['iol', 'dynamips', 'qemu']
        :type node_type: str, optional
        :param template: [description], defaults to ""
        :type template: template for device type
        :param top: margin from top, in percentage (i.e. 35%), default
                    is a random value between 30% and 70%, defaults to
                    randint(30, 70)
        :type top: int, optional
        :param left: margin from left, in percentage (i.e. 35%), default
                    is a random value between 30% and 70%, defaults to
                    randint(30, 70)
        :type left: int, optional
        :param console: can be telnet or vnc, default is telnet,
        defaults to "telnet". (qemu)
        :type console: str, optional
        :param config: can be 'Unconfigured' or 'Saved',
                    defaults to "Unconfigured"
        :type config: str, optional
        :param ethernet: number of ethernet porgroups (each portgroup
                        configures four interfaces), defaults to None. if None,
                        node will be created with a default of 2
        :type ethernet: int, optional
        :param serial: num of serial portgroups (each portgroup configures
                        four interfaces), defaults to None. if None,
                        node is created >with 2.
        :type serial: int, optional
        :param image: image used to start the node, default is latest
                        included in “List node templates”, defaults to None
        :type image: str, optional
        :param icon: filename for icon used to display the node, default
                is Router.png; (located in /opt/unetlab/html/images/icons/),
                defaults to None
        :type icon: str, optional
        :param ram: MB of RAM configured for the node, default is 1024
        :type ram: int, optional
        :param cpu: number of configured CPU, defaults to None. if None,
                    the number of configured CPU will be  1 (qemu)
        :type cpu: int, optional
        :param nvram: size of NVRAM in KB, default is 1024
        :type nvram: int, optional
        :param idlepc: value used for Dynamips optimization (i.e. 0x80369ac4),
                    default is 0x0 (no optimization) (Dynamips)
        :param slot: 0-9]+ the module configured in a specific slot
                    (i.e. slot1=NM-1FE-TX).
        :type slot: int, optional
        """
        url = f"/labs{self.normalize_path(path)}/nodes"
        resp = self.node_template_detail(template)
        template_defaults = resp["data"]["options"]

        ethernet = ethernet or template_defaults.get("ethernet", {}).get("value")
        serial = serial or template_defaults.get("serial", {}).get("value")
        data = {
            "type": node_type,
            "template": template,
            "config": config,
            "delay": delay,
            "icon": icon or template_defaults.get("icon", {}).get("value"),
            "image": image or template_defaults.get("image", {}).get("value"),
            "name": name,
            "left": left,
            "top": top,
            "ram": ram or template_defaults.get("ram", {}).get("value"),
            "cpu": cpu or template_defaults.get("cpu", {}).get("value"),
            "console": console,
            "ethernet": int(ethernet) if ethernet else "",
            "serial": int(serial) if serial else "",
            "nvram": nvram or template_defaults.get("nvram", {}).get("value"),
        }

        if node_type == "dynamips" and idlepc is not None:
            data["idlepc"] = idlepc

        if slot is not None:
            data["slot"] = slot

        resp = {}
        if not self.node_exists(path, name):
            resp = self.client.post(url, data=json.dumps(data))
        return resp

__init__(self, client) special

EVE-NG API wrapper object

Parameters:

Name Type Description Default
client evengsdk.client.EvengClient

the EvengClient object for managing REST calls

required
timeout int, optional

connection timeout in seconds, defaults to 30

required
Source code in evengsdk/api.py
def __init__(self, client):
    """EVE-NG API wrapper object

    :param client: the EvengClient object for managing REST calls
    :type client: evengsdk.client.EvengClient
    :param timeout: connection timeout in seconds, defaults to 30
    :type timeout: int, optional
    """
    self.client = client
    self.log = client.log
    self.version = None
    self.supports_multi_tenants = False
    self.is_community = True

    status = self.get_server_status()
    self.version = status["data"]["version"]

    if self.version and "pro" in self.version.lower():
        self.is_community = False

add_lab_network(self, path, network_type='', visibility=0, name='', left=58, top=57)

Add new network to lab

Parameters:

Name Type Description Default
path str

[description]

required
network_type str

[description], defaults to ""

''
visibility int

[description], defaults to 0

0
name str

network name (i.e. Core Network), default is NetX (X = network_id), defaults to ""

''
left int

margin from left, in percentage (i.e. 35%), default is a random value between 30% and 70%, defaults to randint(30, 70)

58
top int

margin from left, in percentage (i.e. 35%), default is a random value between 30% and 70%, defaults to randint(30, 70)

57
Source code in evengsdk/api.py
def add_lab_network(
    self,
    path: str,
    network_type: str = "",
    visibility: int = 0,
    name: str = "",
    left: int = randint(30, 70),
    top: int = randint(30, 70),
) -> Dict:
    """Add new network to lab

    :param path: [description]
    :type path: str
    :param network_type: [description], defaults to ""
    :type network_type: str, optional
    :param visibility: [description], defaults to 0
    :type visibility: int, optional
    :param name: network name (i.e. Core Network), default is
                NetX (X = network_id), defaults to ""
    :type name: str, optional
    :param left: margin from left, in percentage (i.e. 35%),
                default is a random value between 30% and 70%,
                defaults to randint(30, 70)
    :type left: int, optional
    :param top:  margin from left, in percentage (i.e. 35%),
                default is a random value between 30% and 70%,
                defaults to randint(30, 70)
    :type top: int, optional
    """
    existing_network = self.get_lab_network_by_name(path, name)
    if existing_network:
        raise ValueError(f"Network already exists: `{name}` in lab {path}")

    additional_network_types = ("internal", "private", "nat")
    if self.is_community:
        if any(
            network_type.startswith(prefix) for prefix in additional_network_types
        ):
            raise ValueError(
                f"Community edition does not support network type: `{network_type}`"
            )
    if network_type not in self.network_types:
        raise ValueError(
            f"invalid network type: {network_type} \
            not member of set {self.network_types}"
        )

    data = {
        "left": left,
        "name": name,
        "top": top,
        "type": network_type,
        "visibility": visibility,
    }
    url = "/labs" + self.normalize_path(path) + "/networks"
    return self.client.post(url, data=json.dumps(data))

add_node(self, path, template, delay=0, name='', node_type='qemu', top=38, left=43, console='telnet', config='Unconfigured', ethernet=None, serial=None, image=None, icon=None, ram=None, cpu=None, nvram=None, idlepc=None, slot='')

Create node and add to lab

Parameters:

Name Type Description Default
path str

[description]

required
delay int

seconds to wait before starting the node, default is 0, defaults to 0

0
name str

node name (i.e. “Core1”), default is NodeX (X = node_id), defaults to ""

''
node_type str

node type, defaults to "qemu". value ccan be one of ['iol', 'dynamips', 'qemu']

'qemu'
template str

[description], defaults to ""

required
top int

margin from top, in percentage (i.e. 35%), default is a random value between 30% and 70%, defaults to randint(30, 70)

38
left int

margin from left, in percentage (i.e. 35%), default is a random value between 30% and 70%, defaults to randint(30, 70)

43
console str

can be telnet or vnc, default is telnet, defaults to "telnet". (qemu)

'telnet'
config str

can be 'Unconfigured' or 'Saved', defaults to "Unconfigured"

'Unconfigured'
ethernet int

number of ethernet porgroups (each portgroup configures four interfaces), defaults to None. if None, node will be created with a default of 2

None
serial int

num of serial portgroups (each portgroup configures four interfaces), defaults to None. if None, node is created >with 2.

None
image str

image used to start the node, default is latest included in “List node templates”, defaults to None

None
icon str

filename for icon used to display the node, default is Router.png; (located in /opt/unetlab/html/images/icons/), defaults to None

None
ram int

MB of RAM configured for the node, default is 1024

None
cpu int

number of configured CPU, defaults to None. if None, the number of configured CPU will be 1 (qemu)

None
nvram int

size of NVRAM in KB, default is 1024

None
idlepc str

value used for Dynamips optimization (i.e. 0x80369ac4), default is 0x0 (no optimization) (Dynamips)

None
slot str

0-9]+ the module configured in a specific slot (i.e. slot1=NM-1FE-TX).

''
Source code in evengsdk/api.py
def add_node(
    self,
    path: str,
    template: str,
    delay: int = 0,
    name: str = "",
    node_type: str = "qemu",
    top: int = randint(30, 70),
    left: int = randint(30, 70),
    console: str = "telnet",
    config: str = "Unconfigured",
    ethernet: int = None,
    serial: int = None,
    image: str = None,
    icon: str = None,
    ram: int = None,
    cpu: int = None,
    nvram: int = None,
    idlepc: str = None,
    slot: str = "",
) -> Dict:
    """Create node and add to lab


    :param path: [description]
    :type path: str
    :param delay: seconds to wait before starting the node,
                default is 0, defaults to 0
    :type delay: int, optional
    :param name: node name (i.e. “Core1”), default is
                NodeX (X = node_id), defaults to ""
    :type name: str, optional
    :param node_type: node type, defaults to "qemu". value ccan be
                    one of ['iol', 'dynamips', 'qemu']
    :type node_type: str, optional
    :param template: [description], defaults to ""
    :type template: template for device type
    :param top: margin from top, in percentage (i.e. 35%), default
                is a random value between 30% and 70%, defaults to
                randint(30, 70)
    :type top: int, optional
    :param left: margin from left, in percentage (i.e. 35%), default
                is a random value between 30% and 70%, defaults to
                randint(30, 70)
    :type left: int, optional
    :param console: can be telnet or vnc, default is telnet,
    defaults to "telnet". (qemu)
    :type console: str, optional
    :param config: can be 'Unconfigured' or 'Saved',
                defaults to "Unconfigured"
    :type config: str, optional
    :param ethernet: number of ethernet porgroups (each portgroup
                    configures four interfaces), defaults to None. if None,
                    node will be created with a default of 2
    :type ethernet: int, optional
    :param serial: num of serial portgroups (each portgroup configures
                    four interfaces), defaults to None. if None,
                    node is created >with 2.
    :type serial: int, optional
    :param image: image used to start the node, default is latest
                    included in “List node templates”, defaults to None
    :type image: str, optional
    :param icon: filename for icon used to display the node, default
            is Router.png; (located in /opt/unetlab/html/images/icons/),
            defaults to None
    :type icon: str, optional
    :param ram: MB of RAM configured for the node, default is 1024
    :type ram: int, optional
    :param cpu: number of configured CPU, defaults to None. if None,
                the number of configured CPU will be  1 (qemu)
    :type cpu: int, optional
    :param nvram: size of NVRAM in KB, default is 1024
    :type nvram: int, optional
    :param idlepc: value used for Dynamips optimization (i.e. 0x80369ac4),
                default is 0x0 (no optimization) (Dynamips)
    :param slot: 0-9]+ the module configured in a specific slot
                (i.e. slot1=NM-1FE-TX).
    :type slot: int, optional
    """
    url = f"/labs{self.normalize_path(path)}/nodes"
    resp = self.node_template_detail(template)
    template_defaults = resp["data"]["options"]

    ethernet = ethernet or template_defaults.get("ethernet", {}).get("value")
    serial = serial or template_defaults.get("serial", {}).get("value")
    data = {
        "type": node_type,
        "template": template,
        "config": config,
        "delay": delay,
        "icon": icon or template_defaults.get("icon", {}).get("value"),
        "image": image or template_defaults.get("image", {}).get("value"),
        "name": name,
        "left": left,
        "top": top,
        "ram": ram or template_defaults.get("ram", {}).get("value"),
        "cpu": cpu or template_defaults.get("cpu", {}).get("value"),
        "console": console,
        "ethernet": int(ethernet) if ethernet else "",
        "serial": int(serial) if serial else "",
        "nvram": nvram or template_defaults.get("nvram", {}).get("value"),
    }

    if node_type == "dynamips" and idlepc is not None:
        data["idlepc"] = idlepc

    if slot is not None:
        data["slot"] = slot

    resp = {}
    if not self.node_exists(path, name):
        resp = self.client.post(url, data=json.dumps(data))
    return resp

add_user(self, username, password, role='user', name='', email='', expiration='-1')

Add a new user in EVE-NG host

Parameters:

Name Type Description Default
username str

a unique alphanumeric string used to login

required
password str

the user password used to login

required
role str

user roles, defaults to 'user'. choices are ['user', 'admin']

'user'
name str

user's full name, defaults to ''

''
email str

the email address of the user, defaults to ''

''
expiration str

date until the user is valid (UNIX timestamp) or -1 if never expires, defaults to '-1'

'-1'
Source code in evengsdk/api.py
def add_user(
    self,
    username: str,
    password: str,
    role: str = "user",
    name: str = "",
    email: str = "",
    expiration: str = "-1",
) -> Dict:
    """Add a new user in EVE-NG host

    :param username: a unique alphanumeric string used to login
    :type username: str
    :param password:  the user password used to login
    :type password: str
    :param role: user roles, defaults to 'user'.
                choices are ['user', 'admin']
    :type role: str, optional
    :param name: user's full name, defaults to ''
    :type name: str, optional
    :param email: the email address of the user, defaults to ''
    :type email: str, optional
    :param expiration: date until the user is valid (UNIX timestamp)
            or -1 if never expires, defaults to '-1'
    :type expiration: str, optional
    """
    return self.client.post(
        "/users",
        data=json.dumps(
            {
                "username": username,
                "name": name,
                "email": email,
                "password": password,
                "role": role,
                "expiration": expiration,
            }
        ),
    )

close_lab(self)

Close the current lab.

Source code in evengsdk/api.py
def close_lab(self) -> Dict:
    """Close the current lab."""
    url = "/labs/close"
    return self.client.delete(url)

connect_node(self, path, src='', src_port='', dst='', dst_port='', dst_type='network', media='')

Connect node to a network or node

Parameters:

Name Type Description Default
path str

path to lab file (include parent folder)

required
src str

source device name, defaults to ""

''
src_port str

source port name, defaults to ""

''
dst str

destination device name, defaults to ""

''
dst_port str

destination port, defaults to ""

''
dst_type str

destination type, defaults to "network". choices are ["node", "network"]

'network'
media str

port media type, defaults to ""

''
Source code in evengsdk/api.py
def connect_node(
    self,
    path: str,
    src: str = "",
    src_port: str = "",
    dst: str = "",
    dst_port: str = "",
    dst_type: str = "network",
    media: str = "",
) -> Dict:
    """Connect node to a network or node

    :param path: path to lab file (include parent folder)
    :type path: str
    :param src: source device name, defaults to ""
    :type src: str, optional
    :param src_port: source port name, defaults to ""
    :type src_port: str, optional
    :param dst: destination device name, defaults to ""
    :type dst: str, optional
    :param dst_port: destination port, defaults to ""
    :type dst_port: str, optional
    :param dst_type: destination type, defaults to "network".
                    choices are ["node", "network"]
    :type dst_type: str, optional
    :param media: port media type, defaults to ""
    :type media: str, optional
    """
    dest_types = ["network", "node"]
    if dst_type not in dest_types:
        raise ValueError(f"destination type not in allowed types: {dest_types}")

    # normalize lab path
    normpath = self.normalize_path(path)

    # Connect node to either cloud (network) or node
    if dst_type == "network":
        self.log.debug(f"{path}: Connecting node {src} to cloud {dst}")
        return self.connect_node_to_cloud(normpath, src, src_port, dst, media=media)
    else:
        self.log.debug(f"{path}: Connecting node {src} to node {dst}")
        return self.connect_node_to_node(
            normpath, src, src_port, dst, dst_port, media=media
        )

connect_node_to_cloud(self, path, src, src_label, dst, media='ethernet')

Connect node to a cloud

Source code in evengsdk/api.py
def connect_node_to_cloud(
    self,
    path: str,
    src: str,
    src_label: str,
    dst: str,
    media: Literal["ethernet", "serial"] = "ethernet",
) -> Dict:
    """Connect node to a cloud"""
    normpath = self.normalize_path(path)
    node = self.get_node_by_name(path, src)
    if node is None:
        raise ValueError(f"node {src} not found or invalid")
    node_id = node.get("id")

    net = self.get_lab_network_by_name(path, dst)
    if net is None:
        raise ValueError(f"network {dst} not found or invalid")
    net_id = net.get("id")

    node_interface = self.find_node_interface(path, node_id, src_label, media)
    if not node_interface:
        raise ValueError(f"{src_label} invalid or missing for " f"{src}")
    interface = node_interface[0]

    url = f"/labs{normpath}/nodes/{node_id}/interfaces"
    return self.client.put(url, data=json.dumps({interface: f"{net_id}"}))

connect_node_to_node(self, path, src, src_label, dst, dst_label, media='ethernet')

Connect node to another node

Parameters:

Name Type Description Default
path str

path to lab file (include parent folder)

required
src str

source device name

required
src_label str

source port name

required
dst str

destination device name

required
dst_label str

destination port name

required
media Literal['ethernet', 'serial']

port media type, defaults to "ethernet"

'ethernet'

Returns:

Type Description
bool

True if successful

Source code in evengsdk/api.py
def connect_node_to_node(
    self,
    path: str,
    src: str,
    src_label: str,
    dst: str,
    dst_label: str,
    media: Literal["ethernet", "serial"] = "ethernet",
) -> bool:
    """Connect node to another node

    :param path: path to lab file (include parent folder)
    :type path: str
    :param src: source device name
    :type src: str
    :param src_label: source port name
    :type src_label: str
    :param dst: destination device name
    :type dst: str
    :param dst_label: destination port name
    :type dst_label: str
    :param media: port media type, defaults to "ethernet"
    :type media: str, optional
    :return: True if successful
    :rtype: bool
    """

    self.client.log.debug(
        f"connecting node {src} to node {dst}"
        f" on interfaces {src_label} <-> {dst_label}"
        f" in lab {path.replace(' ', '_')}"
    )
    # find nodes using node names
    s_node_dict = self.get_node_by_name(path, src)
    d_node_dict = self.get_node_by_name(path, dst)

    # Validate that we found the hosts to connect
    s_node_id = s_node_dict.get("id")
    d_node_id = d_node_dict.get("id")
    if not all((s_node_id, d_node_id)):
        raise ValueError("host(s) not found or invalid")

    # find the p2p interfaces on each of the nodes
    src_int = self.find_node_interface(path, s_node_id, src_label, media)
    if not src_int:
        raise ValueError(f"{src_label} invalid or missing for " f"{src}")
    dst_int = self.find_node_interface(path, d_node_id, dst_label, media)
    if not dst_int:
        raise ValueError(f"{dst_label} invalid or missing for " f"{dst}")

    # create the bridge for the p2p interfaces
    self.client.log.debug(
        f"creating bridge for p2p link: node{s_node_id} <-> node{d_node_id}"
    )
    net_resp = self.add_lab_network(path, network_type="bridge", visibility="1")
    net_id = net_resp.get("data", {}).get("id")
    self.client.log.debug(f"created bridge ID: {net_id}")

    if not net_id:
        raise ValueError("Failed to create bridge")

    # connect the p2p interfaces to the bridge
    self.client.log.debug(f"connecting node{s_node_id} -> net:{net_id}")
    r1 = self.connect_p2p_interface(path, s_node_id, src_int, net_id)
    self.client.log.debug(f"connecting node{d_node_id} -> net:{net_id}")
    r2 = self.connect_p2p_interface(path, d_node_id, dst_int, net_id)
    return r1["status"] == "success" and r2["status"] == "success"

connect_p2p_interface(self, path, node_id, interface, net_id)

Connect node interface to a network

Parameters:

Name Type Description Default
path str

path to lab file (include parent folder)

required
node_id str

Node ID

required
interface Tuple

[description]

required
net_id str

[description]

required
Source code in evengsdk/api.py
def connect_p2p_interface(
    self, path: str, node_id: str, interface: Tuple, net_id: str
) -> Dict:
    """Connect node interface to a network

    :param path: path to lab file (include parent folder)
    :type path: str
    :param node_id: Node ID
    :type node_id: str
    :param interface: [description]
    :type interface: Tuple
    :param net_id: [description]
    :type net_id: str
    """
    url = "/labs" f"{self.normalize_path(path)}/nodes/{node_id}/interfaces"

    # connect interfaces
    interface_id = interface[0]
    payload = {interface_id: str(net_id)}
    self.client.put(url, data=json.dumps(payload))

    # set visibility for bridge to "0" to hide bridge in the GUI
    return self.edit_lab_network(path, net_id, data={"visibility": "0"})

create_lab(self, name, path='/', author='', body='', version='1.0', description='', scripttimeout=600, lock=0, tenant='')

Create a new lab

Parameters:

Name Type Description Default
name str

name of the lab

required
path str

path to lab file on EVE-NG server, defaults to "/"

'/'
author str

lab author, defaults to ""

''
body str

long description for lab, defaults to ""

''
version str

lab version. Defaults to "1.0"

'1.0'
description str

short description, defaults to ""

''
scripttimeout int

value in seconds used for the “Configuration Export” and “Boot from exported configs” operations, defaults to 600

600
lock int

set lab as as readonly. Defaults to 0

0
tenant str

tenant (username) to create lab for, defaults to ""

''
Source code in evengsdk/api.py
def create_lab(
    self,
    name: str,
    path: str = "/",
    author: str = "",
    body: str = "",
    version: str = "1.0",
    description: str = "",
    scripttimeout: int = 600,
    lock: int = 0,
    tenant: str = "",
) -> Dict:
    """Create a new lab

    :param name: name of the lab
    :type name: str
    :param path: path to lab file on EVE-NG server, defaults to "/"
    :type path: str, optional
    :param author: lab author, defaults to ""
    :type author: str, optional
    :param body: long description for lab, defaults to ""
    :type body: str, optional
    :param version: lab version. Defaults to "1.0"
    :type version: str, optional
    :param description: short description, defaults to ""
    :type description: str, optional
    :param scripttimeout: value in seconds used for the
            “Configuration Export” and “Boot from exported configs”
            operations, defaults to 600
    :type scripttimeout: int, optional
    :param lock: set lab as as readonly. Defaults to 0
    :type lock: int, optional
    :param tenant: tenant (username) to create lab for, defaults to ""
    :type tenant: str, optional
    """
    data = {
        "path": path,
        "name": name,
        "version": version,
        "author": author,
        "description": description,
        "body": body,
        "lock": lock,
        "scripttimeout": scripttimeout,
    }

    url = "/labs"
    if self.supports_multi_tenants:
        existing_user = self.get_user(tenant)
        if existing_user:
            url += "{tenant}/"
    return self.client.post(url, data=json.dumps(data))

delete_lab(self, path)

Delete an existing lab

Parameters:

Name Type Description Default
path str

[description]

required
Source code in evengsdk/api.py
def delete_lab(self, path: str) -> Dict:
    """Delete an existing lab

    :param path: [description]
    :type path: str
    """
    url = "/labs" + self.normalize_path(path)
    return self.client.delete(url)

delete_node(self, path, node_id)

Delete a node from the lab

Parameters:

Name Type Description Default
path str

path to lab file (include parent folder)

required
node_id str

node ID to delete

required
Source code in evengsdk/api.py
def delete_node(self, path: str, node_id: str) -> Dict:
    """Delete a node from the lab

    :param path: path to lab file (include parent folder)
    :type path: str
    :param node_id: node ID to delete
    :type node_id: str
    """
    url = "/labs" + f"{self.normalize_path(path)}/nodes/{node_id}"
    return self.client.delete(url)

edit_lab(self, path, param)

Edit an existing lab. The request can set only one single parameter. Optional parameter can be reverted to the default setting an empty string “”.

Parameters:

Name Type Description Default
path str

[description]

required
param dict

[description]

required
Source code in evengsdk/api.py
def edit_lab(self, path: str, param: dict) -> Dict:
    """Edit an existing lab. The request can set only one single
    parameter. Optional parameter can be reverted to the default
    setting an empty string “”.

    :param path: [description]
    :type path: str
    :param param: [description]
    :type param: dict
    """
    valid_params = (
        "name",
        "version",
        "author",
        "description",
        "body",
        "lock",
        "scripttimeout",
    )
    if len(param) > 1:
        raise ValueError(
            "API allows updating a single paramater per request. "
            f"received {len(param)}."
        )
    for key, _ in param.items():
        if key not in valid_params:
            raise ValueError(f"{key} is an invalid or unsupported paramater")

    url = "/labs" + f"{self.normalize_path(path)}"
    return self.client.put(url, data=json.dumps(param))

edit_lab_network(self, path, net_id, data=None)

Edit lab network

Parameters:

Name Type Description Default
path str

[description]

required
net_id int

[description]

required
data Dict

[description], defaults to None

None
Source code in evengsdk/api.py
def edit_lab_network(self, path: str, net_id: int, data: Dict = None) -> Dict:
    """Edit lab network

    :param path: [description]
    :type path: str
    :param net_id: [description]
    :type net_id: int
    :param data: [description], defaults to None
    :type data: [type], optional
    """
    if not data:
        raise ValueError("data field is required.")
    url = "/labs" + self.normalize_path(path) + f"/networks/{net_id}"
    return self.client.put(url, data=json.dumps(data))

edit_user(self, username, data=None)

Edit user details

Parameters:

Name Type Description Default
username str

the user name for user to update

required
data dict

payload for user details to update, defaults to None

None

Exceptions:

Type Description
ValueError

when data value is missing

Source code in evengsdk/api.py
def edit_user(self, username: str, data: dict = None) -> Dict:
    """Edit user details

    :param username: the user name for user to update
    :type username: str
    :param data: payload for user details to update, defaults to None
    :type data: dict, optional
    :raises ValueError: when data value is missing
    """
    if not data:
        raise ValueError("data field is required.")

    url = self.client.url_prefix + f"/users/{username}"
    existing_user = self.get_user(username)

    updated_user = {}
    if existing_user:
        updated_user = copy.deepcopy(existing_user)
        updated_user.update(data)
        return self.client.put(url, data=json.dumps(updated_user))

enable_node_config(self, path, node_id)

Enable a node's startup config

Parameters:

Name Type Description Default
path str

path to lab file (include parent folder)

required
node_id str

node ID to enable config for

required
Source code in evengsdk/api.py
def enable_node_config(self, path: str, node_id: str) -> Dict:
    """Enable a node's startup config

    :param path: path to lab file (include parent folder)
    :type path: str
    :param node_id: node ID to enable config for
    :type node_id: str
    """
    url = "/labs" + f"{self.normalize_path(path)}/nodes/{node_id}"
    return self.client.put(url, data=json.dumps({"id": node_id, "config": 1}))

export_all_nodes(self, path)

Export one or all nodes configured in a lab. Exporting means saving the startup-config into the lab file.

Parameters:

Name Type Description Default
path str

[description]

required
Source code in evengsdk/api.py
def export_all_nodes(self, path: str) -> Dict:
    """Export one or all nodes configured in a lab.
    Exporting means saving the startup-config into
    the lab file.

    :param path: [description]
    :type path: str
    """
    url = "/labs" + self.normalize_path(path) + "/nodes/export"
    return self.client.put(url)

export_lab(self, path, filename=None)

Export and download a lab as a .unl file

Parameters:

Name Type Description Default
path str

the path of the lab (include parent folder)

required
filename str

filename to save the export. defaults to 'lab_export.zip'

None

Returns:

Type Description
Tuple[bool, Optional[BinaryIO]]

tuple of (success, file)

Source code in evengsdk/api.py
def export_lab(
    self, path: str, filename: str = None
) -> Tuple[bool, Optional[BinaryIO]]:
    """Export and download a lab as a .unl file

    :param path: the path of the lab (include parent folder)
    :type path: str
    :param filename: filename to save the export.
                    defaults to 'lab_export.zip'
    :type filename: str, optional
    :return: tuple of (success, file)
    """
    lab_filepath = Path(path)

    payload = {"0": str(lab_filepath), "path": ""}
    resp = self.client.post("/export", data=json.dumps(payload))
    zip_file_endpoint = resp.get("data", "")
    zip_filename = zip_file_endpoint.split("/")[-1]

    if resp:
        client = self.client
        download_url = f"{client.protocol}://{client.host}{zip_file_endpoint}"
        r = self.client.get(download_url, use_prefix=False)

        with open(filename or zip_filename, "wb") as handle:
            handle.write(r.content)
        return (True, zip_filename)
    return (False, None)

export_node(self, path, node_id)

Export node configuration. Exporting means saving the startup-config into the lab file.

Parameters:

Name Type Description Default
path str

lab path

required
node_id int

node ID for to export config from

required
Source code in evengsdk/api.py
def export_node(self, path: str, node_id: int) -> Dict:
    """Export node configuration. Exporting means
    saving the startup-config into the lab file.

    :param path: lab path
    :type path: str
    :param node_id: node ID for to export config from
    :type node_id: int
    """
    url = "/labs" + f"{self.normalize_path(path)}/nodes/{node_id}/export"
    return self.client.put(url)

get_folder(self, folder)

Return details for given folder. folders contain lab files.

Parameters:

Name Type Description Default
folder str

path to folder on server. ex. my_lab_folder

required
Source code in evengsdk/api.py
def get_folder(self, folder: str) -> Dict:
    """Return details for given folder. folders contain lab files.

    :param folder: path to folder on server. ex. my_lab_folder
    :type folder: str
    """
    return self.client.get(f"/folders/{folder}")

get_lab(self, path)

Return details for a single lab

Parameters:

Name Type Description Default
path str

path to lab file(including parent folder)

required
Source code in evengsdk/api.py
def get_lab(self, path: str) -> Dict:
    """Return details for a single lab

    :param path: path to lab file(including parent folder)
    :type path: str
    """
    url = "/labs" + self.normalize_path(path)
    return self.client.get(url)

get_lab_network(self, path, net_id)

Retrieve details for a single network in a lab

Parameters:

Name Type Description Default
path str

path to lab file (include parent folder)

required
net_id int

unique id for the lab network

required
Source code in evengsdk/api.py
def get_lab_network(self, path: str, net_id: int) -> Dict:
    """Retrieve details for a single network in a lab

    :param path: path to lab file (include parent folder)
    :type path: str
    :param net_id: unique id for the lab network
    :type net_id: int
    """
    normalized_path = self.normalize_path(path)
    url = "/labs" + f"{normalized_path}/networks/{net_id}"
    return self.client.get(url)

get_lab_network_by_name(self, path, name)

retrieve details for a single network using the lab name

Parameters:

Name Type Description Default
path str

path to lab file (include parent folder)

required
name str

name of the network

required
Source code in evengsdk/api.py
def get_lab_network_by_name(self, path: str, name: str) -> Dict:
    """retrieve details for a single network using the
    lab name

    :param path: path to lab file (include parent folder)
    :type path: str
    :param name: name of the network
    :type name: str
    """
    resp = self.list_lab_networks(path)
    if networks := resp.get("data"):
        return next((v for _, v in networks.items() if v["name"] == name), None)
    return

get_lab_picture_details(self, path, picture_id)

Retrieve single picture

Parameters:

Name Type Description Default
path str

[description]

required
picture_id int

[description]

required
Source code in evengsdk/api.py
def get_lab_picture_details(self, path: str, picture_id: int) -> Dict:
    """Retrieve single picture

    :param path: [description]
    :type path: str
    :param picture_id: [description]
    :type picture_id: int
    """
    url = "/labs" + self.normalize_path(path) + f"/pictures/{picture_id}"
    return self.client.get(url)

get_lab_pictures(self, path)

Get one or all pictures configured in a lab

Parameters:

Name Type Description Default
path str

[description]

required
Source code in evengsdk/api.py
def get_lab_pictures(self, path: str) -> Dict:
    """Get one or all pictures configured in a lab

    :param path: [description]
    :type path: str
    """
    url = f"/labs{self.normalize_path(path)}/pictures"
    return self.client.get(url)

get_lab_topology(self, path)

Get the lab topology

Parameters:

Name Type Description Default
path str

[description]

required
Source code in evengsdk/api.py
def get_lab_topology(self, path: str) -> Dict:
    """Get the lab topology

    :param path: [description]
    :type path: str
    """
    url = "/labs" + self.normalize_path(path) + "/topology"
    return self.client.get(url)

get_node(self, path, node_id)

Retrieve single node from lab by ID

Parameters:

Name Type Description Default
path str

path to lab file (include parent folder)

required
node_id str

node ID to retrieve

required
Source code in evengsdk/api.py
def get_node(self, path: str, node_id: str) -> Dict:
    """Retrieve single node from lab by ID

    :param path: path to lab file (include parent folder)
    :type path: str
    :param node_id: node ID to retrieve
    :type node_id: str
    """
    url = "/labs" + f"{self.normalize_path(path)}/nodes/{node_id}"
    return self.client.get(url)

get_node_by_name(self, path, name)

Retrieve single node from lab by name

Parameters:

Name Type Description Default
path str

path to lab file (include parent folder)

required
name str

node name

required
Source code in evengsdk/api.py
def get_node_by_name(self, path: str, name: str) -> Dict:
    """Retrieve single node from lab by name

    :param path: path to lab file (include parent folder)
    :type path: str
    :param name: node name
    :type name: str
    """
    r = self.list_nodes(path)
    node_data = r["data"]
    if node_data:
        return next((v for _, v in node_data.items() if v["name"] == name), None)
    return

get_node_config_by_id(self, path, node_id, configset='default')

Return configuration information about a specific node given the configuration ID

Parameters:

Name Type Description Default
path str

path to lab file (include parent folder)

required
node_id int

ID for node to retrieve configuration for

required
configset str

name of the configset to retrieve configs for (pro version)

'default'
Source code in evengsdk/api.py
def get_node_config_by_id(
    self, path: str, node_id: int, configset: str = "default"
) -> Dict:
    """Return configuration information about a specific node given
    the configuration ID

    :param path: path to lab file (include parent folder)
    :type path: str
    :param node_id: ID for node to retrieve configuration for
    :type node_id: int
    :param configset: name of the configset to retrieve configs for (pro version)
    :type configset: str, optional
    """
    url = "/labs" + f"{self.normalize_path(path)}/configs/{node_id}"
    if not self.is_community:
        return self.client.post(url, data=json.dumps({"cfsid": configset}))
    return self.client.get(url)

get_node_configs(self, path, configset='default')

Return information about node configs

Parameters:

Name Type Description Default
path str

path to lab file (include parent folder)

required
configset str

name of the configset to retrieve configs for (pro version)

'default'
Source code in evengsdk/api.py
def get_node_configs(self, path: str, configset: str = "default") -> Dict:
    """Return information about node configs

    :param path: path to lab file (include parent folder)
    :type path: str
    :param configset: name of the configset to retrieve configs for (pro version)
    :type configset: str, optional
    """
    url = "/labs" + f"{self.normalize_path(path)}/configs"
    if not self.is_community:
        return self.client.post(url, data=json.dumps({"cfsid": configset}))
    return self.client.get(url)

get_node_interfaces(self, path, node_id)

Get configured interfaces from a node.

Parameters:

Name Type Description Default
path str

lab path

required
node_id int

node id in lab

required
Source code in evengsdk/api.py
def get_node_interfaces(self, path: str, node_id: int) -> Dict:
    """Get configured interfaces from a node.

    :param path: lab path
    :type path: str
    :param node_id: node id in lab
    :type node_id: int
    """
    url = "/labs" + self.normalize_path(path) + f"/nodes/{node_id}/interfaces"
    return self.client.get(url)

get_server_status(self)

Returns EVE-NG server status

Source code in evengsdk/api.py
def get_server_status(self) -> Dict:
    """Returns EVE-NG server status"""
    return self.client.get("/status")

get_user(self, username)

get user details. Returns empty dictionary if the user does not exist.

Parameters:

Name Type Description Default
username str

username to retrieve details for

required
Source code in evengsdk/api.py
def get_user(self, username: str) -> Dict:
    """get user details. Returns empty dictionary if the user does
    not exist.

    :param username: username to retrieve details for
    :type username: str
    """
    return self.client.get(f"/users/{username}")

import_lab(self, path, folder='/')

Import a lab from a .unl file

Parameters:

Name Type Description Default
path str

the source .zip file path of the lab

required
folder str

the destination folder(s) for the lab as a path string defaults to '/'

'/'

Returns:

Type Description
bool

True if import was successful, False otherwise

Source code in evengsdk/api.py
def import_lab(self, path: str, folder: str = "/") -> bool:
    """Import a lab from a .unl file

    :param path: the source .zip file path of the lab
    :type path: str
    :param folder: the destination folder(s) for the lab as a path string
                    defaults to '/'
    :type folder: str, optional
    :return: True if import was successful, False otherwise
    """
    if not Path(path).exists():
        raise FileNotFoundError(f"{path} does not exist.")

    # retrieve the current cookies and reset the client custom headers
    cookies = self.client.session.cookies.get_dict()
    headers = {
        "Accept": "*/*",
        "Cookie": "; ".join(f"{k}={v}" for k, v in cookies.items()),
    }
    self.client.session.headers = headers

    # upload the file
    files = {"file": open(path, "rb")}
    return self.client.post("/import", data={"path": folder}, files=files)

list_folders(self)

List all folders, including the labs contained within each

Source code in evengsdk/api.py
def list_folders(self) -> Dict:
    """List all folders, including the labs contained within each"""
    return self.client.get("/folders/")

Get all remote endpoint for both ethernet and serial interfaces

Parameters:

Name Type Description Default
path str

path to lab file (include parent folder)

required
Source code in evengsdk/api.py
def list_lab_links(self, path: str) -> Dict:
    """Get all remote endpoint for both ethernet and serial interfaces

    :param path: path to lab file (include parent folder)
    :type path: str
    """
    url = "/labs" + f"{self.normalize_path(path)}/links"
    return self.client.get(url)

list_lab_networks(self, path)

Get all networks configured in a lab

Parameters:

Name Type Description Default
path str

path to lab file (include parent folder)

required
Source code in evengsdk/api.py
def list_lab_networks(self, path: str) -> Dict:
    """Get all networks configured in a lab

    :param path: path to lab file (include parent folder)
    :type path: str
    """
    normalized_path = self.normalize_path(path)
    url = f"/labs{normalized_path}/networks"
    return self.client.get(url)

list_networks(self)

List network types

Source code in evengsdk/api.py
def list_networks(self) -> Dict:
    """List network types"""
    return self.client.get("/list/networks")

list_node_templates(self)

List available node templates from EVE-NG

Source code in evengsdk/api.py
def list_node_templates(self) -> Dict:
    """List available node templates from EVE-NG"""
    return self.client.get("/list/templates/")

list_nodes(self, path)

List all nodes in the lab

Parameters:

Name Type Description Default
path str

path to lab file (include parent folder)

required
Source code in evengsdk/api.py
def list_nodes(self, path: str) -> Dict:
    """List all nodes in the lab

    :param path: path to lab file (include parent folder)
    :type path: str
    """
    url = "/labs" + f"{self.normalize_path(path)}/nodes"
    return self.client.get(url)

list_user_roles(self)

Return user roles

Source code in evengsdk/api.py
def list_user_roles(self) -> Dict:
    """Return user roles"""
    return self.client.get("/list/roles")

list_users(self)

Return list of EVE-NG users

Source code in evengsdk/api.py
def list_users(self) -> Dict:
    """Return list of EVE-NG users"""
    return self.client.get("/users/")

lock_lab(self, path)

Lock lab to prevent edits

Source code in evengsdk/api.py
def lock_lab(self, path: str) -> Dict:
    """Lock lab to prevent edits"""
    url = "/labs" + f"{self.normalize_path(path)}/Lock"
    return self.client.put(url)

node_template_detail(self, node_type)

List details for single node template all available images for the selected template will be included in the output.

Parameters:

Name Type Description Default
node_type str

Node type name to retrieve details for

required

Returns:

Type Description
Dict

response dict

Source code in evengsdk/api.py
def node_template_detail(self, node_type: str) -> Dict:
    """List details for single node template all available
    images for the selected template will be included in the
    output.

    :param node_type: Node type name to retrieve details for
    :type node_type: str
    :return: response dict
    :rtype: str
    """
    return self.client.get(f"/list/templates/{node_type}")

start_all_nodes(self, path)

Start one or all nodes configured in a lab

Parameters:

Name Type Description Default
path str

path to lab file (including parent folder)

required
Source code in evengsdk/api.py
def start_all_nodes(self, path: str) -> Dict:
    """Start one or all nodes configured in a lab

    :param path: path to lab file (including parent folder)
    :type path: str
    """
    if self.is_community:
        url = f"/labs{self.normalize_path(path)}/nodes/start"
        return self.client.get(url)
    return self._update_nodes(path, "start")

start_node(self, path, node_id)

Start single node in a lab

Parameters:

Name Type Description Default
path str

[description]

required
node_id str

[description]

required
Source code in evengsdk/api.py
def start_node(self, path: str, node_id: str) -> Dict:
    """Start single node in a lab

    :param path: [description]
    :type path: str
    :param node_id: [description]
    :type node_id: str
    """
    url = "/labs" + self.normalize_path(path) + f"/nodes/{node_id}/start"
    return self.client.get(url)

stop_all_nodes(self, path)

Stop one or all nodes configured in a lab

Parameters:

Name Type Description Default
path str

[description]

required
Source code in evengsdk/api.py
def stop_all_nodes(self, path: str) -> Dict:
    """Stop one or all nodes configured in a lab

    :param path: [description]
    :type path: str
    """
    if self.is_community:
        url = f"/labs{self.normalize_path(path)}/nodes/stop"
        return self.client.get(url)
    return self._update_nodes(path, "stop")

stop_node(self, path, node_id)

Stop single node in a lab

Parameters:

Name Type Description Default
path str

[description]

required
node_id str

[description]

required
Source code in evengsdk/api.py
def stop_node(self, path: str, node_id: str) -> Dict:
    """Stop single node in a lab

    :param path: [description]
    :type path: str
    :param node_id: [description]
    :type node_id: str
    """
    url = "/labs" + self.normalize_path(path) + f"/nodes/{node_id}/stop"
    if not self.is_community:
        url += "/stopmode=3"
    return self.client.get(url)

unlock_lab(self, path)

Unlock lab to allow edits

Source code in evengsdk/api.py
def unlock_lab(self, path: str) -> Dict:
    """Unlock lab to allow edits"""
    url = "/labs" + f"{self.normalize_path(path)}/Unlock"
    return self.client.put(url)

upload_node_config(self, path, node_id, config, configset='default')

Upload node's startup config.

Parameters:

Name Type Description Default
path str

path to lab file (include parent folder)

required
node_id str

node ID to upload config for

required
config str

configuration string

required
enable bool, optional

enable the node config after upload, defaults to False

required
Source code in evengsdk/api.py
def upload_node_config(
    self, path: str, node_id: str, config: str, configset: str = "default"
) -> Dict:
    """Upload node's startup config.

    :param path: path to lab file (include parent folder)
    :type path: str
    :param node_id: node ID to upload config for
    :type node_id: str
    :param config: configuration string
    :type config: str
    :param enable: enable the node config after upload, defaults to False
    :type enable: bool, optional
    """
    url = "/labs" + f"{self.normalize_path(path)}/configs/{node_id}"
    payload = {"id": node_id, "data": config}
    if not self.is_community:
        payload["cfsid"] = configset
    return self.client.put(url, data=json.dumps(payload))

wipe_all_nodes(self, path)

Wipe one or all nodes configured in a lab. Wiping deletes all user config, included startup-config, VLANs, and so on. The next start will rebuild node from selected image.

Parameters:

Name Type Description Default
path str

[description]

required

Returns:

Type Description
Dict

str

Source code in evengsdk/api.py
def wipe_all_nodes(self, path: str) -> Dict:
    """Wipe one or all nodes configured in a lab. Wiping deletes
    all user config, included startup-config, VLANs, and so on. The
    next start will rebuild node from selected image.

    :param path: [description]
    :type path: [type]
    :return: str
    """
    if self.is_community:
        url = f"/labs{self.normalize_path(path)}/nodes/wipe"
        return self.client.get(url)
    return self._update_nodes(path, "wipe")

wipe_node(self, path, node_id)

Wipe single node configured in a lab. Wiping deletes all user config, included startup-config, VLANs, and so on. The next start will rebuild node from selected image.

Parameters:

Name Type Description Default
path str

[description]

required
node_id int

[description]

required
Source code in evengsdk/api.py
def wipe_node(self, path: str, node_id: int) -> Dict:
    """Wipe single node configured in a lab. Wiping deletes
    all user config, included startup-config, VLANs, and so on. The
    next start will rebuild node from selected image.

    :param path: [description]
    :type path: [type]
    :param node_id: [description]
    :type node_id: [type]
    """
    url = "/labs" + self.normalize_path(path) + f"/nodes/{node_id}/wipe"
    return self.client.get(url)
Back to top