Notification policy
Purpose
Info
Please note, we currently don't support importing rego.v1. For more details, refer to the policy introduction.
Notification policies can filter, route, and adjust the body of notification messages sent by Spacelift. The policy works at the space level, meaning that it does not need to be attached to a specific stack. Notification policices always verify the space they're in can be accessed by whatever action is being evaluated. If you are new to spaces, consider exploring our spaces documentation.
All notifications go through the policy evaluation, so any of them can be redirected to the routes defined in the policy.
A notification policy can define the following rules:
- inbox: Allows messages to be routed to the Spacelift notification inbox.
- slack: Allows messages to be routed to a given Slack channel.
- webhook: Allows messages to be routed to a given webhook.
- pull_request: Allows messages to be routed to one or more pull requests.
If no rules match, no action is taken.
This is the schema of the data input that each policy request can receive:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174 | {
"account": {
"name": "string"
},
"module_version": {
"module": {
"id": "string - unique ID of the module",
"administrative": "boolean - is the module administrative",
"branch": "string - tracked branch of the module",
"labels": ["string - list of arbitrary, user-defined selectors"],
"namespace": "string - repository namespace, only relevant to GitLab repositories",
"name": "string - name of the module",
"project_root": "optional string - project root as set on the Module, if any",
"repository": "string - name of the source repository",
"terraform_provider": "string - name of the main Terraform provider used by the module",
"space": {
"id": "string - id of a space",
"labels": ["string - list of arbitrary, user-defined selectors"],
"name": "string - name of a the space"
},
"worker_pool": {
"public": "boolean - worker pool information",
"id": "string - unique ID of the worker pool",
"name": "string - name of the worker pool",
"labels": ["string - list of arbitrary, user-defined selectors"]
}
},
"version": {
"commit": {
"author": "string",
"branch": "string",
"created_at": "number (timestamp in nanoseconds)",
"hash": "string",
"message": "string",
"url": "string"
},
"created_at": "number - creation Unix timestamp in nanoseconds",
"id": "string - id of the version being created",
"latest": "boolean - is the module version latest",
"number": "string - semver version number",
"state": "string - current module state: ACTIVE, FAILED",
"test_runs": [{
"created_at": "number (timestamp in nanoseconds)",
"id": "string - id of the test",
"state": "string - state of the test",
"title": "string - title of the test",
"updated_at": "number (timestamp in nanoseconds)",
}]
}
},
"run_updated": {
"state": "string",
"username": "string",
"note": "string",
"run":{
"based_on_local_workspace": "boolean - whether the run stems from a local preview",
"branch": "string - the branch the run was triggered from",
"changes": [
{
"action": "string enum - added | changed | deleted",
"entity": {
"address": "string - full address of the entity",
"data": "object - detailed information about the entity, shape depends on the vendor and type",
"name": "string - name of the entity",
"type": "string - full resource type or \"output\" for outputs",
"entity_vendor": "string - the name of the vendor",
"entity_type": "string - the type of entity, possible values depend on the vendor"
},
"phase": "string enum - plan | apply"
}
],
"command": "string",
"commit": {
"author": "string",
"branch": "string",
"created_at": "number (timestamp in nanoseconds)",
"hash": "string",
"message": "string",
"url": "string"
},
"created_at": "number (timestamp in nanoseconds)",
"creator_session": {
"admin": "boolean",
"creator_ip": "string",
"login": "string",
"name": "string",
"teams": ["string"],
"machine": "boolean - whether the run was kicked off by a human or a machine"
},
"drift_detection": "boolean",
"flags": ["string"],
"id": "string",
"runtime_config": {
"after_apply": ["string"],
"after_destroy": ["string"],
"after_init": ["string"],
"after_perform": ["string"],
"after_plan": ["string"],
"after_run": ["string"],
"before_apply": ["string"],
"before_destroy": ["string"],
"before_init": ["string"],
"before_perform": ["string"],
"before_plan": ["string"],
"environment": "map[string]string",
"project_root": "string",
"runner_image": "string",
"terraform_version": "string"
},
"policy_receipts": [{
"flags": ["string - flag assigned to the policy"],
"name": "string - name of the policy",
"outcome": "string - outcome of the policy",
"type": "string - type of the policy"
}],
"state": "string",
"triggered_by": "string or null",
"type": "string - PROPOSED or TRACKED",
"updated_at": "number (timestamp in nanoseconds)",
"user_provided_metadata": ["string"]
},
"stack": {
"administrative": "boolean",
"autodeploy": "boolean",
"autoretry": "boolean",
"branch": "string",
"id": "string",
"labels": ["string"],
"locked_by": "string or null",
"name": "string",
"namespace": "string or null",
"project_root": "string or null",
"repository": "string",
"space": {
"id": "string",
"labels": ["string"],
"name": "string"
},
"state": "string",
"terraform_version": "string or null",
"tracked_commit": {
"author": "string",
"branch": "string",
"created_at": "number (timestamp in nanoseconds)",
"hash": "string",
"message": "string",
"url": "string"
}
},
"timing": [
{
"duration": "number (in nanoseconds)",
"state": "string"
}
],
"urls": {
"run": "string - URL to the run"
}
},
"webhook_endpoints": [
{
"id": "custom-hook2",
"labels": [
"example-label1",
"example-label2"
]
}
],
"internal_error": {
"error": "string",
"message": "string",
"severity": "string - INFO, WARNING, ERROR"
}
}
|
The final JSON object received as input will depend on the type of notification being sent. Event-dependent objects will only be present when those events happen.
The best way to see what input your Notification policy received is to enable sampling and check the policy workbench. You can also use the table in the next section as a reference.
Ansible vendor changes structure
For Ansible runs, the changes array contains objects with different action values and entity objects that contain Ansible-specific fields:
1
2
3
4
5
6
7
8
9
10
11
12
13 | "changes": [
{
"action": "string enum - changed | ok | skipped | rescued | ignored | unreachable | failed",
"entity": {
"host_name": "string",
"playbook_name": "string",
"role_name": "string",
"task_name": "string",
"task_action": "string"
},
"phase": "string enum - plan | apply"
}
]
|
| Object Received |
Event |
account |
Any event |
webhook_endpoints |
Any event |
run_updated |
Run Updated |
internal_error |
Internal error occurred |
module_version |
Module version updated |
Notification policy in practice
Using the notification policy, you can completely re-write notifications or control where and when they are sent. Let's look into how the policy works for each of the defined routes.
Choose a space for your policy
When creating notification policies, take into account the space in which you're creating them. Generally the policy follows the same conventions as any other Spacelift component, with a few small caveats.
Inbox notifications
Inbox notifications are what you receive in your Spacelift notification inbox. By default, these are errors that happened during action execution inside Spacelift and are always sent even if you do not have a policy created.
However, with notification policices you can alter the body of those errors to add additional context or create your own unique notifications.
The inbox rule accepts multiple configurable parameters, all optional:
title: A custom title for the message.
body: A custom message body.
severity: The severity level for the message.
Create new inbox notifications
This inbox rule will send INFO level notification messages to your inbox when a tracked run has finished:
1
2
3
4
5
6
7
8
9
10
11
12 | package spacelift
inbox[{
"title": "Tracked run finished!",
"body": sprintf("http://example.app.spacelift.io/stack/%s/run/%s has finished", [stack.id, run.id]),
"severity": "INFO",
}] {
stack := input.run_updated.stack
run := input.run_updated.run
run.type == "TRACKED"
run.state == "FINISHED"
}
|
Slack messages
Slack messages can also be controlled using the notification policy. Before creating any policies that interact with Slack, you will need to add the slack integration to your Spacelift account.
Info
The documentation about Slack contains more information about available actions, Slack access policies, and more.
The rules for Slack require a channel_id to be defined, which can be found at the bottom of a channel's About section in Slack:

