Ansible Playbooks: 详细指南
OmniCRM 产品通过 Ansible 进行配置,允许根据每个产品及其相关库存的特定要求进行自动化服务管理。
另请参见:SIM Card Provisioning <concepts_sim_provisioning> 以获取基于 Ansible 的移动服务配置的完整示例,包括物理 SIM 卡和 eSIM。
Playbooks 和产品如何协同工作
关键概念: Playbooks 实际上是在 OmniCRM 中创建服务的工具。当您将 playbook 分配给产品时,您正在定义 发生什么 当该产品被配置 - 但这对不同产品可能意味着不同的事情。
产品触发 Playbooks
当产品在 OmniCRM 中被配置时:
- 产品定义指定要运行的 playbook(通过
provisioning_play字段) - 产品将变量传递给 playbook(通过
provisioning_json_vars和库存选择) - playbook 执行并执行其编程的操作
- playbook 确定创建什么(如果有的话)
Playbooks 可以做什么
单个配置 playbook 可以:
创建多个服务
一个捆绑产品的 playbook 可能创建:
- 一个主要的互联网服务记录
- 一个 IPTV 附加服务记录
- 一个 VoIP 服务记录
- 所有这些都通过一个产品配置操作完成
不创建服务
有些 playbooks 根本不创建服务记录:
- 一个仅配置 CPE 设备的 playbook
- 一个将配置发送到网络设备的 playbook
- 一个更新外部系统的 playbook
创建一个服务
最常见的模式:
- 为客户创建一个服务记录
- 将库存链接到该服务
- 为该服务设置计费
修改现有服务
充值和附加 playbooks:
- 不创建新服务
- 更新现有服务记录(添加数据、延长到期等)
- 向现有计费账户添加余额
执行没有服务记录的操作
一些 playbooks 纯粹是操作性的:
- 重置账户余额
- 在客户之间交换库存项目
- 生成报告或配置
示例:不同的 Playbook 行为
# 产品 1:移动 SIM 服务(创建 1 个服务)
# provisioning_play: play_simple_service
- 在 CRM 中创建服务记录
- 在 OCS 中创建计费账户
- 分配 SIM 卡和电话号码库存
- 发送欢迎电子邮件
# 产品 2:互联网捆绑包(创建 3 个服务)
# provisioning_play: play_bundle_internet_tv_voice
- 创建互联网服务记录
- 创建 IPTV 服务记录
- 创建 VoIP 服务记录
- 将所有服务链接到同一客户
- 为捆绑包创建单一计费账户
# 产品 3:数据充值(创建 0 个服务)
# provisioning_play: play_topup_no_charge
- 根据 service_id 查找现有服务
- 向现有 OCS 账户添加数据余额
- 更新服务到期日期
- 不创建新服务
# 产品 4:CPE 配置(创建 0 个服务)
# provisioning_play: play_prov_cpe_mikrotik
- 生成路由器配置
- 用配置更新库存记录
- 将配置通过电子邮件发送给支持团队
- 不创建服务(仅设备设置)
关键点:playbook 定义行为,产品只是触发器。
Plays 与 Tasks
理解 Plays 和 Tasks 之间的区别对于使用 OmniCRM playbooks 至关重要。
Play(Playbook)
一个完整的配置工作流,协调多个任务以实现业务目标。 Plays 是存储在 OmniCRM-API/Provisioners/plays/ 中的顶级 playbooks,并在产品定义中引用。
示例:
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- 激活 OLT 设备task_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 结构和组成
所有 OmniCRM playbooks 遵循一致的结构。理解这一结构对于创建和维护 playbooks 至关重要。
基本结构
每个 playbook 都以这些标准头开始:
- name: Descriptive Name of the Playbook
hosts: localhost # 始终为 localhost
gather_facts: no # 为了性能禁用
become: False # 不提升权限
tasks:
- name: Main block
block:
# 配置任务在这里
rescue:
# 回滚/清理任务在这里
头部解释
name
在配置日志和 UI 中显示的描述性名称。这在配置记录中显示为 playbook_description。
hosts: localhost
所有 OmniCRM playbooks 都在 localhost 上运行,因为它们通过 API 与远程系统交互,而不是通过 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"
Topup/Recharge 模式
用于向现有服务添加信用、数据或时间。
- 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 配置模式
用于配置客户场所设备(路由器、调制解调器、ONT)。
- 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
生成随机值
UUID 和标识符:
- 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
文档
记录复杂逻辑:
# 计算部分月份的按比例收费
# 如果客户在 15 号注册,而计费在 1 号,
# 则按剩余天数收取 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 员工将与客户合作:
- 定义产品要求
- 开发必要的 Ansible playbooks 以自动化配置过程
- 在暂存环境中测试 playbooks
- 部署到生产
这确保每项服务都以一致和可靠的方式部署,减少错误的风险,并确保所有必要步骤以正确的顺序完成。
Ansible 变量
传递给 Ansible playbooks 的变量包括:
产品变量
来自 OmniCRM 产品配置,定义服务应如何设置。
库存变量
从库存中选择,包括调制解调器、SIM 卡、IP 地址块或配置所需的电话号码等项目。
系统变量
由 OmniCRM 自动添加:
product_id- 正在配置的产品customer_id- 接收服务的客户service_id- 正在修改的服务(用于充值/更改)access_token- 用于 API 身份验证的 JWT
去配置
当服务不再需要时,Ansible Playbooks 也用于使用 rescue 块模式去配置服务。这:
- 移除任何配置
- 将库存释放回池中
- 删除计费账户
- 确保系统保持干净
去配置方法
去配置主要有两种触发方式:
1. 自动回滚(配置失败)
当主配置块中的任何任务失败时,回收部分自动执行以清理部分更改。
2. 手动去配置
在运行 playbook 时设置 action: "deprovision",故意触发回收块以移除服务。
去配置模式
所有 OmniCRM playbooks 遵循此结构以安全清理:
- name: Service Provisioning Playbook
hosts: localhost
gather_facts: no
become: False
tasks:
- name: Main block
block:
# 所有配置任务在这里
- name: Create OCS account
uri: ...
- name: Create service record
uri: ...
register: service_creation_response
- name: Add transaction
uri: ...
rescue:
# 去配置/回滚任务在这里
- name: Print all vars for debugging
debug:
var: hostvars[inventory_hostname]
# 清理任务按相反顺序执行
- name: Remove account in OCS
uri: ...
- name: Delete Service from CRM
uri: ...
- name: Fail if not intentional deprovision
assert:
that:
- action == "deprovision"
完整去配置示例
以下是 play_simple_service.yaml 的完整示例:
rescue:
# 1. 调试信息
- name: Print all vars for Deprovision/Rollback
debug:
var: hostvars[inventory_hostname]
- name: Get today's date
set_fact:
today: "{{ lookup('pipe', 'date +%Y-%m-%d') }}"
# 2. 如果可用,获取服务信息
- name: Try to get Service information from CRM API to Deprovision
uri:
url: "http://localhost:5000/crm/service/service_id/{{ service_creation_response.json.service_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: api_response_service
ignore_errors: True
when: service_creation_response is defined and service_creation_response.json is defined and service_creation_response.json.service_id is defined
- name: Print api_response_service
debug:
var: api_response_service
when: api_response_service is defined
# 3. 为清理设置 service_uuid
- name: Set service_uuid facts for Deprovision
set_fact:
service_uuid: "{{ api_response_service.json.service_uuid }}"
when:
- service_uuid is not defined
- api_response_service is defined
- api_response_service.json is defined
# 4. 移除 OCS 账户
- name: Remove account in OCS
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
headers:
Content-Type: "application/json"
Authorization: "Bearer {{ access_token }}"
body:
{
"method": "ApierV2.RemoveAccount",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"ReloadScheduler": true
}]
}
status_code: 200
register: response
ignore_errors: True
when: service_uuid is defined
- name: Print response from OCS account removal
debug:
var: response
when: response is defined
# 5. 从 CRM 删除服务记录
- 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
register: delete_service_response
ignore_errors: True
when: service_creation_response is defined and service_creation_response.json is defined and service_creation_response.json.service_id is defined
- name: Print delete service response
debug:
var: delete_service_response
when: delete_service_response is defined
# 6. 确定这是故意的还是失败
- name: Set status to "Success" if Manual deprovision / Fail if failed provision
assert:
that:
- action == "deprovision"
fail_msg: "Provisioning failed and was rolled back"
success_msg: "Service deprovisioned successfully"
关键去配置概念
ignore_errors: True
大多数清理任务使用 ignore_errors: True,因为资源可能不存在(例如,如果配置在创建它们之前失败)。
条件执行
使用 when 子句仅清理已创建的资源:
when: service_creation_response is defined
反向顺序
清理按创建的反向顺序进行:
- 首先删除依赖资源(交易、库存分配)
- 删除服务记录
- 删除 OCS/计费账户
- 释放任何被占用的资源
最终断言
最终断言区分:
- 故意去配置 (
action == "deprovision") → 成功 - 配置失败(未设置操作) → 失败
去配置不同资源类型
OCS/CGRateS 账户:
- 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
CRM 服务记录:
- name: Delete Service from CRM
uri:
url: "http://localhost:5000/crm/service/service_id/{{ service_id }}"
method: DELETE
headers:
Authorization: "Bearer {{ access_token }}"
status_code: 200
ignore_errors: True
when: service_id is defined
库存项目(返回池):
- name: Release SIM card back to inventory pool
uri:
url: "http://localhost:5000/crm/inventory/inventory_id/{{ sim_inventory_id }}"
method: PATCH
headers:
Authorization: "Bearer {{ access_token }}"
body_format: json
body:
{
"customer_id": null,
"service_id": null,
"item_state": "Available"
}
status_code: 200
ignore_errors: True
when: sim_inventory_id is defined
ActionPlans(定期收费):
- 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
付款授权:
- name: Release payment authorization
uri:
url: "http://localhost:5000/crm/payments/release/{{ authorization_id }}"
method: POST
headers:
Authorization: "Bearer {{ access_token }}"
body_format: json
body:
{
"metadata": {
"release_reason": "provisioning_failed"
}
}
return_content: yes
ignore_errors: True
when: authorization_id is defined
手动去配置工作流
要手动去配置服务:
- 确定要去配置的服务
- 运行原始配置 playbook,设置
action: "deprovision" - Playbook 立即进入回收块
- 所有清理任务执行
- 服务被干净地移除
示例 API 调用:
POST /crm/provision/run_playbook
{
"product_id": 123,
"customer_id": 456,
"service_id": 789,
"action": "deprovision"
}
部分清理场景
场景 1:OCS 账户已创建,服务创建失败
- OCS 账户存在
- 服务记录不存在
- 回收块移除 OCS 账户
- 没有服务可删除(安全跳过)
场景 2:服务已创建,交易失败
- OCS 账户存在
- 服务记录存在
- 交易不存在
- 回收块移除 OCS 账户和服务
- 没有交易可作废(从未创建)
场景 3:完整配置,付款捕获失败
- OCS 账户存在
- 服务记录存在
- 付款已授权但未捕获
- 回收块:
- 释放付款授权(客户未收费)
- 移除服务记录
- 移除 OCS 账户
去配置最佳实践
1. 广泛使用 ignore_errors
清理应宽容 - 如果资源不存在,则不失败。
2. 使用 when 子句检查资源是否存在
仅在资源已创建时尝试清理:
when: service_creation_response is defined
3. 打印调试信息
始终包括调试任务以帮助故障排除失败的配置:
- name: Print all vars for debugging
debug:
var: hostvars[inventory_hostname]
4. 以反向依赖顺序清理
在父项之前删除子项:
- 交易在服务之前
- 服务在 OCS 账户之前
- 库存分配在库存释放之前
5. 处理付款授权
始终在回收块中释放付款���权,以避免对失败的配置向客户收费。
6. 清理后重新加载调度程序
在移除 OCS 资源时,包括 ReloadScheduler: true 以确保 CGRateS 立即更新。
去配置与删除
去配置(通过 playbook 回收):
- 从所有系统中移除服务
- 释放库存
- 取消定期收费
- 保留审计记录
- 推荐的方法
直接删除(通过 API):
- 仅删除 CRM 记录
- 不清理 OCS 账户
- 不释放库存
- 可能留下孤立的资源
- 不推荐用于生产
回滚和错误处理
Ansible 的 block/rescue 特性在配置和去配置过程中用于优雅地处理错误。如果在配置的任何点任务失败,回收部分将自动回滚更改,以返回到一致状态。这确保了可靠性并减少了部分或失败部署的风险。
错误处理示例
- name: Main block
block:
- name: Step 1: Create account
uri: ...
register: response
- name: Verify step 1
assert:
that:
- response.status == 200
- name: Step 2: Create service
uri: ...
rescue:
# 如果任何断言失败或任何任务出错:
# 1. 执行跳转到回收块
# 2. 清理任务执行
# 3. Playbook 失败并显示错误信息
- name: Cleanup partial changes
uri: ...
有关配置系统、工作流和身份验证的完整详细信息,请参见 concepts_provisioning。