summaryrefslogtreecommitdiffstats
path: root/eos_downloader/eos.py
blob: ada0eb6ff69b5271f2c6c1eb1568c1edd3e7f9b4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#!/usr/bin/python
# coding: utf-8 -*-
# flake8: noqa: F811

"""
Specific EOS inheritance from object_download
"""

import os
import xml.etree.ElementTree as ET
from typing import List, Union

import rich
from loguru import logger
from rich import console

from eos_downloader.models.version import (
    BASE_BRANCH_STR,
    BASE_VERSION_STR,
    REGEX_EOS_VERSION,
    RTYPE_FEATURE,
    EosVersion,
)
from eos_downloader.object_downloader import ObjectDownloader

# logger = logging.getLogger(__name__)

console = rich.get_console()


class EOSDownloader(ObjectDownloader):
    """
    EOSDownloader Object to download EOS images from Arista.com website

    Supercharge ObjectDownloader to support EOS specific actions

    Parameters
    ----------
    ObjectDownloader : ObjectDownloader
        Base object
    """

    eos_versions: Union[List[EosVersion], None] = None

    @staticmethod
    def _disable_ztp(file_path: str) -> None:
        """
        _disable_ztp Method to disable ZTP in EOS image

        Create a file in the EOS image to disable ZTP process during initial boot

        Parameters
        ----------
        file_path : str
            Path where EOS image is located
        """
        logger.info("Mounting volume to disable ZTP")
        console.print("🚀 Mounting volume to disable ZTP")
        raw_folder = os.path.join(file_path, "raw")
        os.system(f"rm -rf {raw_folder}")
        os.system(f"mkdir -p {raw_folder}")
        os.system(
            f'guestmount -a {os.path.join(file_path, "hda.qcow2")} -m /dev/sda2 {os.path.join(file_path, "raw")}'
        )
        ztp_file = os.path.join(file_path, "raw/zerotouch-config")
        with open(ztp_file, "w", encoding="ascii") as zfile:
            zfile.write("DISABLE=True")
        logger.info(f"Unmounting volume in {file_path}")
        os.system(f"guestunmount {os.path.join(file_path, 'raw')}")
        os.system(f"rm -rf {os.path.join(file_path, 'raw')}")
        logger.info(f"Volume has been successfully unmounted at {file_path}")

    def _parse_xml_for_version(
        self,
        root_xml: ET.ElementTree,
        xpath: str = './/dir[@label="Active Releases"]/dir/dir/[@label]',
    ) -> List[EosVersion]:
        """
        Extract list of available EOS versions from Arista.com website

        Create a list of EosVersion object for all versions available on Arista.com

        Args:
            root_xml (ET.ElementTree): XML file with all versions available
            xpath (str, optional): XPATH to use to extract EOS version. Defaults to './/dir[@label="Active Releases"]/dir/dir/[@label]'.

        Returns:
            List[EosVersion]: List of EosVersion representing all available EOS versions
        """
        # XPATH: .//dir[@label="Active Releases"]/dir/dir/[@label]
        if self.eos_versions is None:
            logger.debug(f"Using xpath {xpath}")
            eos_versions = []
            for node in root_xml.findall(xpath):
                if "label" in node.attrib and node.get("label") is not None:
                    label = node.get("label")
                    if label is not None and REGEX_EOS_VERSION.match(label):
                        eos_version = EosVersion.from_str(label)
                        eos_versions.append(eos_version)
                        logger.debug(f"Found {label} - {eos_version}")
            logger.debug(f"List of versions found on arista.com is: {eos_versions}")
            self.eos_versions = eos_versions
        else:
            logger.debug(
                "receiving instruction to download versions, but already available"
            )
        return self.eos_versions

    def _get_branches(self, with_rtype: str = RTYPE_FEATURE) -> List[str]:
        """
        Extract all EOS branches available from arista.com

        Call self._parse_xml_for_version and then build list of available branches

        Args:
            rtype (str, optional): Release type to find. Can be M or F, default to F

        Returns:
            List[str]: A lsit of string that represent all availables EOS branches
        """
        root = self.get_folder_tree()
        versions = self._parse_xml_for_version(root_xml=root)
        return list(
            {version.branch for version in versions if version.rtype == with_rtype}
        )

    def latest_branch(self, rtype: str = RTYPE_FEATURE) -> EosVersion:
        """
        Get latest branch from semver standpoint

        Args:
            rtype (str, optional): Release type to find. Can be M or F, default to F

        Returns:
            EosVersion: Latest Branch object
        """
        selected_branch = EosVersion.from_str(BASE_BRANCH_STR)
        for branch in self._get_branches(with_rtype=rtype):
            branch = EosVersion.from_str(branch)
            selected_branch = max(selected_branch, branch)
        return selected_branch

    def get_eos_versions(
        self, branch: Union[str, None] = None, rtype: Union[str, None] = None
    ) -> List[EosVersion]:
        """
        Get a list of available EOS version available on arista.com

        If a branch is provided, only version in this branch are listed.
        Otherwise, all versions are provided.

        Args:
            branch (str, optional): An EOS branch to filter. Defaults to None.
            rtype (str, optional): Release type to find. Can be M or F, default to F

        Returns:
            List[EosVersion]: A list of versions available
        """
        root = self.get_folder_tree()
        result = []
        for version in self._parse_xml_for_version(root_xml=root):
            if branch is None and (version.rtype == rtype or rtype is None):
                result.append(version)
            elif (
                branch is not None
                and version.is_in_branch(branch)
                and version.rtype == rtype
            ):
                result.append(version)
        return result

    def latest_eos(
        self, branch: Union[str, None] = None, rtype: str = RTYPE_FEATURE
    ) -> EosVersion:
        """
        Get latest version of EOS

        If a branch is provided, only version in this branch are listed.
        Otherwise, all versions are provided.
        You can select what type of version to consider: M or F

        Args:
            branch (str, optional): An EOS branch to filter. Defaults to None.
            rtype (str, optional): An EOS version type to filter, Can be M or F. Defaults to None.

        Returns:
            EosVersion: latest version selected
        """
        selected_version = EosVersion.from_str(BASE_VERSION_STR)
        if branch is None:
            latest_branch = self.latest_branch(rtype=rtype)
        else:
            latest_branch = EosVersion.from_str(branch)
        for version in self.get_eos_versions(
            branch=str(latest_branch.branch), rtype=rtype
        ):
            if version > selected_version:
                if rtype is not None and version.rtype == rtype:
                    selected_version = version
                if rtype is None:
                    selected_version = version
        return selected_version