Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 123 additions & 1 deletion docs/how_to/parameterization.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ spark_pool:
semantic_model_binding:
- connection_id: "connection_id"
semantic_model_name: "semantic_model_name"

semantic_model_parameters:
- semantic_model_name: "semantic_model_name"
parameters:
- name: "ParameterName"
new_value: "ParameterValue"
```

Raise a [feature request](https://github.com/microsoft/fabric-cicd/issues/new?template=2-feature.yml) for additional parameterization capabilities.
Expand Down Expand Up @@ -151,6 +157,104 @@ semantic_model_binding:
semantic_model_name: [<semantic_model_name1>,<semantic_model_name2>,...]
```

### `semantic_model_parameters`

The `semantic_model_parameters` parameter enables automatic updates of Power BI dataset parameters during deployment. This is particularly useful for environment-specific values such as database connection strings, server names, API endpoints, or feature flags. Parameters are updated using the Power BI REST API immediately after the semantic model is deployed and connections are bound (if `semantic_model_binding` is configured).

**Use Cases:**

- Update database server names or connection strings for different environments (dev, test, prod)
- Configure API endpoints specific to each environment
- Set environment-specific feature flags
- Update file paths or storage account names

**How it works:** The semantic model is deployed first, then connections are bound (if configured), and finally parameters are updated via the Power BI API `/Default.UpdateParameters` endpoint.

```yaml
semantic_model_parameters:
# Single semantic model with multiple parameters
- semantic_model_name: <semantic_model_name>
parameters:
- name: <parameter_name>
new_value: <parameter_value>
- name: <another_parameter_name>
new_value: <another_parameter_value>

# Multiple semantic models sharing the same parameters
- semantic_model_name: [<model1>, <model2>, <model3>]
parameters:
- name: <parameter_name>
new_value: <shared_parameter_value>
```

**Example - SQL Server Configuration:**

```yaml
semantic_model_parameters:
# Configure SQL Server connection parameters
- semantic_model_name: "Sales Model"
parameters:
- name: "ServerName"
new_value: "prod-sqlserver.database.windows.net"
- name: "DatabaseName"
new_value: "SalesDB"
- name: "Port"
new_value: 1433
```

**Example - Multiple Models with Shared API Configuration:**

```yaml
semantic_model_parameters:
# Update API endpoint for multiple models
- semantic_model_name: ["Finance Model", "HR Model", "Marketing Model"]
parameters:
- name: "ApiEndpoint"
new_value: "https://api.production.com"
- name: "ApiVersion"
new_value: "v2"
- name: "TimeoutSeconds"
new_value: 300
```

**Example - Combined with Connection Binding:**

```yaml
# First bind the connection
semantic_model_binding:
- connection_id: "prod-connection-id"
semantic_model_name: "Sales Model"

# Then update parameters
semantic_model_parameters:
- semantic_model_name: "Sales Model"
parameters:
- name: "DatabaseName"
new_value: "ProductionDB"
- name: "RefreshSchedule"
new_value: "Daily"
```

**Parameter Value Types:**

All parameter values are converted to strings before being sent to the API. You can use:

- **Strings**: `"prod-server.database.windows.net"`
- **Numbers**: `1433` (automatically converted to `"1433"`)
- **Booleans**: `true` (automatically converted to `"true"`)

**Important Notes:**

- Parameter names are **case-sensitive** and must exactly match the parameters defined in your semantic model
- To find parameter names, open your `.pbix` file in Power BI Desktop and go to **Home** → **Transform data** → **Edit Parameters**
- If a semantic model is not found in the repository, a warning is logged and deployment continues
- If a parameter update fails, an error is logged but deployment of other models continues
- The `semantic_model_name` field accepts either a single string or a list of strings

