├── requirements.txt
├── .gitattributes
├── v-tool
├── figures
│ ├── figure_1.jpg
│ ├── figure_10.jpg
│ ├── figure_11.jpg
│ ├── figure_2.jpg
│ ├── figure_3.jpg
│ ├── figure_4.jpg
│ ├── figure_5.jpg
│ ├── figure_6.jpg
│ ├── figure_7.jpg
│ ├── figure_8.jpg
│ └── figure_9.jpg
├── V-Tool-API-Example-Guide.docx
├── Token-Org-URL.txt
└── preReqInstallPythonVtool.py
├── scripts
├── configuration
│ ├── config_webhook_settings.json
│ └── config_auto_site_assignment.py
├── sites
│ ├── site_conf_wlan_settings.json
│ ├── site_conf_psk.py
│ ├── import_psk.py
│ └── site_conf_wlan.py
├── exports
│ ├── compare_org_events_summary.py
│ └── export_inventory.py
├── orgs
│ ├── admins
│ │ └── import_admins.py
│ └── org_complete_backup.py
├── reports
│ ├── report_rogues.py
│ ├── report_app_usage.py
│ ├── report_sites.py
│ └── report_inventory_site_notes.py
├── devices
│ ├── switches
│ │ └── toggle_poe.py
│ ├── aps
│ │ └── configure_ap_mgmt_vlan.py
│ └── gateways
│ │ └── fix_gateway_backup_firmware.py
└── nac
│ └── import_user_macs.py
├── LICENSE
├── .gitignore
├── CODE_OF_CONDUCT.md
├── .dccache
├── CONTRIBUTING.md
└── README.md
/requirements.txt:
--------------------------------------------------------------------------------
1 | mistapi
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/v-tool/figures/figure_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmunzer/mist_library/HEAD/v-tool/figures/figure_1.jpg
--------------------------------------------------------------------------------
/v-tool/figures/figure_10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmunzer/mist_library/HEAD/v-tool/figures/figure_10.jpg
--------------------------------------------------------------------------------
/v-tool/figures/figure_11.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmunzer/mist_library/HEAD/v-tool/figures/figure_11.jpg
--------------------------------------------------------------------------------
/v-tool/figures/figure_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmunzer/mist_library/HEAD/v-tool/figures/figure_2.jpg
--------------------------------------------------------------------------------
/v-tool/figures/figure_3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmunzer/mist_library/HEAD/v-tool/figures/figure_3.jpg
--------------------------------------------------------------------------------
/v-tool/figures/figure_4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmunzer/mist_library/HEAD/v-tool/figures/figure_4.jpg
--------------------------------------------------------------------------------
/v-tool/figures/figure_5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmunzer/mist_library/HEAD/v-tool/figures/figure_5.jpg
--------------------------------------------------------------------------------
/v-tool/figures/figure_6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmunzer/mist_library/HEAD/v-tool/figures/figure_6.jpg
--------------------------------------------------------------------------------
/v-tool/figures/figure_7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmunzer/mist_library/HEAD/v-tool/figures/figure_7.jpg
--------------------------------------------------------------------------------
/v-tool/figures/figure_8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmunzer/mist_library/HEAD/v-tool/figures/figure_8.jpg
--------------------------------------------------------------------------------
/v-tool/figures/figure_9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmunzer/mist_library/HEAD/v-tool/figures/figure_9.jpg
--------------------------------------------------------------------------------
/v-tool/V-Tool-API-Example-Guide.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmunzer/mist_library/HEAD/v-tool/V-Tool-API-Example-Guide.docx
--------------------------------------------------------------------------------
/scripts/configuration/config_webhook_settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "url": "https://myserver.local/webhooks",
3 | "enabled": false,
4 | "splunk_token": "",
5 | "secret": "mprzx74ACMqM",
6 | "name": "demo",
7 | "type": "http-post",
8 | "verify_cert": true,
9 | "headers": {}
10 | }
--------------------------------------------------------------------------------
/v-tool/Token-Org-URL.txt:
--------------------------------------------------------------------------------
1 | token=
2 | org_id=
3 | base_url=https://api.mist.com/api/v1 #Global01
4 | #base_url=https://api.gc1.mist.com/api/v1 #Global02
5 | #base_url=https://api.ac2.mist.com/api/v1 #Global03
6 | #base_url=https://api.gc2.mist.com/api/v1 #Global04
7 | #base_url=https://api.gc4.mist.com/api/v1 #Global05
8 | #base_url=https://api.eu.mist.com/api/v1 #EMEA01
9 | #base_url=https://api.gc3.mist.com/api/v1 #EMEA02
10 | #base_url=https://api.ac6.mist.com/api/v1 #EMEA03
11 | #base_url=https://api.ac5.mist.com/api/v1 #APAC5
12 | #get token for integration =https://api.mist.com/api/v1/self/apitokens
13 |
14 |
--------------------------------------------------------------------------------
/scripts/sites/site_conf_wlan_settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "ssid": "mistworkshop",
3 | "enabled": true,
4 | "auth": {
5 | "type": "psk",
6 | "psk": "mistworkshop"
7 | },
8 | "roam_mode": "none",
9 | "band": "both",
10 | "band_steer": false,
11 | "rateset": {
12 | "24": {
13 | "min_rssi": 0,
14 | "template": "high-density"
15 | },
16 | "5": {
17 | "min_rssi": -70,
18 | "template": "high-density"
19 | }
20 | },
21 | "disable_11ax": false,
22 | "vlan_enabled": false,
23 | "hide_ssid": false,
24 | "isolation": false,
25 | "arp_filter": false,
26 | "limit_bcast": false,
27 | "allow_mdns": false,
28 | "apply_to": "site",
29 | "wxtag_ids": [],
30 | "ap_ids": [],
31 | "schedule": {
32 | "enabled": false
33 | },
34 | "qos": {
35 | "overwrite": false
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | ## MIT LICENSE
2 |
3 | Copyright (c) 2023 Thomas Munzer
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/scripts/sites/site_conf_psk.py:
--------------------------------------------------------------------------------
1 | '''
2 | Written by Thomas Munzer (tmunzer@juniper.net)
3 | Github repository: https://github.com/tmunzer/Mist_library/
4 | '''
5 |
6 |
7 | #### IMPORTS #####
8 | import logging
9 | import sys
10 |
11 | MISTAPI_MIN_VERSION = "0.44.1"
12 |
13 | try:
14 | import mistapi
15 | from mistapi.__logger import console
16 | except:
17 | print("""
18 | Critical:
19 | \"mistapi\" package is missing. Please use the pip command to install it.
20 |
21 | # Linux/macOS
22 | python3 -m pip install mistapi
23 |
24 | # Windows
25 | py -m pip install mistapi
26 | """)
27 | sys.exit(2)
28 |
29 | #### PARAMETERS #####
30 | PSK = {"name":'myUser', "passphrase":'myBadPassword', "ssid":'mySSID', "usage":'multi'}
31 | LOG_FILE = "./sites_scripts.log"
32 | ENV_FILE = "./.env"
33 | #### LOGS ####
34 | LOGGER = logging.getLogger(__name__)
35 | #### FUNCTIONS #####
36 |
37 | def start(apisession):
38 | site_id = mistapi.cli.select_site(apisession, allow_many=False)
39 | mistapi.api.v1.sites.psks.createSitePsk(apisession, site_id, PSK)
40 | response = mistapi.api.v1.sites.psks.listSitePsks(apisession, site_id)
41 | psks = mistapi.get_all(apisession, response)
42 | mistapi.cli.pretty_print(psks)
43 |
44 | ##### ENTRY POINT ####
45 |
46 | if __name__ == "__main__":
47 | #### LOGS ####
48 | logging.basicConfig(filename=LOG_FILE, filemode='w')
49 | LOGGER.setLevel(logging.DEBUG)
50 | ### START ###
51 | APISESSION = mistapi.APISession(env_file=ENV_FILE)
52 | APISESSION.login()
53 | start(APISESSION)
54 |
--------------------------------------------------------------------------------
/v-tool/preReqInstallPythonVtool.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """
3 | ===============================================================================
4 | Pre-Requisites Installation Script for Upgrade Management Script
5 | ===============================================================================
6 | Version: 1.0
7 | Date: February 13, 2025
8 | Author: SV
9 |
10 | Description:
11 | ------------
12 | This script installs the following required Python packages for the Upgrade Management
13 | Script:
14 | - requests (Used for making HTTP requests to the API)
15 | - pandas (Used for data manipulation and Excel file operations)
16 | - openpyxl (Used for reading and writing Excel files)
17 |
18 | Standard library modules used in this script:
19 | - os (For operating system interactions)
20 | - sys (For accessing system-specific parameters and functions)
21 | - subprocess (For invoking pip3 via the command line)
22 |
23 | Prerequisites:
24 | --------------
25 | - Python 3.6 or higher must be installed.
26 | - This script uses pip3 to install required packages.
27 | - To run the script, use:
28 | python3 preReqInstallPython.py
29 |
30 | Usage:
31 | ------
32 | Run the script from the command line to automatically install the required packages:
33 | python3 preReqInstallPython.py
34 |
35 | ===============================================================================
36 | """
37 |
38 | import subprocess
39 | import sys
40 |
41 | # List of required packages to install via pip3
42 | packages = [
43 | "requests",
44 | "pandas",
45 | "openpyxl"
46 | ]
47 |
48 | def install_package(package):
49 | """
50 | Installs the given package using pip3.
51 | """
52 | print(f"Installing {package} ...")
53 | try:
54 | # Use python3 -m pip install ... to ensure pip3 is used
55 | subprocess.check_call([sys.executable, "-m", "pip", "install", package])
56 | print(f"[SUCCESS] {package} installed successfully.\n")
57 | except subprocess.CalledProcessError as e:
58 | print(f"[ERROR] Failed to install {package}. Error: {e}")
59 | sys.exit(1)
60 |
61 | def main():
62 | """
63 | Main function to install all required packages.
64 | """
65 | print("Starting installation of required packages...\n")
66 | for pkg in packages:
67 | install_package(pkg)
68 | print("All required packages have been installed successfully.")
69 |
70 | if __name__ == "__main__":
71 | main()
72 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # credential files
2 | config.py
3 | session.py
4 | floorplans/
5 | # script generated files
6 | *.png
7 | *.csv
8 | backup
9 | org_backup
10 | site_backup
11 | rg_conf_file*.json
12 | org_inventory_file.json
13 |
14 | # others
15 | launch.json
16 | settings.json
17 | test.py
18 |
19 | .DS_Store
20 |
21 | # Byte-compiled / optimized / DLL files
22 | __pycache__/
23 | *.py[cod]
24 | *$py.class
25 |
26 | # C extensions
27 | *.so
28 |
29 | # Distribution / packaging
30 | .Python
31 | build/
32 | develop-eggs/
33 | dist/
34 | downloads/
35 | eggs/
36 | .eggs/
37 | lib/
38 | lib64/
39 | parts/
40 | sdist/
41 | var/
42 | wheels/
43 | pip-wheel-metadata/
44 | share/python-wheels/
45 | *.egg-info/
46 | .installed.cfg
47 | *.egg
48 | MANIFEST
49 |
50 | # PyInstaller
51 | # Usually these files are written by a python script from a template
52 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
53 | *.manifest
54 | *.spec
55 |
56 | # Installer logs
57 | pip-log.txt
58 | pip-delete-this-directory.txt
59 |
60 | # Unit test / coverage reports
61 | htmlcov/
62 | .tox/
63 | .nox/
64 | .coverage
65 | .coverage.*
66 | .cache
67 | nosetests.xml
68 | coverage.xml
69 | *.cover
70 | .hypothesis/
71 | .pytest_cache/
72 |
73 | # Translations
74 | *.mo
75 | *.pot
76 |
77 | # Django stuff:
78 | *.log
79 | local_settings.py
80 | db.sqlite3
81 |
82 | # Flask stuff:
83 | instance/
84 | .webassets-cache
85 |
86 | # Scrapy stuff:
87 | .scrapy
88 |
89 | # Sphinx documentation
90 | docs/_build/
91 |
92 | # PyBuilder
93 | target/
94 |
95 | # Jupyter Notebook
96 | .ipynb_checkpoints
97 |
98 | # IPython
99 | profile_default/
100 | ipython_config.py
101 |
102 | # pyenv
103 | .python-version
104 |
105 | # pipenv
106 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
107 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
108 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
109 | # install all needed dependencies.
110 | #Pipfile.lock
111 |
112 | # celery beat schedule file
113 | celerybeat-schedule
114 |
115 | # SageMath parsed files
116 | *.sage.py
117 |
118 | # Environments
119 | .env
120 | .venv
121 | env/
122 | venv/
123 | ENV/
124 | env.bak/
125 | venv.bak/
126 |
127 | # Spyder project settings
128 | .spyderproject
129 | .spyproject
130 |
131 | # Rope project settings
132 | .ropeproject
133 |
134 | # mkdocs documentation
135 | /site
136 |
137 | # mypy
138 | .mypy_cache/
139 | .dmypy.json
140 | dmypy.json
141 |
142 | # Pyre type checker
143 | .pyre/
144 | *.py~
145 | test2.py
146 | .dccache
147 | scripts/orgs/org_conf_deploy_v2.py
148 | scripts/devices/switches/cli_commit_events/
--------------------------------------------------------------------------------
/scripts/sites/import_psk.py:
--------------------------------------------------------------------------------
1 | '''
2 | Written by Thomas Munzer (tmunzer@juniper.net)
3 | Github repository: https://github.com/tmunzer/Mist_library/
4 |
5 | This script will import PSKs from a CSV file to one or multiple sites.
6 | Usage:
7 | python3 import_psk.py path_to_the_csv_file.csv
8 |
9 | CSV file format:
10 |
11 | pskName1,pskValue1,Wlan1
12 | pskName2,pskValue2,Wlan2
13 |
14 | '''
15 |
16 | #### IMPORTS #####
17 | import sys
18 | import csv
19 | import logging
20 |
21 | MISTAPI_MIN_VERSION = "0.44.1"
22 |
23 | try:
24 | import mistapi
25 | from mistapi.__logger import console
26 | except:
27 | print("""
28 | Critical:
29 | \"mistapi\" package is missing. Please use the pip command to install it.
30 |
31 | # Linux/macOS
32 | python3 -m pip install mistapi
33 |
34 | # Windows
35 | py -m pip install mistapi
36 | """)
37 | sys.exit(2)
38 |
39 | #### PARAMETERS #####
40 | LOG_FILE = "./scripts.log"
41 | ENV_FILE = "./.env"
42 | #### LOGS ####
43 | LOGGER = logging.getLogger(__name__)
44 |
45 | #### FUNCTIONS #####
46 |
47 | def import_psk(apisession, site_id, psks):
48 | print("")
49 | print("".center(80, "-"))
50 | print(f"Starting PSKs import for site {site_id}".center(80, "-"))
51 | print("")
52 | for psk in psks:
53 | print(f'PSK {psk["username"]}')
54 | body = {
55 | "username":psk["username"],
56 | "passphrase":psk["passphrase"],
57 | "ssid":psk["ssid"]
58 | }
59 | result = mistapi.api.v1.sites.psks.createSitePsk(apisession, site_id, body=body).data
60 | mistapi.cli.pretty_print(result)
61 |
62 | def read_csv(csv_file):
63 | print("")
64 | print("".center(80, "-"))
65 | print(f"Opening CSV file {csv_file}".center(80, "-"))
66 | print("")
67 | psks = []
68 | try:
69 | with open(sys.argv[1], 'r') as my_file:
70 | ppsk_file = csv.reader(my_file, delimiter=',')
71 | ppsk_file = [[c.replace("\ufeff", "") for c in row] for row in ppsk_file]
72 | for row in ppsk_file:
73 | username = row[0]
74 | passphrase = row[1]
75 | ssid = row[2]
76 | psks.append({"username": username,"passphrase": passphrase,"ssid": ssid})
77 | return psks
78 | except:
79 | print("Error while opening the CSV file... Aborting")
80 |
81 | def list_psks(apisession, site_id):
82 | print("")
83 | print("".center(80, "-"))
84 | print(f"List of current PSKs for site {site_id}".center(80, "-"))
85 | print("")
86 | response = mistapi.api.v1.sites.psks.listSitePsks(apisession, site_id)
87 | psks = mistapi.get_all(apisession, response)
88 | mistapi.cli.pretty_print(psks)
89 |
90 |
91 | def start(apisession):
92 | site_ids = mistapi.cli.select_site(apisession, allow_many=True)
93 | print("")
94 | print("".center(80, "-"))
95 | print(site_ids)
96 |
97 | psks = read_csv(sys.argv[1])
98 |
99 | for site_id in site_ids:
100 | import_psk(apisession, site_id, psks)
101 |
102 | for site_id in site_ids:
103 | list_psks(apisession, site_id)
104 |
105 | #### SCRIPT ENTRYPOINT #####
106 | if __name__ == "__main__":
107 | #### LOGS ####
108 | logging.basicConfig(filename=LOG_FILE, filemode='w')
109 | LOGGER.setLevel(logging.DEBUG)
110 | ### START ###
111 | APISESSION = mistapi.APISession(env_file=ENV_FILE)
112 | APISESSION.login()
113 | start(APISESSION)
114 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at tmunzer@juniper.net. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/.dccache:
--------------------------------------------------------------------------------
1 | {"/Users/tmunzer/4_dev/Mist/mist_library/scripts/exports/export_inventory.py":[10503,1677509346223.2495,"e7a9974574ded3222d8a95541cd7642d8246209b3e181a1fcf0aa26fdcaa75e8"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/exports/export_search.py":[50508,1677008238504.406,"6278840fb3b6ae29d9cab84f5b3a01d33c872f841eb745f028ffca0adc669643"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/reports/report_app_usage.py":[9275,1675961548911.968,"e8236876c4c938bbecd5ee3ca364c16be0cacbca3f2db18c023bdbef6ffbc155"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/reports/report_bssids.py":[9012,1675961554648.989,"9830a86ddf453e81a9a947c2df5204ce572712358cd0ed0148e3ba396155b7ac"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/reports/report_rogues.py":[9033,1675961554650.8918,"3e86b6cfb2d0599ab06359313f9f4cee2f806dab5829c7a32067662d012208ca"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/reports/report_switch_snapshot.py":[11299,1675961554650.958,"aeaa7aaef32281f06c0fdd9c599f69ee80b937f4832daca215b2294cb8fb02f2"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/reports/report_wlans.py":[5856,1675933963691.9958,"7aa36f7bc7c0db8a9c7592960a4f4c8b0763441fb93028e5fb68b9463a5124dc"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/orgs/admins_import.py":[4705,1677059182912.431,"80e855c1da3c8ff9fe4eb01d8f4f734e31b1e2f3e2d79b5756fd9906964de637"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/orgs/assign_inventory.py":[19143,1679038826643.5806,"4312f51f71aaa4dab87e0a25b6540e5e6c8fbac398dbbf8d5fafa00d03d3ceca"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/orgs/configure_ap_auto_upgrade.py":[4843,1677053504209.3894,"0903c9f985dd3ad7835289d3fbc846c7c4290dbed5b71a095918423f9234e96a"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/orgs/configure_auto_site_assignment.py":[11408,1675961542833.2292,"249ae57edf9ce7c3623253982cba3bdf1c69e66959849c9c86cc0ba7d027bf03"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/orgs/org_clone.py":[17340,1677691043242.407,"eb4e8e8fff93af7fca24bb336feab152e3418680894a2c64ab6344badc4206d7"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/orgs/org_complete_backup.py":[10720,1677865625579.286,"b90600311bff2ccc90c96dfa4d1950c0d04bcf81dbb1d08688e39aa912ab887c"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/orgs/org_conf_backup.py":[20909,1677092939348.7646,"35cb694ed6e1b3de0c364b73f0bca60ad01e3b3b980ad71f08173961b7264890"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/orgs/org_conf_deploy.py":[37734,1679594740809.2305,"0692abb60c7b94465c5fae4d9e0517db87856436a942d4cdf09f80438d9b0e65"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/orgs/org_conf_deploy_only.py":[36095,1679594729529.1064,"b7295db0ae5039624f38b3bed83546ffa9ca26bb7e37cacd6950800ac7eb3e57"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/orgs/org_conf_zeroise.py":[15412,1676372763305.2349,"d199ba43782684dd7d4396e89677eade55f44b839252f67272f5dfce8f2ad77d"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/orgs/org_inventory_backup.py":[19806,1678089694392.9531,"f7b2c0c9242c71de4e301822e1c5a82da46be382257075b53fa13be7da3183cb"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/orgs/org_inventory_deploy.py":[45239,1677484895376.9302,"e5913e44e3bb18c0310ba45588546f35c1e6a015709504571ad00a3d41e359b2"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/orgs/org_inventory_restore_pictures.py":[29916,1677484533465.5293,"8bda25aa7b9ee2447d9a0a1f7225d4143c91a70912baaa3e975b61de5e00b0df"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/orgs/org_migration.py":[21045,1677691387435.9124,"83f0b124060091e82400239f985d07efc62bbd78045236c05c0c29b716b9a780"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/sites/import_sites.py":[32001,1678972181288.2024,"ba8b8147beed48cebf17e91046e6935024995aa5cc335715abc126b726ee12cf"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/sites/site_conf_backup.py":[15356,1676291266383.9922,"9030e0c0cb5132fd13b261ff51f3c73cc27ad3c762821ed391f868f633e956c4"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/sites/site_conf_psk.py":[956,1675933963741.3247,"38118966ae248b2a6ad64f506adbd1bc1b25181ab7491772cb3add33232ae447"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/sites/site_conf_psk_import_csv.py":[2574,1675933963748.7458,"fe3e48b662ee098d63995dc132e640486153e616dba0eb2ff5572fa0ccf85fe2"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/sites/site_conf_webhook.py":[6715,1675933963749.6323,"d2e1aafa84840c7e29beadb3527d93da5f68ffc8679451b0904561ada9184177"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/sites/site_conf_wlan.py":[4665,1675933963751.0168,"02962b9af134c954e7f3cdf0c6130c9e0f235b2a90635e2cba4570cb72cfdee6"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/devices/aps/configure_ap_mgmt_vlan.py":[11604,1676881687079.4246,"9608aaee82fd1c3a62c07ec558046c457197082b750e71f9f169cccf6a8638dc"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/devices/switches/toggle_poe.py":[12283,1676884258043.2393,"512cbf70fcb45ac5e8e7fd01ac68e729a386d856edca4a9115e2050c37cd9bd9"],"/Users/tmunzer/4_dev/Mist/mist_library/scripts/sites/not_migrated_to_mistapi/site_conf_restore.py":[20105,1675933963739.149,"3c49dfdc5c42498a2f1c4fdcaf235a17789cdb37b4490602f6bc4d3d9015626e"]}
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Thank you for contributing to the mist_library repository.
2 |
3 | This document explains how to get started, how to report issues, and the expectations for pull requests so changes are safe, testable and easy to review.
4 |
5 | ## Quick start (local)
6 |
7 | 1. Fork the repository and create a feature branch from `master`:
8 |
9 | - Use a short, descriptive branch name: `fix/-` or `feat/-`.
10 |
11 | 2. Create and activate a Python virtual environment (macOS/Linux):
12 |
13 | ```bash
14 | python -m venv .venv
15 | source .venv/bin/activate
16 | python -m pip install --upgrade pip
17 | python -m pip install -r requirements.txt
18 | ```
19 |
20 | 3. Run a script's help to confirm the environment is working:
21 |
22 | ```bash
23 | python scripts/orgs/org_conf_deploy.py -h
24 | ```
25 |
26 | ## Environment / secrets
27 |
28 | - The repository's scripts expect an environment file (default `~/.mist_env`).
29 | - Never commit credentials, tokens, or secret files to the repository or a PR. Remove them from diffs immediately if accidentally included and rotate the credentials.
30 | - If you need to demonstrate a flow, provide a sanitized example env file (see README for an example) and avoid real tokens.
31 |
32 | ## Reporting issues
33 |
34 | When opening an issue, include:
35 |
36 | - A short, descriptive title.
37 | - Steps to reproduce the problem or exact command you ran.
38 | - The script path and line number (if applicable).
39 | - Your Python version and `mistapi` version (run `python3 -c "import mistapi; print(mistapi.__version__)"`).
40 | - Relevant, sanitized logs or error messages.
41 |
42 | Good issues are reproducible and include minimal data necessary for debugging.
43 |
44 | ## Pull requests (PRs)
45 |
46 | Best practices for PRs:
47 |
48 | - One logical change per PR.
49 | - Keep PRs small and focused.
50 | - Include tests or a short smoke-check when you change behavior.
51 | - Update `README.md` or script header docstrings when you change behavior or add new scripts.
52 | - Update or add `MISTAPI_MIN_VERSION` in script headers if the change requires a newer `mistapi` version.
53 | - If a change may be destructive (mass deletes, claims), require a CLI `--dry-run` first and document risks in the PR description.
54 |
55 | Suggested PR checklist (include in the PR description):
56 |
57 | - [ ] I have run the relevant script(s) locally and verified the change (or added a smoke test).
58 | - [ ] There are no credentials in the diff.
59 | - [ ] I updated the README or script header where applicable.
60 | - [ ] I updated `MISTAPI_MIN_VERSION` in scripts that require a newer mistapi.
61 | - [ ] I added tests or a reproducible example for non-trivial logic.
62 |
63 | ## Coding style and tests
64 |
65 | - Language: Python 3.8+ (some scripts assume newer language features; prefer 3.10+).
66 | - Keep changes consistent with existing code style (short, well-documented CLI scripts). Use type hints where appropriate.
67 | - Add or update docstrings at the top of script files explaining the purpose, options and example calls.
68 | - Where practical, include a small unit test or a smoke test (follow the repository's existing patterns). If you add tests, include the commands to run them in the PR.
69 |
70 | ## Logging and debug output
71 |
72 | - Many scripts use numeric logging levels (e.g., `CONSOLE_LOG_LEVEL`, `LOGGING_LOG_LEVEL`). Preserve those options when editing scripts.
73 | - Avoid printing sensitive data to logs.
74 |
75 | ## Adding new scripts
76 |
77 | When adding a new script, follow these guidelines:
78 |
79 | - Place the script in the appropriate `scripts/` subfolder.
80 | - Add a clear module-level docstring with purpose, usage examples and the default `ENV_FILE` value (e.g., `ENV_FILE = "~/.mist_env"`).
81 | - If the script depends on specific `mistapi` features, include `MISTAPI_MIN_VERSION = "x.y.z"` near the top.
82 | - Provide at least one usage example in the header and verify `-h` output is helpful.
83 | - Add the script to the `README` index or propose `SCRIPTS_INDEX.md` generation if the list becomes large.
84 |
85 | ## Security and sensitive operations
86 |
87 | - Mark destructive changes in the PR title and description (e.g., `dangerous`, `destructive`) and provide a dry-run mode.
88 | - If a change can modify many objects in a Mist org, include a clear rollback or validation strategy in the PR.
89 |
90 | ## Licensing and copyright
91 |
92 | - The project is licensed under the MIT License. By contributing you agree that your contribution can be distributed under the repository license.
93 |
94 | ## Maintainers and reviews
95 |
96 | - PRs are reviewed on GitHub. Expect maintainers to ask for small, incremental improvements during review.
97 | - If you want to take on larger work (refactor, test-suite addition), open an issue first describing scope and plan.
98 |
99 | ---
100 |
101 | Thanks for contributing — clear, small PRs with examples and no secrets make reviews fast and safe.
102 |
--------------------------------------------------------------------------------
/scripts/sites/site_conf_wlan.py:
--------------------------------------------------------------------------------
1 | '''
2 | Written by Thomas Munzer (tmunzer@juniper.net)
3 | Github repository: https://github.com/tmunzer/Mist_library/
4 |
5 | This script can be used to list/add/delete an SSID from Org/Site
6 | '''
7 |
8 | #### IMPORTS ####
9 | import json
10 | import sys
11 | import logging
12 |
13 | MISTAPI_MIN_VERSION = "0.55.5"
14 |
15 | try:
16 | import mistapi
17 | from mistapi.__logger import console
18 | except:
19 | print("""
20 | Critical:
21 | \"mistapi\" package is missing. Please use the pip command to install it.
22 |
23 | # Linux/macOS
24 | python3 -m pip install mistapi
25 |
26 | # Windows
27 | py -m pip install mistapi
28 | """)
29 | sys.exit(2)
30 |
31 | #### PARAMETERS #####
32 | LOG_FILE = "./sites_scripts.log"
33 | DEFAULT_WLAN_FILE = "./site_conf_wlan_settings.json"
34 | ENV_FILE = "./.env"
35 | #### LOGS ####
36 | LOGGER = logging.getLogger(__name__)
37 |
38 |
39 | def add_wlan(apisession, site_id):
40 | wlan_file = input(f"Path to the WLAN configuration JSON file (default: {DEFAULT_WLAN_FILE}): ")
41 | if wlan_file == "":
42 | wlan_file = DEFAULT_WLAN_FILE
43 | try:
44 | with open(wlan_file, "r") as f:
45 | wlan = json.load(f)
46 | except:
47 | print("Error while loading the configuration file... exiting...")
48 | sys.exit(255)
49 | try:
50 | wlan_json = json.dumps(wlan)
51 | except:
52 | print("Error while loading the wlan settings from the file... exiting...")
53 | sys.exit(255)
54 | mistapi.api.v1.sites.wlans.createSiteWlan(apisession, site_id, wlan_json)
55 |
56 | def remove_wlan(apisession, site_id):
57 | wlans = mistapi.api.v1.sites.wlans.listSiteWlans(apisession, site_id).data
58 | resp = -1
59 | while True:
60 | print()
61 | print("Available WLANs:")
62 | i = 0
63 | for wlan in wlans:
64 | print(f"{i}) {wlan['ssid']} (id: {wlan['id']})")
65 | i+=1
66 | print()
67 | resp = input(f"Which WLAN do you want to delete (0-{i-1}, or q to quit)? ")
68 | if resp.lower() == "q":
69 | sys.exit(0)
70 | else:
71 | try:
72 | resp_num = int(resp)
73 | if resp_num >= 0 and resp_num <= i:
74 | wlan = wlans[resp_num]
75 | print()
76 | confirmation = input(f"Are you sure you want to delete WLAN {wlan['ssid']} (y/N)? ")
77 | if confirmation.lower() == "y":
78 | break
79 | else:
80 | print(f"{resp_num} is not part of the possibilities.")
81 | except:
82 | print("Only numbers are allowed.")
83 | mistapi.api.v1.sites.wlans.deleteSiteWlan(apisession, site_id, wlan["id"])
84 |
85 |
86 | def display_wlan(apisession, site_id):
87 | fields = ["id","ssid", "enabled", "auth", "auth_servers", "acct_servers", "band", "interface", "vlan_id", "dynamic_vlan", "hide_ssid"]
88 | site_wlans = mistapi.api.v1.sites.wlans.listSiteWlanDerived(apisession, site_id).data
89 | mistapi.cli.display_list_of_json_as_table(site_wlans, fields)
90 |
91 | def start_site_conf_wlan(apisession, site_id):
92 | while True:
93 | print()
94 | print(" ===================")
95 | print(" == CURRENT WLANS ==")
96 | display_wlan(apisession, site_id)
97 | print(" ===================")
98 | print()
99 | actions = ["add WLAN", "remove WLAN"]
100 | print("What do you want to do:")
101 | i = -1
102 | for action in actions:
103 | i+= 1
104 | print(f"{i}) {action}")
105 | print()
106 | resp = input(f"Choice (0-{i}, q to quit): ")
107 | if resp.lower() == "q":
108 | sys.exit(0)
109 | else:
110 | try:
111 | resp_num = int(resp)
112 | except:
113 | print("Only numbers are allowed.")
114 | if resp_num >= 0 and resp_num <= i:
115 | if actions[resp_num] == "add WLAN":
116 | add_wlan(apisession, site_id)
117 | print()
118 | print(" ========================")
119 | print(" == WLANS AFTER CHANGE ==")
120 | display_wlan(apisession, site_id)
121 | print(" ========================")
122 | break
123 | elif actions[resp_num] == "remove WLAN":
124 | remove_wlan(apisession, site_id)
125 | print()
126 | print(" ========================")
127 | print(" == WLANS AFTER CHANGE ==")
128 | display_wlan(apisession, site_id)
129 | print(" ========================")
130 | break
131 | else:
132 | print(f"{resp_num} is not part of the possibilities.")
133 |
134 | def start(apisession):
135 | org_id = mistapi.cli.select_org(apisession)[0]
136 | site_id = mistapi.cli.select_site(apisession, org_id=org_id, allow_many=False)[0]
137 | start_site_conf_wlan(apisession, site_id)
138 |
139 | ##### ENTRY POINT ####
140 |
141 | if __name__ == "__main__":
142 | #### LOGS ####
143 | logging.basicConfig(filename=LOG_FILE, filemode='w')
144 | LOGGER.setLevel(logging.DEBUG)
145 | ### START ###
146 | APISESSION = mistapi.APISession(env_file=ENV_FILE)
147 | APISESSION.login()
148 | start(APISESSION)
149 |
--------------------------------------------------------------------------------
/scripts/exports/compare_org_events_summary.py:
--------------------------------------------------------------------------------
1 | """
2 | -------------------------------------------------------------------------------
3 |
4 | Written by Thomas Munzer (tmunzer@juniper.net)
5 | Github repository: https://github.com/tmunzer/Mist_library/
6 |
7 | This script is licensed under the MIT License.
8 |
9 | -------------------------------------------------------------------------------
10 |
11 | Python script to compare two different version of summaries generated by the
12 | export_org_events.py script.
13 | This can be used to easily detect new sites referenced.
14 |
15 | -------
16 | Options:
17 | -h, --help display this help
18 | -n, --new= latest export file
19 | -o, --old= oldest export file
20 |
21 | -------
22 | Examples:
23 | python3 ./compare_org_events_summary \
24 | -n org_events_summary_2024-09-08T23.35.51.csv
25 | -o org_events_summary_2024-09-07T23.31.25.csv
26 |
27 | """
28 |
29 | #### IMPORTS ####
30 | import sys
31 | import csv
32 | import getopt
33 | import tabulate
34 |
35 | def _load_data(file:str):
36 | """
37 | format summaries
38 | output should be something like
39 | data{"site_a": {"link": "...", "event_types": ["e_type_a", "e_type_b"]}, "site_b": ...}
40 | """
41 | try:
42 | with open(file, 'r') as f:
43 | data = {}
44 | headers = {}
45 | rows = csv.reader(f, delimiter=",")
46 | i = -1
47 | for row in rows:
48 | i+=1
49 | if i == 0:
50 | pass
51 | elif i == 1:
52 | for header_name in row:
53 | if header_name not in ["site", "link"]:
54 | headers[header_name] = row.index(header_name)
55 | else:
56 | event_types = []
57 | for header_name, header_index in headers.items():
58 | try:
59 | val = int(row[header_index])
60 | if val > 0:
61 | event_types.append(header_name)
62 | except:
63 | pass
64 | if event_types:
65 | event_types = sorted(event_types)
66 | data[row[0]] = {
67 | "link": row[1],
68 | "event_types": event_types
69 | }
70 | return data
71 | except:
72 | print(f"Error: Unable to read {file}")
73 | sys.exit(1)
74 |
75 | def _compare(new:str, old:str):
76 | """
77 | create a diff.
78 | add a new line with the site name, site link, and the list of new events for the site
79 | """
80 | new_data = _load_data(new)
81 | old_data = _load_data(old)
82 | diff = []
83 | for site_name, new_site_data in new_data.items():
84 | new_events = []
85 | new_site_events = new_site_data.get("event_types")
86 | if old_data.get(site_name):
87 | old_site_events = old_data[site_name].get("event_types")
88 | for event_type in new_site_events:
89 | if event_type not in old_site_events:
90 | new_events.append(event_type)
91 | else:
92 | for event_type in new_site_events:
93 | new_events.append(event_type)
94 | if new_events:
95 | diff.append([
96 | site_name,
97 | new_site_data.get("link"),
98 | ", ".join(new_events)
99 | ])
100 | return diff
101 |
102 | def _save(new:str, old: str, data:list):
103 | filename = new.replace("summary", "diff")
104 | with open(filename, "w") as f:
105 | csv_writer = csv.writer(f)
106 | csv_writer.writerow([f"#compare between {new} and {old}"])
107 | csv_writer.writerow(["#site","link", "new events"])
108 | for r in data:
109 | csv_writer.writerow(r)
110 | return filename
111 |
112 | def start(
113 | new: str,
114 | old: str
115 | ):
116 | if not new:
117 | print("Error: missing new file parameter")
118 | if not old:
119 | print("Error: missing old file parameter")
120 | diff = _compare(new, old)
121 | filename = _save(new, old, diff)
122 | print()
123 | print(" Result ".center(80, "-"))
124 | print(tabulate.tabulate(diff[1:], headers={
125 | "site":"site",
126 | "link":"link",
127 | "new events":"new events"
128 | }))
129 | print()
130 | print(f"Saved into {filename}")
131 |
132 | def usage(message: str = None):
133 | """Function to display Help"""
134 | print(
135 | f"""
136 | -------------------------------------------------------------------------------
137 |
138 | Written by Thomas Munzer (tmunzer@juniper.net)
139 | Github repository: https://github.com/tmunzer/Mist_library/
140 |
141 | This script is licensed under the MIT License.
142 |
143 | -------------------------------------------------------------------------------
144 |
145 | Python script to compare two different version of summaries generated by the
146 | export_org_events.py script.
147 | This can be used to easily detect new sites referenced.
148 |
149 | -------
150 | Options:
151 | -h, --help display this help
152 | -n, --new= latest export file
153 | -o, --old= oldest export file
154 |
155 | -------
156 | Examples:
157 | Examples:
158 | python3 ./compare_org_events_summary \
159 | -n org_events_summary_2024-09-08T23.35.51.csv
160 | -o org_events_summary_2024-09-07T23.31.25.csv
161 |
162 | """
163 | )
164 | if message:
165 | print(f"ERROR: {message}")
166 | sys.exit(0)
167 |
168 |
169 | #####################################################################
170 | #### SCRIPT ENTRYPOINT ####
171 | if __name__ == "__main__":
172 | try:
173 | opts, args = getopt.getopt(
174 | sys.argv[1:],
175 | "hn:o:",
176 | [
177 | "help",
178 | "new=",
179 | "old=",
180 | ],
181 | )
182 | except getopt.GetoptError as err:
183 | print(err)
184 | usage()
185 |
186 | NEW = None
187 | OLD = None
188 | for o, a in opts: # type: ignore
189 | if o in ["-h", "--help"]:
190 | usage()
191 | elif o in ["-n", "--new"]:
192 | NEW = a
193 | elif o in ["-o", "--old"]:
194 | OLD = a
195 | else:
196 | assert False, "unhandled option"
197 |
198 |
199 | start(NEW, OLD)
200 |
--------------------------------------------------------------------------------
/scripts/orgs/admins/import_admins.py:
--------------------------------------------------------------------------------
1 | '''
2 | -------------------------------------------------------------------------------
3 |
4 | Written by Thomas Munzer (tmunzer@juniper.net)
5 | Github repository: https://github.com/tmunzer/Mist_library/
6 |
7 | This script is licensed under the MIT License.
8 |
9 | -------------------------------------------------------------------------------
10 | Python script to invite/add administrators from a CSV file.
11 |
12 | It is recommended to use an environment file to store the required information
13 | to request the Mist Cloud (see https://pypi.org/project/mistapi/ for more
14 | information about the available parameters).
15 |
16 | -------
17 | Requirements:
18 | mistapi: https://pypi.org/project/mistapi/
19 |
20 | -------
21 | Usage:
22 | This script requires one parameters pointing to the CSV file.
23 | The CSV file must have 3 columns: email, first name, last name.
24 | The organization and the admin roles/scopes will be asked by the script.
25 |
26 | -------
27 | CSV Example:
28 | owkenobi@unknown.com,Obi-Wan,Kenobi
29 | pamidala@unknown.com,Padme,Amidala
30 |
31 |
32 |
33 | -------
34 | Example:
35 | python3 import_admins.py "
36 |
37 | '''
38 | #### IMPORTS ####
39 | import sys
40 | import csv
41 | import logging
42 |
43 | MISTAPI_MIN_VERSION = "0.44.1"
44 |
45 | try:
46 | import mistapi
47 | from mistapi.__logger import console
48 | except:
49 | print("""
50 | Critical:
51 | \"mistapi\" package is missing. Please use the pip command to install it.
52 |
53 | # Linux/macOS
54 | python3 -m pip install mistapi
55 |
56 | # Windows
57 | py -m pip install mistapi
58 | """)
59 | sys.exit(2)
60 |
61 | #### PARAMETERS #####
62 | csv_separator = ","
63 | privileges = []
64 | env_file="~/.mist_env"
65 | log_file = "./script.log"
66 |
67 | #####################################################################
68 | #### LOGS ####
69 | LOGGER = logging.getLogger(__name__)
70 |
71 | #### CONSTANTS ####
72 | roles = {"s": "admin", "n": "write", "o": "read", "h":"helpdesk"}
73 |
74 | #### FUNCTIONS ####
75 | def define_privileges(apisession, org_id):
76 | '''
77 | Generate the privilege parameters for the specified orgs.
78 | Will ask if the privileges have to be applied to the entire org or to a specific site/s, and the privilege level.
79 | There is no return value, the new privileges are stored into the global "privilege" variable
80 | '''
81 | role = ""
82 | while role not in roles:
83 | role = input("Which level of privilege at the org level (\"s\" for Super Admin, \"n\" for Network Admin,\"o\" for observer,\"h\" for helpdesk)? ")
84 | while True:
85 | all_sites = input("Do you want to select specific sites (y/N)? ")
86 | if all_sites.lower()=="y":
87 | site_ids = mistapi.cli.select_site(apisession, org_id, True)
88 | for site_id in site_ids:
89 | privileges.append({"scope": "site", "org_id": org_id, "site_id": site_id, "role":roles[role]})
90 | break
91 | elif all_sites.lower() == "n" or all_sites == "":
92 | site_ids = mistapi.api.v1.orgs.sites.listOrgSites(apisession, org_id)
93 | site_id=""
94 | for site in site_ids.data:
95 | if "site_id" in site:
96 | site_id = site["site_id"]
97 | break
98 | privileges.append({"scope": "org", "org_id": org_id, "site_id":site_id, "role":roles[role]})
99 | break
100 |
101 | def import_admins(apisession, file_path, org_id):
102 | '''
103 | create all the administrators from the "file_path" file.
104 | '''
105 | print(f"Opening CSV file {file_path}")
106 | try:
107 | with open(file_path, 'r') as my_file:
108 | invite_file = csv.reader(my_file, delimiter=csv_separator)
109 | for row in invite_file:
110 | body = {
111 | "email": row[0],
112 | "first_name":row[1],
113 | "last_name": row[2],
114 | "privileges": privileges
115 | }
116 | print(', '.join(row).ljust(80), end="", flush=True)
117 | try:
118 | response = mistapi.api.v1.orgs.invites.inviteOrgAdmin(apisession, org_id, body)
119 | if response.status_code == 200:
120 | print("\033[92m\u2714\033[0m")
121 | else:
122 | print('\033[31m\u2716\033[0m')
123 | except:
124 | print('\033[31m\u2716\033[0m')
125 | except Exception as e:
126 | print("Error while opening the CSV file... Aborting")
127 | print(e)
128 |
129 | def check_mistapi_version():
130 | """Check if the installed mistapi version meets the minimum requirement."""
131 |
132 | current_version = mistapi.__version__.split(".")
133 | required_version = MISTAPI_MIN_VERSION.split(".")
134 |
135 | try:
136 | for i, req in enumerate(required_version):
137 | if current_version[int(i)] > req:
138 | break
139 | if current_version[int(i)] < req:
140 | raise ImportError(
141 | f'"mistapi" package version {MISTAPI_MIN_VERSION} is required '
142 | f"but version {mistapi.__version__} is installed."
143 | )
144 | except ImportError as e:
145 | LOGGER.critical(str(e))
146 | LOGGER.critical("Please use the pip command to update it.")
147 | LOGGER.critical("")
148 | LOGGER.critical(" # Linux/macOS")
149 | LOGGER.critical(" python3 -m pip install --upgrade mistapi")
150 | LOGGER.critical("")
151 | LOGGER.critical(" # Windows")
152 | LOGGER.critical(" py -m pip install --upgrade mistapi")
153 | print(
154 | f"""
155 | Critical:\r\n
156 | {e}\r\n
157 | Please use the pip command to update it.
158 | # Linux/macOS
159 | python3 -m pip install --upgrade mistapi
160 | # Windows
161 | py -m pip install --upgrade mistapi
162 | """
163 | )
164 | sys.exit(2)
165 | finally:
166 | LOGGER.info(
167 | '"mistapi" package version %s is required, '
168 | "you are currently using version %s.",
169 | MISTAPI_MIN_VERSION,
170 | mistapi.__version__
171 | )
172 |
173 | #####################################################################
174 | ##### ENTRY POINT ####
175 | if __name__ == "__main__":
176 | #### LOGS ####
177 | logging.basicConfig(filename=log_file, filemode='w')
178 | LOGGER.setLevel(logging.DEBUG)
179 | check_mistapi_version()
180 |
181 | ### START ###
182 | file_path = sys.argv[1]
183 | apisession = mistapi.APISession(env_file=env_file)
184 | apisession.login()
185 |
186 | org_id = mistapi.cli.select_org(apisession)[0]
187 |
188 | define_privileges(apisession, org_id)
189 | import_admins(apisession, file_path, org_id)
190 |
191 | admins = mistapi.api.v1.orgs.admins.listOrgAdmins(apisession, org_id).data
192 |
193 |
--------------------------------------------------------------------------------
/scripts/reports/report_rogues.py:
--------------------------------------------------------------------------------
1 | '''
2 | -------------------------------------------------------------------------------
3 |
4 | Written by Thomas Munzer (tmunzer@juniper.net)
5 | Github repository: https://github.com/tmunzer/Mist_library/
6 |
7 | This script is licensed under the MIT License.
8 |
9 | -------------------------------------------------------------------------------
10 | Python script to generate a Rogue AP report.
11 |
12 | -------
13 | Requirements:
14 | mistapi: https://pypi.org/project/mistapi/
15 |
16 | -------
17 | Usage:
18 | This script can be run as is (without parameters), or with the options below.
19 | If no options are defined, or if options are missing, the missing options will
20 | be asked by the script or the default values will be used.
21 |
22 | It is recommended to use an environment file to store the required information
23 | to request the Mist Cloud (see https://pypi.org/project/mistapi/ for more
24 | information about the available parameters).
25 |
26 | -------
27 | Options:
28 | -h, --help display this help
29 | -s, --site_id= Set the site_id
30 | -d, --duration= Duration (10m, 1h, 1d, 1w)
31 | default is 1d
32 | -r, --rogue_types= Types of rogues to include in the report, comma separated
33 | possible values: spoof, lan, honeypot, others
34 | default is spoof,lan,honeypot,others
35 | -f, --out_file= define the filepath/filename where to save the data
36 | default is "./report_rogues.csv"
37 | -l, --log_file= define the filepath/filename where to write the logs
38 | default is "./script.log"
39 | -e, --env= define the env file to use (see mistapi env file documentation
40 | here: https://pypi.org/project/mistapi/)
41 | default is "~/.mist_env"
42 |
43 | -------
44 | Examples:
45 | python3 ./report_rogues.py
46 | python3 ./report_rogues.py --site_ids=203d3d02-xxxx-xxxx-xxxx-76896a3330f4 --duration=4d -r lan,spoof
47 |
48 | '''
49 |
50 | #### IMPORTS #####
51 | import sys
52 | import csv
53 | import getopt
54 | import logging
55 |
56 | MISTAPI_MIN_VERSION = "0.44.1"
57 |
58 | try:
59 | import mistapi
60 | from mistapi.__logger import console
61 | except:
62 | print("""
63 | Critical:
64 | \"mistapi\" package is missing. Please use the pip command to install it.
65 |
66 | # Linux/macOS
67 | python3 -m pip install mistapi
68 |
69 | # Windows
70 | py -m pip install mistapi
71 | """)
72 | sys.exit(2)
73 |
74 |
75 | #### LOGS ####
76 | LOGGER = logging.getLogger(__name__)
77 | out=sys.stdout
78 |
79 | #### PARAMETERS #####
80 |
81 | rogue_types = [ "honeypot", "lan", "others", "spoof"]
82 | duration = "1d"
83 | log_file = "./script.log"
84 | csv_delimiter = ","
85 | csv_file = "./report_rogues.csv"
86 | env_file = "~/.mist_env"
87 |
88 |
89 | ###############################################################################
90 | ### PROGRESS BAR
91 | def _progress_bar_update(count:int, total:int, size:int):
92 | if total == 0:
93 | return
94 | if count > total:
95 | count = total
96 | x = int(size*count/total)
97 | out.write(f"Progress: ".ljust(10))
98 | out.write(f"[{'█'*x}{'.'*(size-x)}]")
99 | out.write(f"{count}/{total}\r".rjust(79 - size - 10))
100 | out.flush()
101 |
102 | def _progress_bar_end(total:int, size:int):
103 | if total == 0:
104 | return
105 | _progress_bar_update(total, total, size)
106 | out.write("\n")
107 | out.flush()
108 |
109 | ###############################################################################
110 | #### FUNCTIONS ####
111 |
112 | def _process_rogues(rogues:list, rogue__type:str, site_name:str, site_id:str):
113 | for rogue in rogues:
114 | rogue["type"] = rogue__type
115 | rogue["site_id"] = site_id
116 | rogue["site_name"] = site_name
117 | return rogues
118 |
119 | def _get_rogues(mist_session, site_id:str, site_name:str, rogue__type:str, site_rogues:list=[]):
120 | response = mistapi.api.v1.sites.insights.listSiteRogueAPs(mist_session, site_id, type=rogue__type, limit=1000, duration=duration)
121 | site_rogues = site_rogues + _process_rogues(response.data["results"], rogue__type, site_name, site_id)
122 | while response.next:
123 | response = mistapi.get_next(mist_session, response)
124 | site_rogues = site_rogues + _process_rogues(response.data["results"], rogue__type, site_name, site_id)
125 | return site_rogues
126 |
127 | def _process_sites(mist_session, site_ids):
128 | i = 0
129 | total = len(site_ids) * len(rogue_types)
130 | rogues = []
131 | _progress_bar_update(i, total, 50)
132 | for site_id in site_ids:
133 | site_name = mistapi.api.v1.sites.sites.getSiteInfo(mist_session, site_id).data["name"]
134 | for rogue__type in rogue_types:
135 | rogues = rogues + _get_rogues(mist_session, site_id, site_name, rogue__type)
136 | i+=1
137 | _progress_bar_update(i, total, 50)
138 | _progress_bar_end(total, 50)
139 | return rogues
140 |
141 | ### SAVE REPORT
142 | def _save_as_csv( data:list, duration:int):
143 | headers=[]
144 | size = 50
145 | total = len(data)
146 | print(" Saving Data ".center(80, "-"))
147 | print()
148 | print("Generating CSV Headers ".ljust(80,"."))
149 | i = 0
150 | for entry in data:
151 | for key in entry:
152 | if not key in headers:
153 | headers.append(key)
154 | i += 1
155 | _progress_bar_update(i, total, size)
156 | _progress_bar_end(total, size)
157 | print()
158 | print("Saving to file ".ljust(80,"."))
159 | i = 0
160 | with open(csv_file, "w", encoding='UTF8', newline='') as f:
161 | csv_writer = csv.writer(f)
162 | csv_writer.writerow([f"#Rogue Report for the last {duration}"])
163 | csv_writer.writerow(headers)
164 | for entry in data:
165 | tmp=[]
166 | for header in headers:
167 | tmp.append(entry.get(header, ""))
168 | csv_writer.writerow(tmp)
169 | i += 1
170 | _progress_bar_update(i, total, size)
171 | _progress_bar_end(total, size)
172 | print()
173 |
174 | ###############################################################################
175 | ### USAGE
176 | def usage():
177 | print('''
178 | -------------------------------------------------------------------------------
179 |
180 | Written by Thomas Munzer (tmunzer@juniper.net)
181 | Github repository: https://github.com/tmunzer/Mist_library/
182 |
183 | This script is licensed under the MIT License.
184 |
185 | -------------------------------------------------------------------------------
186 | Python script generates a Rogue AP report.
187 |
188 | -------
189 | Requirements:
190 | mistapi: https://pypi.org/project/mistapi/
191 |
192 | -------
193 | Usage:
194 | This script can be run as is (without parameters), or with the options below.
195 | If no options are defined, or if options are missing, the missing options will
196 | be asked by the script or the default values will be used.
197 |
198 | It is recommended to use an environment file to store the required information
199 | to request the Mist Cloud (see https://pypi.org/project/mistapi/ for more
200 | information about the available parameters).
201 |
202 | -------
203 | Options:
204 | -h, --help display this help
205 | -s, --site_id= Set the site_id
206 | -d, --duration= Duration (10m, 1h, 1d, 1w)
207 | default is 1d
208 | -r, --rogue_types= Types of rogues to include in the report, comma separated
209 | possible values: spoof, lan, honeypot, others
210 | default is spoof,lan,honeypot,others
211 | -f, --out_file= define the filepath/filename where to save the data
212 | default is "./report_rogues.csv"
213 | -l, --log_file= define the filepath/filename where to write the logs
214 | default is "./script.log"
215 | -e, --env= define the env file to use (see mistapi env file documentation
216 | here: https://pypi.org/project/mistapi/)
217 | default is "~/.mist_env"
218 |
219 | -------
220 | Examples:
221 | python3 ./report_rogues.py
222 | python3 ./report_rogues.py --site_ids=203d3d02-xxxx-xxxx-xxxx-76896a3330f4 --duration=4d -r lan,spoof
223 |
224 | ''')
225 | sys.exit(0)
226 |
227 | def check_mistapi_version():
228 | """Check if the installed mistapi version meets the minimum requirement."""
229 |
230 | current_version = mistapi.__version__.split(".")
231 | required_version = MISTAPI_MIN_VERSION.split(".")
232 |
233 | try:
234 | for i, req in enumerate(required_version):
235 | if current_version[int(i)] > req:
236 | break
237 | if current_version[int(i)] < req:
238 | raise ImportError(
239 | f'"mistapi" package version {MISTAPI_MIN_VERSION} is required '
240 | f"but version {mistapi.__version__} is installed."
241 | )
242 | except ImportError as e:
243 | LOGGER.critical(str(e))
244 | LOGGER.critical("Please use the pip command to update it.")
245 | LOGGER.critical("")
246 | LOGGER.critical(" # Linux/macOS")
247 | LOGGER.critical(" python3 -m pip install --upgrade mistapi")
248 | LOGGER.critical("")
249 | LOGGER.critical(" # Windows")
250 | LOGGER.critical(" py -m pip install --upgrade mistapi")
251 | print(
252 | f"""
253 | Critical:\r\n
254 | {e}\r\n
255 | Please use the pip command to update it.
256 | # Linux/macOS
257 | python3 -m pip install --upgrade mistapi
258 | # Windows
259 | py -m pip install --upgrade mistapi
260 | """
261 | )
262 | sys.exit(2)
263 | finally:
264 | LOGGER.info(
265 | '"mistapi" package version %s is required, '
266 | "you are currently using version %s.",
267 | MISTAPI_MIN_VERSION,
268 | mistapi.__version__
269 | )
270 |
271 |
272 |
273 | ###############################################################################
274 | ### ENTRY POINT
275 | if __name__ == "__main__":
276 | try:
277 | opts, args = getopt.getopt(sys.argv[1:], "hs:d:l:f:c:e:t:r:", ["help", "site_ids=", "duration=", "out_file=", "env=", "log_file=", "rogue_types="])
278 | except getopt.GetoptError as err:
279 | console.error(err)
280 | usage()
281 |
282 | site_ids=None
283 | query_params={}
284 | for o, a in opts:
285 | if o in ["-h", "--help"]:
286 | usage()
287 | elif o in ["-s", "--site_ids"]:
288 | site_ids = a.split(",")
289 | elif o in ["-d", "--duration"]:
290 | duration = a
291 | elif o in ["-r", "--rogue_types"]:
292 | rogue_types = a.split(",")
293 | elif o in ["-f", "--out_file"]:
294 | csv_file=a
295 | elif o in ["-e", "--env"]:
296 | env_file=a
297 | elif o in ["-l", "--log_file"]:
298 | log_file = a
299 |
300 | else:
301 | assert False, "unhandled option"
302 |
303 | #### LOGS ####
304 | logging.basicConfig(filename=log_file, filemode='w')
305 | LOGGER.setLevel(logging.DEBUG)
306 | check_mistapi_version()
307 | ### MIST SESSION ###
308 | mist_session = mistapi.APISession(env_file=env_file)
309 | mist_session.login()
310 | if not site_ids:
311 | site_ids = mistapi.cli.select_site(mist_session, allow_many=True)
312 | ### START ###
313 | print(" Process Started ".center(80, '-'))
314 | data = _process_sites(mist_session, site_ids)
315 |
316 |
317 | print(" Process Done ".center(80, '-'))
318 | mistapi.cli.pretty_print(data)
319 | _save_as_csv(data, duration)
320 |
321 |
322 |
--------------------------------------------------------------------------------
/scripts/reports/report_app_usage.py:
--------------------------------------------------------------------------------
1 | '''
2 | -------------------------------------------------------------------------------
3 |
4 | Written by Thomas Munzer (tmunzer@juniper.net)
5 | Github repository: https://github.com/tmunzer/Mist_library/
6 |
7 | This script is licensed under the MIT License.
8 |
9 | -------------------------------------------------------------------------------
10 | Python script to generate a report of the application usage on a specific site
11 |
12 | -------
13 | Requirements:
14 | mistapi: https://pypi.org/project/mistapi/
15 |
16 | -------
17 | Usage:
18 | This script can be run as is (without parameters), or with the options below.
19 | If no options are defined, or if options are missing, the missing options will
20 | be asked by the script or the default values will be used.
21 |
22 | It is recommended to use an environment file to store the required information
23 | to request the Mist Cloud (see https://pypi.org/project/mistapi/ for more
24 | information about the available parameters).
25 |
26 | -------
27 | Options:
28 | -h, --help display this help
29 | -s, --site_id= required for Site reports. Set the site_id
30 | -d, --duration= Hours to report
31 | default is 96
32 | -l, --log_file= define the filepath/filename where to write the logs
33 | default is "./script.log"
34 | -f, --out_file= define the filepath/filename where to save the data
35 | default is "./report_app_usage.csv"
36 | -e, --env= define the env file to use (see mistapi env file documentation
37 | here: https://pypi.org/project/mistapi/)
38 | default is "~/.mist_env"
39 |
40 | -------
41 | Examples:
42 | python3 ./report_app_usage.py
43 | python3 ./report_app_usage.py --site_id=203d3d02-xxxx-xxxx-xxxx-76896a3330f4 --duration=48
44 |
45 | '''
46 |
47 | #### IMPORTS ####
48 | import logging
49 | import csv
50 | import datetime
51 | import sys
52 | import getopt
53 |
54 | MISTAPI_MIN_VERSION = "0.44.1"
55 |
56 | try:
57 | import mistapi
58 | from mistapi.__logger import console
59 | except:
60 | print("""
61 | Critical:
62 | \"mistapi\" package is missing. Please use the pip command to install it.
63 |
64 | # Linux/macOS
65 | python3 -m pip install mistapi
66 |
67 | # Windows
68 | py -m pip install mistapi
69 | """)
70 | sys.exit(2)
71 |
72 |
73 | #### PARAMETERS #####
74 |
75 | log_file = "./script.log"
76 | hours_to_report = 96
77 | csv_delimiter = ","
78 | csv_file = "report_app_usage.csv"
79 | env_file = "~/.mist_env"
80 |
81 | #### LOGS ####
82 | LOGGER = logging.getLogger(__name__)
83 | out=sys.stdout
84 |
85 | ###############################################################################
86 | ### PROGRESS BAR
87 | def _progress_bar_update(count:int, total:int, size:int):
88 | if total == 0:
89 | return
90 | elif count > total:
91 | count = total
92 | x = int(size*count/total)
93 | out.write(f"Progress: ".ljust(10))
94 | out.write(f"[{'█'*x}{'.'*(size-x)}]")
95 | out.write(f"{count}/{total}\r".rjust(79 - size - 10))
96 | out.flush()
97 |
98 | def _progress_bar_end(total:int, size:int):
99 | if total == 0:
100 | return
101 | _progress_bar_update(total, total, size)
102 | out.write("\n")
103 | out.flush()
104 |
105 | ###############################################################################
106 | ### FUNCTIONS
107 |
108 | def _get_clients_list(mist_session, site_id):
109 | clients = mistapi.api.v1.sites.stats.listSiteWirelessClientsStats(mist_session, site_id).data
110 | return clients
111 |
112 |
113 | def _get_site_name(mist_session, site_id):
114 | site_info = mistapi.api.v1.sites.sites.getSiteInfo(mist_session, site_id).data
115 | return site_info["name"]
116 |
117 | def _convert_numbers(size):
118 | # 2**10 = 1024
119 | power = 2**10
120 | n = 0
121 | power_labels = {0 : '', 1: 'K', 2: 'M', 3: 'G', 4: 'T'}
122 | while size > power:
123 | size /= power
124 | n += 1
125 | size = round(size, 2)
126 | return "%s %sB" %(size, power_labels[n])
127 |
128 | def _generate_site_report(mist_session, site_name, site_id, start, stop, interval):
129 | app_usage = []
130 | clients = _get_clients_list(mist_session, site_id)
131 | console.info("%s clients to process... Please wait..." %(len(clients)))
132 | i=0
133 | _progress_bar_update(0, len(clients), 50)
134 | for client in clients:
135 | client_mac = client["mac"]
136 | if "username" in client: client_username = client["username"]
137 | else: client_username = ""
138 | if "hostname" in client: client_hostname = client["hostname"]
139 | else: client_hostname = ""
140 | client_app = mistapi.api.v1.sites.insights.getSiteInsightMetricsForClient(mist_session, site_id, client_mac=client_mac, start=start, end=stop, interval=interval, metric="top-app-by-bytes").data
141 | tmp={"site name": site_name, "site id": site_id, "client mac": client_mac, "username": client_username, "hostname": client_hostname}
142 | for app in client_app.get("results", []):
143 | usage = _convert_numbers(app["total_bytes"])
144 | tmp[app["app"]] = usage
145 | i+=1
146 | _progress_bar_update(i, len(clients), 50)
147 | app_usage.append(tmp)
148 | _progress_bar_end(len(clients), 50)
149 | return app_usage
150 |
151 | ### SAVE REPORT
152 | def _save_report(app_usage):
153 | console.info("Saving to file %s..." %(csv_file))
154 | fields = []
155 | for row in app_usage:
156 | for key in row:
157 | if not key in fields: fields.append(key)
158 |
159 | with open(csv_file, 'w') as output_file:
160 | dict_writer = csv.DictWriter(output_file, restval="-", fieldnames=fields, delimiter=csv_delimiter)
161 | dict_writer.writeheader()
162 | dict_writer.writerows(app_usage)
163 | console.info("File %s saved!" %(csv_file))
164 |
165 | ### GENERATE REPORT
166 | def generate_report(mist_session, site_ids, time):
167 | app_usage = []
168 | if type(site_ids) == str:
169 | site_ids = [ site_ids]
170 | for site_id in site_ids:
171 | site_name = _get_site_name(mist_session, site_id)
172 | console.info("Processing site %s (id %s)" %(site_name, site_id))
173 | app_usage += _generate_site_report(mist_session, site_name, site_id, time["start"], time["stop"], time["interval"])
174 | mistapi.cli.pretty_print(app_usage)
175 | _save_report(app_usage)
176 |
177 | def _ask_period(hours):
178 | now = datetime.datetime.now()
179 | start = round((datetime.datetime.now() - datetime.timedelta(hours=hours)).timestamp(), 0)
180 | stop = round(now.timestamp(), 0)
181 | interval = 3600
182 | return {"start": start, "stop": stop, "interval": interval}
183 |
184 | ###############################################################################
185 | ### USAGE
186 | def usage():
187 | print("""
188 | -------------------------------------------------------------------------------
189 |
190 | Written by Thomas Munzer (tmunzer@juniper.net)
191 | Github repository: https://github.com/tmunzer/Mist_library/
192 |
193 | This script is licensed under the MIT License.
194 |
195 | -------------------------------------------------------------------------------
196 | Python script to generate a report of the application usage on a specific site
197 |
198 | Requirements:
199 | mistapi: https://pypi.org/project/mistapi/
200 |
201 | Usage:
202 | This script can be run as is (without parameters), or with the options below.
203 | If no options are defined, or if options are missing, the missing options will
204 | be asked by the script or the default values will be used.
205 |
206 | It is recommended to use an environment file to store the required information
207 | to request the Mist Cloud (see https://pypi.org/project/mistapi/ for more
208 | information about the available parameters).
209 |
210 | Options:
211 | -h, --help display this help
212 | -s, --site_id= required for Site reports. Set the site_id
213 | -d, --duration= Hours to report
214 | default is 96
215 | -l, --log_file= define the filepath/filename where to write the logs
216 | default is "./script.log"
217 | -f, --out_file= define the filepath/filename where to save the data
218 | default is "./report_app_usage.csv"
219 | -e, --env= define the env file to use (see mistapi env file documentation
220 | here: https://pypi.org/project/mistapi/)
221 | default is "~/.mist_env"
222 |
223 | Examples:
224 | python3 ./report_app_usage.py
225 | python3 ./report_app_usage.py --site_id=203d3d02-xxxx-xxxx-xxxx-76896a3330f4 --duration=48
226 |
227 | """)
228 | sys.exit(0)
229 |
230 | def check_mistapi_version():
231 | """Check if the installed mistapi version meets the minimum requirement."""
232 |
233 | current_version = mistapi.__version__.split(".")
234 | required_version = MISTAPI_MIN_VERSION.split(".")
235 |
236 | try:
237 | for i, req in enumerate(required_version):
238 | if current_version[int(i)] > req:
239 | break
240 | if current_version[int(i)] < req:
241 | raise ImportError(
242 | f'"mistapi" package version {MISTAPI_MIN_VERSION} is required '
243 | f"but version {mistapi.__version__} is installed."
244 | )
245 | except ImportError as e:
246 | LOGGER.critical(str(e))
247 | LOGGER.critical("Please use the pip command to update it.")
248 | LOGGER.critical("")
249 | LOGGER.critical(" # Linux/macOS")
250 | LOGGER.critical(" python3 -m pip install --upgrade mistapi")
251 | LOGGER.critical("")
252 | LOGGER.critical(" # Windows")
253 | LOGGER.critical(" py -m pip install --upgrade mistapi")
254 | print(
255 | f"""
256 | Critical:\r\n
257 | {e}\r\n
258 | Please use the pip command to update it.
259 | # Linux/macOS
260 | python3 -m pip install --upgrade mistapi
261 | # Windows
262 | py -m pip install --upgrade mistapi
263 | """
264 | )
265 | sys.exit(2)
266 | finally:
267 | LOGGER.info(
268 | '"mistapi" package version %s is required, '
269 | "you are currently using version %s.",
270 | MISTAPI_MIN_VERSION,
271 | mistapi.__version__
272 | )
273 |
274 |
275 | ###############################################################################
276 | ### ENTRY POINT
277 | if __name__ == "__main__":
278 | try:
279 | opts, args = getopt.getopt(sys.argv[1:], "hs:d:f:e:l:", ["help", "site_id=", "duration=", "out_file=", "env=", "log_file="])
280 | except getopt.GetoptError as err:
281 | console.error(err)
282 | usage()
283 |
284 | site_id=None
285 | query_params={}
286 | for o, a in opts:
287 | if o in ["-h", "--help"]:
288 | usage()
289 | elif o in ["-s", "--site_id"]:
290 | site_id = a
291 | elif o in ["-d", "--duration"]:
292 | try:
293 | hours_to_report = int(a)
294 | except:
295 | console.error(f"Duration value \"{a}\" is not valid")
296 | usage()
297 | elif o in ["-f", "--out_file"]:
298 | csv_file=a
299 | elif o in ["-e", "--env"]:
300 | env_file=a
301 | elif o in ["-l", "--log_file"]:
302 | log_file = a
303 |
304 | else:
305 | assert False, "unhandled option"
306 |
307 | #### LOGS ####
308 | logging.basicConfig(filename=log_file, filemode='w')
309 | LOGGER.setLevel(logging.DEBUG)
310 | check_mistapi_version()
311 | ### MIST SESSION ###
312 | mist_session = mistapi.APISession(env_file=env_file)
313 | mist_session.login()
314 | if not site_id:
315 | site_id = mistapi.cli.select_site(mist_session, allow_many=True)
316 | ### START ###
317 | time = _ask_period(hours_to_report)
318 | generate_report(mist_session, site_id, time)
319 |
--------------------------------------------------------------------------------
/scripts/devices/switches/toggle_poe.py:
--------------------------------------------------------------------------------
1 | '''
2 | -------------------------------------------------------------------------------
3 |
4 | Written by Thomas Munzer (tmunzer@juniper.net)
5 | Github repository: https://github.com/tmunzer/Mist_library/
6 |
7 | This script is licensed under the MIT License.
8 |
9 | -------------------------------------------------------------------------------
10 | Python script to enable/disable/toggle PoE for a specified Port Profile in a
11 | Switch Template.
12 |
13 | This script will not change/create/delete/touch any existing objects. It will
14 | just retrieve every single object from the organization.
15 |
16 | -------
17 | Requirements:
18 | mistapi: https://pypi.org/project/mistapi/
19 |
20 | -------
21 | Usage:
22 | This script can be run as is (without parameters), or with the options below.
23 | If no options are defined, or if options are missing, the missing options will
24 | be asked by the script or the default values will be used.
25 |
26 | It is recommended to use an environment file to store the required information
27 | to request the Mist Cloud (see https://pypi.org/project/mistapi/ for more
28 | information about the available parameters).
29 |
30 | -------
31 | Options:
32 | -o, --org_id= Set the org_id
33 | -t, --tmpl_id= Set the Switch template_id
34 | -p, --profile= Set the Port Profile name
35 | -a, --action= Set the action to execupte. It must be one of the following:
36 | - status: Retrieve the PoE status
37 | - on: Enable PoE on the port profile
38 | - off: Disable PoE on the port profile
39 | - toggle: toogle PoE on the port profile (i.e. turn it off if
40 | currently enable, turn it on if currently enabled)
41 | -e, --env= define the env file to use (see mistapi env file documentation
42 | here: https://pypi.org/project/mistapi/)
43 | default is "~/.mist_env"
44 |
45 | -------
46 | Examples:
47 | python3 ./toggle_poe.py
48 | python3 ./toggle_poe.py -o 203d3d02-xxxx-xxxx-xxxx-76896a3330f4 -t 2c57044e-xxxx-xxxx-xxxx-69374b32a070 -p ap -a toggle
49 |
50 | '''
51 |
52 | #### IMPORTS ####
53 | import logging
54 | import sys
55 | import getopt
56 |
57 | MISTAPI_MIN_VERSION = "0.55.5"
58 |
59 | try:
60 | import mistapi
61 | from mistapi.__logger import console
62 | except:
63 | print("""
64 | Critical:
65 | \"mistapi\" package is missing. Please use the pip command to install it.
66 |
67 | # Linux/macOS
68 | python3 -m pip install mistapi
69 |
70 | # Windows
71 | py -m pip install mistapi
72 | """)
73 | sys.exit(2)
74 |
75 |
76 | #####################################################################
77 | #### PARAMETERS #####
78 | log_file = "./script.log"
79 | env_file = "~/.mist_env"
80 |
81 | #####################################################################
82 | #### LOGS ####
83 | logger = logging.getLogger(__name__)
84 |
85 | #####################################################################
86 | #### FUNCTIONS ####
87 | def _get_port_usages(apisession:mistapi.APISession, org_id:str, tmpl_id:str):
88 | print("Retrieving data from Mist ".ljust(79, "."), end="", flush=True)
89 | try:
90 | res = mistapi.api.v1.orgs.networktemplates.getOrgNetworkTemplate(apisession, org_id, tmpl_id)
91 | disabled = res.data.get("port_usages", {})
92 | print("\033[92m\u2714\033[0m")
93 | return disabled
94 | except:
95 | print('\033[31m\u2716\033[0m')
96 | sys.exit(4)
97 |
98 | def _status(apisession:mistapi.APISession, org_id:str, tmpl_id:str, profile:str):
99 | port_usages = _get_port_usages(apisession, org_id, tmpl_id)
100 | print("Extracting profile data ".ljust(79, "."), end="", flush=True)
101 | profile = port_usages.get(profile, {})
102 | if not profile:
103 | print('\033[31m\u2716\033[0m')
104 | print(f"Profile {profile} not found")
105 | else:
106 | print("\033[92m\u2714\033[0m")
107 | return profile.get("poe_disabled", False)
108 |
109 | def _display_status(apisession:mistapi.APISession, org_id:str, tmpl_id:str, profile:str):
110 | disabled = _status(apisession, org_id, tmpl_id, profile)
111 | print()
112 | print(" Result ".center(80, "-"))
113 | print()
114 | if disabled == True:
115 | print(f"PoE Current status: DISABLED")
116 | else:
117 | print(f"PoE Current status: ENABLED")
118 |
119 | def _update(apisession:mistapi.APISession, org_id:str, tmpl_id:str, port_usages:dict):
120 | try:
121 | print("Updating PoE Status ".ljust(79, "."), end="", flush=True)
122 | res = mistapi.api.v1.orgs.networktemplates.updateOrgNetworkTemplate(apisession, org_id, tmpl_id, {"port_usages": port_usages})
123 | if res.status_code == 200:
124 | print("\033[92m\u2714\033[0m")
125 | else:
126 | raise Exception
127 | except:
128 | print('\033[31m\u2716\033[0m')
129 | sys.exit(5)
130 |
131 | def _change(apisession:mistapi.APISession, org_id:str, tmpl_id:str, profile:str, disabled:bool):
132 | port_usages = _get_port_usages(apisession, org_id, tmpl_id)
133 | print("Extracting profile data ".ljust(79, "."), end="", flush=True)
134 | if not profile in port_usages:
135 | print('\033[31m\u2716\033[0m')
136 | print(f"Profile {profile} not found")
137 | else:
138 | print("\033[92m\u2714\033[0m")
139 | port_usages[profile]["poe_disabled"] = disabled
140 | _update(apisession, org_id, tmpl_id, port_usages)
141 |
142 | def _toggle(apisession:mistapi.APISession, org_id:str, tmpl_id:str, profile:str):
143 | port_usages = _get_port_usages(apisession, org_id, tmpl_id)
144 | print("Extracting profile data ".ljust(79, "."), end="", flush=True)
145 | if not profile in port_usages:
146 | print('\033[31m\u2716\033[0m')
147 | print(f"Profile {profile} not found")
148 | else:
149 | print("\033[92m\u2714\033[0m")
150 | port_usages[profile]["poe_disabled"] = not port_usages[profile]["poe_disabled"]
151 | _update(apisession, org_id, tmpl_id, port_usages)
152 |
153 |
154 | ####################
155 | ## MENU
156 | def _show_menu(header:str, menu:list) -> str:
157 | print()
158 | print("".center(80, "-"))
159 | resp=None
160 | menu = sorted(menu, key=str.casefold)
161 | while True:
162 | print(f"{header}")
163 | i=0
164 | for entry in menu:
165 | print(f"{i}) {entry}")
166 | i+=1
167 | resp = input(f"Please select an option (0-{i-1}, q to quit): ")
168 | if resp.lower() == "q":
169 | sys.exit(0)
170 | else:
171 | try:
172 | resp=int(resp)
173 | except:
174 | console.error("Please enter a number\r\n ")
175 | if resp < 0 or resp >= i:
176 | console.error(f"Please enter a number between 0 and {i -1}.")
177 | else:
178 | return menu[resp]
179 |
180 | def _check_parameters(apisession:mistapi.APISession, org_id:str, tmpl_id:str, profile:str, action:str):
181 | if not org_id:
182 | org_id = mistapi.cli.select_org(apisession)[0]
183 |
184 | if not tmpl_id:
185 | response = mistapi.api.v1.orgs.networktemplates.listOrgNetworkTemplates(apisession, org_id)
186 | templates = mistapi.get_all(apisession, response)
187 | template_names = {}
188 | template_menu = []
189 | for template in templates:
190 | template_menu.append(template.get("name"))
191 | template_names[template.get("name")] = template["id"]
192 | tmpl_name = _show_menu("Please select a Switch Template", template_menu)
193 | tmpl_id = template_names[tmpl_name]
194 |
195 | if not profile:
196 | template_settings = mistapi.api.v1.orgs.networktemplates.getOrgNetworkTemplate(apisession, org_id, tmpl_id).data
197 | port_usages = template_settings.get("port_usages")
198 | if not port_usages:
199 | console.error("There is no profile to update in this switch template")
200 | sys.exit(2)
201 | else:
202 | profile_names = []
203 | for port_usage in port_usages:
204 | profile_names.append(port_usage)
205 | profile = _show_menu("Please select a Profile", profile_names)
206 |
207 | if not action:
208 | action = _show_menu("Please select an Action", ["status", "on", "off", "toggle"])
209 | if action != "status":
210 | action_str = action
211 | if action != "toggle":
212 | action_str = f"turn {action}"
213 | while True:
214 | print()
215 | confirm = input(f"Do you confirm you want to {action_str} PoE on port profile {profile} (y/n)?")
216 | if confirm.lower() == "y":
217 | break
218 | elif confirm.lower() == "n":
219 | sys.exit(0)
220 | return org_id, tmpl_id, profile, action
221 |
222 | def start(apisession:mistapi.APISession, org_id:str, tmpl_id:str, profile:str, action:str):
223 | org_id, tmpl_id, profile, action = _check_parameters(apisession, org_id, tmpl_id, profile, action)
224 | print(" Processing ".center(80, "-"))
225 | print()
226 | if action == "status":
227 | _display_status(apisession, org_id, tmpl_id, profile)
228 | if action == "off":
229 | _change(apisession, org_id, tmpl_id, profile, True)
230 | _display_status(apisession, org_id, tmpl_id, profile)
231 | if action == "on":
232 | _change(apisession, org_id, tmpl_id, profile, False)
233 | _display_status(apisession, org_id, tmpl_id, profile)
234 | if action == "toggle":
235 | _toggle(apisession, org_id, tmpl_id, profile)
236 | _display_status(apisession, org_id, tmpl_id, profile)
237 |
238 | #####################################################################
239 | ##### USAGE ####
240 | def usage():
241 | print('''
242 | -------------------------------------------------------------------------------
243 |
244 | Written by Thomas Munzer (tmunzer@juniper.net)
245 | Github repository: https://github.com/tmunzer/Mist_library/
246 |
247 | This script is licensed under the MIT License.
248 |
249 | -------------------------------------------------------------------------------
250 | Python script to enable/disable/toggle PoE for a specified Port Profile in a
251 | Switch Template.
252 |
253 | This script will not change/create/delete/touch any existing objects. It will
254 | just retrieve every single object from the organization.
255 |
256 | -------
257 | Requirements:
258 | mistapi: https://pypi.org/project/mistapi/
259 |
260 | -------
261 | Usage:
262 | This script can be run as is (without parameters), or with the options below.
263 | If no options are defined, or if options are missing, the missing options will
264 | be asked by the script or the default values will be used.
265 |
266 | It is recommended to use an environment file to store the required information
267 | to request the Mist Cloud (see https://pypi.org/project/mistapi/ for more
268 | information about the available parameters).
269 |
270 | -------
271 | Options:
272 | -o, --org_id= Set the org_id
273 | -t, --tmpl_id= Set the Switch template_id
274 | -p, --profile= Set the Port Profile name
275 | -a, --action= Set the action to execupte. It must be one of the following:
276 | - status: Retrieve the PoE status
277 | - on: Enable PoE on the port profile
278 | - off: Disable PoE on the port profile
279 | - toggle: toogle PoE on the port profile (i.e. turn it off if
280 | currently enable, turn it on if currently enabled)
281 | -e, --env= define the env file to use (see mistapi env file documentation
282 | here: https://pypi.org/project/mistapi/)
283 | default is "~/.mist_env"
284 |
285 | -------
286 | Examples:
287 | python3 ./toggle_poe.py
288 | python3 ./toggle_poe.py -o 203d3d02-xxxx-xxxx-xxxx-76896a3330f4 -t 2c57044e-xxxx-xxxx-xxxx-69374b32a070 -p ap -a toggle
289 | '''
290 | )
291 | sys.exit(0)
292 |
293 | #####################################################################
294 | ##### ENTRY POINT ####
295 | if __name__ == "__main__":
296 | try:
297 | opts, args = getopt.getopt(sys.argv[1:], "e:o:t:p:a:", ["env=", "org-id=", "tmpl_id=", "profile=", "action="])
298 | except getopt.GetoptError as err:
299 | print(err)
300 | usage()
301 | sys.exit(2)
302 |
303 | org_id=None
304 | tmpl_id=None
305 | profile=None
306 | action=None
307 | for o, a in opts:
308 | if o in ["-h", "--help"]:
309 | usage()
310 | sys.exit()
311 | elif o in ["-e", "--env"]:
312 | env_file = a
313 | elif o in ["-o", "--org_id"]:
314 | org_id=a
315 | elif o in ["-t", "--tmpl_id"]:
316 | tmpl_id=a
317 | elif o in ["-p", "--profile"]:
318 | profile=a
319 | elif o in ["-a", "--action"]:
320 | if a not in ["status", "on", "off", "toggle"]:
321 | console.error(f"Unknown action \"{o}\"")
322 | usage()
323 | sys.exit(0)
324 | else:
325 | action=a
326 | else:
327 | assert False, "unhandled option"
328 |
329 | #### LOGS ####
330 | logging.basicConfig(filename=log_file, filemode='w')
331 | logger.setLevel(logging.DEBUG)
332 | ### START ###
333 | apisession = mistapi.APISession(env_file=env_file)
334 | apisession.login()
335 | start(apisession, org_id, tmpl_id, profile, action)
336 |
337 |
338 |
--------------------------------------------------------------------------------
/scripts/configuration/config_auto_site_assignment.py:
--------------------------------------------------------------------------------
1 | '''
2 | -------------------------------------------------------------------------------
3 |
4 | Written by Thomas Munzer (tmunzer@juniper.net)
5 | Github repository: https://github.com/tmunzer/Mist_library/
6 |
7 | This script is licensed under the MIT License.
8 |
9 | -------------------------------------------------------------------------------
10 | Python script to update the org auto assignment rules. The script is displaying
11 | options enable/disable the auto assignment, and to build the rules before
12 | updating the org settings
13 |
14 | -------
15 | Requirements:
16 | mistapi: https://pypi.org/project/mistapi/
17 |
18 | -------
19 | Usage:
20 | This script can be run as is (without parameters), or with the options below.
21 | If no options are defined, or if options are missing, the missing options will
22 | be asked by the script or the default values will be used.
23 |
24 | It is recommended to use an environment file to store the required information
25 | to request the Mist Cloud (see https://pypi.org/project/mistapi/ for more
26 | information about the available parameters).
27 |
28 | -------
29 | Options:
30 | -h, --help display this help
31 | -o, --org_id= Set the org_id (only one of the org_id or site_id can be defined)
32 | --enable Enable the auto assignment (additional configuration will be asked
33 | by the script). Only one action enable/disable is allowed
34 | --disable Disable the auto assignment Only one action enable/disable is
35 | allowed
36 | -l, --log_file= define the filepath/filename where to write the logs
37 | default is "./script.log"
38 | -e, --env= define the env file to use (see mistapi env file documentation
39 | here: https://pypi.org/project/mistapi/)
40 | default is "~/.mist_env"
41 |
42 | -------
43 | Examples:
44 | python3 ./config_auto_site_assignment.py
45 | python3 ./config_auto_site_assignment.py --org_id=203d3d02-xxxx-xxxx-xxxx-76896a3330f4 --disable
46 |
47 | '''
48 |
49 | #### IMPORTS #####
50 | import sys
51 | import logging
52 | import getopt
53 |
54 | MISTAPI_MIN_VERSION = "0.44.1"
55 |
56 | try:
57 | import mistapi
58 | from mistapi.__logger import console
59 | except:
60 | print("""
61 | Critical:
62 | \"mistapi\" package is missing. Please use the pip command to install it.
63 |
64 | # Linux/macOS
65 | python3 -m pip install mistapi
66 |
67 | # Windows
68 | py -m pip install mistapi
69 | """)
70 | sys.exit(2)
71 |
72 |
73 | #### LOGS ####
74 | LOGGER = logging.getLogger(__name__)
75 |
76 | #### PARAMETERS #####
77 | auto_site_assignment = {
78 | "enable": True,
79 | "rules": []
80 | }
81 |
82 | LOG_FILE = "./script.log"
83 | ENV_FILE = "~/.mist_env"
84 | #### GLOBAL VARIABLES ####
85 | AUTO_ASSIGNMENT_RULES = ["name", "subnet", "lldp_system_name", "dns_suffix", "model" ]
86 |
87 | #### FUNCTIONS ####
88 | def create_rule(apisession, org_id):
89 | rules = []
90 | while True:
91 | rule_conf = {}
92 | rule_conf = select_rule_type(apisession, org_id)
93 | mistapi.cli.pretty_print(rule_conf)
94 | resp = input("Is it correct (Y/n)?")
95 | if resp.lower() == 'y' or resp == "":
96 | rules.append(rule_conf)
97 | while True:
98 | resp = input("Do you want to add a new rule (y/N)?")
99 | if resp.lower() == 'n' or resp == "":
100 | return rules
101 | elif resp.lower() == 'y':
102 | break
103 |
104 | def select_rule_type(apisession, org_id):
105 | rule_conf = {}
106 | while True:
107 | print("Type of auto assignment rule:")
108 | i = -1
109 | for rule in AUTO_ASSIGNMENT_RULES:
110 | i+=1
111 | print(f"{i}) {rule}")
112 | resp = input(f"Please select the type of rule (0-{i})")
113 | try:
114 | resp_num = int(resp)
115 | except:
116 | print(f"Error. The value {resp} is not valid")
117 | if resp_num >= 0 and resp_num <= i:
118 | rule_conf['src'] = AUTO_ASSIGNMENT_RULES[resp_num]
119 | return configure_rule(apisession, org_id, rule_conf)
120 | else:
121 | print(f"Error: {resp_num} is not authorized")
122 |
123 |
124 | def configure_rule(apisession, org_id, rule_conf):
125 | if rule_conf['src'] == "name":
126 | # // use device name (via Installer APIs)
127 | #{
128 | # "src": "name",
129 | # "expression": "[0:3]", // "abcdef" -> "abc"
130 | # "split(.)[1]", // "a.b.c" -> "b"
131 | # "split(-)[1][0:3], // "a1234-b5678-c90" -> "b56"
132 | # "prefix": "XX-",
133 | # "suffix": "-YY"
134 | # },
135 | print("Expression to extract the site name from the LLDP system name")
136 | print("Example: \"[0:3]\", // \"abcdef\" -> \"abc\"")
137 | print(" \"split(.)[1]\", // \"a.b.c\" -> \"b\"")
138 | print(" \"split(-)[1][0:3]\", // \"a1234-b5678-c90\" -> \"b56\"")
139 | rule_conf['expression'] = input("Expression: ")
140 | rule_conf['prefix'] = input("Prefix (XX-): " )
141 | rule_conf['suffix'] = input("Suffix (-XX): ")
142 | elif rule_conf['src'] == "subnet":
143 | # // use subnet
144 | # {
145 | # "src": "subnet",
146 | # "subnet": "10.1.2.0/18",
147 | # "value": "s1351"
148 | # },
149 | rule_conf['subnet'] = input("Please enter the subnet value (ex: 10.1.2.0/18): ")
150 | site_id = mistapi.cli.select_site(apisession, org_id=org_id)[0]
151 | rule_conf['value'] = mistapi.api.v1.sites.stats.getSiteStats(apisession, site_id).data['name']
152 | elif rule_conf['src'] == "lldp_system_name":
153 | # // use LLDP System Name
154 | # {
155 | # "src": "lldp_system_name",
156 | # "expression": "..." // same as above
157 | # },
158 | print("Expression to extract the site name from the LLDP system name")
159 | print("Example: \"[0:3]\", // \"abcdef\" -> \"abc\"")
160 | print(" \"split(.)[1]\", // \"a.b.c\" -> \"b\"")
161 | print(" \"split(-)[1][0:3]\", // \"a1234-b5678-c90\" -> \"b56\"")
162 | rule_conf['expression'] = input("Expression: ")
163 | elif rule_conf['src'] == "dns_suffix":
164 | # // use DNS Suffix
165 | # {
166 | # "src": "dns_suffix",
167 | # "expression": "..." // same as above
168 | # },
169 | print("Expression to extract the site name from the DNS suffix name")
170 | print("Example: \"[0:3]\", // \"abcdef\" -> \"abc\"")
171 | print(" \"split(.)[1]\", // \"a.b.c\" -> \"b\"")
172 | print(" \"split(-)[1][0:3]\", // \"a1234-b5678-c90\" -> \"b56\"")
173 | rule_conf['expression'] = input("Expression: ")
174 | elif rule_conf['src'] == "model":
175 | # {
176 | # "src": "model",
177 | # "model": "AP41",
178 | # "value": "s1351"
179 | # }
180 | rule_conf['model'] = input("Please enter the model of AP: ")
181 | site_id = mistapi.cli.select_site(apisession, org_id=org_id)[0]
182 | rule_conf['value'] = mistapi.api.v1.sites.stats.getSiteStats(apisession, site_id).data['name']
183 | return rule_conf
184 |
185 |
186 | ###############################################################################
187 | ### USAGE
188 | def usage():
189 | print('''
190 | -------------------------------------------------------------------------------
191 |
192 | Written by Thomas Munzer (tmunzer@juniper.net)
193 | Github repository: https://github.com/tmunzer/Mist_library/
194 |
195 | This script is licensed under the MIT License.
196 |
197 | -------------------------------------------------------------------------------
198 | Python script to update the org auto assignment rules. The script is displaying
199 | options enable/disable the auto assignment, and to build the rules before
200 | updating the org settings
201 |
202 | -------
203 | Requirements:
204 | mistapi: https://pypi.org/project/mistapi/
205 |
206 | -------
207 | Usage:
208 | This script can be run as is (without parameters), or with the options below.
209 | If no options are defined, or if options are missing, the missing options will
210 | be asked by the script or the default values will be used.
211 |
212 | It is recommended to use an environment file to store the required information
213 | to request the Mist Cloud (see https://pypi.org/project/mistapi/ for more
214 | information about the available parameters).
215 |
216 | -------
217 | Options:
218 | -h, --help display this help
219 | -o, --org_id= Set the org_id (only one of the org_id or site_id can be defined)
220 | --enable Enable the auto assignment (additional configuration will be asked
221 | by the script). Only one action enable/disable is allowed
222 | --disable Disable the auto assignment Only one action enable/disable is
223 | allowed
224 | -l, --log_file= define the filepath/filename where to write the logs
225 | default is "./script.log"
226 | -e, --env= define the env file to use (see mistapi env file documentation
227 | here: https://pypi.org/project/mistapi/)
228 | default is "~/.mist_env"
229 |
230 | -------
231 | Examples:
232 | python3 ./config_auto_site_assignment.py
233 | python3 ./config_auto_site_assignment.py --org_id=203d3d02-xxxx-xxxx-xxxx-76896a3330f4 --disable
234 |
235 | ''')
236 | sys.exit(0)
237 |
238 | def check_mistapi_version():
239 | """Check if the installed mistapi version meets the minimum requirement."""
240 |
241 | current_version = mistapi.__version__.split(".")
242 | required_version = MISTAPI_MIN_VERSION.split(".")
243 |
244 | try:
245 | for i, req in enumerate(required_version):
246 | if current_version[int(i)] > req:
247 | break
248 | if current_version[int(i)] < req:
249 | raise ImportError(
250 | f'"mistapi" package version {MISTAPI_MIN_VERSION} is required '
251 | f"but version {mistapi.__version__} is installed."
252 | )
253 | except ImportError as e:
254 | LOGGER.critical(str(e))
255 | LOGGER.critical("Please use the pip command to update it.")
256 | LOGGER.critical("")
257 | LOGGER.critical(" # Linux/macOS")
258 | LOGGER.critical(" python3 -m pip install --upgrade mistapi")
259 | LOGGER.critical("")
260 | LOGGER.critical(" # Windows")
261 | LOGGER.critical(" py -m pip install --upgrade mistapi")
262 | print(
263 | f"""
264 | Critical:\r\n
265 | {e}\r\n
266 | Please use the pip command to update it.
267 | # Linux/macOS
268 | python3 -m pip install --upgrade mistapi
269 | # Windows
270 | py -m pip install --upgrade mistapi
271 | """
272 | )
273 | sys.exit(2)
274 | finally:
275 | LOGGER.info(
276 | '"mistapi" package version %s is required, '
277 | "you are currently using version %s.",
278 | MISTAPI_MIN_VERSION,
279 | mistapi.__version__
280 | )
281 |
282 |
283 | ###############################################################################
284 | ### ENTRY POINT
285 | if __name__ == "__main__":
286 | try:
287 | opts, args = getopt.getopt(sys.argv[1:], "ho:e:l:", ["help", "org_id=", "env=", "log_file=", "enable", "disable"])
288 | except getopt.GetoptError as err:
289 | console.error(err)
290 | usage()
291 |
292 | ORG_ID=None
293 | ACTION=None
294 | for o, a in opts:
295 | if o in ["-h", "--help"]:
296 | usage()
297 | elif o in ["-o", "--org_id"]:
298 | ORG_ID = a
299 | elif o in ["-e", "--env"]:
300 | env_file=a
301 | elif o in ["-l", "--log_file"]:
302 | LOG_FILE = a
303 | elif o in ["-a", "--enable"]:
304 | if ACTION:
305 | console.error("Only one action \"enable\" or \"disable\" is allowed")
306 | sys.exit(0)
307 | ACTION ="enable"
308 | auto_site_assignment["enable"] = True
309 | elif o in ["-b", "--disable"]:
310 | if ACTION:
311 | console.error("Only one action \"enable\" or \"disable\" is allowed")
312 | sys.exit(0)
313 | ACTION ="disable"
314 | auto_site_assignment["enable"] = False
315 | else:
316 | assert False, "unhandled option"
317 |
318 | #### LOGS ####
319 | logging.basicConfig(filename=LOG_FILE, filemode='w')
320 | LOGGER.setLevel(logging.DEBUG)
321 | check_mistapi_version()
322 | ### MIST SESSION ###
323 | APISESSION = mistapi.APISession(env_file=ENV_FILE)
324 | APISESSION.login()
325 |
326 | if not ORG_ID:
327 | ORG_ID = mistapi.cli.select_org(APISESSION)
328 |
329 | while not ACTION:
330 | resp = input("Do you want to (E)nable of (D)isable auto site assignment (e/d)?")
331 | if resp.lower() == 'e':
332 | auto_site_assignment["enable"] = True
333 | ACTION = "enable"
334 | elif resp.lower() == 'd':
335 | auto_site_assignment["enable"] = False
336 | ACTION = "disable"
337 | else:
338 | console.error("Only \"e\" and \"d\" are allowed, to Enable or Disable auto site assignment")
339 |
340 | if auto_site_assignment["enable"] == True:
341 | auto_site_assignment["rules"] = create_rule(APISESSION, ORG_ID)
342 |
343 |
344 | print("Configuration to upload".center(80, "-"))
345 | mistapi.cli.pretty_print({"auto_site_assignment": auto_site_assignment})
346 | mistapi.api.v1.orgs.setting.updateOrgSettings(APISESSION, ORG_ID, {"auto_site_assignment": auto_site_assignment})
347 | print("Configuration from Mist".center(80, "-"))
348 | conf_from_mist = mistapi.api.v1.orgs.setting.getOrgSettings(APISESSION, ORG_ID).data["auto_site_assignment"]
349 | mistapi.cli.pretty_print({"auto_site_assignment": conf_from_mist})
350 |
--------------------------------------------------------------------------------
/scripts/devices/aps/configure_ap_mgmt_vlan.py:
--------------------------------------------------------------------------------
1 | '''
2 | -------------------------------------------------------------------------------
3 |
4 | Written by Thomas Munzer (tmunzer@juniper.net)
5 | Github repository: https://github.com/tmunzer/Mist_library/
6 |
7 | This script is licensed under the MIT License.
8 |
9 | -------------------------------------------------------------------------------
10 | Python script reconfigure Management VLAN on all the Mist APs from one or
11 | multiple sites.
12 |
13 | -------
14 | Requirements:
15 | mistapi: https://pypi.org/project/mistapi/
16 |
17 | -------
18 | Usage:
19 | This script can be run as is (without parameters), or with the options below.
20 | If no options are defined, or if options are missing, the missing options will
21 | be asked by the script or the default values will be used.
22 |
23 | It is recommended to use an environment file to store the required information
24 | to request the Mist Cloud (see https://pypi.org/project/mistapi/ for more
25 | information about the available parameters).
26 |
27 | -------
28 | Script Parameters:
29 | -h, --help display this help
30 | -s, --site_ids= list of sites to use, comma separated
31 | -v, --vlan_id= Set the mgmt VLAN ID, 0 for untagged
32 | -l, --log_file= define the filepath/filename where to write the logs
33 | default is "./script.log"
34 | -e, --env= define the env file to use (see mistapi env file
35 | documentation here: https://pypi.org/project/mistapi/)
36 | default is "~/.mist_env"
37 |
38 | -------
39 | Examples:
40 | python3 ./configure_ap_mgmt_vlan.py
41 | python3 ./configure_ap_mgmt_vlan.py -s 203d3d02-xxxx-xxxx-xxxx-76896a3330f4,203d3d02-xxxx-xxxx-xxxx-76896a3330f4 -v 31
42 |
43 | '''
44 |
45 | #####################################################################
46 | #### IMPORTS ####
47 | import logging
48 | import sys
49 | import getopt
50 |
51 | MISTAPI_MIN_VERSION = "0.44.1"
52 |
53 | try:
54 | import mistapi
55 | from mistapi.__logger import console
56 | except:
57 | print("""
58 | Critical:
59 | \"mistapi\" package is missing. Please use the pip command to install it.
60 |
61 | # Linux/macOS
62 | python3 -m pip install mistapi
63 |
64 | # Windows
65 | py -m pip install mistapi
66 | """)
67 | sys.exit(2)
68 |
69 |
70 | #####################################################################
71 | #### PARAMETERS #####
72 | log_file = "./script.log"
73 | env_file = "~/.mist_env"
74 |
75 | #####################################################################
76 | #### LOGS ####
77 | LOGGER = logging.getLogger(__name__)
78 |
79 | #####################################################################
80 | # PROGRESS BAR AND DISPLAY
81 | class ProgressBar():
82 | def __init__(self):
83 | self.steps_total = 0
84 | self.steps_count = 0
85 |
86 | def _pb_update(self, size:int=80):
87 | if self.steps_count > self.steps_total:
88 | self.steps_count = self.steps_total
89 |
90 | percent = self.steps_count/self.steps_total
91 | delta = 17
92 | x = int((size-delta)*percent)
93 | print(f"Progress: ", end="")
94 | print(f"[{'█'*x}{'.'*(size-delta-x)}]", end="")
95 | print(f"{int(percent*100)}%".rjust(5), end="")
96 |
97 | def _pb_new_step(self, message:str, result:str, inc:bool=False, size:int=80, display_pbar:bool=True):
98 | if inc: self.steps_count += 1
99 | text = f"\033[A\033[F{message}"
100 | print(f"{text} ".ljust(size + 4, "."), result)
101 | print("".ljust(80))
102 | if display_pbar: self._pb_update(size)
103 |
104 | def _pb_title(self, text:str, size:int=80, end:bool=False, display_pbar:bool=True):
105 | print("\033[A")
106 | print(f" {text} ".center(size, "-"),"\n\n")
107 | if not end and display_pbar:
108 | print("".ljust(80))
109 | self._pb_update(size)
110 |
111 | def set_steps_total(self, steps_total:int):
112 | self.steps_total = steps_total
113 |
114 | def log_message(self, message, display_pbar:bool=True):
115 | self._pb_new_step(message, " ", display_pbar=display_pbar)
116 |
117 | def log_success(self, message, inc:bool=False, display_pbar:bool=True):
118 | LOGGER.info(f"{message}: Success")
119 | self._pb_new_step(message, "\033[92m\u2714\033[0m\n", inc=inc, display_pbar=display_pbar)
120 |
121 | def log_failure(self, message, inc:bool=False, display_pbar:bool=True):
122 | LOGGER.error(f"{message}: Failure")
123 | self._pb_new_step(message, '\033[31m\u2716\033[0m\n', inc=inc, display_pbar=display_pbar)
124 |
125 | def log_title(self, message, end:bool=False, display_pbar:bool=True):
126 | LOGGER.info(message)
127 | self._pb_title(message, end=end, display_pbar=display_pbar)
128 |
129 | pb = ProgressBar()
130 | #####################################################################
131 | #### FUNCTIONS ####
132 |
133 | def _get_device_ids(apisession:mistapi.APISession, site_name:str, site_id:str):
134 | LOGGER.info(f"{site_id}: Retrieving devices list")
135 | devices = []
136 | try:
137 | response = mistapi.api.v1.sites.devices.listSiteDevices(apisession, site_id=site_id, type="ap")
138 | devices = mistapi.get_all(apisession, response)
139 | except:
140 | LOGGER.error(f"{site_id}: Unable to retrieve devices list")
141 | print(f"Unable to retrieve devices list from site {site_name}")
142 | finally:
143 | LOGGER.info(f"{site_id}: {len(devices)} devices will be updated")
144 | return devices
145 |
146 | def _update_vlan_id(ip_config:dict, vlan_id:int):
147 | # set "vlan_id" in AP settings if vlan_id > 0
148 | if vlan_id > 0:
149 | ip_config["vlan_id"] = vlan_id
150 | # or delete the "vlan_id" key from the AP settings
151 | elif ip_config.get("vlan_id", None) is not None:
152 | del ip_config["vlan_id"]
153 | return ip_config
154 |
155 |
156 | def _update_devices(apisession:mistapi.APISession, site_name:str, site_id:str, vlan_id:int):
157 | try:
158 | message="Retrieving devices list"
159 | pb.log_message(message, display_pbar=False)
160 | devices = _get_device_ids(apisession, site_name, site_id)
161 | pb.log_success(message, display_pbar=False)
162 | except:
163 | pb.log_failure(message, display_pbar=False)
164 |
165 | if len(devices) == 0:
166 | pb.log_success("No APs assigned to this site")
167 | LOGGER.info(f"{site_id}: no devices to process")
168 | else:
169 | count = len(devices)
170 | pb.set_steps_total(count)
171 | for device in devices:
172 | device_id = device["id"]
173 | device_name = device.get("name", "No Name")
174 | device_mac = device.get("mac")
175 | if not device_name: device_name = device_mac
176 | message=f"Updating device {device_name} ({device_mac})"
177 | pb.log_message(message)
178 | try:
179 | device_settings = mistapi.api.v1.sites.devices.getSiteDevice(apisession, site_id=site_id, device_id=device_id).data
180 | LOGGER.debug(device_settings)
181 | ip_config = device_settings.get("ip_config", {})
182 | LOGGER.debug(f"ip_config before change: {ip_config}")
183 | ip_config = _update_vlan_id(ip_config, vlan_id)
184 | LOGGER.debug(f"ip_config after change: {ip_config}")
185 | device_settings["ip_config"] = ip_config
186 | mistapi.api.v1.sites.devices.updateSiteDevice(apisession, site_id, device_id, device_settings)
187 | pb.log_success(message, inc=True)
188 | except:
189 | pb.log_failure(message, inc=True)
190 |
191 | #####################################################################
192 | ##### MENU ####
193 | def _enter_vlan_id():
194 | vid = -1
195 | while vid < 0 or vid > 4095:
196 | print("")
197 | resp = input("Management VLAN ID (0 for untagged): ")
198 | try:
199 | resp = int(resp)
200 | if resp < 0 or resp > 4095:
201 | print("Please enter a number between 0 and 4095")
202 | else:
203 | vid = resp
204 | except:
205 | print("Please enter a number between 0 and 4095")
206 | return vid
207 |
208 |
209 | def process_sites(apisession:mistapi.APISession, site_ids:list, vlan_id:int):
210 | print()
211 | for site_id in site_ids:
212 | LOGGER.info(f"{site_id}: Processing site")
213 | site_info = mistapi.api.v1.sites.sites.getSiteInfo(apisession, site_id).data
214 | site_name = site_info["name"]
215 | LOGGER.info(f"{site_id}: name is {site_name}")
216 | pb.log_title(f"Processing Site {site_name}", display_pbar=False)
217 | _update_devices(apisession, site_name, site_id, vlan_id)
218 | pb.log_title("VLAN configuration done", end=True)
219 |
220 |
221 | def start(apisession:mistapi.APISession, site_ids:list=None, vlan_id:int=-1):
222 | if not site_ids:
223 | site_ids = mistapi.cli.select_site(apisession, allow_many=True)
224 | if not type(vlan_id) == int or vlan_id < 0:
225 | vlan_id = _enter_vlan_id()
226 | LOGGER.info(f"Site IDs: {site_ids}")
227 | LOGGER.info(f"VLAN ID : {vlan_id}")
228 | process_sites(apisession, site_ids, vlan_id)
229 |
230 |
231 | #####################################################################
232 | ##### USAGE ####
233 | def usage():
234 | print('''
235 | -------------------------------------------------------------------------------
236 |
237 | Written by Thomas Munzer (tmunzer@juniper.net)
238 | Github repository: https://github.com/tmunzer/Mist_library/
239 |
240 | This script is licensed under the MIT License.
241 |
242 | -------------------------------------------------------------------------------
243 | Python script reconfigure Mist APs with a tagged managed VLAN
244 |
245 | -------
246 | Requirements:
247 | mistapi: https://pypi.org/project/mistapi/
248 |
249 | -------
250 | Usage:
251 | This script can be run as is (without parameters), or with the options below.
252 | If no options are defined, or if options are missing, the missing options will
253 | be asked by the script or the default values will be used.
254 |
255 | It is recommended to use an environment file to store the required information
256 | to request the Mist Cloud (see https://pypi.org/project/mistapi/ for more
257 | information about the available parameters).
258 |
259 | -------
260 | Script Parameters:
261 | -h, --help display this help
262 | -s, --site_ids= list of sites to use, comma separated
263 | -v, --vlan_id= Set the mgmt VLAN ID, 0 for untagged
264 | -l, --log_file= define the filepath/filename where to write the logs
265 | default is "./script.log"
266 | -e, --env= define the env file to use (see mistapi env file
267 | documentation here: https://pypi.org/project/mistapi/)
268 | default is "~/.mist_env"
269 |
270 | -------
271 | Examples:
272 | python3 ./configure_ap_mgmt_vlan.py
273 | python3 ./configure_ap_mgmt_vlan.py -s 203d3d02-xxxx-xxxx-xxxx-76896a3330f4,203d3d02-xxxx-xxxx-xxxx-76896a3330f4 -v 31
274 |
275 | '''
276 | )
277 | sys.exit(0)
278 |
279 | def check_mistapi_version():
280 | """Check if the installed mistapi version meets the minimum requirement."""
281 |
282 | current_version = mistapi.__version__.split(".")
283 | required_version = MISTAPI_MIN_VERSION.split(".")
284 |
285 | try:
286 | for i, req in enumerate(required_version):
287 | if current_version[int(i)] > req:
288 | break
289 | if current_version[int(i)] < req:
290 | raise ImportError(
291 | f'"mistapi" package version {MISTAPI_MIN_VERSION} is required '
292 | f"but version {mistapi.__version__} is installed."
293 | )
294 | except ImportError as e:
295 | LOGGER.critical(str(e))
296 | LOGGER.critical("Please use the pip command to update it.")
297 | LOGGER.critical("")
298 | LOGGER.critical(" # Linux/macOS")
299 | LOGGER.critical(" python3 -m pip install --upgrade mistapi")
300 | LOGGER.critical("")
301 | LOGGER.critical(" # Windows")
302 | LOGGER.critical(" py -m pip install --upgrade mistapi")
303 | print(
304 | f"""
305 | Critical:\r\n
306 | {e}\r\n
307 | Please use the pip command to update it.
308 | # Linux/macOS
309 | python3 -m pip install --upgrade mistapi
310 | # Windows
311 | py -m pip install --upgrade mistapi
312 | """
313 | )
314 | sys.exit(2)
315 | finally:
316 | LOGGER.info(
317 | '"mistapi" package version %s is required, '
318 | "you are currently using version %s.",
319 | MISTAPI_MIN_VERSION,
320 | mistapi.__version__
321 | )
322 |
323 | #####################################################################
324 | ##### ENTRY POINT ####
325 | if __name__ == "__main__":
326 | try:
327 | opts, args = getopt.getopt(sys.argv[1:], "hs:v:e:l:", [
328 | "help", "site_ids=", "vlan_id=", "env=", "log_file="])
329 | except getopt.GetoptError as err:
330 | console.error(err)
331 | usage()
332 |
333 | site_ids = []
334 | vlan_id = -1
335 | for o, a in opts:
336 | if o in ["-h", "--help"]:
337 | usage()
338 | elif o in ["-s", "--site_ids"]:
339 | try:
340 | for site_id in a.split(","):
341 | site_ids.append(site_id.strip())
342 | except:
343 | console.critical("Unable to parse the Site IDs from the parameters")
344 | sys.exit(1)
345 | elif o in ["-v", "--vlan_id"]:
346 | try:
347 | vlan_id = int(a)
348 | except:
349 | console.critical("Unable to parse the VLAN ID from the parameters")
350 | sys.exit(1)
351 | elif o in ["-e", "--env"]:
352 | env_file = a
353 | elif o in ["-l", "--log_file"]:
354 | log_file = a
355 | else:
356 | assert False, "unhandled option"
357 |
358 | #### LOGS ####
359 | logging.basicConfig(filename=log_file, filemode='w')
360 | LOGGER.setLevel(logging.DEBUG)
361 | check_mistapi_version()
362 | ### START ###
363 | apisession = mistapi.APISession(env_file=env_file)
364 | apisession.login()
365 | start(apisession, site_ids, vlan_id)
366 |
367 |
368 |
369 |
370 |
--------------------------------------------------------------------------------
/scripts/devices/gateways/fix_gateway_backup_firmware.py:
--------------------------------------------------------------------------------
1 | '''
2 | -------------------------------------------------------------------------------
3 |
4 | Written by Thomas Munzer (tmunzer@juniper.net)
5 | Github repository: https://github.com/tmunzer/Mist_library/
6 |
7 | This script is licensed under the MIT License.
8 |
9 | -------------------------------------------------------------------------------
10 | Python script trigger a snapshot/firmware backup on SRX devices.
11 | This script is using the CSV report for the report_gateway_firmware.py script to
12 | identify the SRX on which the command must be triggered.
13 |
14 | -------
15 | Requirements:
16 | mistapi: https://pypi.org/project/mistapi/
17 |
18 | -------
19 | Usage:
20 | This script can be run as is (without parameters), or with the options below.
21 | If no options are defined, or if options are missing, the missing options will
22 | be asked by the script or the default values will be used.
23 |
24 | It is recommended to use an environment file to store the required information
25 | to request the Mist Cloud (see https://pypi.org/project/mistapi/ for more
26 | information about the available parameters).
27 |
28 | -------
29 | Options:
30 | -h, --help display this help
31 |
32 | -s, --site_id= Set the site_id if only devices from a specific site must be
33 | processed. If not set, the script will process all the devices
34 | from the report that need a firmware backup
35 | -f, --in_file= path to the report generated by the report_gateway_firmware script
36 | default is "./report_gateway_firmware.csv"
37 | --auto-approve Does not ask for user confirmation before triggering the
38 | firmware backup API Calls
39 |
40 | -l, --log_file= define the filepath/filename where to write the logs
41 | default is "./script.log"
42 | -e, --env= define the env file to use (see mistapi env file documentation
43 | here: https://pypi.org/project/mistapi/)
44 | default is "~/.mist_env"
45 |
46 | -------
47 | Examples:
48 | python3 ./fix_gateway_backup_firmware.py
49 | python3 ./fix_gateway_backup_firmware.py --site_id=203d3d02-xxxx-xxxx-xxxx-76896a3330f4
50 |
51 | '''
52 |
53 | #### IMPORTS #####
54 | import sys
55 | import csv
56 | import getopt
57 | import logging
58 |
59 | MISTAPI_MIN_VERSION = "0.53.0"
60 |
61 | try:
62 | import mistapi
63 | from mistapi.__logger import console as CONSOLE
64 | except:
65 | print("""
66 | Critical:
67 | \"mistapi\" package is missing. Please use the pip command to install it.
68 |
69 | # Linux/macOS
70 | python3 -m pip install mistapi
71 |
72 | # Windows
73 | py -m pip install mistapi
74 | """)
75 | sys.exit(2)
76 |
77 |
78 |
79 | #### LOGS ####
80 | LOGGER = logging.getLogger(__name__)
81 |
82 | #### PARAMETERS #####
83 | CSV_FILE = "./report_gateway_firmware.csv"
84 | LOG_FILE = "./script.log"
85 | ENV_FILE = "~/.mist_env"
86 |
87 |
88 |
89 | #####################################################################
90 | # PROGRESS BAR AND DISPLAY
91 | class ProgressBar:
92 | def __init__(self):
93 | self.steps_total = 0
94 | self.steps_count = 0
95 |
96 | def _pb_update(self, size: int = 80):
97 | if self.steps_count > self.steps_total:
98 | self.steps_count = self.steps_total
99 |
100 | percent = self.steps_count / self.steps_total
101 | delta = 17
102 | x = int((size - delta) * percent)
103 | print(f"Progress: ", end="")
104 | print(f"[{'█'*x}{'.'*(size-delta-x)}]", end="")
105 | print(f"{int(percent*100)}%".rjust(5), end="")
106 |
107 | def _pb_new_step(
108 | self,
109 | message: str,
110 | result: str,
111 | inc: bool = False,
112 | size: int = 80,
113 | display_pbar: bool = True,
114 | ):
115 | if inc:
116 | self.steps_count += 1
117 | text = f"\033[A\033[F{message}"
118 | print(f"{text} ".ljust(size + 4, "."), result)
119 | print("".ljust(80))
120 | if display_pbar:
121 | self._pb_update(size)
122 |
123 | def _pb_title(
124 | self, text: str, size: int = 80, end: bool = False, display_pbar: bool = True
125 | ):
126 | print("\033[A")
127 | print(f" {text} ".center(size, "-"), "\n\n")
128 | if not end and display_pbar:
129 | print("".ljust(80))
130 | self._pb_update(size)
131 |
132 | def set_steps_total(self, steps_total: int):
133 | self.steps_total = steps_total
134 |
135 | def log_message(self, message, display_pbar: bool = True):
136 | self._pb_new_step(message, " ", display_pbar=display_pbar)
137 |
138 | def log_success(self, message, inc: bool = False, display_pbar: bool = True):
139 | LOGGER.info(f"{message}: Success")
140 | self._pb_new_step(
141 | message, "\033[92m\u2714\033[0m\n", inc=inc, display_pbar=display_pbar
142 | )
143 |
144 | def log_warning(self, message, inc: bool = False, display_pbar: bool = True):
145 | LOGGER.warning(f"{message}: Warning")
146 | self._pb_new_step(
147 | message, "\033[93m\u2B58\033[0m\n", inc=inc, display_pbar=display_pbar
148 | )
149 |
150 | def log_failure(self, message, inc: bool = False, display_pbar: bool = True):
151 | LOGGER.error(f"{message}: Failure")
152 | self._pb_new_step(
153 | message, "\033[31m\u2716\033[0m\n", inc=inc, display_pbar=display_pbar
154 | )
155 |
156 | def log_title(self, message, end: bool = False, display_pbar: bool = True):
157 | LOGGER.info(message)
158 | self._pb_title(message, end=end, display_pbar=display_pbar)
159 |
160 |
161 | PB = ProgressBar()
162 |
163 | ###############################################################################
164 | #### FUNCTIONS ####
165 | def _process_gateways(apisession:mistapi.APISession, gateways:list) -> list:
166 | i=0
167 | PB.set_steps_total(len(gateways))
168 | for gateway in gateways:
169 | site_id = gateway.get("cluster_site_id")
170 | device_id = gateway.get("cluster_device_id")
171 | device_mac = gateway.get("module_mac")
172 | message = f"Processing device {device_id}"
173 | PB.log_message(message)
174 | if not site_id:
175 | PB.log_failure(message, inc=True)
176 | CONSOLE.error(f"Missing site_id for device {device_mac}")
177 | continue
178 | if not device_id:
179 | PB.log_failure(message, inc=True)
180 | CONSOLE.error(f"Missing device_id for device {device_mac}")
181 | continue
182 | try:
183 | resp = mistapi.api.v1.sites.devices.createSiteDeviceSnapshot(apisession, site_id, device_id)
184 | if resp.status_code == 200:
185 | PB.log_success(message, inc=True)
186 | else:
187 | PB.log_failure(message, inc=True)
188 | except:
189 | PB.log_failure(message, inc=True)
190 | LOGGER.error("Exception occurred", exc_info=True)
191 |
192 | ### READ REPORT
193 | def _read_csv(csv_file:str, site_id:str) -> list:
194 | data = []
195 | device_ids = []
196 | message="Reading CSV Report"
197 | PB.log_message(message, display_pbar=False)
198 | try:
199 | with open(csv_file, newline='') as f:
200 | reader = csv.DictReader(filter(lambda row: row[0]!='#', f))
201 | for row in reader:
202 | if not "SRX" in row.get("module_model"):
203 | continue
204 | if site_id and row.get("cluster_site_id") != site_id:
205 | continue
206 | if row.get("module_need_snapshot") != "True":
207 | continue
208 | if row.get("cluster_device_id") in device_ids:
209 | continue
210 | data.append(row)
211 | device_ids.append(row.get("cluster_device_id"))
212 | PB.log_success(message, inc=False, display_pbar=False)
213 | except:
214 | PB.log_failure(message, inc=False, display_pbar=False)
215 | LOGGER.error("Exception occurred", exc_info=True)
216 | sys.exit(1)
217 | return data
218 |
219 | def _request_approval(data:dict):
220 | print("".center(80, "-"))
221 | print("List of gateways to process:")
222 | print()
223 | mistapi.cli.pretty_print(data)
224 | r = input("Do you want to continue (y/N)? ")
225 | if r.lower() == "y":
226 | print("".center(80, "-"))
227 | return
228 | else:
229 | CONSOLE.info("process stopped by the user. Exiting...")
230 | sys.exit(0)
231 |
232 |
233 | ###############################################################################
234 | ### START
235 | def _start(apisession: mistapi.APISession, site_id: str, csv_file:str=CSV_FILE, auto_approve:bool=False) -> None:
236 |
237 | data = _read_csv(csv_file, site_id)
238 | if not data:
239 | print("All the gateways are compliant... Exiting...")
240 | sys.exit(0)
241 | if auto_approve:
242 | CONSOLE.info("auto-approve parameter has been set to True. Starting the process")
243 | else:
244 | _request_approval(data)
245 | _process_gateways(apisession, data)
246 |
247 |
248 | ###############################################################################
249 | ### USAGE
250 | def usage():
251 | print('''
252 | -------------------------------------------------------------------------------
253 |
254 | Written by Thomas Munzer (tmunzer@juniper.net)
255 | Github repository: https://github.com/tmunzer/Mist_library/
256 |
257 | This script is licensed under the MIT License.
258 |
259 | -------------------------------------------------------------------------------
260 | Python script trigger a snapshot/firmware backup on SRX devices.
261 | This script is using the CSV report for the report_gateway_firmware.py script to
262 | identify the SRX on which the command must be triggered.
263 |
264 | -------
265 | Requirements:
266 | mistapi: https://pypi.org/project/mistapi/
267 |
268 | -------
269 | Usage:
270 | This script can be run as is (without parameters), or with the options below.
271 | If no options are defined, or if options are missing, the missing options will
272 | be asked by the script or the default values will be used.
273 |
274 | It is recommended to use an environment file to store the required information
275 | to request the Mist Cloud (see https://pypi.org/project/mistapi/ for more
276 | information about the available parameters).
277 |
278 | -------
279 | Options:
280 | -h, --help display this help
281 |
282 | -s, --site_id= Set the site_id if only devices from a specific site must be
283 | processed. If not set, the script will process all the devices
284 | from the report that need a firmware backup
285 | -f, --in_file= path to the report generated by the report_gateway_firmware script
286 | default is "./report_gateway_firmware.csv"
287 | --auto-approve Does not ask for user confirmation before triggering the
288 | firmware backup API Calls
289 |
290 | -l, --log_file= define the filepath/filename where to write the logs
291 | default is "./script.log"
292 | -e, --env= define the env file to use (see mistapi env file documentation
293 | here: https://pypi.org/project/mistapi/)
294 | default is "~/.mist_env"
295 |
296 | -------
297 | Examples:
298 | python3 ./fix_gateway_backup_firmware.py
299 | python3 ./fix_gateway_backup_firmware.py --site_id=203d3d02-xxxx-xxxx-xxxx-76896a3330f4
300 |
301 | ''')
302 | sys.exit(0)
303 |
304 | def check_mistapi_version():
305 | """Check if the installed mistapi version meets the minimum requirement."""
306 |
307 | current_version = mistapi.__version__.split(".")
308 | required_version = MISTAPI_MIN_VERSION.split(".")
309 |
310 | try:
311 | for i, req in enumerate(required_version):
312 | if current_version[int(i)] > req:
313 | break
314 | if current_version[int(i)] < req:
315 | raise ImportError(
316 | f'"mistapi" package version {MISTAPI_MIN_VERSION} is required '
317 | f"but version {mistapi.__version__} is installed."
318 | )
319 | except ImportError as e:
320 | LOGGER.critical(str(e))
321 | LOGGER.critical("Please use the pip command to update it.")
322 | LOGGER.critical("")
323 | LOGGER.critical(" # Linux/macOS")
324 | LOGGER.critical(" python3 -m pip install --upgrade mistapi")
325 | LOGGER.critical("")
326 | LOGGER.critical(" # Windows")
327 | LOGGER.critical(" py -m pip install --upgrade mistapi")
328 | print(
329 | f"""
330 | Critical:\r\n
331 | {e}\r\n
332 | Please use the pip command to update it.
333 | # Linux/macOS
334 | python3 -m pip install --upgrade mistapi
335 | # Windows
336 | py -m pip install --upgrade mistapi
337 | """
338 | )
339 | sys.exit(2)
340 | finally:
341 | LOGGER.info(
342 | '"mistapi" package version %s is required, '
343 | "you are currently using version %s.",
344 | MISTAPI_MIN_VERSION,
345 | mistapi.__version__
346 | )
347 |
348 |
349 |
350 | ###############################################################################
351 | ### ENTRY POINT
352 | if __name__ == "__main__":
353 | try:
354 | opts, args = getopt.getopt(sys.argv[1:], "hs:f:e:l:", ["help", "site_id", "in_file=", "auto-approve", "env=", "log_file="])
355 | except getopt.GetoptError as err:
356 | CONSOLE.error(err)
357 | usage()
358 |
359 | SITE_ID=None
360 | AUTO_APPROVE=False
361 | for o, a in opts:
362 | if o in ["-h", "--help"]:
363 | usage()
364 | elif o in ["-s", "--site_id"]:
365 | SITE_ID = a
366 | elif o in ["-f", "--in_file"]:
367 | CSV_FILE=a
368 | elif o in ["--auto-approve"]:
369 | AUTO_APPROVE=True
370 | elif o in ["-e", "--env"]:
371 | ENV_FILE=a
372 | elif o in ["-l", "--log_file"]:
373 | LOG_FILE = a
374 |
375 | else:
376 | assert False, "unhandled option"
377 |
378 | #### LOGS ####
379 | logging.basicConfig(filename=LOG_FILE, filemode='w')
380 | LOGGER.setLevel(logging.DEBUG)
381 | check_mistapi_version()
382 | ### MIST SESSION ###
383 | APISESSION = mistapi.APISession(env_file=ENV_FILE)
384 | APISESSION.login()
385 | ### START ###
386 | _start(APISESSION, SITE_ID, CSV_FILE, AUTO_APPROVE)
387 |
--------------------------------------------------------------------------------
/scripts/exports/export_inventory.py:
--------------------------------------------------------------------------------
1 | '''
2 | -------------------------------------------------------------------------------
3 |
4 | Written by Thomas Munzer (tmunzer@juniper.net)
5 | Github repository: https://github.com/tmunzer/Mist_library/
6 |
7 | This script is licensed under the MIT License.
8 |
9 | -------------------------------------------------------------------------------
10 |
11 | Python script to export the inventory from an organization. The export will
12 | include all the information available from the org inventory, including the
13 | claim codes.
14 | The current version is not exporting Mist Edges inventory.
15 |
16 | -------
17 | Requirements:
18 | mistapi: https://pypi.org/project/mistapi/
19 |
20 | -------
21 | Usage:
22 | This script can be run as is (without parameters), or with the options below.
23 | If no options are defined, or if options are missing, the missing options will
24 | be asked by the script.
25 |
26 | It is recommended to use an environment file to store the required information
27 | to request the Mist Cloud (see https://pypi.org/project/mistapi/ for more
28 | information about the available parameters).
29 |
30 | -------
31 | Options:
32 | -h, --help display this help
33 | -o, --org_id= required for Org reports. Set the org_id
34 | -a, --all add the "virtual" devices to the export (e.g. add the
35 | virtual device used by Mist to identify a virtual chassis)
36 |
37 | -l, --log_file= define the filepath/filename where to write the logs
38 | default is "./script.log"
39 | -f, --out_file= define the filepath/filename where to save the data
40 | default is "./export.csv"
41 | --out_format= define the output format (csv or json)
42 | default is csv
43 | -e, --env= define the env file to use (see mistapi env file documentation
44 | here: https://pypi.org/project/mistapi/)
45 | default is "~/.mist_env"
46 |
47 | -------
48 | Examples:
49 | python3 ./export_inventory.py
50 | python3 ./export_inventory.py --org_id=203d3d02-xxxx-xxxx-xxxx-76896a3330f4
51 | '''
52 |
53 | #### IMPORTS ####
54 |
55 | import sys
56 | import json
57 | import csv
58 | import os
59 | import logging
60 | import datetime
61 | import getopt
62 |
63 | MISTAPI_MIN_VERSION = "0.44.1"
64 |
65 | try:
66 | import mistapi
67 | from mistapi.__logger import console
68 | except:
69 | print("""
70 | Critical:
71 | \"mistapi\" package is missing. Please use the pip command to install it.
72 |
73 | # Linux/macOS
74 | python3 -m pip install mistapi
75 |
76 | # Windows
77 | py -m pip install mistapi
78 | """)
79 | sys.exit(2)
80 |
81 | #### PARAMETERS #####
82 |
83 | log_file = "./script.log"
84 | env_file = os.path.join(os.path.expanduser('~'), ".mist_env")
85 | out_file_format="csv"
86 | out_file_path="./export.csv"
87 |
88 | #### LOGS ####
89 | LOGGER = logging.getLogger(__name__)
90 |
91 |
92 | #####################################################################
93 | # PROGRESS BAR AND DISPLAY
94 | class ProgressBar():
95 | def __init__(self):
96 | self.steps_total = 0
97 | self.steps_count = 0
98 |
99 | def _pb_update(self, size:int=80):
100 | if self.steps_count > self.steps_total:
101 | self.steps_count = self.steps_total
102 | if self.steps_total > 0:
103 | percent = self.steps_count/self.steps_total
104 | else:
105 | percent = 0
106 | delta = 17
107 | x = int((size-delta)*percent)
108 | print(f"Progress: ", end="")
109 | print(f"[{'█'*x}{'.'*(size-delta-x)}]", end="")
110 | print(f"{int(percent*100)}%".rjust(5), end="")
111 |
112 | def _pb_new_step(self, message:str, result:str, inc:bool=False, size:int=80, display_pbar:bool=True):
113 | if inc: self.steps_count += 1
114 | text = f"\033[A\033[F{message}"
115 | print(f"{text} ".ljust(size + 4, "."), result)
116 | print("".ljust(80))
117 | if display_pbar: self._pb_update(size)
118 |
119 | def _pb_title(self, text:str, size:int=80, end:bool=False, display_pbar:bool=True):
120 | print("\033[A")
121 | print(f" {text} ".center(size, "-"),"\n\n")
122 | if not end and display_pbar:
123 | print("".ljust(80))
124 | self._pb_update(size)
125 |
126 | def set_steps_total(self, steps_total:int):
127 | self.steps_total = steps_total
128 |
129 | def log_message(self, message, display_pbar:bool=True):
130 | self._pb_new_step(message, " ", display_pbar=display_pbar)
131 |
132 | def log_success(self, message, inc:bool=False, display_pbar:bool=True):
133 | LOGGER.info(f"{message}: Success")
134 | self._pb_new_step(message, "\033[92m\u2714\033[0m\n", inc=inc, display_pbar=display_pbar)
135 |
136 | def log_failure(self, message, inc:bool=False, display_pbar:bool=True):
137 | LOGGER.error(f"{message}: Failure")
138 | self._pb_new_step(message, '\033[31m\u2716\033[0m\n', inc=inc, display_pbar=display_pbar)
139 |
140 | def log_title(self, message, end:bool=False, display_pbar:bool=True):
141 | LOGGER.info(message)
142 | self._pb_title(message, end=end, display_pbar=display_pbar)
143 |
144 | def inc_only(self, size:int=80):
145 | print("\033[F")
146 | self.steps_count += 1
147 | self._pb_update(size)
148 |
149 | def reinit(self):
150 | self.steps_count = 0
151 |
152 |
153 | pb = ProgressBar()
154 |
155 | ####################
156 | ## REQUEST
157 | def _resolve_vcmembers_site_id(switches:list, all:bool) -> list:
158 | mac_to_site_id = {}
159 | data = []
160 | for switch in switches:
161 | if switch.get("vc_mac") == switch.get("mac"):
162 | mac_to_site_id[switch["mac"]] = switch["site_id"]
163 | for switch in switches:
164 | if switch.get("vc_mac") and switch.get("vc_mac") != switch.get("mac"):
165 | switch["site_id"] = mac_to_site_id.get(switch["vc_mac"])
166 | if all or not switch.get("mac").startswith("020003"):
167 | data.append(switch)
168 | return data
169 |
170 | def _process_export(apisession:mistapi.APISession, org_id:str, all:bool):
171 | data = []
172 |
173 | print()
174 | pb.log_title("Retrieving Data from Mist", display_pbar=False)
175 |
176 | for device_type in ["ap", "switch", "gateway"]:
177 | try:
178 | message = f"Retrieving {device_type.title()} Inventory"
179 | pb.log_message(message, display_pbar=False)
180 | response = mistapi.api.v1.orgs.inventory.getOrgInventory(apisession, org_id, type=device_type, vc=True)
181 | devices = mistapi.get_all(apisession, response)
182 | if device_type == "switch":
183 | devices = _resolve_vcmembers_site_id(devices, all)
184 | data+=devices
185 | pb.log_success(message, display_pbar=False)
186 | except:
187 | pb.log_failure(message, display_pbar=False)
188 | LOGGER.error("Exception occurred", exc_info=True)
189 | return data
190 | ####################
191 | ## SAVE TO FILE
192 | def _save_as_csv(data:list, org_name:str, org_id:str):
193 | headers=[]
194 | total = len(data)
195 | pb.log_title("Saving Data", display_pbar=False)
196 | pb.set_steps_total(total)
197 | message = "Generating CSV Headers"
198 | pb.log_message(message)
199 | i = 0
200 | for entry in data:
201 | for key in entry:
202 | if not key in headers:
203 | headers.append(key)
204 | i += 1
205 | pb.inc_only()
206 | pb.log_success(message, inc=True)
207 |
208 | message="Saving to file"
209 | pb.set_steps_total(total)
210 | pb.reinit()
211 | pb.log_message(message)
212 | i = 0
213 | with open(out_file_path, "w", encoding='UTF8', newline='') as f:
214 | csv_writer = csv.writer(f)
215 | csv_writer.writerow([f"#Report: Device Inventory Export", f"Date: {datetime.datetime.now()}", f"Org Name: {org_name}", f"Org ID: {org_id}"])
216 | csv_writer.writerow(headers)
217 | for entry in data:
218 | tmp=[]
219 | for header in headers:
220 | tmp.append(entry.get(header, ""))
221 | csv_writer.writerow(tmp)
222 | i += 1
223 | pb.inc_only()
224 | pb.log_success(message, inc=True)
225 | print("\033[FDone.".ljust(80))
226 |
227 |
228 | def _save_as_json(data:list, org_name:str, org_id:str):
229 | print(" Saving Data ".center(80, "-"))
230 | print()
231 | json_data = {
232 | 'report': "Device Inventory Export",
233 | 'Date': f'{datetime.datetime.now()}',
234 | 'Org Name': org_name,
235 | 'Org ID': org_id,
236 | 'data': data
237 | }
238 | with open(os.path.abspath(out_file_path), 'w') as f:
239 | json.dump(json_data, f)
240 | print("Done.")
241 |
242 | ####################
243 | ## MENU
244 |
245 | def start(apisession, org_id:str=None, all:bool=False):
246 | if not org_id:
247 | org_id = mistapi.cli.select_org(apisession)[0]
248 | org_name = mistapi.api.v1.orgs.orgs.getOrg(apisession, org_id).data["name"]
249 | data=_process_export(apisession, org_id, all)
250 | if out_file_format == "csv":
251 | _save_as_csv(data, org_name, org_id)
252 | elif out_file_format == "json":
253 | _save_as_json(data, org_name, org_id)
254 | else:
255 | console.error(f"file format {out_file_format} not supported")
256 |
257 | def usage():
258 | print(f"""
259 | -------------------------------------------------------------------------------
260 |
261 | Written by Thomas Munzer (tmunzer@juniper.net)
262 | Github repository: https://github.com/tmunzer/Mist_library/
263 |
264 | This script is licensed under the MIT License.
265 |
266 | -------------------------------------------------------------------------------
267 |
268 | Python script to export the inventory from an organization. The export will
269 | include all the information available from the org inventory, including the
270 | claim codes.
271 | The current version is not exporting Mist Edges inventory.
272 |
273 | -------
274 | Requirements:
275 | mistapi: https://pypi.org/project/mistapi/
276 |
277 | -------
278 | Usage:
279 | This script can be run as is (without parameters), or with the options below.
280 | If no options are defined, or if options are missing, the missing options will
281 | be asked by the script.
282 |
283 | It is recommended to use an environment file to store the required information
284 | to request the Mist Cloud (see https://pypi.org/project/mistapi/ for more
285 | information about the available parameters).
286 |
287 | -------
288 | Options:
289 | -h, --help display this help
290 | -o, --org_id= required for Org reports. Set the org_id
291 | -a, --all add the "virtual" devices to the export (e.g. add the
292 | virtual device used by Mist to identify a virtual chassis)
293 |
294 | -l, --log_file= define the filepath/filename where to write the logs
295 | default is {log_file}
296 | -f, --out_file= define the filepath/filename where to save the data
297 | default is {out_file_path}
298 | --out_format= define the output format (csv or json)
299 | default is csv
300 | -e, --env= define the env file to use (see mistapi env file documentation
301 | here: https://pypi.org/project/mistapi/)
302 | default is {env_file}
303 |
304 | -------
305 | Examples:
306 | python3 ./export_inventory.py
307 | python3 ./export_inventory.py --org_id=203d3d02-xxxx-xxxx-xxxx-76896a3330f4
308 | """)
309 | sys.exit(0)
310 |
311 | def check_mistapi_version():
312 | """Check if the installed mistapi version meets the minimum requirement."""
313 |
314 | current_version = mistapi.__version__.split(".")
315 | required_version = MISTAPI_MIN_VERSION.split(".")
316 |
317 | try:
318 | for i, req in enumerate(required_version):
319 | if current_version[int(i)] > req:
320 | break
321 | if current_version[int(i)] < req:
322 | raise ImportError(
323 | f'"mistapi" package version {MISTAPI_MIN_VERSION} is required '
324 | f"but version {mistapi.__version__} is installed."
325 | )
326 | except ImportError as e:
327 | LOGGER.critical(str(e))
328 | LOGGER.critical("Please use the pip command to update it.")
329 | LOGGER.critical("")
330 | LOGGER.critical(" # Linux/macOS")
331 | LOGGER.critical(" python3 -m pip install --upgrade mistapi")
332 | LOGGER.critical("")
333 | LOGGER.critical(" # Windows")
334 | LOGGER.critical(" py -m pip install --upgrade mistapi")
335 | print(
336 | f"""
337 | Critical:\r\n
338 | {e}\r\n
339 | Please use the pip command to update it.
340 | # Linux/macOS
341 | python3 -m pip install --upgrade mistapi
342 | # Windows
343 | py -m pip install --upgrade mistapi
344 | """
345 | )
346 | sys.exit(2)
347 | finally:
348 | LOGGER.info(
349 | '"mistapi" package version %s is required, '
350 | "you are currently using version %s.",
351 | MISTAPI_MIN_VERSION,
352 | mistapi.__version__
353 | )
354 |
355 | #### SCRIPT ENTRYPOINT ####
356 |
357 | if __name__ == "__main__":
358 | try:
359 | opts, args = getopt.getopt(
360 | sys.argv[1:], "ho:f:e:l:a",
361 | [
362 | "help",
363 | "org_id=",
364 | "out_format=",
365 | "out_file=",
366 | "env=",
367 | "log_file=",
368 | "all"
369 | ]
370 | )
371 | except getopt.GetoptError as err:
372 | console.error(err)
373 | usage()
374 |
375 | org_id=None
376 | all=False
377 | for o, a in opts:
378 | if o in ["-h", "--help"]:
379 | usage()
380 | elif o in ["-o", "--org_id"]:
381 | org_id = a
382 | elif o in ["-a", "--all"]:
383 | all=True
384 | elif o in ["--out_format"]:
385 | if a in ["csv", "json"]:
386 | out_file_format=a
387 | else:
388 | console.error(f"Out format {a} not supported")
389 | usage()
390 | elif o in ["-f", "--out_file"]:
391 | out_file_path=a
392 | elif o in ["-e", "--env"]:
393 | env_file=a
394 | elif o in ["-l", "--log_file"]:
395 | log_file = a
396 | else:
397 | assert False, "unhandled option"
398 |
399 | #### LOGS ####
400 | logging.basicConfig(filename=log_file, filemode='w')
401 | LOGGER.setLevel(logging.DEBUG)
402 | check_mistapi_version()
403 | ### START ###
404 | apisession = mistapi.APISession(env_file=env_file)
405 | apisession.login()
406 | start(apisession, org_id, all)
407 |
--------------------------------------------------------------------------------
/scripts/orgs/org_complete_backup.py:
--------------------------------------------------------------------------------
1 | """
2 | -------------------------------------------------------------------------------
3 |
4 | Written by Thomas Munzer (tmunzer@juniper.net)
5 | Github repository: https://github.com/tmunzer/Mist_library/
6 |
7 | This script is licensed under the MIT License.
8 |
9 | -------------------------------------------------------------------------------
10 | Python script to backup a whole organization configuration and devices.
11 |
12 | This script will not change/create/delete/touch any existing objects. It will
13 | just retrieve every single object from the organization.
14 |
15 | -------
16 | Requirements:
17 | mistapi: https://pypi.org/project/mistapi/
18 |
19 | This script requires the following scripts to be in the same folder:
20 | - org_conf_backup.py
21 | - org_inventory_backup.py
22 |
23 | -------
24 | Usage:
25 | This script can be run as is (without parameters), or with the options below.
26 | If no options are defined, or if options are missing, the missing options will
27 | be asked by the script or the default values will be used.
28 |
29 | It is recommended to use an environment file to store the required information
30 | to request the Mist Cloud (see https://pypi.org/project/mistapi/ for more
31 | information about the available parameters).
32 |
33 | -------
34 | Script Parameters:
35 | -h, --help display this help
36 |
37 | -o, --org_id= Optional, org_id of the org to clone
38 |
39 | --src_env= Optional, env file to use to access the src org (see
40 | mistapi env file documentation here:
41 | https://pypi.org/project/mistapi/)
42 | default is "~/.mist_env"
43 |
44 | -l, --log_file= define the filepath/filename where to write the logs
45 | default is "./script.log"
46 | -b, --backup_folder= Path to the folder where to save the org backup (a
47 | subfolder will be created with the org name)
48 | default is "./org_backup"
49 |
50 | -d, --datetime append the current date and time (ISO format) to the
51 | backup name
52 | -t, --timestamp append the current timestamp to the backup
53 |
54 | -------
55 | Examples:
56 | python3 ./org_complete_backup.py
57 | python3 ./org_complete_backup.py \
58 | --org_id=203d3d02-xxxx-xxxx-xxxx-76896a3330f4
59 |
60 | """
61 |
62 | #####################################################################
63 | #### IMPORTS ####
64 | import sys
65 | import logging
66 | import argparse
67 | import datetime
68 |
69 | MISTAPI_MIN_VERSION = "0.52.0"
70 | try:
71 | import mistapi
72 | from mistapi.__logger import console
73 | except ImportError:
74 | print(
75 | """
76 | Critical:
77 | \"mistapi\" package is missing. Please use the pip command to install it.
78 |
79 | # Linux/macOS
80 | python3 -m pip install mistapi
81 |
82 | # Windows
83 | py -m pip install mistapi
84 | """
85 | )
86 | sys.exit(2)
87 |
88 | try:
89 | import org_conf_backup
90 | import org_inventory_backup
91 | except ImportError:
92 | print(
93 | """
94 | Critical:
95 | This script is using other scripts from the mist_library to perform all the
96 | action. Please make sure the following python files are in the same folder
97 | as the org_clone.py file:
98 | - org_conf_backup.py
99 | - org_inventory_backup.py
100 | """
101 | )
102 | sys.exit(2)
103 |
104 | #####################################################################
105 | #### PARAMETERS #####
106 | DEFAULT_BACKUP_FOLDER = "./org_backup"
107 | LOG_FILE = "./script.log"
108 | SRC_ENV_FILE = "~/.mist_env"
109 |
110 | #####################################################################
111 | #### LOGS ####
112 | LOGGER = logging.getLogger(__name__)
113 |
114 |
115 | #####################################################################
116 | #### ORG FUNCTIONS ####
117 | def _backup_org(
118 | source_mist_session: mistapi.APISession,
119 | org_id: str,
120 | backup_folder_param: str,
121 | backup_name: str,
122 | ) -> None:
123 | LOGGER.debug("org_complete_backup:_backup_org")
124 | LOGGER.debug("org_complete_backup:_backup_org:parameter:org_id: %s", org_id)
125 | LOGGER.debug(
126 | "org_complete_backup:_backup_org:parameter:backup_folder_param: %s",
127 | backup_folder_param,
128 | )
129 | LOGGER.debug(
130 | "org_complete_backup:_backup_org:parameter:backup_name: %s", backup_name
131 | )
132 |
133 | try:
134 | _print_new_step("Backing up SOURCE Org Configuration")
135 | org_conf_backup.start(
136 | mist_session=source_mist_session,
137 | org_id=org_id,
138 | backup_folder_param=backup_folder_param,
139 | backup_name=backup_name,
140 | )
141 | except Exception:
142 | sys.exit(255)
143 |
144 |
145 | #######
146 | #######
147 |
148 |
149 | def _backup_inventory(
150 | source_mist_session: mistapi.APISession,
151 | org_id: str,
152 | backup_folder_param: str,
153 | backup_name: str,
154 | ) -> None:
155 | LOGGER.debug("org_complete_backup:_backup_inventory")
156 | LOGGER.debug("org_complete_backup:_backup_inventory:parameter:org_id: %s", org_id)
157 | LOGGER.debug(
158 | "org_complete_backup:_backup_inventory:parameter:backup_folder_param: %s",
159 | backup_folder_param,
160 | )
161 | LOGGER.debug(
162 | "org_complete_backup:_backup_inventory:parameter:backup_name: %s", backup_name
163 | )
164 |
165 | _print_new_step("Backing up SOURCE Org Inventory")
166 | org_inventory_backup.start(
167 | mist_session=source_mist_session,
168 | org_id=org_id,
169 | backup_folder=backup_folder_param,
170 | backup_name=backup_name,
171 | )
172 |
173 |
174 | #######
175 | #######
176 |
177 |
178 | def _print_new_step(message) -> None:
179 | print()
180 | print("".center(80, "*"))
181 | print(f" {message} ".center(80, "*"))
182 | print("".center(80, "*"))
183 | print()
184 | LOGGER.info(message)
185 |
186 |
187 | #######
188 | #######
189 | def start(
190 | apisession: mistapi.APISession,
191 | org_id: str = "",
192 | backup_folder_param: str = "",
193 | backup_name: str = "",
194 | backup_name_date: bool = False,
195 | backup_name_ts: bool = False,
196 | ) -> None:
197 | """
198 | Start the process to clone the src org to the dst org
199 |
200 | PARAMS
201 | -------
202 | apisession : mistapi.APISession
203 | mistapi session with `Super User` access the source Org, already logged in
204 | org_id : str
205 | Optional, org_id of the org to clone
206 | backup_folder_param : str
207 | Path to the folder where to save the org backup (a subfolder will be created
208 | with the org name). default is "./org_backup"
209 | backup_name : str
210 | Name of the subfolder where the the backup files will be saved
211 | default is the org name
212 | backup_name_date : bool, default = False
213 | if `backup_name_date`==`True`, append the current date and time (ISO
214 | format) to the backup name
215 | backup_name_ts : bool, default = False
216 | if `backup_name_ts`==`True`, append the current timestamp to the backup
217 | name
218 | """
219 | LOGGER.debug("org_complete_backup:start")
220 | LOGGER.debug("org_complete_backup:start:parameter:org_id: %s", org_id)
221 | LOGGER.debug(
222 | "org_complete_backup:start:parameter:backup_folder_param: %s",
223 | backup_folder_param,
224 | )
225 | LOGGER.debug("org_complete_backup:start:parameter:backup_name: %s", backup_name)
226 | LOGGER.debug(
227 | "org_complete_backup:start:parameter:backup_name_date: %s", backup_name_date
228 | )
229 | LOGGER.debug(
230 | "org_complete_backup:start:parameter:backup_name_ts: %s", backup_name_ts
231 | )
232 |
233 | if not backup_folder_param:
234 | backup_folder_param = DEFAULT_BACKUP_FOLDER
235 | if not org_id:
236 | org_id = mistapi.cli.select_org(apisession)[0]
237 | org_name = mistapi.api.v1.orgs.orgs.getOrg(apisession, org_id).data["name"]
238 |
239 | if not backup_name:
240 | backup_name = org_name
241 | if backup_name_date:
242 | backup_name = f"{backup_name}_{datetime.datetime.isoformat(datetime.datetime.now()).split('.')[0].replace(':', '.')}"
243 | elif backup_name_ts:
244 | backup_name = f"{backup_name}_{round(datetime.datetime.timestamp(datetime.datetime.now()))}"
245 |
246 | _backup_org(apisession, org_id, backup_folder_param, backup_name)
247 | _backup_inventory(apisession, org_id, backup_folder_param, backup_name)
248 | _print_new_step("Process finished")
249 |
250 |
251 | ###############################################################################
252 | #### USAGE ####
253 | def usage(error_message: str = "") -> None:
254 | """
255 | display script usage
256 | """
257 | print(
258 | """
259 | -------------------------------------------------------------------------------
260 |
261 | Written by Thomas Munzer (tmunzer@juniper.net)
262 | Github repository: https://github.com/tmunzer/Mist_library/
263 |
264 | This script is licensed under the MIT License.
265 |
266 | -------------------------------------------------------------------------------
267 | Python script to backup a whole organization configuration and devices.
268 |
269 | This script will not change/create/delete/touch any existing objects. It will
270 | just retrieve every single object from the organization.
271 |
272 | -------
273 | Requirements:
274 | mistapi: https://pypi.org/project/mistapi/
275 |
276 | This script requires the following scripts to be in the same folder:
277 | - org_conf_backup.py
278 | - org_inventory_backup.py
279 |
280 | -------
281 | Usage:
282 | This script can be run as is (without parameters), or with the options below.
283 | If no options are defined, or if options are missing, the missing options will
284 | be asked by the script or the default values will be used.
285 |
286 | It is recommended to use an environment file to store the required information
287 | to request the Mist Cloud (see https://pypi.org/project/mistapi/ for more
288 | information about the available parameters).
289 |
290 | -------
291 | Script Parameters:
292 | -h, --help display this help
293 |
294 | --org_id= Optional, org_id of the org to clone
295 |
296 | -e, --env= Optional, env file to use to access the src org (see
297 | mistapi env file documentation here:
298 | https://pypi.org/project/mistapi/)
299 | default is "~/.mist_env"
300 |
301 | -l, --log_file= define the filepath/filename where to write the logs
302 | default is "./script.log"
303 | -b, --backup_folder= Path to the folder where to save the org backup (a
304 | subfolder will be created with the org name)
305 | default is "./org_backup"
306 |
307 | -d, --datetime append the current date and time (ISO format) to the
308 | backup name
309 | -t, --timestamp append the current timestamp to the backup
310 |
311 | -------
312 | Examples:
313 | python3 ./org_complete_backup.py
314 | python3 ./org_complete_backup.py \
315 | --org_id=203d3d02-xxxx-xxxx-xxxx-76896a3330f4
316 |
317 | """
318 | )
319 | if error_message:
320 | console.critical(error_message)
321 | sys.exit(0)
322 |
323 |
324 | def check_mistapi_version():
325 | """Check if the installed mistapi version meets the minimum requirement."""
326 |
327 | current_version = mistapi.__version__.split(".")
328 | required_version = MISTAPI_MIN_VERSION.split(".")
329 |
330 | try:
331 | for i, req in enumerate(required_version):
332 | if current_version[int(i)] > req:
333 | break
334 | if current_version[int(i)] < req:
335 | raise ImportError(
336 | f'"mistapi" package version {MISTAPI_MIN_VERSION} is required '
337 | f"but version {mistapi.__version__} is installed."
338 | )
339 | except ImportError as e:
340 | LOGGER.critical(str(e))
341 | LOGGER.critical("Please use the pip command to update it.")
342 | LOGGER.critical("")
343 | LOGGER.critical(" # Linux/macOS")
344 | LOGGER.critical(" python3 -m pip install --upgrade mistapi")
345 | LOGGER.critical("")
346 | LOGGER.critical(" # Windows")
347 | LOGGER.critical(" py -m pip install --upgrade mistapi")
348 | print(
349 | f"""
350 | Critical:\r\n
351 | {e}\r\n
352 | Please use the pip command to update it.
353 | # Linux/macOS
354 | python3 -m pip install --upgrade mistapi
355 | # Windows
356 | py -m pip install --upgrade mistapi
357 | """
358 | )
359 | sys.exit(2)
360 | finally:
361 | LOGGER.info(
362 | '"mistapi" package version %s is required, '
363 | "you are currently using version %s.",
364 | MISTAPI_MIN_VERSION,
365 | mistapi.__version__,
366 | )
367 |
368 |
369 | ###############################################################################
370 | #### SCRIPT ENTRYPOINT ####
371 | if __name__ == "__main__":
372 | parser = argparse.ArgumentParser(
373 | description="Backup a whole organization configuration and devices"
374 | )
375 | parser.add_argument("-o", "--org_id", help="Optional, org_id of the org to backup")
376 | parser.add_argument(
377 | "-e",
378 | "--env",
379 | "--src_env",
380 | dest="env",
381 | default=SRC_ENV_FILE,
382 | help="Optional, env file to use to access the src org",
383 | )
384 | parser.add_argument(
385 | "-l",
386 | "--log_file",
387 | default=LOG_FILE,
388 | help="define the filepath/filename where to write the logs",
389 | )
390 | parser.add_argument(
391 | "-b",
392 | "--backup_folder",
393 | default=DEFAULT_BACKUP_FOLDER,
394 | help="Path to the folder where to save the org backup",
395 | )
396 |
397 | time_group = parser.add_mutually_exclusive_group()
398 | time_group.add_argument(
399 | "-d",
400 | "--datetime",
401 | action="store_true",
402 | help="append the current date and time (ISO format) to the backup name",
403 | )
404 | time_group.add_argument(
405 | "-t",
406 | "--timestamp",
407 | action="store_true",
408 | help="append the current timestamp to the backup name",
409 | )
410 |
411 | args = parser.parse_args()
412 |
413 | ORG_ID = args.org_id
414 | BACKUP_FOLDER = args.backup_folder
415 | BACKUP_NAME = ""
416 | BACKUP_NAME_DATE = args.datetime
417 | BACKUP_NAME_TS = args.timestamp
418 | LOG_FILE = args.log_file
419 | SRC_ENV_FILE = args.env
420 |
421 | #### LOGS ####
422 | logging.basicConfig(filename=LOG_FILE, filemode="w")
423 | LOGGER.setLevel(logging.DEBUG)
424 | check_mistapi_version()
425 | ### MIST SESSION ###
426 | print(" API Session to access the Source Org ".center(80, "_"))
427 | APISESSION = mistapi.APISession(env_file=SRC_ENV_FILE)
428 | APISESSION.login()
429 |
430 | ### START ###
431 | start(
432 | APISESSION,
433 | org_id=ORG_ID,
434 | backup_folder_param=BACKUP_FOLDER,
435 | backup_name=BACKUP_NAME,
436 | backup_name_date=BACKUP_NAME_DATE,
437 | backup_name_ts=BACKUP_NAME_TS,
438 | )
439 |
--------------------------------------------------------------------------------
/scripts/reports/report_sites.py:
--------------------------------------------------------------------------------
1 | """
2 | -------------------------------------------------------------------------------
3 |
4 | Written by Thomas Munzer (tmunzer@juniper.net)
5 | Github repository: https://github.com/tmunzer/Mist_library/
6 |
7 | This script is licensed under the MIT License.
8 |
9 | -------------------------------------------------------------------------------
10 | Python script to generate a report of the Mist Sites and resolving the site
11 | group names.
12 | The script will display the report in the console and save it to a CSV file.
13 | It is possible to customize the columns to include in the report by modifying
14 | the `REPORT_HEADERS` list.
15 |
16 | -------
17 | Requirements:
18 | mistapi: https://pypi.org/project/mistapi/
19 |
20 | -------
21 | Usage:
22 | This script can be run as is (without parameters), or with the options below.
23 | If no options are defined, or if options are missing, the missing options will
24 | be asked by the script or the default values will be used.
25 |
26 | It is recommended to use an environment file to store the required information
27 | to request the Mist Cloud (see https://pypi.org/project/mistapi/ for more
28 | information about the available parameters).
29 |
30 | -------
31 | Options:
32 | -h, --help display this help
33 | -o, --org_id= Set the org_id
34 | -f, --out_file= define the filepath/filename where to save the data
35 | default is "./report_rogues.csv"
36 | -l, --log_file= define the filepath/filename where to write the logs
37 | default is "./script.log"
38 | -e, --env= define the env file to use (see mistapi env file documentation
39 | here: https://pypi.org/project/mistapi/)
40 | default is "~/.mist_env"
41 |
42 | -------
43 | Examples:
44 | python3 ./report_sites.py
45 | python3 ./report_sites.py --org_id=203d3d02-xxxx-xxxx-xxxx-76896a3330f4
46 |
47 | """
48 |
49 | #### IMPORTS #####
50 | import sys
51 | import csv
52 | import argparse
53 | import logging
54 |
55 | MISTAPI_MIN_VERSION = "0.56.4"
56 |
57 | try:
58 | import mistapi
59 | from mistapi.__logger import console as CONSOLE
60 | except ImportError:
61 | print("""
62 | Critical:
63 | \"mistapi\" package is missing. Please use the pip command to install it.
64 |
65 | # Linux/macOS
66 | python3 -m pip install mistapi
67 |
68 | # Windows
69 | py -m pip install mistapi
70 | """)
71 | sys.exit(2)
72 |
73 |
74 | #### PARAMETERS #####
75 | ENV_FILE = "~/.mist_env"
76 | CSV_FILE = "./report_sites.csv"
77 | LOG_FILE = "./script.log"
78 | CSV_DELIMITER = ","
79 | REPORT_HEADERS = [
80 | "country_code",
81 | "timezone",
82 | "address",
83 | "sitegroup_names",
84 | "sitegroup_ids",
85 | "notes",
86 | "latlng",
87 | "id",
88 | "name",
89 | "org_id",
90 | "created_time",
91 | "modified_time",
92 | "rftemplate_id",
93 | "aptemplate_id",
94 | "secpolicy_id",
95 | "alarmtemplate_id",
96 | "networktemplate_id",
97 | "gatewaytemplate_id",
98 | "sitetemplate_id",
99 | "tzoffset",
100 | "engagementEnabled",
101 | "analyticEnabled",
102 | "lat",
103 | "lng",
104 | ]
105 | #### LOGS ####
106 | LOGGER = logging.getLogger(__name__)
107 | out = sys.stdout
108 |
109 |
110 | #####################################################################
111 | # PROGRESS BAR AND DISPLAY
112 | class ProgressBar:
113 | """
114 | PROGRESS BAR AND DISPLAY
115 | """
116 |
117 | def __init__(self):
118 | self.steps_total = 0
119 | self.steps_count = 0
120 |
121 | def _pb_update(self, size: int = 80):
122 | if self.steps_count > self.steps_total:
123 | self.steps_count = self.steps_total
124 |
125 | percent = self.steps_count / self.steps_total
126 | delta = 17
127 | x = int((size - delta) * percent)
128 | print("Progress: ", end="")
129 | print(f"[{'█' * x}{'.' * (size - delta - x)}]", end="")
130 | print(f"{int(percent * 100)}%".rjust(5), end="")
131 |
132 | def _pb_new_step(
133 | self,
134 | message: str,
135 | result: str,
136 | inc: bool = False,
137 | size: int = 80,
138 | display_pbar: bool = True,
139 | ):
140 | if inc:
141 | self.steps_count += 1
142 | text = f"\033[A\033[F{message}"
143 | print(f"{text} ".ljust(size + 4, "."), result)
144 | print("".ljust(80))
145 | if display_pbar:
146 | self._pb_update(size)
147 |
148 | def _pb_title(
149 | self, text: str, size: int = 80, end: bool = False, display_pbar: bool = True
150 | ):
151 | print("\033[A")
152 | print(f" {text} ".center(size, "-"), "\n")
153 | if not end and display_pbar:
154 | print("".ljust(80))
155 | self._pb_update(size)
156 |
157 | def set_steps_total(self, steps_total: int):
158 | self.steps_total = steps_total
159 |
160 | def log_message(self, message, display_pbar: bool = True):
161 | self._pb_new_step(message, " ", display_pbar=display_pbar)
162 |
163 | def log_success(self, message, inc: bool = False, display_pbar: bool = True):
164 | LOGGER.info("%s: Success", message)
165 | self._pb_new_step(
166 | message, "\033[92m\u2714\033[0m\n", inc=inc, display_pbar=display_pbar
167 | )
168 |
169 | def log_warning(self, message, inc: bool = False, display_pbar: bool = True):
170 | LOGGER.warning("%s: Warning", message)
171 | self._pb_new_step(
172 | message, "\033[93m\u2b58\033[0m\n", inc=inc, display_pbar=display_pbar
173 | )
174 |
175 | def log_failure(self, message, inc: bool = False, display_pbar: bool = True):
176 | LOGGER.error("%s: Failure", message)
177 | self._pb_new_step(
178 | message, "\033[31m\u2716\033[0m\n", inc=inc, display_pbar=display_pbar
179 | )
180 |
181 | def log_title(self, message, end: bool = False, display_pbar: bool = True):
182 | LOGGER.info("%s", message)
183 | self._pb_title(message, end=end, display_pbar=display_pbar)
184 |
185 |
186 | PB = ProgressBar()
187 |
188 | ###############################################################################
189 | #### FUNCTIONS ####
190 |
191 |
192 | def _get_sitegroups(apisession: mistapi.APISession, org_id: str) -> dict:
193 | sitegroups = {}
194 | message = "Retrieving site groups..."
195 | PB.log_message(message, display_pbar=False)
196 | try:
197 | response = mistapi.api.v1.orgs.sitegroups.listOrgSiteGroups(
198 | apisession, org_id, limit=1000
199 | )
200 | if response.status_code == 200:
201 | data = mistapi.get_all(apisession, response)
202 | for site in data:
203 | sitegroups[site["id"]] = site["name"]
204 | PB.log_success(message, inc=False, display_pbar=False)
205 | else:
206 | PB.log_failure(message, inc=False, display_pbar=False)
207 | except Exception:
208 | PB.log_failure(message, inc=False, display_pbar=False)
209 | LOGGER.error("Exception occurred", exc_info=True)
210 | sys.exit(2)
211 | return sitegroups
212 |
213 |
214 | def _get_sites(
215 | apisession: mistapi.APISession,
216 | org_id: str,
217 | ) -> list:
218 | sites = []
219 | message = "Retrieving sites..."
220 | PB.log_message(message, display_pbar=False)
221 | try:
222 | response = mistapi.api.v1.orgs.sites.listOrgSites(
223 | apisession, org_id, limit=1000
224 | )
225 | if response.status_code == 200:
226 | sites = mistapi.get_all(apisession, response)
227 | PB.log_success(message, inc=False, display_pbar=False)
228 | else:
229 | PB.log_failure(message, inc=False, display_pbar=False)
230 | except Exception:
231 | PB.log_failure(message, inc=False, display_pbar=False)
232 | LOGGER.error("Exception occurred", exc_info=True)
233 | sys.exit(2)
234 | return sites
235 |
236 |
237 | def _process_sites(
238 | api_session: mistapi.APISession,
239 | org_id: str,
240 | sitegroups: dict,
241 | ) -> list:
242 | sites = []
243 | results = _get_sites(api_session, org_id)
244 | for r in results:
245 | sitegroup_ids = r.get("sitegroup_ids", [])
246 | sitegroup_names = []
247 | for sg_id in sitegroup_ids:
248 | sitegroup_names.append(sitegroups.get(sg_id, ""))
249 | r["sitegroup_names"] = sitegroup_names
250 | sites.append(r)
251 | return sites
252 |
253 |
254 | ### SAVE REPORT
255 | def _format_data(data: list) -> list:
256 | formatted = []
257 | message = "Formatting results..."
258 | PB.log_message(message, display_pbar=False)
259 | formatted.append(REPORT_HEADERS)
260 | for site_data in data:
261 | tmp = []
262 | for header in REPORT_HEADERS:
263 | tmp.append(site_data.get(header, ""))
264 | formatted.append(tmp)
265 | return formatted
266 |
267 |
268 | def _save_as_csv(data: list, csv_file: str):
269 | PB.log_title(f"Saving report to {csv_file}", display_pbar=False)
270 | with open(csv_file, "w", encoding="UTF8", newline="") as f:
271 | csv_writer = csv.writer(f, delimiter=CSV_DELIMITER)
272 | csv_writer.writerows(data)
273 | PB.log_success("Saving to file ", display_pbar=False)
274 |
275 |
276 | def _display_report(data: list):
277 | PB.log_title("Displaying report", display_pbar=False)
278 | print(mistapi.cli.tabulate(data[1:], headers=data[0], tablefmt="rounded_grid"))
279 |
280 |
281 | def start(
282 | api_session: mistapi.APISession,
283 | org_id: str,
284 | csv_file: str = CSV_FILE,
285 | ):
286 | """
287 | Start the report generation process.
288 |
289 | PARAMS
290 | ------
291 | apisession : mistapi.APISession
292 | mistapi session with `Observer` access the Org, already logged in
293 | org_id : str
294 | Mist Organization ID
295 | csv_file : str
296 | path to the CSV file to save the report
297 | """
298 | if not org_id:
299 | org_id = mistapi.cli.select_org(api_session)[0]
300 |
301 | sitegroups_map = _get_sitegroups(api_session, org_id)
302 | sites = _process_sites(api_session, org_id, sitegroups_map)
303 | formatted_data = _format_data(sites)
304 | _save_as_csv(formatted_data, csv_file)
305 | _display_report(formatted_data)
306 |
307 |
308 | ###############################################################################
309 | ### USAGE
310 |
311 |
312 | def usage(error_message: str | None = None):
313 | """
314 | show script usage
315 | """
316 | print(
317 | """
318 | -------------------------------------------------------------------------------
319 |
320 | Written by Thomas Munzer (tmunzer@juniper.net)
321 | Github repository: https://github.com/tmunzer/Mist_library/
322 |
323 | This script is licensed under the MIT License.
324 |
325 | -------------------------------------------------------------------------------
326 | Python script to generate a report of the Mist Sites and resolving the site
327 | group names.
328 | The script will display the report in the console and save it to a CSV file.
329 | It is possible to customize the columns to include in the report by modifying
330 | the `REPORT_HEADERS` list.
331 |
332 | -------
333 | Requirements:
334 | mistapi: https://pypi.org/project/mistapi/
335 |
336 | -------
337 | Usage:
338 | This script can be run as is (without parameters), or with the options below.
339 | If no options are defined, or if options are missing, the missing options will
340 | be asked by the script or the default values will be used.
341 |
342 | It is recommended to use an environment file to store the required information
343 | to request the Mist Cloud (see https://pypi.org/project/mistapi/ for more
344 | information about the available parameters).
345 |
346 | -------
347 | Options:
348 | -h, --help display this help
349 | -o, --org_id= Set the org_id
350 | -f, --out_file= define the filepath/filename where to save the data
351 | default is "./report_rogues.csv"
352 | -l, --log_file= define the filepath/filename where to write the logs
353 | default is "./script.log"
354 | -e, --env= define the env file to use (see mistapi env file documentation
355 | here: https://pypi.org/project/mistapi/)
356 | default is "~/.mist_env"
357 |
358 | -------
359 | Examples:
360 | python3 ./report_sites.py
361 | python3 ./report_sites.py --org_id=203d3d02-xxxx-xxxx-xxxx-76896a3330f4
362 | """
363 | )
364 | if error_message:
365 | CONSOLE.critical(error_message)
366 | sys.exit(0)
367 |
368 |
369 | def check_mistapi_version():
370 | """Check if the installed mistapi version meets the minimum requirement."""
371 |
372 | current_version = mistapi.__version__.split(".")
373 | required_version = MISTAPI_MIN_VERSION.split(".")
374 |
375 | try:
376 | for i, req in enumerate(required_version):
377 | if current_version[int(i)] > req:
378 | break
379 | if current_version[int(i)] < req:
380 | raise ImportError(
381 | f'"mistapi" package version {MISTAPI_MIN_VERSION} is required '
382 | f"but version {mistapi.__version__} is installed."
383 | )
384 | except ImportError as e:
385 | LOGGER.critical(str(e))
386 | LOGGER.critical("Please use the pip command to update it.")
387 | LOGGER.critical("")
388 | LOGGER.critical(" # Linux/macOS")
389 | LOGGER.critical(" python3 -m pip install --upgrade mistapi")
390 | LOGGER.critical("")
391 | LOGGER.critical(" # Windows")
392 | LOGGER.critical(" py -m pip install --upgrade mistapi")
393 | print(
394 | f"""
395 | Critical:\r\n
396 | {e}\r\n
397 | Please use the pip command to update it.
398 | # Linux/macOS
399 | python3 -m pip install --upgrade mistapi
400 | # Windows
401 | py -m pip install --upgrade mistapi
402 | """
403 | )
404 | sys.exit(2)
405 | finally:
406 | LOGGER.info(
407 | '"mistapi" package version %s is required, '
408 | "you are currently using version %s.",
409 | MISTAPI_MIN_VERSION,
410 | mistapi.__version__,
411 | )
412 |
413 |
414 | ###############################################################################
415 | ### ENTRY POINT
416 | if __name__ == "__main__":
417 | parser = argparse.ArgumentParser(
418 | description="Display list of open events/alarms that are not cleared"
419 | )
420 | parser.add_argument(
421 | "-e", "--env_file", help="define the env file to use", default=None
422 | )
423 | parser.add_argument(
424 | "-o",
425 | "--org_id",
426 | help="ID of the Mist organization",
427 | default="",
428 | )
429 | parser.add_argument(
430 | "-l",
431 | "--log_file",
432 | help="define the filepath/filename where to write the logs",
433 | default=LOG_FILE,
434 | )
435 | parser.add_argument(
436 | "-c",
437 | "--csv_file",
438 | help="Path to the CSV file where to save the result",
439 | default=CSV_FILE,
440 | )
441 |
442 | args = parser.parse_args()
443 |
444 | ENV_FILE = args.env_file
445 | ORG_ID = args.org_id
446 | LOG_FILE = args.log_file
447 | CSV_FILE = args.csv_file
448 |
449 | #### LOGS ####
450 | logging.basicConfig(filename=LOG_FILE, filemode="w")
451 | LOGGER.setLevel(logging.DEBUG)
452 | check_mistapi_version()
453 | ### MIST SESSION ###
454 | API_SESSION = mistapi.APISession(env_file=ENV_FILE)
455 | API_SESSION.login()
456 | ### START ###
457 | start(API_SESSION, ORG_ID, CSV_FILE)
458 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mist_library
2 |
3 | Utilities and example Python scripts that use the Mist Cloud APIs (via the [`mistapi`](https://pypi.org/project/mistapi/) package) to automate configuration, backups, imports and reporting for Mist organizations and sites.
4 |
5 | This repository collects small, focused scripts that are ready to run and easy to adapt for your environment.
6 |
7 |
8 | ## License
9 |
10 | This project is licensed under the MIT License — see the [LICENSE](LICENSE) file for details.
11 |
12 | ## Highlights
13 |
14 | - Collection of scripts for: configuration, EVPN, device operations, org/site backups & restores, imports, and reports.
15 | - Uses the [`mistapi`](https://pypi.org/project/mistapi/) Python package for authentication and API calls.
16 | - Scripts accept an environment file or interactive credentials and support dry-run modes where applicable.
17 |
18 | ## Quick start
19 |
20 | Prerequisites:
21 |
22 | - Python 3.8+ (recommend 3.9+)
23 | - A Mist API token or username/password with appropriate privileges
24 | - Repository checked out locally
25 |
26 | Install dependencies (recommended in a virtualenv):
27 |
28 | ```bash
29 | python -m pip install -r requirements.txt
30 | ```
31 |
32 | Run a script example (prints help):
33 |
34 | ```bash
35 | python scripts/orgs/org_conf_deploy.py -h
36 | ```
37 |
38 | Or run with a specific environment file:
39 |
40 | ```bash
41 | python scripts/orgs/org_conf_deploy.py -e my_env_file
42 | ```
43 |
44 | ## Environment file
45 |
46 | You can store credentials and common options in an environment file. By default the scripts look for `~/.mist_env` (scripts commonly use `ENV_FILE = "~/.mist_env"`) but you can pass `-e /path/to/env` to use a different file.
47 |
48 | Authoritative env vars:
49 |
50 | - `MIST_HOST` — Mist API host (for example `api.mist.com`).
51 | - `MIST_APITOKEN` — API token (preferred authentication).
52 | - `MIST_USER` / `MIST_PASSWORD` — username / password fallback when no token is provided.
53 | - `MIST_VAULT_MOUNT_POINT`, `MIST_VAULT_PATH`, `MIST_VAULT_TOKEN`, `MIST_VAULT_URL` — Vault integration variables used by `mistapi` when retrieving secrets from a HashiCorp Vault instance.
54 | - `CONSOLE_LOG_LEVEL`, `LOGGING_LOG_LEVEL` — optional numeric logging levels used in several scripts (not required by `mistapi` itself, but useful to control verbosity).
55 |
56 | If no credentials are present in the env file, most scripts will prompt interactively for missing values or fall back to the `mistapi` login flow.
57 |
58 | Example `~/.mist_env` (minimal):
59 |
60 | ```ini
61 | # Required: set either MIST_APITOKEN or the user/password pair
62 | MIST_HOST=api.mist.com
63 | MIST_APITOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
64 | # Or, alternatively:
65 | # MIST_USER=your.username@example.com
66 | # MIST_PASSWORD=yourpassword
67 |
68 | # Optional logging controls used by some scripts
69 | CONSOLE_LOG_LEVEL=20
70 | LOGGING_LOG_LEVEL=10
71 |
72 | # Optional: Vault integration (only if you use HashiCorp Vault with mistapi)
73 | # MIST_VAULT_URL=https://vault.example.com
74 | # MIST_VAULT_TOKEN=xxxxx
75 | # MIST_VAULT_MOUNT_POINT=secret
76 | # MIST_VAULT_PATH=secret/data/mist
77 | ```
78 |
79 | Notes:
80 |
81 | - The list above for [`mistapi`](https://pypi.org/project/mistapi/) env vars was verified against the installed `mistapi` package (v0.57.x). If you pin or use an older `mistapi` version, the available behavior may differ; several scripts in this repo include a `MISTAPI_MIN_VERSION` constant — check the script header for compatibility notes.
82 |
83 | ## Repository layout
84 |
85 | - `scripts/` — main scripts grouped by topic (configuration, orgs, sites, devices, exports, reports, nac, clients)
86 | - `utils/` — helper utilities (e.g. encryption)
87 | - `v-tool/` — additional tooling
88 | - `requirements.txt` — Python dependencies
89 |
90 | Each script contains a short header describing purpose, options and examples. Run any script with `-h` for usage details.
91 |
92 | ## Example workflows
93 |
94 | - Backup an organization configuration:
95 |
96 | ```bash
97 | python scripts/orgs/org_conf_backup.py -e ~/.mist_env --org-id
98 | ```
99 |
100 | - Deploy a saved org configuration (dry-run first):
101 |
102 | ```bash
103 | python scripts/orgs/org_conf_deploy.py -e ~/.mist_env --file backup.json --dry-run
104 | ```
105 |
106 | - Import guests from CSV into a site:
107 |
108 | ```bash
109 | python scripts/clients/import_guests.py -e ~/.mist_env --site --csv guests.csv
110 | ```
111 |
112 | ## Finding scripts
113 |
114 | There are many scripts. Here are a few high-level categories (see `scripts/` for the full list):
115 |
116 | - Configuration: `scripts/configuration/` (webhooks, auto-assignment, AP auto-upgrade)
117 | - EVPN helpers: `scripts/configuration/evpn_topology/`
118 | - Orgs / Backups: `scripts/orgs/` (backup, deploy, clone, inventory)
119 | - Devices: `scripts/devices/` (AP, gateway, switch helpers)
120 | - Imports: `scripts/clients/`, `scripts/nac/`, `scripts/sites/` (CSV-driven imports)
121 | - Reports / Exports: `scripts/reports/`, `scripts/exports/`
122 |
123 |
124 | ## Scripts index
125 |
126 | Below is a full index of scripts included in this repository. Each entry contains a short description and a relative link to the script.
127 |
128 | ### Organization / backup / deploy
129 |
130 | - [scripts/orgs/org_complete_backup.py](scripts/orgs/org_complete_backup.py) — Backup an entire organization configuration and devices (combines conf + inventory backups).
131 | - [scripts/orgs/org_complete_backup_encrypted.py](scripts/orgs/org_complete_backup_encrypted.py) — Same as `org_complete_backup.py` but writes an AES-encrypted backup.
132 | - [scripts/orgs/org_conf_backup.py](scripts/orgs/org_conf_backup.py) — Backup organization configuration objects (sites, templates, policies, etc.).
133 | - [scripts/orgs/org_conf_backup_encrypted.py](scripts/orgs/org_conf_backup_encrypted.py) — AES-encrypted organization configuration backup.
134 | - [scripts/orgs/org_conf_deploy.py](scripts/orgs/org_conf_deploy.py) — Deploy an organization configuration backup/template to a destination org (supports dry-run).
135 | - [scripts/orgs/org_conf_deploy_only.py](scripts/orgs/org_conf_deploy_only.py) — Deploy configuration objects only (helper used during deploy flows).
136 | - [scripts/orgs/org_conf_zeroize.py](scripts/orgs/org_conf_zeroize.py) — Zeroize an organization (remove config, sites and objects).
137 | - [scripts/orgs/org_inventory_backup.py](scripts/orgs/org_inventory_backup.py) — Backup all devices from an organization (claims, config, map positions, pictures).
138 | - [scripts/orgs/org_inventory_backup_encrypted.py](scripts/orgs/org_inventory_backup_encrypted.py) — AES-encrypted inventory backup.
139 | - [scripts/orgs/org_inventory_deploy.py](scripts/orgs/org_inventory_deploy.py) — Deploy inventory backup files to an organization (supports dry-run).
140 | - [scripts/orgs/org_inventory_restore_pictures.py](scripts/orgs/org_inventory_restore_pictures.py) — Restore device images from an inventory backup.
141 | - [scripts/orgs/org_clone.py](scripts/orgs/org_clone.py) — Clone a full organization to another org (configuration + optional creations).
142 | - [scripts/orgs/org_migration.py](scripts/orgs/org_migration.py) — Migrate an organization and optionally its devices to another org (supports unclaim options).
143 | - [scripts/orgs/inventory_claim.py](scripts/orgs/inventory_claim.py) — Claim devices to an org from a CSV list of claim codes.
144 | - [scripts/orgs/inventory_assign.py](scripts/orgs/inventory_assign.py) — Assign claimed devices to sites from a CSV.
145 | - [scripts/orgs/clone_template.py](scripts/orgs/clone_template.py) — Clone a single template (WLAN/LAN/WAN/HUB) between orgs.
146 | - [scripts/orgs/validate_site_variables.py](scripts/orgs/validate_site_variables.py) — Validate that variables used by templates are configured at the site level.
147 |
148 | #### Org admins
149 |
150 | - [scripts/orgs/admins/import_admins.py](scripts/orgs/admins/import_admins.py) — Invite/add administrators from a CSV.
151 | - [scripts/orgs/admins/delete_admins.py](scripts/orgs/admins/delete_admins.py) — Python script to revoke (remove) administrator access from a Mist organization using a CSV file containing administrator information. This script provides a safe and efficient way to bulk remove multiple administrators from an organization.
152 | - [scripts/orgs/admins/delete_expired_invites.py](scripts/orgs/admins/delete_expired_invites.py) — Python script to automatically identify and remove expired administrator invitations from a Mist organization. This script helps maintain clean administrator lists by removing stale invitations that were never accepted.
153 |
154 |
155 | ### Configuration
156 |
157 | - [scripts/configuration/config_webhook.py](scripts/configuration/config_webhook.py) — List/add/delete org/site webhooks.
158 | - [scripts/configuration/config_webhook_settings.json](scripts/configuration/config_webhook_settings.json) — Example/default webhook settings used by the webhook scripts.
159 | - [scripts/configuration/config_ap_auto_upgrade.py](scripts/configuration/config_ap_auto_upgrade.py) — Update AP auto-upgrade parameters in site settings.
160 | - [scripts/configuration/config_auto_site_assignment.py](scripts/configuration/config_auto_site_assignment.py) — Update org auto-assignment rules (IP subnet based site assignment).
161 |
162 | #### EVPN helpers
163 |
164 | - [scripts/configuration/evpn_topology/provision_evpntoplogy_vlans.py](scripts/configuration/evpn_topology/provision_evpntoplogy_vlans.py) — Generate VLANs and VRFs for EVPN topologies.
165 | - [scripts/configuration/evpn_topology/update_evpn_switch_ip.py](scripts/configuration/evpn_topology/update_evpn_switch_ip.py) — Update switch IP addresses inside an EVPN topology.
166 |
167 |
168 | ### Devices
169 |
170 | - [scripts/devices/rename_devices.py](scripts/devices/rename_devices.py) — Rename devices (AP, Switch, Router) from a CSV; finds site automatically.
171 | - [scripts/devices/update_managed_status.py](scripts/devices/update_managed_status.py) — Enable/disable managed mode on devices from a CSV; finds site automatically.
172 |
173 | #### AP helpers
174 |
175 | - [scripts/devices/aps/configure_ap_mgmt_vlan.py](scripts/devices/aps/configure_ap_mgmt_vlan.py) — Reconfigure management VLAN on APs across sites.
176 | - [scripts/devices/aps/report_power_constrained_aps.py](scripts/devices/aps/report_power_constrained_aps.py) — Report APs with power constraints; output CSV.
177 | - [scripts/devices/aps/report_bssids.py](scripts/devices/aps/report_bssids.py) — List APs and their BSSIDs for orgs/sites.
178 |
179 | #### Gateway helpers
180 |
181 | - [scripts/devices/gateways/report_gateway_firmware.py](scripts/devices/gateways/report_gateway_firmware.py) — Report SRX firmware versions and snapshot/backup status.
182 | - [scripts/devices/gateways/cluster_node_check.py](scripts/devices/gateways/cluster_node_check.py) — Report cluster node roles across an org.
183 | - [scripts/devices/gateways/fix_gateway_backup_firmware.py](scripts/devices/gateways/fix_gateway_backup_firmware.py) — Trigger snapshot/firmware backup on SRX devices.
184 | - [scripts/devices/gateways/bgp_peers_peak_values.py](scripts/devices/gateways/bgp_peers_peak_values.py) — Retrieve VPN peer statistics for gateways.
185 |
186 | #### Switch helpers
187 |
188 | - [scripts/devices/switches/check_local_commit_events.py](scripts/devices/switches/check_local_commit_events.py) — Retrieve CLI commit events from switches in an org.
189 | - [scripts/devices/switches/fix_switch_backup_firmware.py](scripts/devices/switches/fix_switch_backup_firmware.py) — Python script to automate firmware snapshot/backup operations on Juniper EX.
190 | - [scripts/devices/switches/update_port_config.py](scripts/devices/switches/update_port_config.py) — Reconfigure switch interfaces from a CSV by creating/updating device overrides.
191 | - [scripts/devices/switches/toggle_poe.py](scripts/devices/switches/toggle_poe.py) — Enable/disable/toggle PoE for a port profile in a switch template.
192 | - [scripts/devices/switches/report_switch_firmware.py](scripts/devices/switches/report_switch_firmware.py) — Generate report of switch snapshot/backup status.
193 |
194 |
195 | ### Clients / NAC / Imports
196 |
197 | - [scripts/clients/import_guests.py](scripts/clients/import_guests.py) — Import or update Guests from a CSV into an org or site.
198 | - [scripts/nac/import_client_macs.py](scripts/nac/import_client_macs.py) — Import client MAC addresses into NAC labels from CSV.
199 | - [scripts/nac/import_user_macs.py](scripts/nac/import_user_macs.py) — Import MAC addresses as NAC endpoints from CSV.
200 |
201 |
202 | ### Sites
203 |
204 | - [scripts/sites/import_sites.py](scripts/sites/import_sites.py) — Create sites in an org from a CSV.
205 | - [scripts/sites/import_floorplans.py](scripts/sites/import_floorplans.py) — Import Ekahau / iBwave floorplans into Mist.
206 | - [scripts/sites/import_psk.py](scripts/sites/import_psk.py) — Import PSKs for sites from a CSV.
207 | - [scripts/sites/site_conf_backup.py](scripts/sites/site_conf_backup.py) — Backup a single site's configuration.
208 | - [scripts/sites/site_conf_psk.py](scripts/sites/site_conf_psk.py) — Site PSK configuration helper.
209 | - [scripts/sites/site_conf_wlan.py](scripts/sites/site_conf_wlan.py) — Manage SSIDs (list/add/delete) at org/site level.
210 | - [scripts/sites/site_conf_wlan_settings.json](scripts/sites/site_conf_wlan_settings.json) — Example WLAN settings used by scripts.
211 | - [scripts/sites/update_sitegroups.py](scripts/sites/update_sitegroups.py) — Update the sitegroups assigned to sites from a CSV (append or replace).
212 | - [scripts/sites/update_sites_templates.py](scripts/sites/update_sites_templates.py) — Update templates assigned to sites and auto-assignment rules from a CSV.
213 | - [scripts/sites/fix_sites_geocoding.py](scripts/sites/fix_sites_geocoding.py) — Ensure sites have geo info (lat/lng, country_code, timezone) and fix missing data.
214 | - [scripts/sites/not_migrated_to_mistapi/site_conf_restore.py](scripts/sites/not_migrated_to_mistapi/site_conf_restore.py) — Legacy/site-specific restore helper (not migrated to mistapi).
215 |
216 |
217 | ### Exports & Reports
218 |
219 | - [scripts/exports/export_inventory.py](scripts/exports/export_inventory.py) — Export org inventory to CSV/JSON (includes claim codes).
220 | - [scripts/exports/export_search.py](scripts/exports/export_search.py) — Export historical data from Mist API to CSV/JSON.
221 | - [scripts/exports/export_org_events.py](scripts/exports/export_org_events.py) — Export organization events.
222 | - [scripts/exports/compare_org_events_summary.py](scripts/exports/compare_org_events_summary.py) — Helper to compare/aggregate exported org event summaries.
223 |
224 | #### Reports
225 |
226 | - [scripts/reports/report_admins_last_login.py](scripts/reports/report_admins_last_login.py) — Python script to generate a CSV report of organization admins and their last login activity
227 | - [scripts/reports/report_app_usage.py](scripts/reports/report_app_usage.py) — Application usage report for a site (hours/duration based).
228 | - [scripts/reports/list_open_events.py](scripts/reports/list_open_events.py) — Display events/alarms that are still open (tries to correlate open/close events).
229 | - [scripts/reports/report_sites.py](scripts/reports/report_sites.py) — Generate a report of sites (resolves site group names); CSV output.
230 | - [scripts/reports/report_sites_sles.py](scripts/reports/report_sites_sles.py) — Generate Site SLE (Service Level Expectations) reports.
231 | - [scripts/reports/report_wlans.py](scripts/reports/report_wlans.py) — List all WLANs and parameters for orgs/sites; CSV output.
232 | - [scripts/reports/report_inventory_site_notes.py](scripts/reports/report_inventory_site_notes.py) — Inventory report augmented with site notes and metadata.
233 | - [scripts/reports/report_rogues.py](scripts/reports/report_rogues.py) — Generate Rogue AP reports.
234 | - [scripts/reports/report_wan_services_usage.py](scripts/reports/report_wan_services_usage.py) — Report WAN Services usage across Gateway Templates, Hub Profiles, Service Policies and Gateways.
235 | - [scripts/reports/list_webhook_deliveries.py](scripts/reports/list_webhook_deliveries.py) — Extract and filter webhook deliveries; CSV export.
236 | - [scripts/reports/report_wlans.py](scripts/reports/report_wlans.py) — WLANs report (duplicate listing for quick access).
237 |
238 |
239 | ### Utilities
240 |
241 | - [scripts/utils/encryption.py](scripts/utils/encryption.py) — Utility functions for AES encryption/decryption used by encrypted backups.
242 |
243 |
244 |
245 | ## Contributing
246 |
247 | Contributions are welcome. Good ways to help:
248 |
249 | - Open issues for bugs or feature requests
250 | - Send small, focused pull requests that include a short description and example usage
251 | - Add or update script documentation at the top of the script file
252 |
253 | When editing scripts, keep backwards compatibility where possible and include tests or a smoke-check if you add complex logic.
--------------------------------------------------------------------------------
/scripts/nac/import_user_macs.py:
--------------------------------------------------------------------------------
1 | '''
2 | -------------------------------------------------------------------------------
3 |
4 | Written by Thomas Munzer (tmunzer@juniper.net)
5 | Github repository: https://github.com/tmunzer/Mist_library/
6 |
7 | This script is licensed under the MIT License.
8 |
9 | -------------------------------------------------------------------------------
10 | Python script import import a list of MAC Address as "NAC Endpoints" from a
11 | CSV File.
12 |
13 | -------
14 | Requirements:
15 | mistapi: https://pypi.org/project/mistapi/
16 |
17 | -------
18 | Usage:
19 | This script can be run as is (without parameters), or with the options below.
20 | If no options are defined, or if options are missing, the missing options will
21 | be asked by the script or the default values will be used.
22 |
23 | It is recommended to use an environment file to store the required information
24 | to request the Mist Cloud (see https://pypi.org/project/mistapi/ for more
25 | information about the available parameters).
26 |
27 | -------
28 | CSV Example:
29 | Example 1:
30 | #mac,labels,vlan,notes,name,radius_group
31 | 921b638445cd,"bldg1,floor1",vlan-100
32 | 721b638445ef,"bldg2,floor2",vlan-101,Canon Printers
33 | 721b638445ee,"bldg3,floor3",vlan-102,Printer2,VIP
34 | 921b638445ce,"bldg4,floor4",vlan-103
35 | 921b638445cf,"bldg5,floor5",vlan-104
36 |
37 |
38 | ------
39 | CSV Parameters
40 | Required:
41 | - mac MAC Address of the Wi-Fi client
42 |
43 | Optional:
44 | - labels Name of the Label. If not provided, a default label value can be
45 | passed in the script parameter with the -d option or will be asked
46 | by the script
47 | - vlan Name of the VLAN to assign to the endpoint
48 | - name Name to assign to the endpoint
49 | - radius_group
50 |
51 | -------
52 | Script Parameters:
53 | -h, --help display this help
54 | -f, --file= OPTIONAL: path to the CSV file
55 |
56 | -o, --org_id= Set the org_id where the webhook must be create/delete/retrieved
57 | This parameter cannot be used if -s/--site_id is used.
58 | If no org_id and not site_id are defined, the script will show
59 | a menu to select the org/the site.
60 |
61 | -f, --file path the to csv file to load
62 | default is ./import_user_macs.csv
63 |
64 | -l, --log_file= define the filepath/filename where to write the logs
65 | default is "./script.log"
66 | -e, --env= define the env file to use (see mistapi env file documentation
67 | here: https://pypi.org/project/mistapi/)
68 | default is "~/.mist_env"
69 |
70 | -------
71 | Examples:
72 | python3 ./import_user_macs.py
73 | python3 ./import_user_macs.py \
74 | -f ./import_user_macs.csv \
75 | --org_id=203d3d02-xxxx-xxxx-xxxx-76896a3330f4
76 |
77 | '''
78 | #### IMPORTS ####
79 | import sys
80 | import csv
81 | import getopt
82 | import logging
83 |
84 | MISTAPI_MIN_VERSION = "0.52.4"
85 |
86 | try:
87 | import mistapi
88 | from mistapi.__logger import console as CONSOLE
89 | except:
90 | print("""
91 | Critical:
92 | \"mistapi\" package is missing. Please use the pip command to install it.
93 |
94 | # Linux/macOS
95 | python3 -m pip install mistapi
96 |
97 | # Windows
98 | py -m pip install mistapi
99 | """)
100 | sys.exit(2)
101 |
102 | #### PARAMETERS #####
103 | ENV_FILE="~/.mist_env"
104 | CSV_FILE="./import_user_macs.csv"
105 | LOG_FILE = "./script.log"
106 |
107 | #####################################################################
108 | #### LOGS ####
109 | LOGGER = logging.getLogger(__name__)
110 |
111 | #####################################################################
112 | # PROGRESS BAR AND DISPLAY
113 | class ProgressBar:
114 | """
115 | PROGRESS BAR AND DISPLAY
116 | """
117 | def __init__(self):
118 | self.steps_total = 0
119 | self.steps_count = 0
120 |
121 | def _pb_update(self, size: int = 80):
122 | if self.steps_count > self.steps_total:
123 | self.steps_count = self.steps_total
124 |
125 | percent = self.steps_count / self.steps_total
126 | delta = 17
127 | x = int((size - delta) * percent)
128 | print(f"Progress: ", end="")
129 | print(f"[{'█'*x}{'.'*(size-delta-x)}]", end="")
130 | print(f"{int(percent*100)}%".rjust(5), end="")
131 |
132 | def _pb_new_step(
133 | self,
134 | message: str,
135 | result: str,
136 | inc: bool = False,
137 | size: int = 80,
138 | display_pbar: bool = True,
139 | ):
140 | if inc:
141 | self.steps_count += 1
142 | text = f"\033[A\033[F{message}"
143 | print(f"{text} ".ljust(size + 4, "."), result)
144 | print("".ljust(80))
145 | if display_pbar:
146 | self._pb_update(size)
147 |
148 | def _pb_title(
149 | self, text: str, size: int = 80, end: bool = False, display_pbar: bool = True
150 | ):
151 | print("\033[A")
152 | print(f" {text} ".center(size, "-"), "\n")
153 | if not end and display_pbar:
154 | print("".ljust(80))
155 | self._pb_update(size)
156 |
157 | def set_steps_total(self, steps_total: int):
158 | self.steps_total = steps_total
159 |
160 | def log_message(self, message, display_pbar: bool = True):
161 | self._pb_new_step(message, " ", display_pbar=display_pbar)
162 |
163 | def log_success(self, message, inc: bool = False, display_pbar: bool = True):
164 | LOGGER.info(f"{message}: Success")
165 | self._pb_new_step(
166 | message, "\033[92m\u2714\033[0m\n", inc=inc, display_pbar=display_pbar
167 | )
168 |
169 | def log_warning(self, message, inc: bool = False, display_pbar: bool = True):
170 | LOGGER.warning(f"{message}: Warning")
171 | self._pb_new_step(
172 | message, "\033[93m\u2B58\033[0m\n", inc=inc, display_pbar=display_pbar)
173 |
174 | def log_failure(self, message, inc: bool = False, display_pbar: bool = True):
175 | LOGGER.error(f"{message}: Failure")
176 | self._pb_new_step(
177 | message, "\033[31m\u2716\033[0m\n", inc=inc, display_pbar=display_pbar
178 | )
179 |
180 | def log_title(self, message, end: bool = False, display_pbar: bool = True):
181 | LOGGER.info(f"{message}")
182 | self._pb_title(message, end=end, display_pbar=display_pbar)
183 |
184 | PB = ProgressBar()
185 |
186 | #####################################################################
187 | # FUNCTIONS
188 |
189 |
190 | def import_usermacs(
191 | apisession:mistapi.APISession,
192 | org_id:str,
193 | usermacs:dict
194 | ):
195 | try:
196 | message = "Importing user macs"
197 | PB.log_message(message, display_pbar=False)
198 | resp = mistapi.api.v1.orgs.usermacs.importOrgUserMacs(apisession, org_id, usermacs)
199 | if resp.status_code == 200:
200 | PB.log_success(message, display_pbar=False)
201 | return
202 | else:
203 | PB.log_failure(message, display_pbar=False)
204 | except:
205 | PB.log_failure(message, display_pbar=False)
206 | LOGGER.error("Exception occurred", exc_info=True)
207 |
208 |
209 | def _read_csv(csv_file:str):
210 | message = "Processing CSV File"
211 | try:
212 | PB.log_message(message, display_pbar=False)
213 | LOGGER.debug(f"_read_csv:opening CSV file {csv_file}")
214 | with open(csv_file, "r") as f:
215 | data = csv.reader(f, skipinitialspace=True, quotechar='"')
216 | data = [[c.replace("\ufeff", "") for c in row] for row in data]
217 | fields = []
218 | entries = []
219 | for line in data:
220 | LOGGER.debug(f"_read_csv:new csv line:{line}")
221 | if not fields:
222 | for column in line:
223 | fields.append(column.replace("#", "").strip())
224 | LOGGER.debug(f"_read_csv:detected CSV fields: {fields}")
225 | if "mac" not in fields:
226 | LOGGER.critical(f"_read_csv:mac address not in CSV file... Exiting...")
227 | PB.log_failure(message, display_pbar=False)
228 | CONSOLE.critical(
229 | "CSV format invalid (MAC Address not found). "
230 | "Please double check it... Exiting..."
231 | )
232 | sys.exit(255)
233 | else:
234 | entry = {}
235 | i = 0
236 | for column in line:
237 | field = fields[i]
238 | if field == "labels":
239 | entry[field] = []
240 | for label in column.split(","):
241 | entry[field].append(label.strip())
242 | else:
243 | entry[field] = column.strip()
244 | i += 1
245 | entries.append(entry)
246 | LOGGER.debug(f"_read_csv:new entry processed: {entry['mac']} with label {entry.get('labels')}")
247 | PB.log_success(message, display_pbar=False)
248 | return entries
249 | except Exception as e:
250 | PB.log_failure(message, display_pbar=False)
251 | LOGGER.error("Exception occurred", exc_info=True)
252 |
253 | def start(apisession:mistapi.APISession, org_id:str=None, csv_file:str=CSV_FILE):
254 | """
255 | Start the process
256 |
257 | PARAMS
258 | -------
259 | apisession : mistapi.APISession
260 | mistapi session with `Super User` access the Org, already logged in
261 | org_id : str
262 | org_id where the webhook guests be added. This parameter cannot be used if "site_id"
263 | is used. If no org_id and not site_id are defined, the script will show a menu to
264 | select the org/the site.
265 | csv_file : str
266 | Path to the CSV file where the guests information are stored.
267 | default is "./import_guests.csv"
268 | """
269 | if not org_id:
270 | org_id = mistapi.cli.select_org(apisession)[0]
271 |
272 | PB.log_title("Reading data", display_pbar=False)
273 | print()
274 | entries = _read_csv(csv_file)
275 |
276 | import_usermacs(apisession, org_id, entries)
277 |
278 |
279 | ###############################################################################
280 | # USAGE
281 | def usage(error_message:str=None):
282 | """
283 | show script usage
284 | """
285 | print("""
286 | -------------------------------------------------------------------------------
287 |
288 | Written by Thomas Munzer (tmunzer@juniper.net)
289 | Github repository: https://github.com/tmunzer/Mist_library/
290 |
291 | This script is licensed under the MIT License.
292 |
293 | -------------------------------------------------------------------------------
294 | Python script import import a list of MAC Address as "NAC Endpoints" from a
295 | CSV File.
296 |
297 | -------
298 | Requirements:
299 | mistapi: https://pypi.org/project/mistapi/
300 |
301 | -------
302 | Usage:
303 | This script can be run as is (without parameters), or with the options below.
304 | If no options are defined, or if options are missing, the missing options will
305 | be asked by the script or the default values will be used.
306 |
307 | It is recommended to use an environment file to store the required information
308 | to request the Mist Cloud (see https://pypi.org/project/mistapi/ for more
309 | information about the available parameters).
310 |
311 | -------
312 | CSV Example:
313 | Example 1:
314 | mac,labels,vlan,notes,name,radius_group
315 | 921b638445cd,"bldg1,floor1",vlan-100
316 | 721b638445ef,"bldg2,floor2",vlan-101,Canon Printers
317 | 721b638445ee,"bldg3,floor3",vlan-102,Printer2,VIP
318 | 921b638445ce,"bldg4,floor4",vlan-103
319 | 921b638445cf,"bldg5,floor5",vlan-104
320 |
321 |
322 | ------
323 | CSV Parameters
324 | Required:
325 | - mac MAC Address of the Wi-Fi client
326 |
327 | Optional:
328 | - labels Name of the Label. If not provided, a default label value can be
329 | passed in the script parameter with the -d option or will be asked
330 | by the script
331 | - vlan Name of the VLAN to assign to the endpoint
332 | - name Name to assign to the endpoint
333 | - radius_group
334 |
335 | -------
336 | Script Parameters:
337 | -h, --help display this help
338 | -f, --file= OPTIONAL: path to the CSV file
339 |
340 | -o, --org_id= Set the org_id where the webhook must be create/delete/retrieved
341 | This parameter cannot be used if -s/--site_id is used.
342 | If no org_id and not site_id are defined, the script will show
343 | a menu to select the org/the site.
344 |
345 | -f, --file path the to csv file to load
346 | default is ./import_user_macs.csv
347 |
348 | -l, --log_file= define the filepath/filename where to write the logs
349 | default is "./script.log"
350 | -e, --env= define the env file to use (see mistapi env file documentation
351 | here: https://pypi.org/project/mistapi/)
352 | default is "~/.mist_env"
353 |
354 | -------
355 | Examples:
356 | python3 ./import_user_macs.py
357 | python3 ./import_user_macs.py \
358 | -f ./import_user_macs.csv \
359 | --org_id=203d3d02-xxxx-xxxx-xxxx-76896a3330f4
360 |
361 | """)
362 | if error_message:
363 | CONSOLE.critical(error_message)
364 | sys.exit(0)
365 |
366 | def check_mistapi_version():
367 | """Check if the installed mistapi version meets the minimum requirement."""
368 |
369 | current_version = mistapi.__version__.split(".")
370 | required_version = MISTAPI_MIN_VERSION.split(".")
371 |
372 | try:
373 | for i, req in enumerate(required_version):
374 | if current_version[int(i)] > req:
375 | break
376 | if current_version[int(i)] < req:
377 | raise ImportError(
378 | f'"mistapi" package version {MISTAPI_MIN_VERSION} is required '
379 | f"but version {mistapi.__version__} is installed."
380 | )
381 | except ImportError as e:
382 | LOGGER.critical(str(e))
383 | LOGGER.critical("Please use the pip command to update it.")
384 | LOGGER.critical("")
385 | LOGGER.critical(" # Linux/macOS")
386 | LOGGER.critical(" python3 -m pip install --upgrade mistapi")
387 | LOGGER.critical("")
388 | LOGGER.critical(" # Windows")
389 | LOGGER.critical(" py -m pip install --upgrade mistapi")
390 | print(
391 | f"""
392 | Critical:\r\n
393 | {e}\r\n
394 | Please use the pip command to update it.
395 | # Linux/macOS
396 | python3 -m pip install --upgrade mistapi
397 | # Windows
398 | py -m pip install --upgrade mistapi
399 | """
400 | )
401 | sys.exit(2)
402 | finally:
403 | LOGGER.info(
404 | '"mistapi" package version %s is required, '
405 | "you are currently using version %s.",
406 | MISTAPI_MIN_VERSION,
407 | mistapi.__version__
408 | )
409 |
410 | #####################################################################
411 | ##### ENTRY POINT ####
412 |
413 | if __name__ == "__main__":
414 | try:
415 | opts, args = getopt.getopt(sys.argv[1:], "ho:f:e:l:", [
416 | "help", "org_id=", "file=", "env=", "log_file="])
417 | except getopt.GetoptError as err:
418 | CONSOLE.error(err)
419 | usage()
420 |
421 | ORG_ID = None
422 | for o, a in opts:
423 | if o in ["-h", "--help"]:
424 | usage()
425 | elif o in ["-o", "--org_id"]:
426 | ORG_ID = a
427 | elif o in ["-f", "--file"]:
428 | CSV_FILE = a
429 | elif o in ["-e", "--env"]:
430 | ENV_FILE = a
431 | elif o in ["-l", "--log_file"]:
432 | LOG_FILE = a
433 | else:
434 | assert False, "unhandled option"
435 | #### LOGS ####
436 | logging.basicConfig(filename=LOG_FILE, filemode='w')
437 | LOGGER.setLevel(logging.DEBUG)
438 | check_mistapi_version()
439 | ### START ###
440 | apisession = mistapi.APISession(env_file=ENV_FILE)
441 | apisession.login()
442 | start(apisession, ORG_ID, CSV_FILE)
443 |
444 |
--------------------------------------------------------------------------------
/scripts/reports/report_inventory_site_notes.py:
--------------------------------------------------------------------------------
1 | """
2 | -------------------------------------------------------------------------------
3 |
4 | Written by Thomas Munzer (tmunzer@juniper.net)
5 | Github repository: https://github.com/tmunzer/Mist_library/
6 |
7 | This script is licensed under the MIT License.
8 |
9 | -------------------------------------------------------------------------------
10 | Python script to generate a report of the Mist Inventory and add information
11 | related to the Site the devices are assigned to.
12 | The script will display the report in the console and save it to a CSV file.
13 | It is possible to customize the columns to include in the report by modifying
14 | the `REPORT_HEADERS` list.
15 |
16 | -------
17 | Requirements:
18 | mistapi: https://pypi.org/project/mistapi/
19 |
20 | -------
21 | Usage:
22 | This script can be run as is (without parameters), or with the options below.
23 | If no options are defined, or if options are missing, the missing options will
24 | be asked by the script or the default values will be used.
25 |
26 | It is recommended to use an environment file to store the required information
27 | to request the Mist Cloud (see https://pypi.org/project/mistapi/ for more
28 | information about the available parameters).
29 |
30 | -------
31 | Options:
32 | -h, --help display this help
33 | -s, --org_id= Set the org_id
34 | -f, --out_file= define the filepath/filename where to save the data
35 | default is "./report_rogues.csv"
36 | -l, --log_file= define the filepath/filename where to write the logs
37 | default is "./script.log"
38 | -e, --env= define the env file to use (see mistapi env file documentation
39 | here: https://pypi.org/project/mistapi/)
40 | default is "~/.mist_env"
41 |
42 | -------
43 | Examples:
44 | python3 ./report_inventory_site_notes.py
45 | python3 ./report_inventory_site_notes.py --site_ids=203d3d02-xxxx-xxxx-xxxx-76896a3330f4
46 |
47 | """
48 |
49 | #### IMPORTS #####
50 | import sys
51 | import csv
52 | import argparse
53 | import logging
54 |
55 | MISTAPI_MIN_VERSION = "0.56.4"
56 |
57 | try:
58 | import mistapi
59 | from mistapi.__logger import console as CONSOLE
60 | except ImportError:
61 | print("""
62 | Critical:
63 | \"mistapi\" package is missing. Please use the pip command to install it.
64 |
65 | # Linux/macOS
66 | python3 -m pip install mistapi
67 |
68 | # Windows
69 | py -m pip install mistapi
70 | """)
71 | sys.exit(2)
72 |
73 |
74 | #### PARAMETERS #####
75 | ENV_FILE = "~/.mist_env"
76 | CSV_FILE = "./report_inventory_site_notes.csv"
77 | LOG_FILE = "./script.log"
78 | CSV_DELIMITER = ","
79 | REPORT_HEADERS = [
80 | # "org_id",
81 | "site_name",
82 | "site_notes",
83 | "site_id",
84 | "type",
85 | "hostname",
86 | # "name",
87 | "mac",
88 | "serial",
89 | "model",
90 | # "magic",
91 | # "id",
92 | # "adopted",
93 | "version",
94 | "deviceprofile_id",
95 | "connected",
96 | # "site_country_code",
97 | # "site_timezone",
98 | # "site_address",
99 | # "site_latlng",
100 | # "site_rftemplate_id",
101 | # "site_aptemplate_id",
102 | # "site_secpolicy_id",
103 | # "site_alarmtemplate_id",
104 | # "site_networktemplate_id",
105 | # "site_gatewaytemplate_id",
106 | # "site_sitetemplate_id",
107 | ]
108 | #### LOGS ####
109 | LOGGER = logging.getLogger(__name__)
110 | out = sys.stdout
111 |
112 |
113 | #####################################################################
114 | # PROGRESS BAR AND DISPLAY
115 | class ProgressBar:
116 | """
117 | PROGRESS BAR AND DISPLAY
118 | """
119 |
120 | def __init__(self):
121 | self.steps_total = 0
122 | self.steps_count = 0
123 |
124 | def _pb_update(self, size: int = 80):
125 | if self.steps_count > self.steps_total:
126 | self.steps_count = self.steps_total
127 |
128 | percent = self.steps_count / self.steps_total
129 | delta = 17
130 | x = int((size - delta) * percent)
131 | print("Progress: ", end="")
132 | print(f"[{'█' * x}{'.' * (size - delta - x)}]", end="")
133 | print(f"{int(percent * 100)}%".rjust(5), end="")
134 |
135 | def _pb_new_step(
136 | self,
137 | message: str,
138 | result: str,
139 | inc: bool = False,
140 | size: int = 80,
141 | display_pbar: bool = True,
142 | ):
143 | if inc:
144 | self.steps_count += 1
145 | text = f"\033[A\033[F{message}"
146 | print(f"{text} ".ljust(size + 4, "."), result)
147 | print("".ljust(80))
148 | if display_pbar:
149 | self._pb_update(size)
150 |
151 | def _pb_title(
152 | self, text: str, size: int = 80, end: bool = False, display_pbar: bool = True
153 | ):
154 | print("\033[A")
155 | print(f" {text} ".center(size, "-"), "\n")
156 | if not end and display_pbar:
157 | print("".ljust(80))
158 | self._pb_update(size)
159 |
160 | def set_steps_total(self, steps_total: int):
161 | self.steps_total = steps_total
162 |
163 | def log_message(self, message, display_pbar: bool = True):
164 | self._pb_new_step(message, " ", display_pbar=display_pbar)
165 |
166 | def log_success(self, message, inc: bool = False, display_pbar: bool = True):
167 | LOGGER.info("%s: Success", message)
168 | self._pb_new_step(
169 | message, "\033[92m\u2714\033[0m\n", inc=inc, display_pbar=display_pbar
170 | )
171 |
172 | def log_warning(self, message, inc: bool = False, display_pbar: bool = True):
173 | LOGGER.warning("%s: Warning", message)
174 | self._pb_new_step(
175 | message, "\033[93m\u2b58\033[0m\n", inc=inc, display_pbar=display_pbar
176 | )
177 |
178 | def log_failure(self, message, inc: bool = False, display_pbar: bool = True):
179 | LOGGER.error("%s: Failure", message)
180 | self._pb_new_step(
181 | message, "\033[31m\u2716\033[0m\n", inc=inc, display_pbar=display_pbar
182 | )
183 |
184 | def log_title(self, message, end: bool = False, display_pbar: bool = True):
185 | LOGGER.info("%s", message)
186 | self._pb_title(message, end=end, display_pbar=display_pbar)
187 |
188 |
189 | PB = ProgressBar()
190 |
191 | ###############################################################################
192 | #### FUNCTIONS ####
193 |
194 |
195 | def _get_inventory(apisession: mistapi.APISession, org_id: str) -> list:
196 | inventory = []
197 | message = "Retrieving inventory..."
198 | PB.log_message(message, display_pbar=False)
199 | try:
200 | response = mistapi.api.v1.orgs.inventory.getOrgInventory(
201 | apisession, org_id, limit=1000
202 | )
203 | if response.status_code == 200:
204 | inventory = mistapi.get_all(apisession, response)
205 | PB.log_success(message, inc=False, display_pbar=False)
206 | else:
207 | PB.log_failure(message, inc=False, display_pbar=False)
208 | except Exception:
209 | PB.log_failure(message, inc=False, display_pbar=False)
210 | LOGGER.error("Exception occurred", exc_info=True)
211 | sys.exit(2)
212 | return inventory
213 |
214 |
215 | def _get_sites(
216 | apisession: mistapi.APISession,
217 | org_id: str,
218 | ) -> list:
219 | sites = []
220 | message = "Retrieving sites..."
221 | PB.log_message(message, display_pbar=False)
222 | try:
223 | response = mistapi.api.v1.orgs.sites.listOrgSites(
224 | apisession, org_id, limit=1000
225 | )
226 | if response.status_code == 200:
227 | sites = mistapi.get_all(apisession, response)
228 | PB.log_success(message, inc=False, display_pbar=False)
229 | else:
230 | PB.log_failure(message, inc=False, display_pbar=False)
231 | except Exception:
232 | PB.log_failure(message, inc=False, display_pbar=False)
233 | LOGGER.error("Exception occurred", exc_info=True)
234 | sys.exit(2)
235 | return sites
236 |
237 |
238 | def _process_inventory(
239 | inventory: list,
240 | sites: list,
241 | ) -> list:
242 | results = []
243 | for device in inventory:
244 | if (
245 | device.get("site_id")
246 | and device["site_id"] != "00000000-0000-0000-0000-000000000000"
247 | ):
248 | site = next((s for s in sites if s["id"] == device["site_id"]), {})
249 | device["site_notes"] = site.get("notes", "")
250 | device["site_name"] = site.get("name", "")
251 | device["site_country_code"] = site.get("country_code", "")
252 | device["site_timezone"] = site.get("timezone", "")
253 | device["site_latlng"] = site.get("latlng", "")
254 | device["site_rftemplate_id"] = site.get("rftemplate_id", "")
255 | device["site_aptemplate_id"] = site.get("aptemplate_id", "")
256 | device["site_secpolicy_id"] = site.get("secpolicy_id", "")
257 | device["site_alarmtemplate_id"] = site.get("alarmtemplate_id", "")
258 | device["site_networktemplate_id"] = site.get("networktemplate_id", "")
259 | device["site_gatewaytemplate_id"] = site.get("gatewaytemplate_id", "")
260 | device["site_sitetemplate_id"] = site.get("sitetemplate_id", "")
261 |
262 | results.append(device)
263 | return results
264 |
265 |
266 | ### SAVE REPORT
267 | def _format_data(data: list) -> list:
268 | formatted = []
269 | message = "Formatting results..."
270 | PB.log_message(message, display_pbar=False)
271 | formatted.append(REPORT_HEADERS)
272 | for site_data in data:
273 | tmp = []
274 | for header in REPORT_HEADERS:
275 | tmp.append(site_data.get(header, ""))
276 | formatted.append(tmp)
277 | return formatted
278 |
279 |
280 | def _save_as_csv(data: list, csv_file: str):
281 | PB.log_title(f"Saving report to {csv_file}", display_pbar=False)
282 | with open(csv_file, "w", encoding="UTF8", newline="") as f:
283 | csv_writer = csv.writer(f, delimiter=CSV_DELIMITER)
284 | csv_writer.writerows(data)
285 | PB.log_success("Saving to file ", display_pbar=False)
286 |
287 |
288 | def _display_report(data: list):
289 | PB.log_title("Displaying report", display_pbar=False)
290 | print(mistapi.cli.tabulate(data[1:], headers=data[0], tablefmt="rounded_grid"))
291 |
292 |
293 | def start(
294 | api_session: mistapi.APISession,
295 | org_id: str,
296 | csv_file: str = CSV_FILE,
297 | ):
298 | """
299 | Start the report generation process.
300 |
301 | PARAMS
302 | ------
303 | apisession : mistapi.APISession
304 | mistapi session with `Observer` access the Org, already logged in
305 | org_id : str
306 | Mist Organization ID
307 | csv_file : str
308 | path to the CSV file to save the report
309 | """
310 | if not org_id:
311 | org_id = mistapi.cli.select_org(api_session)[0]
312 |
313 | inventory = _get_inventory(api_session, org_id)
314 | sites = _get_sites(api_session, org_id)
315 | results = _process_inventory(inventory, sites)
316 | formatted_data = _format_data(results)
317 | _save_as_csv(formatted_data, csv_file)
318 | _display_report(formatted_data)
319 |
320 |
321 | ###############################################################################
322 | ### USAGE
323 |
324 |
325 | def usage(error_message: str | None = None):
326 | """
327 | show script usage
328 | """
329 | print(
330 | """
331 | -------------------------------------------------------------------------------
332 |
333 | Written by Thomas Munzer (tmunzer@juniper.net)
334 | Github repository: https://github.com/tmunzer/Mist_library/
335 |
336 | This script is licensed under the MIT License.
337 |
338 | -------------------------------------------------------------------------------
339 | Python script to generate a report of the Mist Inventory and add information
340 | related to the Site the devices are assigned to.
341 | The script will display the report in the console and save it to a CSV file.
342 | It is possible to customize the columns to include in the report by modifying
343 | the `REPORT_HEADERS` list.
344 |
345 | -------
346 | Requirements:
347 | mistapi: https://pypi.org/project/mistapi/
348 |
349 | -------
350 | Usage:
351 | This script can be run as is (without parameters), or with the options below.
352 | If no options are defined, or if options are missing, the missing options will
353 | be asked by the script or the default values will be used.
354 |
355 | It is recommended to use an environment file to store the required information
356 | to request the Mist Cloud (see https://pypi.org/project/mistapi/ for more
357 | information about the available parameters).
358 |
359 | -------
360 | Options:
361 | -h, --help display this help
362 | -s, --org_id= Set the org_id
363 | -f, --out_file= define the filepath/filename where to save the data
364 | default is "./report_rogues.csv"
365 | -l, --log_file= define the filepath/filename where to write the logs
366 | default is "./script.log"
367 | -e, --env= define the env file to use (see mistapi env file documentation
368 | here: https://pypi.org/project/mistapi/)
369 | default is "~/.mist_env"
370 |
371 | -------
372 | Examples:
373 | python3 ./report_inventory_site_notes.py
374 | python3 ./report_inventory_site_notes.py --site_ids=203d3d02-xxxx-xxxx-xxxx-76896a3330f4
375 |
376 | """
377 | )
378 | if error_message:
379 | CONSOLE.critical(error_message)
380 | sys.exit(0)
381 |
382 |
383 | def check_mistapi_version():
384 | """Check if the installed mistapi version meets the minimum requirement."""
385 |
386 | current_version = mistapi.__version__.split(".")
387 | required_version = MISTAPI_MIN_VERSION.split(".")
388 |
389 | try:
390 | for i, req in enumerate(required_version):
391 | if current_version[int(i)] > req:
392 | break
393 | if current_version[int(i)] < req:
394 | raise ImportError(
395 | f'"mistapi" package version {MISTAPI_MIN_VERSION} is required '
396 | f"but version {mistapi.__version__} is installed."
397 | )
398 | except ImportError as e:
399 | LOGGER.critical(str(e))
400 | LOGGER.critical("Please use the pip command to update it.")
401 | LOGGER.critical("")
402 | LOGGER.critical(" # Linux/macOS")
403 | LOGGER.critical(" python3 -m pip install --upgrade mistapi")
404 | LOGGER.critical("")
405 | LOGGER.critical(" # Windows")
406 | LOGGER.critical(" py -m pip install --upgrade mistapi")
407 | print(
408 | f"""
409 | Critical:\r\n
410 | {e}\r\n
411 | Please use the pip command to update it.
412 | # Linux/macOS
413 | python3 -m pip install --upgrade mistapi
414 | # Windows
415 | py -m pip install --upgrade mistapi
416 | """
417 | )
418 | sys.exit(2)
419 | finally:
420 | LOGGER.info(
421 | '"mistapi" package version %s is required, '
422 | "you are currently using version %s.",
423 | MISTAPI_MIN_VERSION,
424 | mistapi.__version__,
425 | )
426 |
427 |
428 | ###############################################################################
429 | ### ENTRY POINT
430 | if __name__ == "__main__":
431 | parser = argparse.ArgumentParser(
432 | description="Display list of open events/alarms that are not cleared"
433 | )
434 | parser.add_argument(
435 | "-e", "--env_file", help="define the env file to use", default=None
436 | )
437 | parser.add_argument(
438 | "-o",
439 | "--org_id",
440 | help="Set the org_id where the webhook must be create/delete/retrieved",
441 | default="",
442 | )
443 | parser.add_argument(
444 | "-l",
445 | "--log_file",
446 | help="define the filepath/filename where to write the logs",
447 | default=LOG_FILE,
448 | )
449 | parser.add_argument(
450 | "-c",
451 | "--csv_file",
452 | help="Path to the CSV file where to save the result",
453 | default=CSV_FILE,
454 | )
455 |
456 | args = parser.parse_args()
457 |
458 | ENV_FILE = args.env_file
459 | ORG_ID = args.org_id
460 |
461 | #### LOGS ####
462 | logging.basicConfig(filename=LOG_FILE, filemode="w")
463 | LOGGER.setLevel(logging.DEBUG)
464 | check_mistapi_version()
465 | ### MIST SESSION ###
466 | API_SESSION = mistapi.APISession(env_file=ENV_FILE)
467 | API_SESSION.login()
468 | ### START ###
469 | start(API_SESSION, ORG_ID, CSV_FILE)
470 |
--------------------------------------------------------------------------------