Skip to content

পাইথনের টেমপ্লেটিং ইঞ্জিন (পুরানো)

টেমপ্লেটিং ল্যাঙ্গুয়েজ

জিনজা2 টেমপ্লেটিং ল্যাঙ্গুয়েজ

এই পৃথিবীতে কে ফাঁকিবাজি না করতে পছন্দ করে? তবে এই ফাঁকিবাজি করতে গিয়ে একই কনফিগারেশন অন্য ডিভাইসে কপি পেস্ট করার সময় ঝামেলা শুরু হয়। অনেক সময় আমরা একটা ডিভাইস থেকে কনফিগারেশন কপি করে অন্য ডিভাইসে বসানোর চেষ্টা করি, কিন্তু নতুন এনভায়রনমেন্টে সেই কনফিগারেশন কাজ করে না। এই সমস্যার সমাধান হিসাবে আমরা টেমপ্লেটিং ল্যাঙ্গুয়েজ ব্যবহার করি, যা নতুন এনভায়রনমেন্টে ডায়নামিক্যালি অ্যাডাপ্ট করতে পারে।

নেটওয়ার্কে সফটওয়্যারের একটা কথা আছে, "ড্রাই" (DRY): ডোন্ট রিপিট ইউরসেলফ। আমরা এমনভাবে কোড বা টেমপ্লেট তৈরি করতে চাই যাতে এটি বিভিন্ন জায়গায় পুনর্ব্যবহার করা যায়, মডিউলার আর্কিটেকচারের মাধ্যমে। এতে করে এক ফাইলকে বারবার রি-রাইট না করে বিভিন্ন মডিউল থেকে কনফিগারেশন ফাইল এনে ভিন্ন এনভায়রনমেন্টে কনফিগারেশন জেনারেট করা যায়। এর জন্য আমরা জিনজা2 (Jinja2) টেমপ্লেট ব্যবহার করব।

নেটওয়ার্ক অটোমেশনের জন্য কনফিগারেশন কনসিসটেন্সি খুবই গুরুত্বপূর্ণ। একই কনফিগারেশন রিপিটেডলি প্রোডাকশন এনভায়রনমেন্টে ব্যবহৃত হলে ভুলের ঝুঁকি কম থাকে। টেমপ্লেটিং ইঞ্জিন ব্যবহার করলে নেটওয়ার্ক ইঞ্জিনিয়ারদের অনেক সময় বেঁচে যায়।

নেটওয়ার্ক কনফিগারেশনের ক্ষেত্রে আমরা ছোট ছোট টেমপ্লেট তৈরি করতে পারি, যেমন ইন্টারফেস কনফিগারেশন, BGP কনফিগারেশন বা OSPF কনফিগারেশন। এরপর এই টেমপ্লেটগুলো কম্বাইন করে একটা পূর্ণ কনফিগারেশন ফাইল তৈরি করা যায়, যা একটা মাস্টার টেমপ্লেট হবে। এর কাজ হল সব জায়গা থেকে ডাটা পুল করে এনে ফাইনাল কনফিগারেশন তৈরি করা।

আমার অভিজ্ঞতায় দেখেছি যে অনেক নেটওয়ার্ক অটোমেশন প্রজেক্টে টেমপ্লেট ব্যবহার করা হয় না। একবার টেমপ্লেট ব্যবহার শুরু করলে এটি এনসিবল (Ansible) এর মত অটোমেশন সফটওয়্যার প্ল্যাটফর্মের মাধ্যমে কনফিগারেশন চেঞ্জগুলোকে প্রোডাকশন এনভায়রনমেন্টে পুশ করতে পারে।

জিনজা2 কি?

জিনজা2 পাইথনের একটা টেমপ্লেটিং ইঞ্জিন। ওয়েবসাইট তৈরি করার সময় ডিজঙ্গো বা ফ্লাস্কের সাথে এটি প্রচুর ব্যবহৃত হয়। নেটওয়ার্ক ইঞ্জিনিয়ারদের জন্য আমরা বিভিন্ন ধরনের নেটওয়ার্ক কনফিগারেশন এই টেমপ্লেটের মাধ্যমে তৈরি করতে পারি।

জিনজা2 ইনস্টলেশন

$ pip install jinja2

জিনজা2 টেমপ্লেট উদাহরণ

from jinja2 import Template

