IR Workflow Mock Testing Guide#
Overview#
This guide documents the comprehensive mock testing infrastructure for validating the complete v2.0/2.1 IR (Intermediate Representation) workflow: Chef Server → IR → AWX/AAP.
Migration Path Model#
Tests follow an Origin → Destination configuration model:
- Origin: Chef Server version you're migrating FROM (12.x, 14.x, 15.x)
- Destination: Ansible platform/version you're migrating TO (Tower 3.8, AWX 21.x-24.x, AAP 2.4+)
Each combination represents a unique migration path with specific API behaviors, authentication protocols, and feature support. Tests validate that the IR framework correctly handles version-specific differences in both Chef Server APIs and Ansible platform APIs.
Example Migration Paths: - Chef 12.x (SHA-1/SHA-256) → AWX 22.x (EE) - Multi-auth origin - Chef 14.x (SHA-256) → AWX 24.6.1 (EE) - Recent Chef, current AWX - Chef 15.10.91 (SHA-256) → AWX 24.6.1 (EE) - Latest releases
Test Coverage#
File: tests/integration/test_ir_chef_awx_workflow.py#
8 comprehensive tests validating end-to-end Chef→Ansible→AWX migration workflows using mocked APIs.
Test Classes#
1. TestChefToIRWorkflow#
Tests Chef Server data extraction and IR graph construction.
test_query_chef_nodes_and_build_ir#
- Mocks Chef Server
/search/nodeendpoint - Queries production nodes via ChefServerClient
- Builds IR graph with node metadata (platform, environment, IP, run_list)
- Validates IR structure and node variables
Validates: - [YES] Chef Server authentication with RSA signatures - [YES] Node search query execution - [YES] IR graph creation from Chef data - [YES] Variable assignment in IR nodes
test_chef_cookbook_to_ir_with_dependencies#
- Mocks Chef Server cookbook metadata endpoint
- Extracts cookbook dependencies (nginx, php)
- Builds IR graph with recipe nodes and dependency nodes
- Validates topological ordering of dependencies
Validates: - [YES] Cookbook metadata parsing - [YES] Dependency graph construction - [YES] Topological dependency resolution - [YES] IR node relationships
2. TestIRToAWXWorkflow#
Tests IR transformation to AWX job templates and inventories.
test_ir_to_awx_job_template_creation#
- Creates IR graph with playbook structure
- Adds actions (install, configure, service)
- Generates AWX job template from IR
- Mocks AWX API
/job_templates/POST endpoint
Validates: - [YES] IR to AWX job template transformation - [YES] Action mapping to playbook tasks - [YES] Extra vars injection (source tracking) - [YES] AWX API authentication
test_ir_to_awx_inventory_creation#
- Creates IR graph with host inventory
- Groups hosts (webservers, databases)
- Generates AWX inventory from IR
- Mocks AWX API
/inventories/POST endpoint
Validates: - [YES] IR host to AWX inventory mapping - [YES] Group and host variable assignment - [YES] Tag propagation to AWX metadata - [YES] Inventory organization structure
3. TestEndToEndChefToAWXWorkflow#
Tests complete migration workflow across all stages.
test_complete_migration_workflow#
Complete 4-stage workflow:
1. Query Chef Server for nodes (/search/node)
2. Query Chef Server for role details (/roles/webserver)
3. Build IR graph from Chef data
4. Generate AWX configurations (inventory, hosts, job templates)
Validates: - [YES] Multi-step Chef Server queries - [YES] IR graph construction from multiple Chef resources - [YES] AWX inventory creation - [YES] AWX host creation - [YES] AWX job template generation - [YES] End-to-end data flow integrity
test_workflow_with_chef_environments_to_awx_groups#
- Queries Chef environments (production, staging)
- Queries nodes per environment
- Maps Chef environments to AWX inventory groups
- Creates AWX groups for each environment
Validates: - [YES] Chef environment enumeration - [YES] Environment-based node filtering - [YES] IR environment modeling - [YES] AWX group creation from environments
4. TestIRValidationWithChefAndAWX#
Tests IR validation against real API constraints.
test_validate_ir_node_against_chef_server#
- Creates IR node for Chef recipe
- Validates recipe exists in Chef Server cookbook
- Marks IR node with validation metadata
Validates: - [YES] IR to Chef Server validation - [YES] Recipe existence checking - [YES] Validation metadata tracking
test_validate_awx_compatibility_from_ir#
- Creates IR graph for Ansible playbook
- Queries AWX
/config/for capabilities - Validates IR compatibility with AWX Ansible version
Validates: - [YES] AWX capability detection - [YES] Ansible version compatibility checking - [YES] IR metadata enrichment
Migration Path Configuration#
Origin: Chef Server Versions#
Configure your test origin by specifying the Chef Server version you're migrating from:
| Version | Release | Auth Protocol | Latest | Test Priority |
|---|---|---|---|---|
| 12.x | 2015 | 1.0, 1.3 | 12.19.36 | Medium |
| 14.x | 2018 | 1.3 (SHA-256) | 14.15.6 | High |
| 15.x | 2020+ | 1.3 (SHA-256) | 15.10.91 | High |
Configuration Example:
# Test configuration for Chef Server 14.x origin
chef_config = {
"version": "14.10.9",
"auth_protocol": "1.3", # SHA-256
"base_url": "https://chef.example.com/organizations/myorg",
"api_features": ["search", "cookbooks", "roles", "environments"],
}
Destination: Ansible Automation Platforms#
Configure your test destination by specifying the Ansible platform and version you're migrating to:
| Platform | Version Range | API Version | Ansible Core | Latest | Test Priority |
|---|---|---|---|---|---|
| AWX | 21.x-22.x | /api/v2/ |
2.12-2.14 | 22.x | Medium |
| AWX | 23.x-24.x | /api/v2/ |
2.14-2.16 | 24.6.1 | High |
| AAP | 2.4.x+ | /api/v2/ |
2.15+ | 2.4+ | High |
Configuration Example:
# Test configuration for AAP 2.4 destination
awx_config = {
"platform": "aap", # or "awx", "tower"
"version": "2.4.0",
"api_version": "v2",
"ansible_version": "2.15.0",
"base_url": "https://aap.example.com",
"api_features": ["inventories", "job_templates", "workflows", "ee"],
}
Test Matrix: Origin → Destination Combinations#
Recommended Test Combinations:
| Origin | Destination | Validation | Priority |
|---|---|---|---|
| Chef 12.x | AWX 22.x | Legacy origin, modern platform | Medium |
| Chef 14.x | AWX 24.6.1 | Mid-version Chef, latest AWX | High |
| Chef 15.10.91 | AWX 24.6.1 | Current Chef → Current AWX | High |
| Chef 15.x | AAP 2.4 | Enterprise production path | Critical |
Example Test Configuration:
@pytest.mark.parametrize("origin,destination", [
({"chef": "14.10.9", "auth": "1.3"}, {"platform": "awx", "version": "22.0"}),
({"chef": "15.0.0", "auth": "1.3"}, {"platform": "aap", "version": "2.4"}),
])
def test_migration_path(origin, destination):
"""Test specific Chef version → Ansible platform migration."""
# Configure mock responses based on version capabilities
pass
API Changes by Version#
Chef Server API: What Actually Changes#
Chef Server 11.x → 12.x:
- [YES] Maintained: All REST endpoints unchanged
- [ADD] Added: X-Ops-Sign version=1.3 support (SHA-256)
- [ADD] Added: /organizations/{org}/policies endpoint
- WARNING Deprecated: Opscode User/Organization endpoints
Chef Server 12.x → 14.x: - [YES] Maintained: All core REST endpoints stable - [SECURE] Changed: Stricter SSL certificate validation (HTTPS required) - [SECURE] Changed: Default authentication protocol → 1.3 (SHA-256) - WARNING Removed: SHA-1 deprecation warnings (still works)
Chef Server 14.x → 15.x: - [YES] Maintained: All REST endpoints identical - [SECURE] Changed: FIPS 140-2 compliance enforced - WARNING Deprecated: SHA-1 support (planned removal in v16+) - [ADD] Added: Enhanced LDAP integration options
API Response Changes: None across all versions (12.x-15.x) - Node search responses: Same structure - Cookbook metadata: Same fields - Role definitions: Same format - Environment queries: Unchanged
Authentication Changes:
# Chef 11.x-12.x: SHA-1 (protocol 1.0)
X-Ops-Sign: version=1.0
X-Ops-Content-Hash: SHA1(body)
# Chef 13.x+: SHA-256 (protocol 1.3)
X-Ops-Sign: version=1.3
X-Ops-Content-Hash: SHA256(body)
AWX/Tower/AAP API: What Actually Changes#
Tower 2.x → Tower 3.x:
- [YES] Maintained: /api/v2/ endpoints unchanged
- [ADD] Added: /api/v2/workflow_job_templates/ (workflow support)
- [ADD] Added: /api/v2/credential_types/ (custom credentials)
- [ADD] Added: Smart inventory support
- [SYNC] Changed: OAuth2 token format (longer tokens)
Tower 3.x → AWX (OSS):
- [YES] Maintained: All /api/v2/ endpoints identical
- [ADD] Added: Faster release cycle (every 2 weeks vs quarterly)
- [ADD] Added: Container-based execution environment preview
- [SYNC] Changed: Branding (Tower → AWX, API unchanged)
AWX 17.x → AWX 21.x:
- [YES] Maintained: /api/v2/ core endpoints stable
- [ADD] Added: Execution Environments (EE) support
- [ADD] Added: /api/v2/execution_environments/ endpoint
- WARNING Deprecated: Custom virtual environments (replaced by EE)
- [SYNC] Changed: Job isolation method (bubblewrap → podman)
AWX 21.x → AWX 24.x / AAP 2.4:
- [YES] Maintained: All existing /api/v2/ endpoints
- [ADD] Added: Ansible content signing support
- [ADD] Added: Enhanced RBAC with organizations
- [ADD] Added: /api/v2/mesh_visualizer/ (topology view)
- [SYNC] Changed: Default Ansible version (2.12 → 2.15)
Breaking Changes:
- WARNING AWX 19.x: Removed some legacy credential fields (migration provided)
- WARNING AWX 21.x: Removed /api/v2/custom_virtualenvs/ (use EE instead)
- WARNING AAP 2.0: Renamed /api/v2/towers/ → /api/v2/instances/
API Response Structure Changes:
// Tower 3.x / AWX <21: Job Template
{
"custom_virtualenv": "/var/lib/awx/venv/ansible" // Deprecated
}
// AWX 21+ / AAP 2.0+: Job Template
{
"execution_environment": 42, // EE ID (required)
"custom_virtualenv": null // Ignored if EE present
}
Version-Specific Mock Configuration#
Chef Server Version Detection:
def configure_chef_mock(version: str):
"""Configure mock responses based on Chef Server version."""
if version.startswith("12."):
# Chef 12.x: Support both SHA-1 and SHA-256
return {"auth_protocols": ["1.0", "1.3"], "features": ["base"]}
elif version.startswith("14.") or version.startswith("15."):
# Chef 14+: Prefer SHA-256, strict SSL
return {"auth_protocols": ["1.3"], "features": ["base", "policies"]}
AWX Platform Version Detection:
def configure_awx_mock(platform: str, version: str):
"""Configure mock responses based on AWX platform version."""
if platform == "tower" and version.startswith("3."):
return {
"api_version": "v2",
"features": ["workflows", "credentials"],
"execution_model": "virtualenv",
}
elif platform == "awx" and int(version.split(".")[0]) >= 21:
return {
"api_version": "v2",
"features": ["workflows", "credentials", "ee"],
"execution_model": "execution_environment",
}
elif platform == "aap":
return {
"api_version": "v2",
"features": ["workflows", "credentials", "ee", "signing"],
"execution_model": "execution_environment",
"ansible_min": "2.15",
}
Mock API Endpoints#
Chef Server API (Mocked)#
| Endpoint | Method | Purpose | Version Notes |
|---|---|---|---|
/organizations/{org}/search/node |
GET | Node search queries | Stable since 12.x |
/organizations/{org}/roles/{name} |
GET | Role definitions | Unchanged |
/organizations/{org}/cookbooks/{name}/{version} |
GET | Cookbook metadata | Stable |
/organizations/{org}/environments |
GET | Environment listing | Unchanged |
Headers Required:
Accept: application/json
X-Chef-Version: 13.0
X-Ops-Sign: version=1.0
X-Ops-Userid: client-name
X-Ops-Timestamp: ISO8601
X-Ops-Content-Hash: SHA1(body)
X-Ops-Authorization-1: <signature-chunk-1>
...
AWX/AAP API (Mocked)#
| Endpoint | Method | Purpose | Version Support |
|---|---|---|---|
/api/v2/inventories/ |
POST | Create inventory | Tower 2.x+ |
/api/v2/inventories/{id}/hosts/ |
POST | Add hosts | Tower 2.x+ |
/api/v2/job_templates/ |
POST | Create job template | Tower 2.x+ |
/api/v2/groups/ |
POST | Create inventory groups | Tower 2.x+ |
/api/v2/config/ |
GET | Query AWX capabilities | Tower 3.x+, AWX 1.0+ |
Headers Required:
Testing Specific Chef + Platform Version Combinations#
When you set a Chef origin version + target platform/version, the mock and IR transformation must adapt to the functional API differences.
Example 1: Chef 14.x → AWX 24.6.1 (with EE)#
import pytest
from unittest import mock
# Set versions
chef_version = "14.15.6"
awx_version = "24.6.1"
awx_platform = "awx"
# Chef Server mock: SHA-256 auth (protocol 1.3)
@pytest.fixture
def chef_14_mock():
"""Chef 14.x mocks use SHA-256 authentication."""
return {
"auth_protocol": "1.3", # SHA-256
"version": "14.15.6",
"endpoints": [
"/organizations/myorg/search/node",
"/organizations/myorg/roles/webserver",
]
}
# AWX 24.6.1 mock: REQUIRES execution environments
@pytest.fixture
def awx_24_mock():
"""AWX 24.6.1 MUST use execution_environment, not custom_virtualenv."""
return {
"version": "24.6.1",
"ansible_version": "2.16.0",
"features": ["execution_environments"], # FUNCTIONAL DIFFERENCE
"job_template_required_fields": {
"execution_environment": "REQUIRED", # This is the key difference
"custom_virtualenv": "IGNORED", # Won't work
}
}
# IR transformation adapts to version combo
def test_chef_14_to_awx_24():
"""Test Chef 14.x origin → AWX 24.6.1 destination."""
# Stage 1: Query Chef 14.x (uses SHA-256)
chef_client = configure_chef_client(
version="14.15.6",
auth_protocol="1.3", # SHA-256 required
key_type="rsa-2048",
)
nodes = chef_client.search_nodes("*:*")
# Stage 2: Build IR from Chef data
ir_graph = chef_to_ir(nodes)
# Stage 3: Transform IR to AWX 24.6.1
# CRITICAL: Use execution_environment, NOT custom_virtualenv
job_template = ir_to_awx_job_template(
ir_graph,
awx_version="24.6.1",
execution_model="execution_environment", # FUNCTIONAL DIFFERENCE
)
# Verify: AWX 24.6.1 requires execution_environment
assert "execution_environment" in job_template
assert job_template["execution_environment"] is not None
assert "custom_virtualenv" not in job_template or job_template["custom_virtualenv"] is None
Example 2: Chef 12.x → AWX 22.x (transition period, supports both)#
def test_chef_12_to_awx_22():
"""Test Chef 12.x origin → AWX 22.x destination (mixed auth, mixed execution)."""
# Chef 12.x: Supports BOTH SHA-1 (1.0) and SHA-256 (1.3)
chef_client = configure_chef_client(
version="12.19.36",
auth_protocol="1.3", # Can use either 1.0 or 1.3
)
nodes = chef_client.search_nodes("*:*")
# AWX 22.x: Supports BOTH virtualenv AND execution environments
# But prefers execution environments
ir_graph = chef_to_ir(nodes)
job_template = ir_to_awx_job_template(
ir_graph,
awx_version="22.0.0",
execution_model="execution_environment", # Preferred by AWX 22.x
fallback="custom_virtualenv", # Still works if needed
)
# AWX 22.x preferably uses EE, but can fall back
assert "execution_environment" in job_template # Primary method
Example 3: Chef 15.10.91 → AAP 2.4 (latest features - COMPLETE WORKING EXAMPLE)#
This is the 2026 production migration path: Latest Chef to Latest AAP.
import responses
import pytest
from unittest import mock
from datetime import datetime
@pytest.fixture
def chef_15_aap_24_mocks():
"""Complete mock setup for Chef 15.10.91 → AAP 2.4.0 migration.
This fixture mocks:
- Chef Server 15.10.91 with SHA-256 authentication
- AAP 2.4.0 with execution environments and content signing
"""
with responses.RequestsMock() as rsps:
# ===== CHEF SERVER 15.10.91 MOCKS (SHA-256, FIPS) =====
# Mock Chef Server node search
chef_nodes_response = {
"rows": [
{
"data": {
"name": "web-prod-01",
"platform": "ubuntu",
"platform_version": "20.04",
"automatic": {
"ipaddress": "10.0.1.10",
"environment": "production",
},
"normal": {
"run_list": ["role[webserver]", "recipe[nginx::default]"]
}
}
},
{
"data": {
"name": "app-prod-01",
"platform": "ubuntu",
"platform_version": "20.04",
"automatic": {
"ipaddress": "10.0.2.10",
"environment": "production",
},
"normal": {
"run_list": ["role[app]", "recipe[chef-app::deploy]"]
}
}
}
],
"total": 2
}
rsps.add(
responses.GET,
"https://chef.example.com/organizations/myorg/search/node",
json=chef_nodes_response,
status=200,
headers={
"X-Ops-API-Version": "2.1",
"Server": "Chef Server 15.10.91",
}
)
# Mock Chef Server role definitions
rsps.add(
responses.GET,
"https://chef.example.com/organizations/myorg/roles/webserver",
json={
"name": "webserver",
"run_list": ["recipe[nginx::default]", "recipe[ssl::default]"],
"default_attributes": {"nginx": {"port": 80}},
"override_attributes": {}
},
status=200
)
rsps.add(
responses.GET,
"https://chef.example.com/organizations/myorg/roles/app",
json={
"name": "app",
"run_list": ["recipe[chef-app::deploy]", "recipe[nodejs::default]"],
"default_attributes": {"app": {"version": "2.1.0"}},
"override_attributes": {}
},
status=200
)
# ===== AAP 2.4.0 MOCKS (Execution Environments, Content Signing) =====
# Mock AAP config endpoint - shows capabilities
rsps.add(
responses.GET,
"https://aap.example.com/api/v2/config/",
json={
"version": "2.4.0",
"ansible_version": "2.15.0",
"license_type": "enterprise",
"features": {
"execution_environments": True,
"content_signing": True,
"mesh": True,
"workflows": True,
}
},
status=200,
headers={"Server": "AWX 24.6.1 / AAP 2.4"}
)
# Mock AAP create execution environment
rsps.add(
responses.POST,
"https://aap.example.com/api/v2/execution_environments/",
json={
"id": 42,
"name": "ee-chef-migration",
"image": "quay.io/ansible/creator-ee:0.5.0",
"pull": "always",
"description": "EE for Chef 15.10.91 → AAP 2.4 migration"
},
status=201
)
# Mock AAP create inventory
rsps.add(
responses.POST,
"https://aap.example.com/api/v2/inventories/",
json={
"id": 1,
"name": "Production-from-Chef",
"description": "Migrated from Chef Server 15.10.91",
"kind": "ssh",
"organization": 1
},
status=201
)
# Mock AAP add hosts to inventory
rsps.add(
responses.POST,
"https://aap.example.com/api/v2/inventories/1/hosts/",
json={
"id": 101,
"name": "web-prod-01",
"inventory": 1,
"variables": '{"ansible_host": "10.0.1.10", "platform": "ubuntu"}'
},
status=201,
match=[responses.matchers.json_params_matcher({
"name": "web-prod-01",
"variables": '{"ansible_host": "10.0.1.10", "platform": "ubuntu"}'
})]
)
rsps.add(
responses.POST,
"https://aap.example.com/api/v2/inventories/1/hosts/",
json={
"id": 102,
"name": "app-prod-01",
"inventory": 1,
"variables": '{"ansible_host": "10.0.2.10", "platform": "ubuntu"}'
},
status=201,
match=[responses.matchers.json_params_matcher({
"name": "app-prod-01",
"variables": '{"ansible_host": "10.0.2.10", "platform": "ubuntu"}'
})]
)
# Mock AAP create project (for playbooks)
rsps.add(
responses.POST,
"https://aap.example.com/api/v2/projects/",
json={
"id": 1,
"name": "chef-migrations",
"scm_type": "git",
"scm_url": "https://git.example.com/migrations/chef-to-ansible.git",
"organization": 1,
"status": "new"
},
status=201
)
# Mock AAP create job template - Chef 15.10.91 → AAP 2.4
# KEY DIFFERENCE: execution_environment is REQUIRED, not optional
rsps.add(
responses.POST,
"https://aap.example.com/api/v2/job_templates/",
json={
"id": 1,
"name": "Deploy-from-Chef-Migration",
"job_type": "run",
"inventory": 1,
"project": 1,
"playbook": "migrate_chef_roles.yml",
"execution_environment": 42, # ← REQUIRED for AAP 2.4
"limit": "",
"extra_vars": '{"source": "chef-15.10.91", "target": "aap-2.4"}',
"verbosity": 1,
"forks": 5,
"tags": "migration",
"skip_tags": "skip",
"organization": 1,
"ask_extra_vars": False,
"ask_tags": False,
"ask_limit": False,
"content_signing": True, # ← AAP 2.4 feature
"sign_key": "https://aap.example.com/api/v2/signing_keys/1/",
},
status=201,
headers={"X-Ansible-Cost": "10"}
)
yield rsps
def test_latest_chef_to_latest_aap(chef_15_aap_24_mocks):
"""
Complete end-to-end migration test: Chef 15.10.91 → AAP 2.4.0
This is the 2026 production migration path using:
- Latest Chef Server: 15.10.91 (Released Feb 10, 2026)
- Latest Ansible Platform: AAP 2.4.0 (Enterprise)
- Authentication: SHA-256 (protocol 1.3) with FIPS compliance
- Execution: Ansible 2.15.0 with Execution Environments
- Features: Content signing enabled for supply chain security
"""
# ===== STAGE 1: Query Chef 15.10.91 =====
print("\n[STAGE 1] Querying Chef Server 15.10.91...")
chef_client = configure_chef_client(
version="15.10.91",
auth_protocol="1.3", # SHA-256 (protocol 1.3)
key_type="rsa-2048",
fips_mode=True,
)
# Query nodes from Chef production environment
nodes = chef_client.search_nodes("environment:production")
assert len(nodes) == 2
assert nodes[0]["name"] == "web-prod-01"
assert nodes[1]["name"] == "app-prod-01"
print(f" [OK] Found {len(nodes)} production nodes from Chef")
# ===== STAGE 2: Build IR Graph =====
print("[STAGE 2] Building Intermediate Representation...")
ir_graph = chef_to_ir(
nodes=nodes,
chef_version="15.10.91",
target_platform="aap",
target_version="2.4.0",
)
# IR graph contains both host and role information
assert ir_graph.source_type == SourceType.CHEF
assert ir_graph.target_type == TargetType.ANSIBLE
assert len(ir_graph.nodes) >= 2
print(f" [OK] Built IR with {len(ir_graph.nodes)} nodes")
for node in ir_graph.nodes:
print(f" - {node.node_id}: platform={node.properties.get('platform')}")
# ===== STAGE 3: Transform IR to AAP 2.4 =====
print("[STAGE 3] Transforming IR to AAP 2.4...")
# Create execution environment in AAP
ee_response = requests.post(
"https://aap.example.com/api/v2/execution_environments/",
headers={"Authorization": "Bearer aap-token"},
json={
"name": "ee-chef-migration",
"image": "quay.io/ansible/creator-ee:0.5.0",
}
)
ee_id = ee_response.json()["id"]
print(f" [OK] Created execution environment EE#{ee_id}")
# Create inventory from Chef nodes
inventory_response = requests.post(
"https://aap.example.com/api/v2/inventories/",
headers={"Authorization": "Bearer aap-token"},
json={
"name": "Production-from-Chef",
"kind": "ssh",
}
)
inventory_id = inventory_response.json()["id"]
print(f" [OK] Created inventory ID#{inventory_id}")
# Add hosts to inventory
for node in nodes:
host_response = requests.post(
f"https://aap.example.com/api/v2/inventories/{inventory_id}/hosts/",
headers={"Authorization": "Bearer aap-token"},
json={
"name": node["name"],
"variables": json.dumps({
"ansible_host": node["automatic"]["ipaddress"],
"platform": node["platform"],
})
}
)
print(f" - Added host: {host_response.json()['name']}")
# Create project for playbooks
project_response = requests.post(
"https://aap.example.com/api/v2/projects/",
headers={"Authorization": "Bearer aap-token"},
json={
"name": "chef-migrations",
"scm_type": "git",
"scm_url": "https://git.example.com/migrations/chef-to-ansible.git",
}
)
project_id = project_response.json()["id"]
print(f" [OK] Created project ID#{project_id}")
# Transform IR to job template
job_template = ir_to_awx_job_template(
ir_graph,
platform="aap",
version="2.4.0",
execution_environment_id=ee_id,
inventory_id=inventory_id,
project_id=project_id,
execution_model="execution_environment", # REQUIRED for AAP 2.4
ansible_version="2.15.0",
signing_enabled=True,
)
print(f" [OK] Generated job template: {job_template['name']}")
# ===== STAGE 4: Validation & Verification =====
print("[STAGE 4] Validating transformation...")
# Verify job template has AAP 2.4 requirements
assert job_template["execution_environment"] == ee_id # REQUIRED
print(f" [OK] Job template uses execution_environment: {ee_id}")
assert job_template["ansible_version"] == "2.15.0"
print(f" [OK] Ansible version: {job_template['ansible_version']}")
assert job_template["content_signing"] is True
print(f" [OK] Content signing enabled: {job_template['content_signing']}")
assert "custom_virtualenv" not in job_template or job_template["custom_virtualenv"] is None
print(f" [OK] Legacy virtualenv NOT used (correct for AAP 2.4)")
# ===== STAGE 5: Create in AAP =====
print("[STAGE 5] Creating job template in AAP 2.4...")
jt_response = requests.post(
"https://aap.example.com/api/v2/job_templates/",
headers={"Authorization": "Bearer aap-token"},
json=job_template
)
created_jt = jt_response.json()
assert created_jt["id"] == 1
assert created_jt["execution_environment"] == ee_id
assert created_jt["content_signing"] is True
print(f" [OK] Job template created: JT#{created_jt['id']}")
print(f" - Name: {created_jt['name']}")
print(f" - EE: {created_jt['execution_environment']}")
print(f" - Signed: {created_jt['content_signing']}")
print("\n[YES] SUCCESS: Chef 15.10.91 → AAP 2.4.0 migration complete!")
print(f" {len(nodes)} nodes migrated to {1} job template with modern AAP features")
Key Points - Chef 15.10.91 → AAP 2.4.0:
| Feature | Chef 15.10.91 | AAP 2.4.0 | IR Transformation |
|---|---|---|---|
| Auth Protocol | SHA-256 (1.3) | OAuth2 Bearer | Use SHA-256 signatures |
| Execution Model | N/A | Execution Environments (REQUIRED) | Must reference EE ID |
| Ansible Version | N/A | 2.15.0 required | Use 2.15+ in job template |
| Content Signing | N/A | Supported | Enable for supply chain security |
| FIPS Compliance | Yes | Yes | Both support FIPS mode |
| Hosts from Chef | Query via SSH signatures | Added to AAP inventory | Parse Chef nodes → AAP hosts |
Output from successful migration:
[STAGE 1] Querying Chef Server 15.10.91...
[OK] Found 2 production nodes from Chef
[STAGE 2] Building Intermediate Representation...
[OK] Built IR with 2 nodes
- web-prod-01: platform=ubuntu
- app-prod-01: platform=ubuntu
[STAGE 3] Transforming IR to AAP 2.4...
[OK] Created execution environment EE#42
[OK] Created inventory ID#1
- Added host: web-prod-01
- Added host: app-prod-01
[OK] Created project ID#1
[OK] Generated job template: Deploy-from-Chef-Migration
[STAGE 4] Validating transformation...
[OK] Job template uses execution_environment: 42
[OK] Ansible version: 2.15.0
[OK] Content signing enabled: True
[OK] Legacy virtualenv NOT used (correct for AAP 2.4)
[STAGE 5] Creating job template in AAP 2.4...
[OK] Job template created: JT#1
- Name: Deploy-from-Chef-Migration
- EE: 42
- Signed: True
[YES] SUCCESS: Chef 15.10.91 → AAP 2.4.0 migration complete!
2 nodes migrated to 1 job template with modern AAP features
Functional API Differences by Version Combo#
When IR transforms to AWX, it MUST adapt based on destination version:
| Chef Origin | AWX Destination | Execution Model | Auth Protocol | IR Change |
|---|---|---|---|---|
| Chef 12.x | AWX 20.x | custom_virtualenv |
1.0 or 1.3 | Use /path/to/venv |
| Chef 12.x | AWX 22.x | execution_environment |
1.3 | Reference EE ID (required) |
| Chef 14.x | AWX 24.6.1 | execution_environment |
1.3 | Reference EE ID (required) |
| Chef 15.10.91 | AAP 2.4 | execution_environment |
1.3 | Reference EE ID + signing |
Code Example: Version-Aware IR Transform
def ir_to_awx_job_template(ir_graph, platform, version, **options):
"""Transform IR to AWX job template, adapting to version differences."""
job_template = {
"name": ir_graph.name,
"job_type": "run",
"inventory": options.get("inventory_id"),
"project": options.get("project_id"),
"playbook": ir_graph.playbook_name,
}
# FUNCTIONAL DIFFERENCE: Execution model depends on version
if platform == "tower" or (platform == "awx" and int(version.split(".")[0]) < 21):
# Old: Tower 3.x, AWX <21 (VIRTUALENV model)
job_template["custom_virtualenv"] = "/var/lib/awx/venv/ansible"
else:
# New: AWX 21+, AAP 2.x (EXECUTION ENVIRONMENT model)
# CRITICAL: Must provide execution_environment ID
job_template["execution_environment"] = options.get("execution_environment_id")
if job_template["execution_environment"] is None:
raise ValueError(f"{platform} {version} requires execution_environment")
# AAP 2.4+ specific features
if platform == "aap" and options.get("signing_enabled"):
job_template["content_signing"] = True
return job_template
Authentication#
Chef Server#
- Method: RSA-signed X-Ops-Authorization headers
- Implementation: Uses
cryptographylibrary to generate valid 2048-bit RSA keys - Headers: Complete Chef Server authentication protocol (SHA-1)
AWX/AAP#
- Method: OAuth2 Bearer token
- Header:
Authorization: Bearer test-token
Running the Tests#
# Run all IR workflow tests
poetry run pytest tests/integration/test_ir_chef_awx_workflow.py -v
# Run with coverage
poetry run pytest tests/integration/test_ir_chef_awx_workflow.py --cov=souschef/ir
# Run specific test class
poetry run pytest tests/integration/test_ir_chef_awx_workflow.py::TestEndToEndChefToAWXWorkflow -v
Integration with Existing Tests#
These IR workflow tests complement the existing mock infrastructure:
- Chef Server Mocks (
tests/integration/test_chef_server_mock.py): ~100 tests for Chef API - AWX Mocks (
tests/integration/test_awx_mock.py): ~50 tests for AWX API - IR Schema Tests (
tests/integration/test_ir_integration.py): ~20 tests for IR operations
Total Mock Test Coverage: 178+ tests validating API emulation
Test Data Structure#
IR Graph Example#
graph = IRGraph(
graph_id="migration-001",
source_type=SourceType.CHEF,
target_type=TargetType.ANSIBLE,
version="1.0.0",
)
node = IRNode(
node_id="host-web-01",
node_type=IRNodeType.RESOURCE,
name="web-01",
source_type=SourceType.CHEF,
)
node.set_variable("platform", "ubuntu")
node.set_variable("environment", "production")
graph.add_node(node)
Chef Server Mock Response#
mock_nodes = {
"rows": [
{
"name": "web-prod-01",
"run_list": ["recipe[nginx]"],
"chef_environment": "production",
"platform": "ubuntu",
"ipaddress": "10.0.1.10",
}
],
"total": 1,
}
AWX API Mock Response#
awx_template = {
"id": 42,
"name": "Deploy webapp",
"job_type": "run",
"inventory": 1,
"project": 1,
"playbook": "deploy_webapp.yml",
}
Design Patterns#
Pattern 1: Multi-Stage Workflow Testing#
Tests follow the actual migration workflow: 1. Discover (Chef Server query) 2. Model (IR graph construction) 3. Transform (IR to Ansible) 4. Deploy (AWX configuration)
Pattern 2: Mock Response Chaining#
Multiple mock responses configured in sequence:
responses.add(GET, chef_url, json=chef_data) # Stage 1
responses.add(GET, role_url, json=role_data) # Stage 2
responses.add(POST, awx_url, json=awx_data) # Stage 3
Pattern 3: Validation Metadata#
Tests track validation status in IR:
node.tags["validated"] = "true"
node.tags["validation_source"] = "chef_server"
graph.metadata["awx_compatible"] = "true"
Coverage Metrics#
Test Coverage: 8 tests covering critical v2.0/2.1 workflows API Endpoints: 9 Chef + 5 AWX endpoints mocked Workflow Stages: 4-stage end-to-end migration validated Authentication: Both Chef RSA and AWX OAuth2 tested
Related Documentation#
Contributing#
When adding new v2.0/2.1 IR features:
- [YES] Add mock tests for new Chef Server endpoints
- [YES] Add mock tests for new AWX API interactions
- [YES] Validate IR graph transformations
- [YES] Test complete workflow integration
- [YES] Document new mock responses
Validation Checklist#
Before merging v2.0/2.1 features:
- All 8 IR workflow tests pass
- No linting errors (
ruff check) - No type errors (
mypy) - Coverage maintained at 86%+
- Mock responses match official API specs
- Authentication headers validated
- Origin configuration: Chef Server version specified and tested
- Destination configuration: Ansible platform/version specified and tested
- API version compatibility: Verified origin→destination path works
- Breaking changes: Documented version-specific behaviors
- Test matrix: At least 2 origin versions and 2 destination versions tested
Version Detection & Compatibility Testing#
Detecting Chef Server Version#
Via API:
response = client._request("GET", "/version")
version_data = response.json()
# Returns: {"chef_server_version": "14.10.9"}
Via Headers (response from any endpoint):
Detecting AWX/Tower Version#
Via Config Endpoint:
response = requests.get(
"https://awx.example.com/api/v2/config/",
headers={"Authorization": "Bearer token"}
)
config = response.json()
print(f"AWX Version: {config['version']}")
print(f"Ansible Version: {config['ansible_version']}")
Response Example:
{
"version": "23.3.1", // AWX version
"ansible_version": "2.15.0", // Ansible core version
"project_base_dir": "/var/lib/awx/projects",
"custom_virtualenvs": []
}
Configuring Test Migrations#
Step 1: Define Origin (Chef Server):
# Example: Chef Server 15.x origin
chef_origin = {
"version": "15.0.0",
"organization": "myorg",
"auth_protocol": "1.3", # SHA-256
"base_url": "https://chef.example.com",
}
# Mock Chef Server responses for 15.x
responses.add(
method=responses.GET,
url=f"{chef_origin['base_url']}/version",
json={"chef_server_version": chef_origin["version"]},
headers={"X-Ops-Server-API-Version": "1.3"},
)
Step 2: Define Destination (Ansible Platform):
# Example: AAP 2.4 destination
awx_destination = {
"platform": "aap",
"version": "2.4.0",
"ansible_version": "2.15.0",
"api_version": "v2",
"base_url": "https://aap.example.com",
"features": ["execution_environments", "workflows"],
}
# Mock AWX/AAP config endpoint
responses.add(
method=responses.GET,
url=f"{awx_destination['base_url']}/api/v2/config/",
json={
"version": awx_destination["version"],
"ansible_version": awx_destination["ansible_version"],
},
)
Step 3: Configure Version-Specific Behaviors:
# Chef 15.x: Uses SHA-256 authentication
if chef_origin["auth_protocol"] == "1.3":
auth_headers = generate_chef_auth_sha256(...)
else:
auth_headers = generate_chef_auth_sha1(...)
# AAP 2.4: Requires execution environments
if "execution_environments" in awx_destination["features"]:
job_template["execution_environment"] = 1 # Required
else:
job_template["custom_virtualenv"] = "/venv/ansible" # Legacy
Testing Cross-Version Compatibility#
Test Origin Compatibility (Chef Server versions):
@pytest.mark.parametrize("chef_version,auth_protocol", [
("12.0", "1.0"), # SHA-1 only
("12.19", "1.3"), # First 1.3 support
("14.0", "1.3"), # SHA-256 preferred
("15.0", "1.3"), # SHA-256 default
])
def test_chef_origin_versions(chef_version, auth_protocol):
"""Test Chef Server versions as migration origins."""
# Configure mock with version-specific auth
client = ChefServerClient(
base_url="https://chef.example.com",
client_name="validator",
private_key=test_key,
auth_version=auth_protocol,
)
# All versions should return same data structure
nodes = client.search_nodes("name:*")
assert "rows" in nodes
Test Destination Compatibility (Ansible platforms):
@pytest.mark.parametrize("platform,version,ansible_version", [
("tower", "3.8.6", "2.9.27"), # Legacy
("awx", "21.0", "2.12.0"), # Transition (virtualenv → EE)
("awx", "24.0", "2.16.0"), # Current
("aap", "2.4.0", "2.15.0"), # Enterprise
])
def test_awx_destination_versions(platform, version, ansible_version):
"""Test Ansible platforms as migration destinations."""
# Configure version-specific API mock
responses.add(
responses.GET,
"https://awx.example.com/api/v2/config/",
json={"version": version, "ansible_version": ansible_version},
)
# Generate IR and transform to platform-specific config
ir_graph = create_test_ir_graph()
# Version-specific transformations
if platform == "aap" or (platform == "awx" and int(version.split(".")[0]) >= 21):
# Use execution environments (required in AAP, AWX 21+)
job_template = ir_to_awx_job_template(ir_graph, use_ee=True)
assert "execution_environment" in job_template
else:
# Use legacy virtualenv (Tower 3.x, early AWX)
job_template = ir_to_awx_job_template(ir_graph, use_ee=False)
assert "custom_virtualenv" in job_template
Test End-to-End Paths:
@pytest.mark.parametrize("origin,destination", [
# Minimum supported: Chef 12.x → Tower 3.8
(
{"chef": "12.19", "auth": "1.3"},
{"platform": "tower", "version": "3.8.6", "ansible": "2.9"},
),
# Mid-range: Chef 14.x → AWX 22.x
(
{"chef": "14.15", "auth": "1.3"},
{"platform": "awx", "version": "22.0", "ansible": "2.14"},
),
# Current: Chef 15.x → AAP 2.4
(
{"chef": "15.0", "auth": "1.3"},
{"platform": "aap", "version": "2.4", "ansible": "2.15"},
),
])
def test_migration_path_compatibility(origin, destination):
"""Test complete Chef → Ansible migration paths."""
# Stage 1: Query Chef Server (origin version)
chef_client = configure_chef_client(origin)
nodes = chef_client.search_nodes("*:*")
# Stage 2: Build IR from Chef data
ir_graph = chef_to_ir(nodes)
# Stage 3: Transform IR to Ansible (destination version)
awx_config = configure_awx_client(destination)
inventory = ir_to_awx_inventory(ir_graph, awx_config)
job_template = ir_to_awx_job_template(ir_graph, awx_config)
# Stage 4: Validate destination compatibility
assert validate_awx_compatibility(ir_graph, destination["ansible"])
Version-Specific Breaking Changes#
When migrating FROM these Chef Server versions:
| Origin Version | Breaking Changes | Migration Impact | Workaround |
|---|---|---|---|
| Chef 11.x | SHA-1 only | Must upgrade to 12.x for SHA-256 | Use auth protocol 1.0 |
| Chef 12.x | Mixed auth support | None - fully compatible | Prefer protocol 1.3 |
| Chef 14.x | Strict SSL/TLS | Requires valid certificates | Update cert validation |
| Chef 15.x | FIPS compliance | Cipher suite restrictions | Use approved ciphers |
When migrating TO these Ansible platforms:
| Destination Version | Breaking Changes | Migration Impact | Workaround |
|---|---|---|---|
| Tower 3.8 | EOL (no EE support) | Limited to virtualenv | Use AWX 19+ for EE |
| AWX 19.x | Removed legacy creds | Credential migration needed | Remap credential types |
| AWX 21.x | EE required | Must provide EE or default | Create default EE |
| AAP 2.x | Renamed entities | API paths changed | Use new paths |
| AAP 2.4 | Ansible 2.15+ | Older playbooks may break | Test playbook syntax |
Tested Migration Paths#
Current Test Coverage (test_ir_chef_awx_workflow.py):
| Test | Origin | Destination | Status | API Validation |
|---|---|---|---|---|
test_query_chef_nodes_and_build_ir |
Chef 14.x | N/A | [YES] Passing | SHA-256 auth, node search |
test_chef_cookbook_to_ir_with_dependencies |
Chef 12.x+ | N/A | [YES] Passing | Cookbook metadata |
test_ir_to_awx_job_template_creation |
N/A | AWX 24.6.1 | [YES] Passing | EE-based templates |
test_ir_to_awx_inventory_creation |
N/A | AWX 24.6.1 | [YES] Passing | Inventory API v2 |
test_complete_migration_workflow |
Chef 14.x | AWX 24.6.1 | [YES] Passing | Full 4-stage pipeline |
test_workflow_with_chef_environments_to_awx_groups |
Chef 14.x | AWX 24.6.1 | [YES] Passing | Environment mapping |
test_validate_ir_node_against_chef_server |
Chef 14.x | N/A | [YES] Passing | Recipe validation |
test_validate_awx_compatibility_from_ir |
N/A | AWX 24.6.1 | [YES] Passing | Ansible version check |
Default Test Configuration:
- Origin: Chef Server 14.x with SHA-256 authentication (protocol 1.3)
- Destination: AWX 24.6.1 with Ansible 2.16 and Execution Environment support
- API Versions: Chef Server (no URL version), AWX /api/v2/
Extending Coverage (recommended):#
# Add tests for other Chef origins
test_chef_12x_mixed_auth() # Protocol 1.0/1.3 support
test_chef_15_fips() # FIPS 140-2 compliance
# Add tests for other AWX versions
test_chef_to_awx_22() # AWX 22.x with EE
test_chef_to_awx_23() # AWX 23.x releases
# Add tests for AAP
test_chef_to_aap_24() # AAP 2.4 features
Future Enhancements#
Potential additions to mock coverage:
Expanded Version Testing#
- Chef 11.x origins: Test SHA-1-only legacy migrations
- Chef 12.x mixed auth: Test protocol 1.0/1.3 transitions
- Chef 15.x FIPS: Test FIPS 140-2 compliant origins
- Tower 3.8 destinations: Test legacy AWX Tower targets
- AWX 24.x destinations: Test latest AWX features
- AAP 2.5+ destinations: Test future AAP releases
Additional API Coverage#
- Chef Policyfiles: Mock policy group queries and policy revisions
- AWX Workflows: Multi-step workflow node creation and dependencies
- Chef Search: Advanced search query patterns (boolean, wildcard, range)
- AWX Surveys: Job template survey generation from Chef attributes
- AWX Execution Environments: Custom EE image configuration
Error Scenario Testing#
- 4xx/5xx responses: API error handling across versions
- Rate limiting: API throttling simulation (429 responses)
- Authentication failures: Invalid keys, expired tokens
- Version mismatches: Incompatible origin→destination combinations
Performance Testing#
- Large-scale migrations: 1000+ nodes, 100+ cookbooks
- Concurrent API calls: Parallel Chef Server queries
- AWX batch operations: Bulk inventory/host creation