**API Reference:** This feature uses the Power BI REST API endpoint: `POST /v1.0/myorg/groups/{workspace_id}/datasets/{dataset_id}/Default.UpdateParameters`. For more information, see [Microsoft's API documentation](https://learn.microsoft.com/en-us/rest/api/power-bi/datasets/update-parameters-in-group).

For additional examples, see the `examples/semantic_model_parameters_example.py` file in the repository.

## Advanced Find and Replace

### `find_value` Regex
Expand Down Expand Up @@ -212,7 +316,6 @@ The `replace_value` field in the `find_replace` and `key_value_replace` paramete
| `$items.<item_type>.<item_name>.$sqlendpoint` | Lakehouse, SQLDatabase, Warehouse | `$items.Lakehouse.MyLakehouse.$sqlendpoint` | `abc123def456.datawarehouse.fabric.microsoft.com` |
| `$items.<item_type>.<item_name>.$sqlendpointid` | Lakehouse | `$items.Lakehouse.MyLakehouse.$sqlendpointid` | `37dc8a41-dea9-465d-b528-3e95043b2356` |
| `$items.<item_type>.<item_name>.$queryserviceuri` | Eventhouse | `$items.Eventhouse.MyEventhouse.$queryserviceuri` | `https://trd-a1b2c3d4e5f6g7h8i9.z4.kusto.fabric.microsoft.com` |

- Attributes should be **lowercase**.
- Item type and name are **case-sensitive**.
- Item type must be valid and in scope.
Expand Down Expand Up @@ -549,6 +652,25 @@ spark_pool:
type: "Workspace" # target spark pool type, only supports Capacity or Workspace
name: "WorkspacePool_Medium" # target spark pool name
item_name: ["World_1", "World_2", "World_3"] # filter on environment files for environments with these names

semantic_model_binding:
- connection_id: "prod-connection-guid" # connection ID for production
semantic_model_name: "Sales Model" # semantic model to bind

semantic_model_parameters:
# Update SQL Server configuration for Sales Model
- semantic_model_name: "Sales Model"
parameters:
- name: "ServerName"
new_value: "prod-sqlserver.database.windows.net"
- name: "DatabaseName"
new_value: "SalesDB"

# Update API endpoint for multiple models
- semantic_model_name: ["Finance Model", "HR Model"]
parameters:
- name: "ApiEndpoint"
new_value: "https://api.production.com"
```

## Examples by Item Type
Expand Down
17 changes: 17 additions & 0 deletions sample/workspace/parameter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,20 @@ gateway_binding:
semantic_model_binding:
- connection_id: "76e05dfe-9855-4e3d-a410-1dda048dbe99"
semantic_model_name: ["cloudconnections", "MySemanticModel_ADLS_Gen2"]

semantic_model_parameters:
# Update parameters for a single semantic model
- semantic_model_name: "Sales Model"
parameters:
- name: "ServerName"
new_value: "prod-sqlserver.database.windows.net"
- name: "DatabaseName"
new_value: "SalesDB"

# Update multiple semantic models with the same parameters
- semantic_model_name: ["Finance Model", "HR Model"]
parameters:
- name: "ApiEndpoint"
new_value: "https://api.production.com"
- name: "TimeoutSeconds"
new_value: 300
144 changes: 129 additions & 15 deletions src/fabric_cicd/_items/_semanticmodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,29 +23,36 @@ def publish_semanticmodels(fabric_workspace_obj: FabricWorkspace) -> None:
exclude_path = r".*\.pbi[/\\].*"
fabric_workspace_obj._publish_item(item_name=item_name, item_type=item_type, exclude_path=exclude_path)

# Handle semantic_model_binding for connection binding
model_with_binding_dict = fabric_workspace_obj.environment_parameter.get("semantic_model_binding", [])

if not model_with_binding_dict:
return
if model_with_binding_dict:
# Build connection mapping from semantic_model_binding parameter
binding_mapping = {}

# Build connection mapping from semantic_model_binding parameter
binding_mapping = {}
for model in model_with_binding_dict:
model_name = model.get("semantic_model_name", [])
connection_id = model.get("connection_id")

for model in model_with_binding_dict:
model_name = model.get("semantic_model_name", [])
connection_id = model.get("connection_id")
if isinstance(model_name, str):
model_name = [model_name]

if isinstance(model_name, str):
model_name = [model_name]
for name in model_name:
binding_mapping[name] = connection_id

for name in model_name:
binding_mapping[name] = connection_id
connections = get_connections(fabric_workspace_obj)

connections = get_connections(fabric_workspace_obj)
if binding_mapping:
bind_semanticmodel_to_connection(
fabric_workspace_obj=fabric_workspace_obj, connections=connections, connection_details=binding_mapping
)

# Handle semantic_model_parameters for parameter updates
model_with_parameters_dict = fabric_workspace_obj.environment_parameter.get("semantic_model_parameters", [])

if binding_mapping:
bind_semanticmodel_to_connection(
fabric_workspace_obj=fabric_workspace_obj, connections=connections, connection_details=binding_mapping
if model_with_parameters_dict:
update_semantic_model_parameters(
fabric_workspace_obj=fabric_workspace_obj, parameter_details=model_with_parameters_dict
)


Expand Down Expand Up @@ -176,3 +183,110 @@ def build_request_body(body: dict) -> dict:
},
}
}


def update_semantic_model_parameters(fabric_workspace_obj: FabricWorkspace, parameter_details: list) -> None:
"""
Updates parameters for semantic models using the Power BI REST API.

This function updates dataset parameters (such as data source URLs, connection strings,
database names, etc.) for deployed semantic models based on environment-specific
configurations defined in the parameter.yml file.

Args:
fabric_workspace_obj: The FabricWorkspace object containing the items to be published.
parameter_details: List of parameter configuration dictionaries from parameter.yml,
each containing semantic_model_name and parameters to update.

Example parameter.yml structure:
semantic_model_parameters:
- semantic_model_name: "Sales Model"
parameters:
- name: "ServerName"
new_value: "prod-sql-server.database.windows.net"
- name: "DatabaseName"
new_value: "SalesDB"
- semantic_model_name: ["Finance Model", "HR Model"]
parameters:
- name: "ApiEndpoint"
new_value: "https://api.production.com"
"""
item_type = "SemanticModel"

# Build parameter mapping from semantic_model_parameters configuration
parameter_mapping = {}

for entry in parameter_details:
model_names = entry.get("semantic_model_name", [])
parameters = entry.get("parameters", [])

# Convert single model name to list for uniform processing
if isinstance(model_names, str):
model_names = [model_names]

# Map each model name to its parameters
for model_name in model_names:
parameter_mapping[model_name] = parameters

# Process each semantic model
for model_name, parameters in parameter_mapping.items():
# Check if this semantic model exists in the repository
if model_name not in fabric_workspace_obj.repository_items.get(item_type, {}):
logger.warning(f"Semantic model '{model_name}' not found in repository, skipping parameter update")
continue

# Get the semantic model object
item_obj = fabric_workspace_obj.repository_items[item_type][model_name]
model_id = item_obj.guid

logger.info(f"Updating parameters for semantic model '{model_name}' (ID: {model_id})")

try:
# Build the parameter update request
update_details = []
for param in parameters:
param_name = param.get("name")
param_value = param.get("new_value")

if not param_name:
logger.warning(f"Parameter missing 'name' for semantic model '{model_name}', skipping")
continue

if param_value is None:
logger.warning(
f"Parameter '{param_name}' missing 'new_value' for semantic model '{model_name}', skipping"
)
continue

update_details.append({"name": param_name, "newValue": str(param_value)})
logger.debug(f" - Setting parameter '{param_name}' = '{param_value}'")

if not update_details:
logger.warning(f"No valid parameters to update for semantic model '{model_name}'")
continue

# Construct the Power BI API endpoint
powerbi_url = f"{constants.DEFAULT_API_ROOT_URL}/v1.0/myorg/groups/{fabric_workspace_obj.workspace_id}/datasets/{model_id}/Default.UpdateParameters"

# Build request body
request_body = {"updateDetails": update_details}

# Make the API call to update parameters
response = fabric_workspace_obj.endpoint.invoke(method="POST", url=powerbi_url, body=request_body)

status_code = response.get("status_code")

if status_code == 200:
logger.info(
f"Successfully updated {len(update_details)} parameter(s) for semantic model '{model_name}'"
)
else:
error_message = response.get("body", {}).get("error", {}).get("message", "Unknown error")
logger.warning(
f"Failed to update parameters for semantic model '{model_name}'. "
f"Status code: {status_code}, Error: {error_message}"
)

except Exception as e:
logger.error(f"Failed to update parameters for semantic model '{model_name}': {e!s}")
continue
Loading