# Set up your jinja template
jtemplate = Template("vlan {{ vlan }}\n name VLAN{{ vlan }}_ABC_Bank\n")

# Create the list of VLANs you want to generate configuration for
vlans = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

# Iterate over the list of vlans and print a usable configuration
for vlan in vlans:
    output = jtemplate.render(vlan=vlan)
    print(output)

এই স্ক্রিপ্টটি চালালে ভিল্যান ১০ থেকে ১০০ পর্যন্ত কনফিগারেশন ফাইল তৈরি হবে। এটার ফাইনাল আউটপুট কি হতে পারে?

vlan 10
 name VLAN10_ABC_Bank
vlan 20
 name VLAN20_ABC_Bank
vlan 30
 name VLAN30_ABC_Bank
vlan 40
 name VLAN40_ABC_Bank
vlan 50
 name VLAN50_ABC_Bank
vlan 60
 name VLAN60_ABC_Bank
vlan 70
 name VLAN70_ABC_Bank
vlan 80
 name VLAN80_ABC_Bank
vlan 90
 name VLAN90_ABC_Bank
vlan 100
 name VLAN100_ABC_Bank

ডিফিকাল্ট মনে হচ্ছে? ফিরে যাই একদম বেসিক ধারণায়। একটা ইন্ডাস্ট্রি স্ট্যান্ডার্ড সি এল আই সিনট্যাক্স ব্যবহার করে একটা সুইচপোর্ট কনফিগার করতে গেলে আমরা কি দিতাম?

interface GigabitEthernet0/1
    description Server Port
    switchport access vlan 10
    switchport mode access
এখানে আরেকটা বেসিক জিনজার টেমপ্লেট দেখি যেখানে ইন্ডাস্ট্রি স্ট্যান্ডার্ড সি এল আই সিনট্যাক্সকে যেটাকে আমরা টেমপ্লেটে কনভার্ট করব যাতে ভবিষ্যতে আমরা শত শত সুইচপোর্ট কনফিগার করতে পারি আমাদের এনভায়রনমেন্টে।

interface {{ interface_name }}
description {{ interface_description }}
switchport access vlan {{ interface_vlan }}
switchport mode access
এখানে ইন্টারফেস নেইম তার পাশাপাশি ইন্টারফেস ডিসক্রিপশন এবং ইন্টারফেস ভিলেন কে আমরা কার্লি ব্রেস এর মধ্যে রেখেছি যাতে এখানে ডায়নামিক্যালি ডাটা ইন্সার্ট করা যায়। সাধারণ প্রোগ্রামিং কনসেপ্ট এর মত এটা অনেকটাই কমন যে ক্লাস অথবা ডিকশনারি একটা ল্যাঙ্গুয়েজে কিভাবে ব্যবহার করতে পারে যখন একটা টেমপ্লেটকে রেন্ডার করতে পারি পাইথনের ভেতর দিয়ে। এ ধরনের কমন কনফিগারেশনের এর সুবিধা হচ্ছে আমরা এটাতে মাল্টিপল ইনস্ট্যান্ট এর ডাটা কে লুক করাতে পারবো যাতে সে এটাকে মাল্টিপল টাইম রাইট করবে যাতে আমাদের ফাইনাল কনফিগারেশন পাওয়া যায়।

পাইথনে জিনজা2 টেমপ্লেট ফাইলকে রেন্ডার করা

from jinja2 import Environment, FileSystemLoader

ENV = Environment(loader=FileSystemLoader('.'))
template = ENV.get_template("template.j2")

প্রথমেই ইমপোর্ট করে নেই দরকারি অবজেক্ট যেটা আসলে লাগবে আমাদের টেমপ্লেটগুলোকে রেন্ডার করার জন্য। দ্বিতীয় লাইনের মানে হচ্ছে আমরা এখানে একটা এনভায়রনমেন্ট অবজেক্ট সেটাপ করে নিলাম যেখানে একটা সিঙ্গেল ডট ব্যবহার করছি যাতে সে বুঝতে পারে যে টেমপ্লেট ফাইলগুলো রাখা আছে একই ডাইরেক্টরি তে যেই ডাইরেক্টরিতে পাইথন ইন্টারপ্রেটর কাজ করছে। তৃতীয় লাইনটার মানে হচ্ছে আমরা যেই টেমপ্লেটটা জেনারেট করছি তা একটা টেমপ্লেট অবজেক্ট ব্যবহার করে, বিশেষ করে স্ট্যাটিকভাবে স্পেসিফাই করা আছে যার নাম হচ্ছে টেমপ্লেট.জে২।