Once the integration is configured in your account, you should be ready to define rules for routing Slack messages. Slack rules allow you to make the same filtering decisions as any other rule in the policy. They also allow you to edit the message bodies themselves to create custom messages.
The Slack rules accept multiple configurable parameters:
channel_id: The Slack channel to which the message will be delivered.
message (optional): A custom message to be sent.
mention_users (optional): An array of users to mention in the default message.
mention_groups (optional): An array of groups to mention in the default message.
Filtering and routing messages
To receive notifications for finished runs only on a specific Slack channel, define a rule like this:
| package spacelift
slack[{"channel_id": "C0000000000"}] {
input.run_updated != null
run := input.run_updated.run
run.state == "FINISHED"
}
|
Changing the message body
To send a custom message when a run which tries to attach a policy requires confirmation, define a rule like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | package spacelift
slack[{
"channel_id": "C0000000000",
"message": sprintf("http://example.app.spacelift.io/stack/%s/run/%s is trying to attach a policy!", [stack.id, run.id]),
}] {
stack := input.run_updated.stack
run := input.run_updated.run
run.type == "TRACKED"
run.state == "UNCONFIRMED"
change := run.changes[_]
change.phase == "plan"
change.entity.type == "spacelift_policy_attachment"
}
|
Mentioning users and groups
You can provide an array of user IDs and group IDs to mention in the default message. To mention a user and a group (conditionally) in a Slack message, define a rule like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | package spacelift
users := {
"bob": "U08MA4D50RY"
}
groups := {
"devs": "S08MA51EW4S"
}
mention_groups(run) = [groups.devs] {
run.state == "UNCONFIRMED"
} else = []
slack[{
"channel_id": "C08MA83LAD9",
"mention_users": [users.bob],
"mention_groups": mention_groups(run)
}] {
run := input.run_updated.run
run.type == "TRACKED"
}
|
Organizing messages in threads
Use the thread_key parameter to group related messages in a single Slack thread. When the first message with a specific thread_key is sent, it creates a new message in the channel. Subsequent messages with the same thread_key will be sent as replies in that thread, instead of creating new messages.
Here's how to use thread_key to group all updates for a single run in one thread:
| package spacelift
slack[{"channel_id": "C08TULY2SBS", "thread_key": run.id}] {
run := input.run_updated.run
run.type == "TRACKED"
}
|
This will send a message every time the run is updated, with each update appearing as a reply in the same thread.
Note
Using thread_key changes the default behavior of run update notifications. Without thread_key, only one message is sent per run and that message is updated every time the run is updated. With thread_key, every state change will be sent as a separate message in the thread.
Webhook requests
Info
You must configure at least one named webhook before using webhook notifications.
With webhook notifications, you can receive webhooks on specific events that happen in Spacelift and craft unique requests to be consumed by some third-party.
The notification policy relies on named webhooks, which can be created and managed in the webhooks section of Spacelift. Any policy evaluation will always receive a list of possible webhooks together with their labels as input. The data received in the policy input should be used to determine which webhook will be used when sending the request.
The webhook policy accepts multiple configurable parameters:
endpoint_id: Endpoint id (slug) to which the webhook will be delivered.
headers (optional): A key value map to append to request headers.
payload (optional): A custom valid JSON object to be sent as request body.
method (optional): The HTTP method to use when sending the request.
Filtering webhook requests
Filter and select webhooks using the received input data. You can create rules where only specific actions trigger a webhook being sent. For example, we could define a rule to allow a webhook to be sent about any drift detection run:
| package spacelift
webhook[{"endpoint_id": endpoint.id}] {
endpoint := input.webhook_endpoints[_]
endpoint.id == "drift-hook"
input.run_updated.run.drift_detection == true
input.run_updated.run.type == "PROPOSED"
}
|
Creating a custom webhook request
All requests sent will include the default headers for verification, a payload appropriate for the message type, and the method set as POST. However, by using the webhook rule we can modify the body of the request, change the method, or add additional headers.
To set up a completely custom request for a tracked run, define a rule like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | package spacelift
webhook[wbdata] {
endpoint := input.webhook_endpoints[_]
endpoint.id == "testing-notifications"
wbdata := {
"endpoint_id": endpoint.id,
"payload": {
"custom_field": "This is a custom message",
"run_type": input.run_updated.run.type,
"run_state": input.run_updated.run.state,
"updated_at": input.run_updated.run.updated_at,
},
"method": "PUT",
"headers": {
"custom-header": "custom",
},
}
input.run_updated.run.type == "TRACKED"
}
|
Using custom webhook requests also makes it easy to integrate Spacelift with any third-party webhook consumer.
Including run logs in webhook requests
You can include logs from various run phases in your webhook requests by using placeholders in any value field of the payload. Placeholders in JSON keys will be ignored:
spacelift::logs::initializing placeholder will be replaced with logs from the initializing phase.
spacelift::logs::preparing placeholder will be replaced with logs from the preparing phase.
spacelift::logs::planning placeholder will be replaced with logs from the planning phase.
spacelift::logs::applying placeholder will be replaced with logs from the applying phase.
Here's an example that includes logs from different phases in the webhook payload:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | package spacelift
webhook[wbdata] {
endpoint := input.webhook_endpoints[_]
endpoint.id == "test"
wbdata := {
"endpoint_id": endpoint.id,
"payload": {
"initializing": "spacelift::logs::initializing",
"preparing": "You can embed the placeholder within text like this: spacelift::logs::preparing",
"planning_and_applying": ["The placeholders also work in lists:", "spacelift::logs::planning", "spacelift::logs::applying"],
},
"method": "PUT",
"headers": {
"custom-header": "custom",
},
}
input.run_updated.run.type == "TRACKED"
input.run_updated.run.state == "FINISHED"
}
|
Discord integration using custom webhook requests
Discord can be integrated to receive updates about Spacelift by creating a new webhook endpoint in your Discord server's integrations section and providing that as the endpoint when creating a new named webhook in Spacelift.
After creating the webhook on both Discord and Spacelift, you will need to define a new webhook rule like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | # Send updates about tracked runs to discord.
webhook[wbdata] {
endpoint := input.webhook_endpoints[_]
endpoint.id == "YOUR_WEBHOOK_ID_HERE"
stack := input.run_updated.stack
run := input.run_updated.run
wbdata := {
"endpoint_id": endpoint.id,
"payload": {
"embeds": [{
"title": "Tracked run triggered!",
"description": sprintf("Stack: [%s](http://example.app.spacelift.io/stack/%s)\nRun ID: [%s](http://example.app.spacelift.io/stack/%s/run/%s)\nRun state: %s", [stack.name,stack.id,run.id,stack.id, run.id,run.state]),
}]
}
}
input.run_updated.run.type == "TRACKED"
}
|
Once the rule is created, you should receive updates about tracked runs on your Discord server:

Pull request notifications
With pull request notifications, you can target a single pull request as well as pull requests targeting a specific branch or commit.
The pull request rule accepts multiple configurable parameters, all optional:
id: A pull request ID.
commit: The target commit SHA.
branch: The target branch.
body: A custom comment body.
deduplication_key: A deduplication key for updating PR comments.
Here's a rule which will add a comment (containing a default body) to the pull request that triggered the run:
| package spacelift
import future.keywords.contains
import future.keywords.if
pull_request contains {"commit": run.commit.hash} if {
run := input.run_updated.run
run.state == "FINISHED"
}
|
Hint
Pull request notifications work best in combination with a push policy to create proposed runs on pull requests.
Here's a rule to add a comment to the pull request that triggered the run and update that comment for every run state change, instead of creating new comments.
1
2
3
4
5
6
7
8
9
10
11
12 | package spacelift
import future.keywords
pull_request contains {
"commit": run.commit.hash,
"body": sprintf("Run %s is %s", [run.id, run.state]),
"deduplication_key": deduplication_key,
} if {
run := input.run_updated.run
deduplication_key := input.run_updated.stack.id
}
|
The essential aspect for updating existing comments is the deduplication_key, which must have a constant value throughout the PR's lifetime to update the same comment. If the deduplication_key changes but a comment was created with the old deduplication_key, a new comment will be created and updated.
The deduplication_key is associated with the PR using it, so using the same deduplication_key on different PRs is safe and will not cause collisions.
To use this with existing policies, simply add deduplication_key := input.run_updated.stack.id (or another constant value) to the condition of the pull_request rule.
Specify a target commit SHA using the commit parameter:
1
2
3
4
5
6
7
8
9
10
11
12
13 | package spacelift
import future.keywords.contains
import future.keywords.if
pull_request contains {
"commit": run.commit.hash,
"body": sprintf("https://%s.app.spacelift.io/stack/%s/run/%s has finished", [input.account.name, stack.id, run.id]),
} if {
stack := input.run_updated.stack
run := input.run_updated.run
run.state == "FINISHED"
}
|
Provide the branch name using the branch parameter:
1
2
3
4
5
6
7
8
9
10
11
12
13 | package spacelift
import future.keywords.contains
import future.keywords.if
pull_request contains {
"branch": "main",
"body": sprintf("https://%s.app.spacelift.io/stack/%s/run/%s has finished", [input.account.name, stack.id, run.id]),
} if {
stack := input.run_updated.stack
run := input.run_updated.run
run.state == "FINISHED"
}
|
Hint
branch is the base branch of the pull request. For example, if it's "branch": input.run_updated.stack.branch, the policy would comment into every pull request that targets the tracked branch of the stack.
Changing the comment body
You can customize the comment body and even include logs from various run phases by including a corresponding placeholder:
spacelift::logs::initializing placeholder will be replaces with logs from the initializing phase.
spacelift::logs::preparing placeholder will be replaces with logs from the preparing phase.
spacelift::logs::planning placeholder will be replaced with logs from the planning phase.
spacelift::logs::applying placeholder will be replaced with logs from the applying phase.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 | package spacelift
import future.keywords.contains
import future.keywords.if
pull_request contains {
"commit": run.commit.hash,
"body": body,
} if {
stack := input.run_updated.stack
run := input.run_updated.run
run.state == "FINISHED"
body := sprintf(
`https://%s.app.spacelift.io/stack/%s/run/%s has finished
## Planning logs
%s
spacelift::logs::planning
%s
`,
[input.account.name, stack.id, run.id, "```", "```"],
)
}
|
This more complex example will add a comment to a pull request where it will list all the resources that were added, changed, deleted, imported, moved, or forgotten.
Note
This is an example policy you can customize completely. If you would like to do so, add sample := true to the the policy and use the policy workbench to see what data is available. This example is also specifically for GitHub.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112 | package spacelift
import future.keywords # Import future syntax features.
# Define a variable `header` to store a formatted string using the `sprintf` function.
# This string will include dynamic information such as the URL for resource changes and
# badges for different actions (add, change, destroy, import, move, forget) based on their counts.
header := sprintf("### Resource changes ([link](%s))\n\n      \n\n| Action | Resource | Changes |\n| --- | --- | --- |", [input.run_updated.urls.run, count(added), count(changed), count(deleted), count(imported), count(moved), count(forgotten)])
# Create strings of resources by joining all resource items in respective categories with newline characters.
addedresources := concat("\n", added) # Join all 'added' resources with newline characters.
changedresources := concat("\n", changed) # Join all 'changed' resources with newline characters.
deletedresources := concat("\n", deleted) # Join all 'deleted' resources with newline characters.
importedresources := concat("\n", imported) # Join all 'imported' resources with newline characters.
movedresources := concat("\n", moved) # Join all 'moved' resources with newline characters.
forgottenresources := concat("\n", forgotten) # Join all 'forgotten' resources with newline characters.
# Define rules to populate the `added` collection with formatted rows when specific conditions are met.
added contains row if {
some x in input.run_updated.run.changes # Iterate over each change in the run's updates.
row := sprintf("| Added | `%s` | <details><summary>Value</summary>`%s`</details> |", [x.entity.address, x.entity.data.values]) # Format a row for an 'added' resource.
x.action == "added" # Check if the action for the change is 'added'.
x.entity.entity_type == "resource" # Ensure the entity type is a 'resource'.
not x.moved # Ensure the resource was not moved.
}
# Additional conditions to consider other types of "added" actions based on replacements.
added contains row if {
some x in input.run_updated.run.changes
row := sprintf("| Added | `%s` | <details><summary>Value</summary>`%s`</details> |", [x.entity.address, x.entity.data.values])
x.action == "destroy-Before-create-replaced" # Check if the action is 'destroy-Before-create-replaced'.
x.entity.entity_type == "resource"
not x.moved
}
added contains row if {
some x in input.run_updated.run.changes
row := sprintf("| Added | `%s` | <details><summary>Value</summary>`%s`</details> |", [x.entity.address, x.entity.data.values])
x.action == "create-Before-destroy-replaced" # Check if the action is 'create-Before-destroy-replaced'.
x.entity.entity_type == "resource"
not x.moved
}
# Define a rule to populate the `changed` collection with formatted rows when specific conditions are met.
changed contains row if {
some x in input.run_updated.run.changes
row := sprintf("| Changed | `%s` | <details><summary>New value</summary>`%s`</details> |", [x.entity.address, x.entity.data.values]) # Format a row for a 'changed' resource.
x.entity.entity_type == "resource" # Ensure the entity type is a 'resource'.
x.action == "changed" # Check if the action for the change is 'changed'.
not x.moved # Ensure the resource was not moved.
}
# Define rules to populate the `deleted` collection with formatted rows when specific conditions are met.
deleted contains row if {
some x in input.run_updated.run.changes
row := sprintf("| Deleted | `%s` | :x: |", [x.entity.address]) # Format a row for a 'deleted' resource.
x.action == "deleted" # Check if the action for the change is 'deleted'.
x.entity.entity_type == "resource" # Ensure the entity type is a 'resource'.
not x.moved # Ensure the resource was not moved.
}
# Additional conditions to consider other types of "deleted" actions based on replacements.
deleted contains row if {
some x in input.run_updated.run.changes
row := sprintf("| Deleted | `%s` | :x: |", [x.entity.address])
x.action == "destroy-Before-create-replaced" # Check if the action is 'destroy-Before-create-replaced'.
x.entity.entity_type == "resource"
not x.moved
}
deleted contains row if {
some x in input.run_updated.run.changes
row := sprintf("| Deleted | `%s` | :x: |", [x.entity.address])
x.action == "create-Before-destroy-replaced" # Check if the action is 'create-Before-destroy-replaced'.
x.entity.entity_type == "resource"
not x.moved
}
# Define a rule to populate the `imported` collection with formatted rows when specific conditions are met.
imported contains row if {
some x in input.run_updated.run.changes
row := sprintf("| Imported | `%s` | <details><summary>New value</summary>`%s`</details> |", [x.entity.address, x.entity.data.values]) # Format a row for an 'imported' resource.
x.action == "import" # Check if the action for the change is 'import'.
x.entity.entity_type == "resource" # Ensure the entity type is a 'resource'.
not x.moved # Ensure the resource was not moved.
}
# Define a rule to populate the `moved` collection with formatted rows when specific conditions are met.
moved contains row if {
some x in input.run_updated.run.changes
row := sprintf("| Moved | `%s` | <details><summary>New value</summary>`%s`</details> |", [x.entity.address, x.entity.data.values]) # Format a row for a 'moved' resource.
x.entity.entity_type == "resource" # Ensure the entity type is a 'resource'.
x.moved # Check if the resource was moved.
}
# Define a rule to populate the `forgotten` collection with formatted rows when specific conditions are met.
forgotten contains row if {
some x in input.run_updated.run.changes
row := sprintf("| Forgotten | `%s` | :x: |", [x.entity.address]) # Format a row for a 'forgotten' resource.
x.action == "forget" # Check if the action for the change is 'forget'.
x.entity.entity_type == "resource" # Ensure the entity type is a 'resource'.
not x.moved # Ensure the resource was not moved.
}
# Define a rule to create a pull request object if certain conditions are met.
pull_request contains {
"commit": input.run_updated.run.commit.hash,
"body": replace(replace(concat("\n", [header, addedresources, changedresources, deletedresources, importedresources, movedresources, forgottenresources]), "\n\n\n", "\n"), "\n\n", "\n"),
} if {
input.run_updated.run.state == "FINISHED" # Ensure the run state is 'FINISHED'.
input.run_updated.run.type == "PROPOSED" # Ensure the run type is 'PROPOSED'.
}
|
The output will look like this:
