Using IOS-Automation with Netmiko via OOP approach2

# NETMIKOを使ったIOS自動化のオブジェクト指向プログラムをつくってみる。その2

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3943132/564f264e-7012-a42b-76c1-880bae041017.png)

引き続き構成は非常に簡単でIOSルーターに管理クラウドを直付けしているだけ。
今回はいろいろプロンプトでアクセスリストを設定できるようにして、
アクセスの際のパスワードは環境に保存する。

R1の設定は以下、前と同じ
```cisco_ios:R1
hostname R1
ip domain-name abc.com
crypto key generate rsa modulus 1024
ip ssh version 2
username admin privilege 15 password cisco

ip access-list standard ACL_MGMT
permit 10.255.1.51


line vty 0 4
login local
transport input all

interface gi 0/0
ip address 10.255.1.101 255.255.255.0
description Connect to MGMT
no shut 
```

プログラム全体の概要
・ACL_Mgmt.py
-->connect(self) /機器への接続
-->fetch_current_config(self)/現在のACLを取得しparse_outputで整形する
-->print_current_acl_config(self,mgmt_acl:Optional[list[dict[str,str]]] = None)
-->/取得したACLを表示する。
-->update_acl_config(self,action:str,address:ip_address)/ACLの設定変更
・management_acl_config.py
-->main/メインプログラム


クラスプログラム
```python:ACL_Mgmt.py
from netmiko import ConnectHandler
from ntc_templates.parse import parse_output
#データを効率的に扱えるクラス詳細はよく分からない。
from dataclasses import dataclass
#Optional使用に必要なライブラリ初期値設定必須
from typing import Optional
#ip addressの形式確認のライブラリ
from ipaddress import ip_address

@dataclass
class ACL_Mgmt:    
    device_address: str
    device_port: int
    device_username: str
    device_password: str
    device_access_list_name: Optional[str] = 'ACL_MGMT'
    
    def connect(self) -> ConnectHandler:
        ssh_connect = ConnectHandler(
            device_type = "cisco_ios",
            host = self.device_address,
            username = self.device_username,
            password = self.device_password,
            port = self.device_port
        )
    
        return ssh_connect

    def fetch_current_config(self) -> list[dict[str,str]]:
        ssh_connect = self.connect()
        mgmt_acl_raw_output = ssh_connect.send_command(f'show ip access-lists {self.device_access_list_name}')
        ssh_connect.disconnect()
        pretty_mgmt_acl_output =parse_output(
            platform = "cisco_ios",
            command = f"show ip access-lists {self.device_access_list_name}",
            data = mgmt_acl_raw_output
        )

        return pretty_mgmt_acl_output
        
    def print_current_acl_config(self,mgmt_acl:Optional[list[dict[str,str]]] = None):
        
        if mgmt_acl is None:
            mgmt_acl = self.fetch_current_config()
        print(f"The following host permissions are defined in Access List {self.device_access_list_name}: ")
        for i,entry in enumerate(mgmt_acl):
            if entry["src_host"] != "":
                print(f'{i} {entry["action"]} host {entry["src_host"]}')
        
    def update_acl_config(self,action:str,address:ip_address) -> bool:
        if action not in ["add","remove"]:
            print("Invalid choice of Action...")
            return False
    
        if action == "remove":
            no_state = "no"
        else:
            no_state = ""
            
        config_cmds = [
            f"ip access-list standard {self.device_access_list_name}",
            f"{no_state} permit host {address}"
        ]

        try:
            ssh_connect = self.connect()
            output = ssh_connect.send_config_set(config_cmds)
            output += ssh_connect.save_config()
            ssh_connect.disconnect()
        
        except Exception as e:
            print("There was an error sending config to the device")
            print(e)
            return False
    
        return True


```

クラスを使うメインプログラム
```python:management_acl_config.py
import os
from ipaddress import ip_address
from ACL_Mgmt import ACL_Mgmt


    
if __name__ == "__main__":
    username = os.getenv("ROUTER_USERNAME")
    password = os.getenv("ROUTER_PASSWORD")
    
    if not username or not password:
        print("Credentials for network access mus be set as ENVs 'ROUTER_USERNAME' and 'ROUTER_PASSWORD' to use this utility script")
    
    #Router_address & Router_port detals can be specified in ENV VAR as well and if not found. use default values
    address = os.getenv("ROUTER_ADDRESS","10.255.1.101")
    port = int(os.getenv("ROUTER_PORT",22))
    
    device = ACL_Mgmt(address,port,username,password)
    
    print(f"Looking up current status of management access from device {device.device_address}")
    print("-" * os.get_terminal_size().columns)
    device.print_current_acl_config()
    print("-" * os.get_terminal_size().columns)

    action = input(f"Would you like to add or remove the access list statement from '{device.device_access_list_name}'? (add/remove):")
    if action not in ["add","remove"]:
        print("Error: Only 'add' or 'remove' are allow inputs. Exiting...")
        exit()
        
    host_address = input(f"What address would you like to {action}? (provide IP address):")
    print("-" * os.get_terminal_size().columns)
    
    try:
        host_address = ip_address(host_address)
    except ValueError:
        print(f"The entry {host_address} is not a valid IPv4 address. Existing")
        exit()
        
    print(f"Updating ACL configuration of {device.device_address} to {action} host {host_address}")
    if device.update_acl_config(action,host_address):
        print("Update Successful")
    else:
        print("Update Unsuccessful")
```

パスワード保存するファイル
```shell:.envrc
export ROUTER_USERNAME="admin"
export ROUTER_PASSWORD="cisco"
```

# 実行してみよう。
ルーターのユーザーネームとパスワードを環境にロードしてから実行する。
```shell-session
#source .envrc
#python manage_acl_config.py 
Looking up current status of management access from device 10.255.1.101
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
The following host permissions are defined in Access List ACL_MGMT: 
1 permit host 10.255.1.51
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Would you like to add or remove the access list statement from 'ACL_MGMT'? (add/remove):add
What address would you like to add? (provide IP address):10.10.10.10
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Updating ACL configuration of 10.255.1.101 to add host 10.10.10.10
Update Successful
```
再度実行。きちんと追加されてる。
```shell-session
#python manage_acl_config.py 
Looking up current status of management access from device 10.255.1.101
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
The following host permissions are defined in Access List ACL_MGMT: 
1 permit host 10.10.10.10
2 permit host 10.255.1.51
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Would you like to add or remove the access list statement from 'ACL_MGMT'? (add/remove):
```

:::note info
今日の課題とまとめ
・プログラムが長くなってくるとどこに問題がでてるか把握がかなり難しい。
・適宜Printして状況確認する必要がある。
・全体図を最初に把握していくとわかりやすい。