এখন আমাদেরকে ডাটা দিয়ে রেন্ডার করে দেখতে হবে। কনফিগারেশন তৈরি করতে হবে না? আমরা যেহেতু কাজটা করে ফেলেছি এখন এই জায়গাটাতে আমাদের ডাটা ঢোকাতে হবে। আমরা চেষ্টা করব আমাদের ডাটাকে আলাদা ভাবে রাখার জন্য যাতে পাইথন স্ক্রিপ্ট এর মধ্যে ডাটা না থাকে। এটা একটা ইন্ডাস্ট্রি বেস্ট প্রাকটিস যে পাইথন স্ক্রিপ্ট এর মধ্যে কনফিগারেশন টেমপ্লেট থাকলেও ডাটা আসবে বাইরে থেকে।

উদাহরণ: নেটওয়ার্ক ইন্টারফেস কনফিগারেশন

interface_dict = {
    "name": "GigabitEthernet0/1",
    "description": "Server Port",
    "vlan": 10,
    "uplink": False
}
আমাদের কাছে যেহেতু এখন সবকিছুই এসেছে, এখন আমাদেরকে টেমপ্লেট ধরে রেন্ডার করতে হবে। এই টেন্ডারিং এর জন্য আমরা রেন্ডার ফাংশন কে কল করব আমাদের টেমপ্লেটের অবজেক্টে যাতে আমরা এর ভেতরে ডাটা পাস করাতে পারি টেমপ্লেট ইঞ্জিনের মধ্যে। এবং ফাইনালি প্রিন্ট ফাংশন ব্যবহার করে সেটার আউটপুট দেখব আমাদের স্ক্রিনে। আমাদের কাজ হচ্ছে টেমপ্লেটটা ঠিকমতো কাজ করছে কিনা সেটা নিশ্চিত করা।

print(template.render(interface=interface_dict))
সেটার আউটপুট এরকম হতে পারে।

interface GigabitEthernet0/1
description Server Port
switchport access vlan 10
switchport mode access

উদাহরণ: ক্লাস অবজেক্ট ব্যবহার করে টেমপ্লেট রেন্ডার

আমরা একটা পাইথন স্ক্রিপ্ট দেখছি এখানে যার মধ্যে নেটওয়ার্ক ইন্টারফেস একটা অবজেক্ট ক্লাস তৈরি করা হয়েছে। এর পাশাপাশি আমরা সেই ডাটা ইন্টারফেস অবজেক্টটাকে আমরা টেমপ্লেটের ভেতরে ঢুকিয়ে রেন্ডার করার চেষ্টা করেছি। এখানে ডাটা অংশটিকে স্ক্রিপ্টের মধ্যে ঢোকানো ঠিক না, তবে উদাহরণ দেখানোর জন্য এখানে আমরা ঢুকিয়েছি। ফাইনালি সেটাকে প্রিন্ট করে আমরা দেখব তার আউটপুট কনফিগারেশন ফাইল দেখায় কিনা।

from jinja2 import Environment, FileSystemLoader

ENV = Environment(loader=FileSystemLoader('.'))
template = ENV.get_template("template.j2")

class NetworkInterface(object):
    def __init__(self, name, description, vlan, uplink=False):
        self.name = name
        self.description = description
        self.vlan = vlan
        self.uplink = uplink

data_interface_obj = NetworkInterface("GigabitEthernet0/1", "Server Port", 10)

print(template.render(interface=data_interface_obj))

কন্ডিশনাল লজিক ব্যবহার করে সুইচপোর্ট কনফিগারেশন

আমাদের আগের উদাহরণ টাকে এখানে নিয়ে আসি। তবে এই নতুন উদাহরণের মধ্যে আমরা একটা সিদ্ধান্তের লুপ ঢুকাবো যে কিভাবে কোন পরিস্থিতিতে কখন একটা কন্ডিশনের উপরে টেমপ্লেট ফাইলটা নিজের মতো করে জেনারেট করবে। এখানে আপনারা বুঝতেই পারছেন যে এ ধরনের কার্লিব্রেসগুলো হচ্ছে একটা স্পেশাল জিনজা ট্যাগ, যার মাধ্যমে বোঝা যায় যে এখানে কিছু কন্ডিশন অর্থাৎ লজিক ব্যবহার হবে। এই জায়গায় আমরা একটা জিনজা এর লুপ ব্যবহার করছি যাতে অনেকগুলো সুইচপোর্টের জন্য আমরা একটা ফাইনাল কনফিগারেশন তৈরি করতে পারি।

