def run_commands(cmds, site): # Filter Nornir to only use site user specified nr_filtered = nr.filter(site=site) # Use credentials user supplied so they do not have to be stored in inventory files nr_filtered.inventory.defaults.username = user nr_filtered.inventory.defaults.password = p nr_filtered.inventory.defaults.connection_options['netmiko'] = ConnectionOptions(extras={"secret":f"{p}"}) # Filter Nornir inventory for list of hosts host_list = nr_filtered.inventory.hosts # Populate dictionary 'dict1' with a key for each hostname for i in host_list: dict1[i] = [] # Filter Nornir inventory for dictionary of host info ip_dict = nr_filtered.inventory.get_hosts_dict() # Append the host IP address to the dictionary 'dict1' for i in host_list: ip = ip_dict[i]['hostname'] dict1[i].append(ip) # Run each command and append output to 'dict1' in the hosts' list for cmd in cmds: result = nr_filtered.run(task=netmiko_send_command,command_string=cmd) for dv_name in result.keys(): value = str(result[dv_name][0].result) value = value.rstrip() dict1[dv_name].append(value) return dict1
def reset_access_device(self): nr = cnaas_nms.confpush.nornir_helper.cnaas_init() nr_filtered = nr.filter(name=self.testdata['init_access_new_hostname']) nr_filtered.inventory.hosts[self.testdata['init_access_new_hostname']].\ connection_options["napalm"] = ConnectionOptions(extras={"timeout": 5}) data_dir = pkg_resources.resource_filename(__name__, 'data') with open(os.path.join(data_dir, 'access_reset.j2'), 'r') as f_reset_config: print(self.testdata['init_access_new_hostname']) config = f_reset_config.read() print(config) nrresult = nr_filtered.run( task=networking.napalm_configure, name="Reset config", replace=False, configuration=config, dry_run=False # TODO: temp for testing ) print_result(nrresult) reset_interfacedb(self.testdata['init_access_new_hostname']) with sqla_session() as session: dev: Device = session.query(Device).filter( Device.hostname == self.testdata['init_access_new_hostname']).one() dev.management_ip = None dev.hostname = self.testdata['init_access_old_hostname'] dev.state = DeviceState.DISCOVERED dev.device_type = DeviceType.UNKNOWN
def adapt_user_password(host): host.username = creds[f"{host}"]["username"] host.password = creds[f"{host}"]["password"] host.connection_options["napalm"] = ConnectionOptions( extras={ "optional_args": { "secret":"admin" } } )
def push_base_management_access(task, device_variables, job_id): set_thread_data(job_id) logger = get_logger() logger.debug("Push basetemplate for host: {}".format(task.host.name)) with open('/etc/cnaas-nms/repository.yml', 'r') as db_file: repo_config = yaml.safe_load(db_file) local_repo_path = repo_config['templates_local'] mapfile = os.path.join(local_repo_path, task.host.platform, 'mapping.yml') if not os.path.isfile(mapfile): raise RepoStructureException( "File {} not found in template repo".format(mapfile)) with open(mapfile, 'r') as f: mapping = yaml.safe_load(f) template = mapping['ACCESS']['entrypoint'] settings, settings_origin = get_settings(task.host.name, DeviceType.ACCESS) # Add all environment variables starting with TEMPLATE_SECRET_ to # the list of configuration variables. The idea is to store secret # configuration outside of the templates repository. template_secrets = {} for env in os.environ: if env.startswith('TEMPLATE_SECRET_'): template_secrets[env] = os.environ[env] # Merge dicts, this will overwrite interface list from settings template_vars = {**settings, **device_variables, **template_secrets} r = task.run(task=text.template_file, name="Generate initial device config", template=template, path=f"{local_repo_path}/{task.host.platform}", **template_vars) #TODO: Handle template not found, variables not defined task.host["config"] = r.result # Use extra low timeout for this since we expect to loose connectivity after changing IP task.host.connection_options["napalm"] = ConnectionOptions( extras={"timeout": 30}) try: task.run(task=networking.napalm_configure, name="Push base management config", replace=True, configuration=task.host["config"], dry_run=False) except Exception: task.run(task=networking.napalm_get, getters=["facts"]) if not task.results[-1].failed: raise InitError( "Device {} did not commit new base management config".format( task.host.name))
def adapt_host_data(host): # This function receives a Host object for manipulation # host.username = host["username"] # host.password = host["password"] host.connection_options["napalm"] = ConnectionOptions( extras={ "optional_args": { 'secret': host.password } } )
def _get_connection_options(data: Dict[str, Any]) -> Dict[str, ConnectionOptions]: cp = {} for cn, c in data.items(): cp[cn] = ConnectionOptions( hostname=c.get('hostname'), port=c.get('port'), username=c.get('username'), password=c.get('password'), platform=c.get('platform'), extras=c.get('extras'), ) return cp
def enable_fast_cli(nr: Nornir) -> None: """ Enable `fast_cli` option in netmiko. This option is only appropriate for Cisco IOS devices. Args: nr: a Nornir object. """ conn_options = ConnectionOptions(extras={"fast_cli": True}) for host in nr.inventory.hosts.values(): if host.platform == "ios": host.connection_options["netmiko"] = conn_options
def _get_connection_options(data: Dict[str, Any]) -> ConnectionOptions: cp = {} for cn, c in data.items(): cp[cn] = ConnectionOptions( hostname=c.get("hostname"), port=c.get("port"), username=c.get("username"), password=c.get("password"), platform=c.get("platform"), extras=c.get("extras"), ) return cp
def _validate_connection_options(ctx, param, value): if value is not None: try: value = { connection_plugin: ConnectionOptions(**connection_params) for connection_plugin, connection_params in _json_loads( [value])[0].items() } return value except (TypeError, AttributeError): ctx.fail( click.BadParameter(f"'--connection_options' / '-co': {value}", ).format_message(), )
def get_interfaces_status(nr): ssh = True r = '' main.session_log(nr) try: r = nr.run(task=networking.netmiko_send_command, name=f'Issue show interfaces status por SSH en {nr.host}', command_string='show interfaces status', use_textfsm=True ).result except NornirSubTaskError: print('Al parecer, SSH no funca asi que cerrando conexion....') ssh = False try: nr.host.close_connections() except ValueError: print('... y abriendo un telnet rico...') pass if not ssh: nr.host.connection_options['netmiko'] = ConnectionOptions( extras={"device_type": 'cisco_ios_telnet'} ) host_info = main.get_device_info(nr) nr.host.connection_options['netmiko'] = ConnectionOptions( extras={"device_type": 'cisco_ios_telnet', "session_log": main.session_log(nr)} ) r = nr.run(task=networking.netmiko_send_command, name=f'Hace un show interfaces status por TELNET en {nr.host}', command_string='show interfaces status', use_textfsm=True ).result main.process_data_trunk(r, nr)
def transform_slog(host): """ Create ConnectionOptions > Extras in the transform function. This pattern presupposes, however, that there is nothing else in existing inventory inside extras that needs retained. The five primary attributes would still get recursively retrieved including inside of connection options. """ # Dynamically set the session_log to be unique per host filename = f"{host}-output.txt" host.connection_options["netmiko"] = ConnectionOptions( extras={"session_log": filename}) netmiko_params = host.get_connection_parameters("netmiko") print(netmiko_params)
def _get_connection_options(data: Dict[str, Any]) -> ConnectionOptions: """ Get connection option information for a given host/group Arguments: data: dictionary of connection options for host/group """ connection_options = {} for connection_name, connection_data in data.items(): connection_options[connection_name] = ConnectionOptions( hostname=connection_data.get("hostname"), port=connection_data.get("port"), username=connection_data.get("username"), password=connection_data.get("password"), platform=connection_data.get("platform"), extras=connection_data.get("extras"), ) return connection_options
def _set_host(data: Dict[str, Any], name: str, groups, host, defaults) -> Host: connection_option = {} for key, value in data.get("connection_options", {}).items(): connection_option[key] = ConnectionOptions( hostname=value.get("hostname"), port=value.get("port"), username=value.get("username"), password=value.get("password"), platform=value.get("platform"), extras=value.get("extras"), ) return Host( name=name, hostname=host["hostname"], username=host["username"], password=host["password"], platform=host["platform"], data=data, groups=groups, defaults=defaults, connection_options=connection_option, )
def push_base_management_access(task, device_variables): logger.debug("Push basetemplate for host: {}".format(task.host.name)) with open('/etc/cnaas-nms/repository.yml', 'r') as db_file: repo_config = yaml.safe_load(db_file) local_repo_path = repo_config['templates_local'] mapfile = os.path.join(local_repo_path, task.host.platform, 'mapping.yml') if not os.path.isfile(mapfile): raise RepoStructureException( "File {} not found in template repo".format(mapfile)) with open(mapfile, 'r') as f: mapping = yaml.safe_load(f) template = mapping['ACCESS']['entrypoint'] settings, settings_origin = get_settings(task.host.name, DeviceType.ACCESS) # Merge dicts template_vars = {**device_variables, **settings} r = task.run(task=text.template_file, name="Generate initial device config", template=template, path=f"{local_repo_path}/{task.host.platform}", **template_vars) #TODO: Handle template not found, variables not defined task.host["config"] = r.result # Use extra low timeout for this since we expect to loose connectivity after changing IP task.host.connection_options["napalm"] = ConnectionOptions( extras={"timeout": 5}) task.run( task=networking.napalm_configure, name="Push base management config", replace=True, configuration=task.host["config"], dry_run=False # TODO: temp for testing )
def create_connection_options(username: str, password: str, secret: str) -> dict: """Helper function to create or connection options. Args: username (str): Username to use to connect to device. password (str): Password to use to connect to device. secret (str): Secret or enable password to use to elevate perissions on the device. Returns: dict: Nornir ConnectionOptions object """ napalm_options = ConnectionOptions( username=username, password=password, extras={"optional_args": { "conn_timeout": 500, "secret": secret }}) connection_options = {"napalm": napalm_options} return connection_options
def host_credentials(host, username, password): host.username = username host.password = password host.connection_options['netmiko'] = ConnectionOptions( extras={'secret': password})
from nornir import InitNornir from nornir.plugins.tasks.data import load_yaml from nornir.plugins.functions.text import print_result from nornir.core.inventory import ConnectionOptions def load_data(task): data = task.run( task=load_yaml, file=f'{task.host["site"]}.yaml' ) task.host["asn"] = data.result["asn"] task.host["networks"] = data.result["networks"] nr = InitNornir() nr.inventory.defaults.connection_options = ConnectionOptions(extras={"secret": "cisco"}) r = nr.run(task=load_data) print_result(r)
def nornir_initialize(args: Namespace) -> Nornir: """ Given the parsed argument Namespace object, initialize a Nornir inventory/execution object and return it. :param args: A parsed/instantiated argpase.Namespace object with our command line arguments :return: """ log_file = pathlib.Path(pathlib.Path(args.logging_dir) / "stockpiler.log") # A directory in the path doesn't exist, this can happen with the default logging path of `/var/log/stockpiler/` if not log_file.parent.exists(): try: log_file.parent.mkdir(parents=True) except PermissionError: sys_user = getpass.getuser() print( "\nERROR: Unable to create parent log directories!" f"\nYou may have to manually do this with 'sudo mkdir -p {str(log_file.parent)};" f"sudo chown {sys_user}:{sys_user} {str(log_file.parent)}'\n" ) sys.exit(1) # Ensure the logfile is able to be written to. try: log_file.touch() except PermissionError: print( f"\nERROR: Unable to access the log file at {str(log_file)}" f" please check permissions on the file/directory!\n" ) sys.exit(1) logging_config = { "level": args.log_level, "file": str(log_file), "loggers": ["nornir", "paramiko", "netmiko", "stockpiler"], } if args.config_file: config_file = args.config_file else: with importlib.resources.path(package="stockpiler", resource="nornir_conf.yaml") as p: config_file = str(p) # See if an SSH config file is specified in args or in the config file, order of precedence is: # First In the inventory: Device -> Group -> Defaults # Then: Args -> Config File -> Packaged SSH Config File if args.ssh_config_file: ssh_config_file = args.ssh_config_file else: cf_path = pathlib.Path(config_file) if not cf_path.is_file(): raise ValueError(f"The provided configuration file {str(cf_path)} is not found.") with cf_path.open() as cf: try: cf_yaml = safe_load(cf) except (ConstructorError, ValueError) as e: raise ValueError(f"Unable to parse the provided config file {str(cf_path)} to YAML: {str(e)}") cf_ssh_config_file = cf_yaml.get("ssh", {}).get("config_file", None) if cf_ssh_config_file is not None: ssh_config_file = cf_ssh_config_file else: with importlib.resources.path(package="stockpiler", resource="ssh_config") as p: ssh_config_file = str(p) # Initialize our nornir object/inventory logger.info("Reading config file and initializing inventory...") norns = InitNornir(config_file=config_file, logging=logging_config, ssh={"config_file": ssh_config_file}) # Check if we need to gather credentials or not: if not args.credential_from_inventory: # Gather credentials: username, password, enable = gather_credentials( credential_prompt=args.credential_prompt, credential_file=args.credential_file ) # Set these into the inventory: norns.inventory.defaults.username = username norns.inventory.defaults.password = password # If there is no Enable, set it to the same as the password. norns.inventory.defaults.connection_options["netmiko"] = ConnectionOptions( extras={"secret": enable or password} ) return norns
def add_conn_options(host): host.username = config('username') host.password = config('password') host.connection_options["netmiko"] = ConnectionOptions( extras={'secret': config('secret')})
def load(self): """Load inventory by fetching devices from netbox.""" if self.filter_parameters: devices: List[pynetbox.modules.dcim. Devices] = self.session.dcim.devices.filter( **self.filter_parameters) else: devices: List[pynetbox.modules.dcim. Devices] = self.session.dcim.devices.all() # fetch all platforms from Netbox and build mapping: platform: napalm_driver platforms = self.session.dcim.platforms.all() platforms_mapping = { platform.slug: platform.napalm_driver for platform in platforms if platform.napalm_driver } hosts = Hosts() groups = Groups() defaults = Defaults() for dev in devices: # Netbox allows devices to be unnamed, but the Nornir model does not allow this # If a device is unnamed we will set the name to the id of the device in netbox dev_name = dev.name or dev.id host = NetworkImporterHost(name=dev_name, connection_options=ConnectionOptions()) # Only add virtual chassis master as inventory element if dev.virtual_chassis and dev.virtual_chassis.master: if dev.id != dev.virtual_chassis.master.id: continue host.data["virtual_chassis"] = True else: host.data["virtual_chassis"] = False # If supported_platforms is provided # skip all devices that do not match the list of supported platforms # TODO need to see if we can filter when doing the query directly if self.supported_platforms: if not dev.platform: continue if dev.platform.slug not in self.supported_platforms: continue # Add value for IP address if self.settings.use_primary_ip and dev.primary_ip: host.hostname = dev.primary_ip.address.split("/")[0] elif self.settings.use_primary_ip and not dev.primary_ip: host.is_reachable = False host.not_reachable_reason = "primary ip not defined in Netbox" elif not self.settings.use_primary_ip and self.settings.fqdn: host.hostname = f"{dev.name}.{self.settings.fqdn}" elif not self.settings.use_primary_ip: host.hostname = dev.name else: host.hostname = dev_name host.site_name = dev.site.slug host.data["serial"] = dev.serial host.data["vendor"] = dev.device_type.manufacturer.slug host.data["asset_tag"] = dev.asset_tag host.data["custom_fields"] = dev.custom_fields host.data["site_id"] = dev.site.id host.data["device_id"] = dev.id host.data["role"] = dev.device_role.slug host.data["model"] = dev.device_type.slug # Attempt to add 'platform' based of value in 'slug' if dev.platform and dev.platform.slug in platforms_mapping: host.connection_options = { "napalm": ConnectionOptions( platform=platforms_mapping[dev.platform.slug]) } if dev.platform: host.platform = dev.platform.slug else: host.platform = None host.groups = ParentGroups([self.global_group]) if dev.site.slug not in groups.keys(): groups[dev.site.slug] = {} if dev.device_role.slug not in groups.keys(): groups[dev.device_role.slug] = {} if host.hostname and host.platform: host.is_reachable = True # Assign temporary dict to outer dict hosts[dev_name] = host return Inventory(hosts=hosts, groups=groups, defaults=defaults)
def nornir_initialize(args: Namespace) -> Nornir: """ Given the parsed argument Namespace object, initialize a Nornir inventory/execution object and return it. :param args: A parsed/instantiated argpase.Namespace object with our command line arguments :return: """ log_file = pathlib.Path(pathlib.Path(args.logging_dir) / "stockpiler.log") pathlib.Path(log_file).touch() logging_config = { "level": args.log_level, "file": str(log_file), "loggers": ["nornir", "paramiko", "netmiko", "stockpiler"], } if args.config_file: config_file = args.config_file else: with importlib.resources.path(package="stockpiler", resource="nornir_conf.yaml") as p: config_file = str(p) # See if an SSH config file is specified in args or in the config file, order of precedence is: # First In the inventory: Device -> Group -> Defaults # Then: Args -> Config File -> Packaged SSH Config File if args.ssh_config_file: ssh_config_file = args.ssh_config_file else: cf_path = pathlib.Path(config_file) if not cf_path.is_file(): raise ValueError( f"The provided configuration file {str(cf_path)} is not found." ) with cf_path.open() as cf: try: cf_yaml = safe_load(cf) except (ConstructorError, ValueError) as e: raise ValueError( f"Unable to parse the provided config file {str(cf_path)} to YAML: {str(e)}" ) cf_ssh_config_file = cf_yaml.get("ssh", {}).get("config_file", None) if cf_ssh_config_file is not None: ssh_config_file = cf_ssh_config_file else: with importlib.resources.path(package="stockpiler", resource="ssh_config") as p: ssh_config_file = str(p) # Initialize our nornir object/inventory logger.info("Reading config file and initializing inventory...") norns = InitNornir(config_file=config_file, logging=logging_config, ssh={"config_file": ssh_config_file}) # Gather credentials: username, password, enable = gather_credentials( prompt_for_credentials=args.prompt_for_credentials) # Set these into the inventory: norns.inventory.defaults.username = username norns.inventory.defaults.password = password # If there is no Enable, set it to the same as the password. norns.inventory.defaults.connection_options["netmiko"] = ConnectionOptions( extras={"secret": enable or password}) return norns
if applyfilter.lower() in ('p'): p1 = p.run(task=netboxinfo) elif applyfilter.lower() in ('pr'): p2 = pr.run(task=netboxinfo) elif applyfilter.lower() in ('prs'): p3 = prs.run(task=netboxinfo) # Enter Credentials and calling Getpass function above. print("") print_title('Enter your credentials below... \n') nornir_set_creds(nr) secret = input("Enter Enable Secret: ") nr.inventory.defaults.connection_options['netmiko'] = ConnectionOptions( extras={'secret': secret}) # Yes or no question, if no the application quits directly, if yes then proceeds. def yes_or_no(question): answer = input(question + "(y/n): ").lower().strip() print("") while not (answer == "y" or answer == "yes" or \ answer == "n" or answer == "no"): print("Input yes or no") answer = input(question + "(y/n):").lower().strip() print("") if answer[0] == "y": return True else:
def adapt_host_data(host, username, password): host.username = username host.password = password host.connection_options['netmiko'] = ConnectionOptions( extras={'secret': password})
from nornir.plugins.tasks.networking import netmiko_send_command from nornir.core.inventory import ConnectionOptions from nornir.plugins.functions.text import print_result #Transform function from Nornir def adapt_user_password(host): host.username = "******" host.password = "******" #Load config.yaml nr = InitNornir(config_file="config.yaml") nr.inventory.defaults.connection_options['netmiko'] = ConnectionOptions( extras={"optional_args": { "secret": "ENABLESECRET" }}) nr.inventory.defaults.connection_options['napalm'] = ConnectionOptions( extras={"optional_args": { "secret": "ENABLESECRET" }}) #Example sw10.domain.com hosts = nr.filter(name="NETBOXFILTER") urllib3.disable_warnings() session = requests.Session() session.verify = False # load PynetBox netbox = pynetbox.api('NETBOXURL', token='NETBOXTOKEN')
def __init__( self, username: Optional[str] = None, password: Optional[str] = None, enable: Optional[bool] = None, supported_platforms: Optional[List[str]] = None, netmiko_extras: Optional[Dict] = None, napalm_extras: Optional[Dict] = None, limit: Optional[str] = None, settings: Optional[Dict] = None, ): """Initialize class and store all top level arguments locally.""" self.username = username self.password = password self.enable = enable self.supported_platforms = supported_platforms self.limit = limit self.netmiko_extras = netmiko_extras self.napalm_extras = napalm_extras self.settings = settings # Define Global Group with Netmiko and Napalm Credentials if provided self.global_group = Group(name="global", connection_options={ "netmiko": ConnectionOptions(), "napalm": ConnectionOptions() }) if self.netmiko_extras: self.global_group.connection_options[ "netmiko"].extras = self.netmiko_extras if self.napalm_extras: self.global_group.connection_options[ "napalm"].extras = self.napalm_extras # Pull the login and password from the NI config object if available if self.username: self.global_group.username = self.username if self.password: self.global_group.password = self.password if self.enable: if not self.global_group.connection_options["netmiko"].extras: self.global_group.connection_options[ "netmiko"].extras = dict() elif "secret" not in self.global_group.connection_options[ "netmiko"].extras: self.global_group.connection_options["netmiko"].extras[ "secret"] = self.password if not self.global_group.connection_options["napalm"].extras: self.global_group.connection_options["napalm"].extras = { "optional_args": { "secret": self.password } } elif isinstance( dict, self.global_group.connection_options["napalm"].extras): if "optional_args" not in self.global_group.connection_options[ "napalm"].extras: self.global_group.connection_options["napalm"].extras[ "optional_args"] = { "secret": self.password } elif isinstance( dict, self.global_group.connection_options["napalm"]. extras["optional_args"]): self.global_group.connection_options["napalm"].extras[ "optional_args"]["secret"] = self.password
def load(self) -> Inventory: defaults = Defaults( connection_options={ "napalm": ConnectionOptions(extras={ "optional_args": { # args to eAPI HttpsEapiConnection for EOS "enforce_verification": True, "context": ssl_context } }) } ) insecure_device_states = [ DeviceState.INIT, DeviceState.DHCP_BOOT, DeviceState.PRE_CONFIGURED, DeviceState.DISCOVERED ] insecure_connection_options = { "napalm": ConnectionOptions(extras={ "optional_args": {"enforce_verification": False} }) } groups = Groups() for device_type in list(DeviceType.__members__): group_name = 'T_'+device_type groups[group_name] = Group(name=group_name, defaults=defaults) for device_state in list(DeviceState.__members__): username, password = self._get_credentials(device_state) group_name = 'S_'+device_state groups[group_name] = Group( name=group_name, username=username, password=password, defaults=defaults) for group_name in get_groups(): groups[group_name] = Group(name=group_name, defaults=defaults) hosts = Hosts() with cnaas_nms.db.session.sqla_session() as session: instance: Device for instance in session.query(Device): hostname = self._get_management_ip(instance.management_ip, instance.dhcp_ip) port = None if instance.port and isinstance(instance.port, int): port = instance.port host_groups = [ 'T_' + instance.device_type.name, 'S_' + instance.state.name ] for member_group in get_groups(instance.hostname): host_groups.append(member_group) if instance.state in insecure_device_states: host_connection_options = insecure_connection_options else: host_connection_options = None hosts[instance.hostname] = Host( name=instance.hostname, hostname=hostname, platform=instance.platform, groups=ParentGroups(groups[g] for g in host_groups), port=port, data={ 'synchronized': instance.synchronized, 'managed': (True if instance.state == DeviceState.MANAGED else False) }, connection_options=host_connection_options, defaults=defaults ) return Inventory(hosts=hosts, groups=groups, defaults=defaults)