أدلة Ansible Playbooks: دليل مفصل
تتم provisioning منتجات OmniCRM باستخدام Ansible، مما يسمح بإدارة الخدمات بشكل آلي بناءً على المتطلبات المحددة لكل منتج ومخزونه المرتبط.
كيف تعمل Playbooks والمنتجات معًا
المفهوم الحاسم: Playbooks هي ما ينشئ فعليًا الخدمات في OmniCRM. عندما تقوم بتعيين Playbook لمنتج، فإنك تحدد ما يحدث عندما يتم provisioning ذلك المنتج - لكن ذلك يمكن أن يعني أشياء مختلفة لمنتجات مختلفة.
المنتجات تحفز Playbooks
عندما يتم provisioning منتج في OmniCRM:
- يحدد تعريف المنتج أي Playbook يجب تشغيله (عبر حقل
provisioning_play) - يمرر المنتج المتغيرات إلى Playbook (عبر
provisioning_json_varsواختيارات المخزون) - يتم تنفيذ Playbook وتقوم بما تم برمجته للقيام به
- يحدد Playbook ما يتم إنشاؤه (إذا كان هناك أي شيء)
ما يمكن أن تفعله Playbooks
يمكن أن تقوم Playbook واحدة بال provisioning بما يلي:
إنشاء خدمات متعددة
قد تقوم Playbook منتج مجمعة بإنشاء:
- سجل خدمة إنترنت رئيسي
- سجل خدمة إضافة IPTV
- سجل خدمة VoIP
- كل ذلك من خلال إجراء provisioning واحد للمنتج
عدم إنشاء أي خدمات
بعض Playbooks لا تنشئ سجلات خدمة على الإطلاق:
- Playbook تقوم فقط بتكوين معدات CPE
- Playbook ترسل التكوين إلى معدات الشبكة
- Playbook تقوم بتحديث الأنظمة الخارجية
إنشاء خدمة واحدة
النمط الأكثر شيوعًا:
- إنشاء سجل خدمة واحد للعميل
- ربط المخزون بتلك الخدمة
- إعداد الفوترة لتلك الخدمة
تعديل الخدمات الحالية
Playbooks التوب-أب والإضافة:
- لا تنشئ خدمات جديدة
- تحديث سجلات الخدمة الحالية (إضافة بيانات، تمديد انتهاء الصلاحية، إلخ)
- إضافة أرصدة إلى حسابات الفوترة الحالية
تنفيذ إجراءات بدون سجلات خدمة
بعض Playbooks هي عمليات تشغيلية بحتة:
- إعادة تعيين أرصدة الحسابات
- تبديل عناصر المخزون بين العملاء
- توليد تقارير أو تكوينات
مثال: سلوكيات Playbook المختلفة
# المنتج 1: خدمة SIM المحمول (تنشئ خدمة واحدة)
# provisioning_play: play_simple_service
- Creates service record in CRM
- Creates billing account in OCS
- Assigns SIM card and phone number inventory
- Sends welcome email
# المنتج 2: حزمة الإنترنت (تنشئ 3 خدمات)
# provisioning_play: play_bundle_internet_tv_voice
- Creates internet service record
- Creates IPTV service record
- Creates VoIP service record
- Links all to same customer
- Single billing account for the bundle
# المنتج 3: إعادة شحن البيانات (تنشئ 0 خدمات)
# provisioning_play: play_topup_no_charge
- Finds existing service by service_id
- Adds data balance to existing OCS account
- Updates service expiry date
- NO new service created
# المنتج 4: تكوين CPE (تنشئ 0 خدمات)
# provisioning_play: play_prov_cpe_mikrotik
- Generates router configuration
- Updates inventory record with config
- Emails config to support team
- NO service created (just equipment setup)
النقطة الرئيسية: تحدد Playbook السلوك، والمنتج هو مجرد محفز.
Plays مقابل المهام
فهم الفرق بين Plays وTasks هو أمر أساسي للعمل مع Playbooks في OmniCRM.
Play (Playbook)
سير عمل provisioning كامل ينظم مهام متعددة لتحقيق هدف تجاري. Plays هي Playbooks على المستوى الأعلى المخزنة في OmniCRM-API/Provisioners/plays/ ويتم الإشارة إليها في تعريفات المنتجات.
أمثلة:
play_simple_service.yaml- توفير خدمة أساسيةplay_topup_no_charge.yaml- تطبيق إعادة شحن مجانية على خدمةplay_prov_cpe_mikrotik.yaml- تكوين معدات العملاء
Task (مكون قابل لإعادة الاستخدام)
مجموعة من العمليات المستقلة والقابلة لإعادة الاستخدام التي يمكن تضمي��ها بواسطة Plays متعددة. يتم تمييز Tasks بـ task_ وتعيش في نفس الدليل.
أمثلة:
task_welcome_email.yaml- إرسال بريد إلكتروني ترحيبي للعميلtask_activate_olt.yaml- تفعيل معدات OLTtask_notify_ocs.yaml- إرسال إشعارات إلى نظام الفوترة
العلاقة بينهما:
# play_simple_service.yaml (A Play)
- name: Simple Provisioning Play
hosts: localhost
tasks:
- name: Main provisioning block
block:
- name: Create service
uri: ...
- name: Configure billing
uri: ...
# Include reusable task
- include_tasks: task_welcome_email.yaml
# Include post-provisioning tasks
- include_tasks: post_provisioning_tasks.yaml
هيكل Playbook وتشريحه
تتبع جميع Playbooks في OmniCRM هيكلًا متسقًا. فهم هذا الهيكل ضروري لإنشاء وصيانة Playbooks.
الهيكل الأساسي
تبدأ كل Playbook بهذه العناوين القياسية:
- name: Descriptive Name of the Playbook
hosts: localhost # دائمًا localhost لـ OmniCRM
gather_facts: no # معطل للأداء
become: False # لا تصعيد للامتيازات
tasks:
- name: Main block
block:
# تذهب مهام provisioning هنا
rescue:
# تذهب مهام التراجع/التنظيف هنا
شرح العناوين
name
اسم وصفي يظهر في سجلات provisioning وواجهة المستخدم. يظهر هذا كـ playbook_description في سجل التوفير.
hosts: localhost
تعمل جميع Playbooks في OmniCRM على localhost لأنها تتفاعل مع الأنظمة البعيدة عبر APIs، وليس SSH.
gather_facts: no
تم تعطيل جمع الحقائق في Ansible لأن:
- لا نحتاج إلى معلومات النظام
- يضيف عبئًا غير ضروري
- يمكن أن يتسبب في تعطل المتصفحات إذا تم عر��ه في مخرجات التصحيح
become: False
لا حاجة لتصعيد الامتيازات لأننا نقوم بإجراء مكالمات API، وليس تعديل ملفات النظام.
تحميل التكوين
يجب على كل Playbook تحميل ملف التكوين المركزي:
tasks:
- name: Include vars of crm_config
ansible.builtin.include_vars:
file: "../../crm_config.yaml"
name: crm_config
هذا يجعل التكوين متاحًا كـ crm_config.ocs.cgrates، crm_config.crm.base_url، إلخ.
عادةً ما يحتوي crm_config.yaml على:
ocs:
cgrates: "10.0.1.100:2080"
ocsTenant: "default_tenant"
crm:
base_url: "https://crm.example.com"
أنماط الوصول إلى المتغيرات
يمكن أن تأتي المتغيرات من عدة مصادر:
من تعريف المنتج:
- name: Access product_id passed by OmniCRM
debug:
msg: "Provisioning product {{ product_id }}"
من اختيار المخزون:
- name: Get inventory ID for SIM Card
set_fact:
sim_card_id: "{{ hostvars[inventory_hostname]['SIM Card'] | int }}"
when: "'SIM Card' in hostvars[inventory_hostname]"
من استجابات API:
- name: Get Product information from CRM API
uri:
url: "http://localhost:5000/crm/product/product_id/{{ product_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: api_response_product
- name: Use the product name
debug:
msg: "Product name is {{ api_response_product.json.product_name }}"
أنماط Playbook الشائعة
نمط توفير الخدمة
هذا هو النمط الأكثر شيوعًا لإنشاء خدمات جديدة.
- name: Service Provisioning Playbook
hosts: localhost
gather_facts: no
become: False
tasks:
- name: Main block
block:
# 1. تحميل التكوين
- name: Include vars of crm_config
ansible.builtin.include_vars:
file: "../../crm_config.yaml"
name: crm_config
# 2. الحصول على معلومات المنتج
- name: Get Product information from CRM API
uri:
url: "http://localhost:5000/crm/product/product_id/{{ product_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
validate_certs: no
register: api_response_product
# 3. الحصول على معلومات العميل
- name: Get Customer information from CRM API
uri:
url: "http://localhost:5000/crm/customer/customer_id/{{ customer_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: api_response_customer
# 4. تعيين الحقائق من البيانات المسترجعة
- name: Set package facts
set_fact:
package_name: "{{ api_response_product.json.product_name }}"
package_comment: "{{ api_response_product.json.comment }}"
setup_cost: "{{ api_response_product.json.retail_setup_cost }}"
monthly_cost: "{{ api_response_product.json.retail_cost }}"
# 5. توليد معرفات فريدة
- name: Generate UUID
set_fact:
uuid: "{{ 99999999 | random | to_uuid }}"
- name: Generate Service UUID
set_fact:
service_uuid: "Service_{{ uuid[0:8] }}"
# 6. إنشاء حساب في نظام الفوترة
- name: Create account in OCS/CGRateS
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
headers:
Content-Type: "application/json"
body:
{
"method": "ApierV2.SetAccount",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"ActionPlanIds": [],
"ActionPlansOverwrite": true,
"ExtraOptions": {
"AllowNegative": false,
"Disabled": false
},
"ReloadScheduler": true
}]
}
status_code: 200
register: ocs_response
- name: Verify OCS account creation
assert:
that:
- ocs_response.status == 200
- ocs_response.json.result == "OK"
# 7. إضافة رصيد أولي
- name: Add 0 Monetary Balance
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.AddBalance",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"BalanceType": "*monetary",
"Categories": "*any",
"Balance": {
"ID": "Initial Balance",
"Value": 0,
"ExpiryTime": "+4320h",
"Weight": 1,
"Blocker": true
}
}]
}
status_code: 200
register: balance_response
# 8. إنشاء سجل خدمة في CRM
- name: Get current date and time in ISO 8601 format
command: date --utc +%Y-%m-%dT%H:%M:%S%z
register: current_date_time
- name: Add Service via API
uri:
url: "http://localhost:5000/crm/service/"
method: PUT
body_format: json
headers:
Content-Type: "application/json"
Authorization: "Bearer {{ access_token }}"
body:
{
"customer_id": "{{ customer_id }}",
"product_id": "{{ product_id }}",
"service_name": "{{ package_name }} - {{ service_uuid }}",
"service_type": "generic",
"service_uuid": "{{ service_uuid }}",
"service_billed": true,
"service_taxable": true,
"service_provisioned_date": "{{ current_date_time.stdout }}",
"service_status": "Active",
"wholesale_cost": "{{ api_response_product.json.wholesale_cost | float }}",
"retail_cost": "{{ monthly_cost | float }}"
}
status_code: 200
register: service_creation_response
# 9. إضافة معاملة تكلفة الإعداد
- name: Add Setup Cost Transaction via API
uri:
url: "http://localhost:5000/crm/transaction/"
method: PUT
headers:
Content-Type: "application/json"
Authorization: "Bearer {{ access_token }}"
body_format: json
body:
{
"customer_id": {{ customer_id | int }},
"service_id": {{ service_creation_response.json.service_id | int }},
"title": "{{ package_name }} - Setup Costs",
"description": "Setup costs for {{ package_comment }}",
"invoice_id": null,
"retail_cost": "{{ setup_cost | float }}"
}
return_content: yes
register: transaction_response
# 10. تضمين مهام ما بعد التوفير
- include_tasks: post_provisioning_tasks.yaml
rescue:
# قسم التراجع/التنظيف
- name: Print all vars for debugging
debug:
var: hostvars[inventory_hostname]
- name: Remove account in OCS
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV2.RemoveAccount",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"ReloadScheduler": true
}]
}
status_code: 200
ignore_errors: True
when: service_uuid is defined
- name: Delete Service from CRM if it was created
uri:
url: "http://localhost:5000/crm/service/service_id/{{ service_creation_response.json.service_id }}"
method: DELETE
headers:
Authorization: "Bearer {{ access_token }}"
status_code: 200
ignore_errors: True
when: service_creation_response is defined
- name: Fail if not intentional deprovision
assert:
that:
- action == "deprovision"
نمط التوب-أب/إعادة الشحن
يستخدم لإضافة أرصدة أو بيانات أو وقت إلى الخدمات الحالية.
- name: Service Topup Playbook
hosts: localhost
gather_facts: no
become: False
tasks:
- name: Include vars of crm_config
ansible.builtin.include_vars:
file: "../../crm_config.yaml"
name: crm_config
# 1. الحصول على معلومات الخدمة
- name: Get Service information from CRM API
uri:
url: "http://localhost:5000/crm/service/service_id/{{ service_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: api_response_service
# 2. الحصول على معلومات المنتج (ما يجب إعادة شحنه)
- name: Get Product information from CRM API
uri:
url: "http://localhost:5000/crm/product/product_id/{{ product_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: api_response_product
# 3. استخراج تفاصيل الخدمة
- name: Set service facts
set_fact:
service_uuid: "{{ api_response_service.json.service_uuid }}"
customer_id: "{{ api_response_service.json.customer_id }}"
package_name: "{{ api_response_product.json.product_name }}"
topup_value: "{{ api_response_product.json.retail_cost }}"
# 4. تنفيذ الإجراء في نظام الفوترة (إعادة شحن مجا��ية)
- name: Execute Action to add credits
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "APIerSv1.ExecuteAction",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"ActionsId": "Action_Topup_Standard"
}]
}
status_code: 200
register: action_response
- name: Verify action executed successfully
assert:
that:
- action_response.status == 200
- action_response.json.result == "OK"
# 5. إعادة تعيين أي حدود تم تشغيلها
- name: Reset ActionTriggers
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "APIerSv1.ResetAccountActionTriggers",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"Executed": false
}]
}
status_code: 200
# 6. تحديث تواريخ الخدمة
- name: Calculate new expiry date
command: "date --utc +%Y-%m-%dT%H:%M:%S%z -d '+30 days'"
register: new_expiry_date
- name: Update Service with new expiry
uri:
url: "http://localhost:5000/crm/service/{{ service_id }}"
method: PATCH
headers:
Authorization: "Bearer {{ access_token }}"
Content-Type: "application/json"
body_format: json
body:
{
"service_deactivate_date": "{{ new_expiry_date.stdout }}",
"service_status": "Active"
}
# 7. اختياري: إرسال إشعار
- name: Send Notification SMS
uri:
url: "http://sms-gateway/api/send"
method: POST
body_format: json
body:
{
"source": "CompanyName",
"destination": "{{ customer_phone }}",
"message": "Your service has been topped up. New expiry: {{ new_expiry_date.stdout }}"
}
status_code: 201
ignore_errors: True
نمط توفير CPE
يستخدم لتكوين معدات العملاء (الموجهات، المودمات، ONTs).
- name: CPE Provisioning Playbook
hosts: localhost
gather_facts: no
become: False
tasks:
- name: Include vars of crm_config
ansible.builtin.include_vars:
file: "../../crm_config.yaml"
name: crm_config
# 1. الحصول على عنصر المخزون لـ CPE
- name: Set CPE inventory ID from hostvars
set_fact:
cpe_inventory_id: "{{ hostvars[inventory_hostname]['WiFi Router CPE'] | int }}"
when: "'WiFi Router CPE' in hostvars[inventory_hostname]"
# 2. الحصول على تفاصيل CPE من المخزون
- name: Get Inventory data for CPE
uri:
url: "{{ crm_config.crm.base_url }}/crm/inventory/inventory_id/{{ cpe_inventory_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: api_response_cpe
# 3. الحصول على معلومات موقع العميل
- name: Get Site info from API
uri:
url: "{{ crm_config.crm.base_url }}/crm/site/customer_id/{{ customer_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: api_response_site
# 4. تحديث مخزون CPE بالموقع
- name: Patch CPE inventory item with location
uri:
url: "{{ crm_config.crm.base_url }}/crm/inventory/inventory_id/{{ cpe_inventory_id }}"
method: PATCH
body_format: json
headers:
Authorization: "Bearer {{ access_token }}"
body:
{
"address_line_1": "{{ api_response_site.json.0.address_line_1 }}",
"city": "{{ api_response_site.json.0.city }}",
"state": "{{ api_response_site.json.0.state }}",
"latitude": "{{ api_response_site.json.0.latitude }}",
"longitude": "{{ api_response_site.json.0.longitude }}"
}
status_code: 200
# 5. توليد بيانات الاعتماد
- name: Set CPE hostname
set_fact:
cpe_hostname: "CPE_{{ cpe_inventory_id }}"
cpe_username: "admin_{{ cpe_inventory_id }}"
- name: Generate random password
set_fact:
cpe_password: "{{ lookup('pipe', 'cat /dev/urandom | tr -dc a-zA-Z0-9 | head -c 16') }}"
# 6. توليد بيانات اعتماد WiFi
- name: Set WiFi SSID
set_fact:
wifi_ssid: "Network_{{ cpe_inventory_id }}"
- name: Generate WiFi password
set_fact:
word_list:
- apple
- cloud
- river
- mountain
- ocean
- name: Create WiFi PSK
set_fact:
random_word: "{{ word_list | random }}"
random_number: "{{ 99999 | random(start=10000) }}"
- name: Combine WiFi PSK
set_fact:
wifi_psk: "{{ random_word }}{{ random_number }}"
# 7. توليد ملف التكوين
- name: Set config filename
set_fact:
config_name: "{{ cpe_hostname }}_{{ lookup('pipe', 'date +%Y%m%d%H%M%S') }}.cfg"
config_dest: "/tmp/{{ cpe_hostname }}_{{ lookup('pipe', 'date +%Y%m%d%H%M%S') }}.cfg"
- name: Create config from template
template:
src: "templates/cpe_router_config.j2"
dest: "{{ config_dest }}"
# 8. قراءة التكوين الناتج
- name: Read config file
ansible.builtin.slurp:
src: "{{ config_dest }}"
register: config_content
# 9. تحديث المخزون بمعلومات التوفير
- name: Patch CPE inventory with config
uri:
url: "{{ crm_config.crm.base_url }}/crm/inventory/inventory_id/{{ cpe_inventory_id }}"
method: PATCH
body_format: json
headers:
Authorization: "Bearer {{ access_token }}"
body:
{
"itemtext3": "{{ wifi_ssid }}",
"itemtext4": "{{ wifi_psk }}",
"management_url": "{{ cpe_hostname }}",
"management_username": "{{ cpe_username }}",
"management_password": "{{ cpe_password }}",
"config_content": "{{ config_content.content | b64decode }}",
"inventory_notes": "Provisioned: {{ lookup('pipe', 'date +%Y-%m-%d') }}"
}
status_code: 200
# 10. إرسال التكوين إلى فريق الدعم
- name: Email configuration to support
uri:
url: "https://api.mailjet.com/v3.1/send"
method: POST
body_format: json
headers:
Content-Type: "application/json"
body:
{
"Messages": [{
"From": {
"Email": "provisioning@example.com",
"Name": "Provisioning System"
},
"To": [{
"Email": "support@example.com",
"Name": "Support Team"
}],
"Subject": "CPE Config - {{ cpe_hostname }}",
"Attachments": [{
"ContentType": "text/plain",
"Filename": "{{ config_name }}",
"Base64Content": "{{ config_content.content }}"
}]
}]
}
user: "{{ mailjet_api_key }}"
password: "{{ mailjet_api_secret }}"
force_basic_auth: true
status_code: 200
نمط التجديد التلقائي
تكوين ال��سوم المتكررة أو التجديدات التلقائية باستخدام CGRateS ActionPlans.
# جزء من Playbook إعادة الشحن الذي يقوم بإعداد التجديد التلقائي
# 1. تطبيع معلمة auto_renew
- name: Normalize auto_renew to boolean
set_fact:
auto_renew_bool: "{{ (auto_renew | string | lower) in ['true', '1', 'yes'] }}"
# 2. إنشاء إجراء للتجديد التلقائي
- name: Create Action for AutoRenew
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.SetActions",
"params": [{
"ActionsId": "Action_AutoTopup_{{ service_uuid }}_{{ product_id }}",
"Overwrite": true,
"Actions": [
{
"Identifier": "*http_post",
"ExtraParameters": "{{ crm_config.crm.base_url }}/crm/provision/simple_provision_addon/service_id/{{ service_id }}/product_id/{{ product_id }}"
},
{
"Identifier": "*cdrlog",
"BalanceType": "*generic",
"ExtraParameters": "{\"Category\":\"^activation\",\"Destination\":\"Auto Renewal\"}"
}
]
}]
}
status_code: 200
register: action_response
when: auto_renew_bool
# 3. إنشاء ActionPlan شهري
- name: Create ActionPlan for Monthly Renewal
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.SetActionPlan",
"params": [{
"Id": "ActionPlan_Monthly_{{ service_uuid }}_{{ product_id }}",
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"ActionPlan": [{
"ActionsId": "Action_AutoTopup_{{ service_uuid }}_{{ product_id }}",
"Years": "*any",
"Months": "*any",
"MonthDays": "*any",
"WeekDays": "*any",
"Time": "*monthly",
"StartTime": "*now",
"Weight": 10
}],
"Overwrite": true,
"ReloadScheduler": true
}]
}
status_code: 200
when: auto_renew_bool
# 4. تعيين ActionPlan للحساب
- name: Assign ActionPlan to account
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV2.SetAccount",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"ActionPlanIds": ["ActionPlan_Monthly_{{ service_uuid }}_{{ product_id }}"],
"ActionPlansOverwrite": true,
"ReloadScheduler": true
}]
}
status_code: 200
when: auto_renew_bool
# 5. إزالة ActionPlan إذا تم تعطيل التجديد التلقائي
- name: Remove ActionPlan from account
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.RemoveActionPlan",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Id": "ActionPlan_Monthly_{{ service_uuid }}_{{ product_id }}"
}]
}
status_code: 200
ignore_errors: true
when: not auto_renew_bool
المهام القابلة لإعادة الاستخدام
المهام القابلة لإعادة الاستخدام هي Playbooks صغيرة ومستقلة يمكن تضمينها بواسطة Plays متعددة. إنها تعزز إعادة استخدام الكود والاتساق.
مهمة البريد الإلكتروني الترحيبي
task_welcome_email.yaml - ترسل بريدًا إلكترونيًا ترحيبيًا للعملاء الجدد.
# تتوقع هذه المهمة تعيين ه��ه المتغيرات بواسطة Play الأب:
# - api_response_customer (تفاصيل العميل)
# - package_name (اسم المنتج)
# - monthly_cost (التكلفة المتكررة)
# - setup_cost (التكلفة لمرة واحدة)
- name: Set email configuration
set_fact:
mailjet_api_key: "{{ lookup('env', 'MAILJET_API_KEY') }}"
mailjet_api_secret: "{{ lookup('env', 'MAILJET_SECRET') }}"
email_from: "noreply@example.com"
recipients: []
- name: Set email subject and sender name
set_fact:
email_subject: "Welcome to our service!"
email_from_name: "Customer Service Team"
- name: Prepare list of recipients from customer contacts
loop: "{{ api_response_customer.json.contacts }}"
set_fact:
recipients: "{{ recipients + [{'Email': item.contact_email, 'Name': item.contact_firstname ~ ' ' ~ item.contact_lastname}] }}"
- name: Get first contact name
set_fact:
first_contact: "{{ api_response_customer.json.contacts[0].contact_firstname }}"
- name: Send welcome email
uri:
url: "https://api.mailjet.com/v3.1/send"
method: POST
body_format: json
headers:
Content-Type: "application/json"
body:
{
"Messages": [{
"From": {
"Email": "{{ email_from }}",
"Name": "{{ email_from_name }}"
},
"To": "{{ recipients }}",
"Subject": "{{ email_subject }}",
"TextPart": "Dear {{ first_contact }}, welcome! Your service is ready.",
"HTMLPart": "Dear {{ first_contact }},<br/><h3>Welcome!</h3><br/>Your {{ package_name }} service is now active.<br/>Monthly cost: ${{ monthly_cost }}<br/>Setup fee: ${{ setup_cost }}<br/>If you have any questions, contact support@example.com"
}]
}
user: "{{ mailjet_api_key }}"
password: "{{ mailjet_api_secret }}"
force_basic_auth: true
status_code: 200
register: email_response
مهام ما بعد التوفير
post_provisioning_tasks.yaml - عمليات التنظيف والإشعارات القياسية التي يتم تشغيلها بعد كل توفير.
# يتم تضمين هذا الملف في نهاية معظم Playbooks التوفير
# يتعامل مع العمليات الشائعة بعد التوفير
- include_tasks: task_notify_ocs.yaml
قد يحتوي task_notify_ocs.yaml على:
- name: Notify OCS of provisioning completion
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "APIerSv1.ReloadCache",
"params": [{
"ArgsCache": "*all"
}]
}
status_code: 200
ignore_errors: true
العمليات الشائعة
العمل مع المخزون
استرجاع تفاصيل المخزون:
- name: Get SIM Card inventory ID
set_fact:
sim_inventory_id: "{{ hostvars[inventory_hostname]['SIM Card'] | int }}"
when: "'SIM Card' in hostvars[inventory_hostname]"
- name: Get SIM Card details
uri:
url: "{{ crm_config.crm.base_url }}/crm/inventory/inventory_id/{{ sim_inventory_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: sim_response
- name: Extract SIM details
set_fact:
iccid: "{{ sim_response.json.iccid }}"
imsi: "{{ sim_response.json.imsi }}"
ki: "{{ sim_response.json.ki }}"
تعيين المخزون للعميل:
- name: Assign SIM to customer
uri:
url: "{{ crm_config.crm.base_url }}/crm/inventory/inventory_id/{{ sim_inventory_id }}"
method: PATCH
headers:
Authorization: "Bearer {{ access_token }}"
body_format: json
body:
{
"customer_id": {{ customer_id }},
"service_id": {{ service_id }},
"item_state": "Assigned"
}
status_code: 200
عمليات التاريخ والوقت
الحصول على التاريخ/الوقت الحالي:
- name: Get current date and time in ISO 8601 format
command: date --utc +%Y-%m-%dT%H:%M:%S%z
register: current_date_time
- name: Get today's date only
set_fact:
today: "{{ lookup('pipe', 'date +%Y-%m-%d') }}"
حساب التواريخ المستقبلية:
- name: Calculate expiry date 30 days from now
command: "date --utc +%Y-%m-%dT%H:%M:%S%z -d '+30 days'"
register: expiry_date
- name: Calculate date 90 days in future
command: "date --utc +%Y-%m-%d -d '+{{ days }} days'"
register: future_date
vars:
days: 90
توليد قيم عشوائية
UUIDs والمعرفات:
- name: Generate UUID
set_fact:
uuid: "{{ 99999999 | random | to_uuid }}"
- name: Generate service identifier
set_fact:
service_uuid: "SVC_{{ uuid[0:8] }}"
كلمات مرور عشوائية:
- name: Generate secure password
set_fact:
password: "{{ lookup('pipe', 'cat /dev/urandom | tr -dc a-zA-Z0-9 | head -c 16') }}"
عبارات مرور سهلة التذكر:
- name: Set word list
set_fact:
words:
- alpha
- bravo
- charlie
- delta
- echo
- name: Generate passphrase
set_fact:
word: "{{ words | random }}"
number: "{{ 99999 | random(start=10000) }}"
- name: Combine into passphrase
set_fact:
passphrase: "{{ word }}{{ number }}"
العمل مع CGRateS/OCS
إنشاء الحسابات:
- name: Create billing account
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV2.SetAccount",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"ActionPlanIds": [],
"ActionPlansOverwrite": true,
"ExtraOptions": {
"AllowNegative": false,
"Disabled": false
},
"ReloadScheduler": true
}]
}
status_code: 200
register: account_response
إضافة الأرصدة:
- name: Add data balance
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.AddBalance",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"BalanceType": "*data",
"Categories": "*any",
"Balance": {
"ID": "Data Package",
"Value": 10737418240,
"ExpiryTime": "+720h",
"Weight": 10
}
}]
}
status_code: 200
تنفيذ الإجراءات:
- name: Execute charging action
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "APIerSv1.ExecuteAction",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"ActionsId": "Action_Standard_Charge"
}]
}
status_code: 200
الحصول على معلومات الحساب:
- name: Get account details
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV2.GetAccount",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}"
}]
}
status_code: 200
register: account_info
العمل مع ملفات تعريف السمات:
- name: Get AttributeProfile
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "APIerSv1.GetAttributeProfile",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"ID": "ATTR_{{ service_uuid }}"
}]
}
return_content: yes
status_code: 200
register: attr_response
ignore_errors: true
- name: Extract attribute value
set_fact:
phone_number: "{{ attr_response.json.result.Attributes | json_query(\"[?Path=='*req.PhoneNumber'].Value[0].Rules\") | first }}"
when: attr_response is defined
المنطق الشرطي
التحقق مما إذا كانت المتغيرات موجودة:
- name: Use custom value or default
set_fact:
monthly_cost: "{{ custom_cost | default(50.00) }}"
- name: Only run if variable is defined
debug:
msg: "Service UUID is {{ service_uuid }}"
when: service_uuid is defined
الشروط البوليانية:
- name: Provision equipment
include_tasks: configure_cpe.yaml
when: provision_cpe | default(false) | bool
- name: Skip if deprovision
assert:
that:
- action != "deprovision"
when: action is defined
شروط متعددة:
- name: Complex conditional task
uri:
url: "{{ endpoint }}"
method: POST
when:
- service_uuid is defined
- customer_id is defined
- action != "deprovision"
- enable_feature | default(true) | bool
الحلقات والتكرار
الحلقات البسيطة:
- name: Create multiple balances
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.AddBalance",
"params": [{
"Account": "{{ service_uuid }}",
"BalanceType": "{{ item.type }}",
"Balance": {
"Value": "{{ item.value }}"
}
}]
}
loop:
- { type: "*voice", value: 3600 }
- { type: "*data", value: 10737418240 }
- { type: "*sms", value: 100 }
التكرار عبر استجابات API:
- name: Get all customer sites
uri:
url: "{{ crm_config.crm.base_url }}/crm/site/customer_id/{{ customer_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
register: sites_response
- name: Configure equipment at each site
debug:
msg: "Configuring site at {{ item.address_line_1 }}"
loop: "{{ sites_response.json }}"
معالجة الأخطاء
استخدام ignore_errors:
- name: Optional SMS notification
uri:
url: "http://sms-gateway/send"
method: POST
body: {...}
ignore_errors: true
التحقق من صحة الاستجابات:
- name: Verify API response
assert:
that:
- response.status == 200
- response.json.result == "OK"
fail_msg: "API call failed: {{ response.json }}"
معالجة الأخطاء الشرطية:
- name: Try to get existing service
uri:
url: "{{ crm_config.crm.base_url }}/crm/service/service_uuid/{{ service_uuid }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
register: service_lookup
failed_when: false
- name: Create service if it doesn't exist
uri:
url: "{{ crm_config.crm.base_url }}/crm/service/"
method: PUT
body: {...}
when: service_lookup.status == 404
أفضل الممارسات
تسمية المتغيرات
استخدم أسماء وصفية ومتسقة:
# جيد
service_uuid: "SVC_12345"
customer_name: "John Smith"
monthly_cost: 49.99
# سيء
svc: "SVC_12345"
name: "John Smith"
cost: 49.99
قم بتمييز المتغيرات حسب المصدر:
api_response_customer: {...}
api_response_product: {...}
cgr_account_info: {...}
التصحيح
طباعة المتغيرات لأغراض استكشاف الأخطاء:
- name: Print all variables
debug:
var: hostvars[inventory_hostname]
- name: Print specific variable
debug:
msg: "Service UUID: {{ service_uuid }}"
- name: Print API response
debug:
var: api_response_product.json
التحقق
تحقق دائمًا من استجابات API الحرجة:
- name: Create account
uri:
url: "{{ billing_endpoint }}"
method: POST
body: {...}
register: response
- name: Verify account creation
assert:
that:
- response.status == 200
- response.json.result == "OK"
fail_msg: "Failed to create account: {{ response.json }}"
التكرار الآمن
صمم المهام لتكون قابلة للتكرار بأمان:
# تحقق مما إذا كان المورد موجودًا أولاً
- name: Check if account exists
uri:
url: "{{ ocs_endpoint }}/get_account"
method: POST
body: {"Account": "{{ service_uuid }}"}
register: account_check
failed_when: false
# إنشاء فقط إذا لم يكن موجودًا
- name: Create account
uri:
url: "{{ ocs_endpoint }}/create_account"
method: POST
body: {...}
when: account_check.status == 404
الأمان
لا تقم بتشفير بيانات الاعتماد:
# سيء
mailjet_api_key: "abc123def456"
# جيد - استخدم المتغيرات البيئية
mailjet_api_key: "{{ lookup('env', 'MAILJET_API_KEY') }}"
# جيد - استخدم ملف التكوين
mailjet_api_key: "{{ crm_config.email.api_key }}"
استخدم دائمًا HTTPS والمصادقة:
- name: Call external API
uri:
url: "https://api.example.com/endpoint"
method: POST
headers:
Authorization: "Bearer {{ access_token }}"
validate_certs: yes
الوثائق
وثق المنطق المعقد:
# حساب الرسوم النسبية لشهر جزئي
# إذا قام العميل بالتسجيل في اليوم الخامس عشر وكانت الفوترة في اليوم الأول،
# فقم بفرض 50% من التكلفة الشهرية للأيام المتبقية
- name: Calculate days until end of month
command: "date -d 'last day of this month' +%d"
register: days_in_month
- name: Get current day
command: "date +%d"
register: current_day
- name: Calculate pro-rata amount
set_fact:
days_remaining: "{{ (days_in_month.stdout | int) - (current_day.stdout | int) }}"
pro_rata_cost: "{{ (monthly_cost | float) * (days_remaining | float) / (days_in_month.stdout | float) }}"
اختبار Playbooks
نهج الاختبار
- جرب أولاً: اختبر مع أنظمة غير إنتاجية
- تحقق من المتغيرات: استخدم مهام التصحيح لتأكيد وجود جميع المتغيرات المطلوبة
- تحقق من الاستجابات: تحقق من استجابات API قبل المتابعة
- اختبار التراجع: افشل المهام عمدًا للتحقق من عمل كتل الإنقاذ
- اختبار إلغاء التوفير: اختبر مع
action: "deprovision"للتحقق من التنظيف
Playbook اختبار المثال:
- name: Test Service Provisioning
hosts: localhost
gather_facts: no
tasks:
- name: Verify required variables
assert:
that:
- product_id is defined
- customer_id is defined
- access_token is defined
fail_msg: "Missing required variables"
- name: Test API connectivity
uri:
url: "http://localhost:5000/crm/health"
method: GET
register: health_check
- name: Verify health check
assert:
that:
- health_check.status == 200
الأخطاء الشائعة
عدم وجود تحويلات نوعية:
# خاطئ - قد تكون سلسلة
customer_id: "{{ customer_id }}"
# صحيح - تأكد من أنها صحيحة
customer_id: {{ customer_id | int }}
عدم التعامل مع المتغيرات غير المعرفة:
# خاطئ - يفشل إذا لم يكن معرفًا
service_uuid: "{{ service_uuid }}"
# صحيح - قدم افتراضيًا
service_uuid: "{{ service_uuid | default('') }}"
نسيان التحقق:
# خاطئ - لا يتحقق من الاستجابة
- name: Create account
uri: ...
register: response
# صحيح - تحقق من الاستجابة
- name: Create account
uri: ...
register: response
- name: Verify creation
assert:
that:
- response.json.result == "OK"
سير العمل في التوفير
بشكل عام، سيعمل موظفو Omnitouch مع العميل على:
- تحديد متطلبات المنتج
- تطوير Playbooks Ansible اللازمة لأتمتة عملية التوفير
- اختبار Playbooks في بيئة staging
- نشرها في الإنتاج
هذا يضمن نشر كل خدمة بشكل م��سق وموثوق، مما يقلل من خطر الأخطاء ويضمن أن جميع الخطوات اللازمة مكتملة بالترتيب الصحيح.
متغيرات Ansible
تشمل المتغيرات المرسلة إلى Playbooks Ansible:
متغيرات المنتج
مشتقة من تكوينات منتج OmniCRM وتحدد كيفية إعداد الخدمة.
متغيرات المخزون
محددة من المخزون، تشمل عناصر مثل المودمات، بطاقات SIM، كتل عناوين IP، أو أرقام الهواتف المطلوبة للتوفير.
متغيرات النظام
تضاف تلقائيًا بواسطة OmniCRM:
product_id- المنتج الذي يتم توفيرهcustomer_id- العميل الذي يتلقى الخدمةservice_id- الخدمة التي يتم تعديلها (لإعادة الشحن/التغييرات)access_token- JWT لمصادقة API
إلغاء التوفير
عندما لم تعد الخدمة مطلوبة، يتم أيضًا استخدام Playbooks Ansible لإلغاء توفير الخدمة باستخدام نمط كتلة rescue. هذا:
- يزيل أي تكوينات
- يحرر المخزون مرة أخرى إلى المجموعة
- يحذف حسابات الفوترة
- يضمن الحفاظ على نظافة النظام
التراجع ومعالجة الأخطاء
يتم استخدام ميزة block/rescue في Ansible أثناء كل من التوفير وإلغاء التوفير للتعامل مع الأخطاء بشكل سلس. إذا فشلت مهمة في أي نقطة أثناء التوفير، فإن قسم الإنقاذ يقوم تلقائيًا بالتراجع عن التغييرات للعودة إلى حالة متسقة. هذا يضمن الموثوقية ويقلل من خطر النشر الجزئي أو الفاشل.
للحصول على تفاصيل كاملة حول نظام التوفير، وسير العمل، والمصادقة، راجع concepts_provisioning.