interface {{ interface.name }}
description {{ interface.description }}
{% if interface.uplink %}
switchport mode trunk
{% else %}
switchport access vlan {{ interface.vlan }}
switchport mode access
{% endif %}

ফর লুপ ব্যবহার করে ভেরিয়েবল দিয়ে কনফিগারেশন জেনারেশন

এটা একটা বেসিক টেমপ্লেট, তবে সামনেই এটাকে আমরা দেখাবো কিভাবে সব মিলিয়ে আসল কনফিগারেশন বের করা যায়। এই উদাহরণে আমরা একই ধরনের কনফিগারেশন দশটা সুইচপোর্ট এর জন্য তৈরি করছি, তবে এটা কেমন হয় যদি আমরা কিছু কিছু সুইচপোর্ট এর জন্য আলাদা কনফিগারেশন তৈরি করতে চাই?

{% for n in range(10) %}
interface GigabitEthernet0/{{ n+1 }}
description {{ interface.description }}
{% if n+1 == 1 %}
switchport mode trunk
{% else %}
switchport access vlan {{ interface.vlan }}
switchport mode access
{% endif %}
{% endfor %}

হাতেকলমে দেখি

Jinja2 টেমপ্লেটিং ইঞ্জিন এবং YAML ডাটা ফাইলের মাধ্যমে কনফিগারেশন জেনারেশন করা খুবই কার্যকর একটা পদ্ধতি। এই বইয়ে আমরা Jinja2 টেমপ্লেটিং ইঞ্জিনের মাধ্যমে কিভাবে ডাইনামিক নেটওয়ার্ক কনফিগারেশন তৈরি করা যায় তা বিশদভাবে আলোচনা করব।

YAML ডাটা ফাইল

প্রথমেই আমাদের একটা YAML ডাটা ফাইল তৈরি করতে হবে যেখানে নেটওয়ার্কের ইন্টারফেসের ডাটা থাকবে। নিচের উদাহরণে, আমরা তিনটি ইন্টারফেসের ডাটা সংরক্ষণ করেছি:

---
- name: GigabitEthernet0/1
  desc: uplink port
  uplink: true
- name: GigabitEthernet0/2
  desc: Server port number one
  vlan: 10
- name: GigabitEthernet0/3
  desc: Server port number two
  vlan: 10

প্রতিটি ইন্টারফেসের জন্য নিম্নলিখিত ফিল্ডগুলো আছে: - name: ইন্টারফেসের নাম। - desc: ইন্টারফেসের বর্ণনা। - uplink: ইন্টারফেসটি আপলিঙ্ক কিনা (এই ফিল্ডটি শুধুমাত্র GigabitEthernet0/1 এর জন্য সত্য)। - vlan: ইন্টারফেসের VLAN নম্বর (যদি থাকে)।

Jinja2 টেমপ্লেট

এখন আমাদের একটা Jinja2 টেমপ্লেট তৈরি করতে হবে যা YAML ডাটা ফাইল থেকে ডাটা নিয়ে কনফিগারেশন ফাইল তৈরি করবে। টেমপ্লেটটি হতে পারে template.j2 নামে। নিচে একটা সম্ভাব্য টেমপ্লেট ফাইলের উদাহরণ দেওয়া হলো:

{% for interface in interface_list %}
interface {{ interface.name }}
description {{ interface.desc }}
{% if interface.uplink %}
switchport mode trunk
{% else %}
switchport access vlan {{ interface.vlan }}
switchport mode access
{% endif %}
{% endfor %}

এই টেমপ্লেটটি প্রতিটি ইন্টারফেসের জন্য কনফিগারেশন তৈরি করবে। এখানে লুপ এবং শর্তাবলী ব্যবহৃত হয়েছে: - for লুপ: interface_list এর প্রতিটি ইন্টারফেসের জন্য লুপ চালাবে। - if শর্তাবলী: যদি uplink সত্য হয় তাহলে switchport mode trunk যুক্ত করবে, অন্যথায় switchport access vlan এবং switchport mode access যুক্ত করবে।

Python কোড

এখন আমাদের Python কোড তৈরি করতে হবে যা YAML ডাটা ফাইলটি পড়বে এবং Jinja2 টেমপ্লেট ব্যবহার করে একটা কনফিগারেশন ফাইল তৈরি করবে।

from jinja2 import Environment, FileSystemLoader
import yaml

# Jinja2 এনভায়রনমেন্ট সেটআপ
ENV = Environment(loader=FileSystemLoader('.'))

# Jinja2 টেমপ্লেট লোড
template = ENV.get_template("template.j2")

# YAML ফাইল ওপেন এবং ডাটা লোড
with open("data.yml") as f:
    interfaces = yaml.load(f, Loader=yaml.SafeLoader)

# Jinja2 টেমপ্লেট রেন্ডার করে আউটপুট প্রিন্ট
print(template.render(interface_list=interfaces))

এর মানে কি হতে পারে?

  1. Jinja2 এবং YAML লাইব্রেরি ইমপোর্ট করা:

    from jinja2 import Environment, FileSystemLoader
    import yaml
    
    এখানে jinja2 এবং yaml লাইব্রেরি ইমপোর্ট করা হয়েছে।

  2. Jinja2 এনভায়রনমেন্ট সেটআপ:

    ENV = Environment(loader=FileSystemLoader('.'))
    
    Environment অবজেক্ট তৈরি করা হয়েছে যা টেমপ্লেট ফাইলগুলো লোড করার জন্য ব্যবহৃত হবে। এখানে FileSystemLoader ব্যবহার করে জানানো হয়েছে যে টেমপ্লেট ফাইলগুলো বর্তমান ডিরেক্টরিতে আছে।

  3. টেমপ্লেট লোড করা:

    template = ENV.get_template("template.j2")
    
    template.j2 নামক টেমপ্লেট ফাইলটি লোড করা হয়েছে।

  4. YAML ফাইল ওপেন এবং ডাটা লোড:

    with open("data.yml") as f:
        interfaces = yaml.load(f, Loader=yaml.SafeLoader)
    
    data.yml ফাইলটি ওপেন করা হয়েছে এবং yaml.load ফাংশনের মাধ্যমে ডাটাগুলো Python ডিকশনারিতে লোড করা হয়েছে। এখানে SafeLoader ব্যবহার করা হয়েছে যাতে YAML ডাটা নিরাপদে লোড করা যায়।

  5. টেমপ্লেট রেন্ডার করা:

    print(template.render(interface_list=interfaces))
    
    interface_list নামে ডাটা পাস করে টেমপ্লেট রেন্ডার করা হয়েছে এবং আউটপুট প্রিন্ট করা হয়েছে।

আউটপুট

উপরের Python স্ক্রিপ্টটি চালানোর পরে আউটপুটটি নিম্নরূপ হতে পারে:

interface GigabitEthernet0/1
description uplink port
switchport mode trunk
interface GigabitEthernet0/2
description Server port number one
switchport access vlan 10
switchport mode access
interface GigabitEthernet0/3
description Server port number two
switchport access vlan 10
switchport mode access

এই আউটপুটটি YAML ডাটাকে ভিত্তি করে Jinja2 টেমপ্লেট ব্যবহার করে নেটওয়ার্ক কনফিগারেশন তৈরি করেছে। প্রতিটি ইন্টারফেসের জন্য কনফিগারেশন ডাটা ডাইনামিকভাবে টেমপ্লেটের মধ্যে প্রবেশ করানো হয়েছে।

আমরা শিখলাম কিভাবে Jinja2 টেমপ্লেট এবং YAML ডাটা ফাইল ব্যবহার করে ডাইনামিক নেটওয়ার্ক কনফিগারেশন তৈরি করা যায়। Jinja2 এর লুপ এবং শর্তাবলী ব্যবহার করে আমরা খুব সহজেই ডাটা থেকে কনফিগারেশন জেনারেট করতে পারি যা বিভিন্ন ধরনের নেটওয়ার্ক ডিভাইসে প্রয়োগ করা যায়। YAML ডাটা ফাইলের মাধ্যমে ডাটা আলাদা রাখার সুবিধা আছে, যা কনফিগারেশন এবং ডাটার মধ্যে স্পষ্ট বিভাজন সৃষ্টি করে। এতে কনফিগারেশন ম্যানেজমেন্ট আরও সহজ এবং কার্যকর হয়।