diff --git a/README.md b/README.md
new file mode 100644
index 0000000..36c335f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,343 @@
+# Port Agent
+
+## [Documentation](https://docs.getport.io/create-self-service-experiences/setup-backend/port-execution-agent/)
+
+## Control the payload of your self-service actions
+
+Some of the 3rd party applications that you may want to integrate with may not accept the raw payload incoming from Port's
+self-service actions. The Port agent allows you to control the payload that is sent to every 3rd party application.
+
+You can alter the requests sent to your third-party application by providing a payload mapping config file when deploying the
+Port-agent container.
+
+### Control the payload mapping
+
+The payload mapping file is a JSON file that specifies how to transform the request sent to the Port agent to the
+request that is sent to the third-party application.
+
+The payload mapping file is mounted to the Port agent as a volume. The path to the payload mapping file is set in the
+`CONTROL_THE_PAYLOAD_CONFIG_PATH` environment variable. By default, the Port agent will look for the payload mapping
+file
+at `~/control_the_payload_config.json`.
+
+The payload mapping file is a json file that contains a list of mappings. Each mapping contains the request fields that
+will be overridden and sent to the 3rd party application.
+
+You can see examples showing how to deploy the Port agent with different mapping configurations for various common use cases below.
+
+Each of the mapping fields can be constructed by JQ expressions. The JQ expression will be evaluated against the
+original payload that is sent to the port agent from Port and the result will be sent to the 3rd party application.
+
+Here is the mapping file schema:
+
+```
+[ # Can have multiple mappings. Will use the first one it will find with enabled = True (Allows you to apply mapping over multiple actions at once)
+ {
+ "enabled": bool || JQ,
+ "url": JQ, # Optional. default is the incoming url from port
+ "method": JQ, # Optional. default is POST. Should return one of the following string values POST / PUT / DELETE / GET
+ "headers": dict[str, JQ], # Optional. default is {}
+ "body": ".body", # Optional. default is the whole payload incoming from Port.
+ "query": dict[str, JQ] # Optional. default is {}
+ }
+]
+```
+
+**The body can be partially constructed by json as follows:**
+
+```json
+{
+ "body": {
+ "key": 2,
+ "key2": {
+ "key3": ".im.a.jq.expression",
+ "key4": "\"im a string\""
+ }
+ }
+}
+```
+
+### The incoming message to base your mapping on
+
+
+Example for incoming event
+
+```json
+{
+ "action": "action_identifier",
+ "resourceType": "run",
+ "status": "TRIGGERED",
+ "trigger": {
+ "by": {
+ "orgId": "org_XXX",
+ "userId": "auth0|XXXXX",
+ "user": {
+ "email": "executor@mail.com",
+ "firstName": "user",
+ "lastName": "userLastName",
+ "phoneNumber": "0909090909090909",
+ "picture": "https://s.gravatar.com/avatar/dd1cf547c8b950ce6966c050234ac997?s=480&r=pg&d=https%3A%2F%2Fcdn.auth0.com%2Favatars%2Fga.png",
+ "providers": [
+ "port"
+ ],
+ "status": "ACTIVE",
+ "id": "auth0|XXXXX",
+ "createdAt": "2022-12-08T16:34:20.735Z",
+ "updatedAt": "2023-11-13T15:11:38.243Z"
+ }
+ },
+ "origin": "UI",
+ "at": "2023-11-13T15:20:16.641Z"
+ },
+ "context": {
+ "entity": "e_iQfaF14FJln6GVBn",
+ "blueprint": "kubecostCloudAllocation",
+ "runId": "r_HardNzG6kzc9vWOQ"
+ },
+ "payload": {
+ "entity": {
+ "identifier": "e_iQfaF14FJln6GVBn",
+ "title": "myEntity",
+ "icon": "Port",
+ "blueprint": "myBlueprint",
+ "team": [],
+ "properties": {
+ },
+ "relations": {},
+ "createdAt": "2023-11-13T15:24:46.880Z",
+ "createdBy": "auth0|XXXXX",
+ "updatedAt": "2023-11-13T15:24:46.880Z",
+ "updatedBy": "auth0|XXXXX"
+ },
+ "action": {
+ "invocationMethod": {
+ "type": "WEBHOOK",
+ "agent": true,
+ "synchronized": false,
+ "method": "POST",
+ "url": "https://myGitlabHost.com"
+ },
+ "trigger": "DAY-2"
+ },
+ "properties": {
+ },
+ "censoredProperties": []
+ }
+}
+```
+
+
+
+### Examples
+
+#### Terraform Cloud
+
+Create the following blueprint, action and mapping to trigger a Terraform Cloud run.
+
+
+Blueprint
+
+```json
+{
+ "identifier": "terraform_cloud_workspace",
+ "title": "Terraform Cloud Workspace",
+ "icon": "Terraform",
+ "schema": {
+ "properties": {
+ "workspace_id": {
+ "title": "Workspace Id",
+ "type": "string"
+ }
+ },
+ "required": [
+ "workspace_id"
+ ]
+ },
+ "mirrorProperties": {},
+ "calculationProperties": {},
+ "relations": {}
+}
+```
+
+
+
+Action
+
+```json
+[
+ {
+ "identifier": "trigger_tf_run",
+ "title": "Trigger TF Cloud run",
+ "icon": "Terraform",
+ "userInputs": {
+ "properties": {},
+ "required": [],
+ "order": []
+ },
+ "invocationMethod": {
+ "type": "WEBHOOK",
+ "agent": true,
+ "synchronized": false,
+ "method": "POST",
+ "url": "https://app.terraform.io/api/v2/runs/"
+ },
+ "trigger": "DAY-2",
+ "requiredApproval": false
+ }
+]
+```
+
+
+
+Mapping - (Should be saved as `invocations.json`)
+
+```json
+{
+ "enabled": ".action == \"trigger_tf_run\"",
+ "headers": {
+ "Authorization": "\"Bearer \" + env.TF_TOKEN",
+ "Content-Type": "\"application/vnd.api+json\""
+ },
+ "body": {
+ "data": {
+ "attributes": {
+ "is-destroy": false,
+ "message": "\"Triggered via Port\"",
+ "variables": ".payload.properties | to_entries | map({key: .key, value: .value})"
+ },
+ "type": "\"runs\"",
+ "relationships": {
+ "workspace": {
+ "data": {
+ "type": "\"workspaces\"",
+ "id": ".payload.entity.properties.workspace_id"
+ }
+ }
+ }
+ }
+ }
+ }
+```
+
+
+**Port agent installation for Terraform cloud example**:
+
+```sh
+helm repo add port-labs https://port-labs.github.io/helm-charts
+
+helm repo update
+
+helm install my-port-agent port-labs/port-agent \
+ --create-namespace --namespace port-agent \
+ --set env.normal.PORT_ORG_ID=YOUR_ORG_ID \
+ --set env.normal.KAFKA_CONSUMER_GROUP_ID=YOUR_KAFKA_CONSUMER_GROUP \
+ --set env.secret.KAFKA_CONSUMER_USERNAME=YOUR_KAFKA_USERNAME \
+ --set env.secret.KAFKA_CONSUMER_PASSWORD=YOUR_KAFKA_PASSWORD \
+ --set env.normal.KAFKA_CONSUMER_BROKERS=PORT_KAFKA_BROKERS \
+ --set env.normal.STREAMER_NAME=KAFKA \
+ --set env.normal.KAFKA_CONSUMER_AUTHENTICATION_MECHANISM=SCRAM-SHA-512 \
+ --set env.normal.KAFKA_CONSUMER_AUTO_OFFSET_RESET=earliest \
+ --set env.normal.KAFKA_CONSUMER_SECURITY_PROTOCOL=SASL_SSL \
+ --set env.secret.TF_TOKEN=YOU_TERRAFORM_CLOUD_TOKEN \
+ --set-file controlThePayloadConfig=./invocations.json
+```
+
+
+
+#### CircleCI
+
+Create the following blueprint, action and mapping to trigger a CircleCI pipeline.
+
+
+Blueprint
+
+```json
+{
+ "identifier": "circle_ci_project",
+ "title": "CircleCI Project",
+ "icon": "CircleCI",
+ "schema": {
+ "properties": {
+ "project_slug": {
+ "title": "Slug",
+ "type": "string"
+ }
+ },
+ "required": [
+ "project_slug"
+ ]
+ },
+ "mirrorProperties": {},
+ "calculationProperties": {},
+ "relations": {}
+}
+```
+
+
+
+Action
+
+```json
+[
+ {
+ "identifier": "trigger_circle_ci_pipeline",
+ "title": "Trigger CircleCI pipeline",
+ "icon": "CircleCI",
+ "userInputs": {
+ "properties": {},
+ "required": [],
+ "order": []
+ },
+ "invocationMethod": {
+ "type": "WEBHOOK",
+ "agent": true,
+ "synchronized": false,
+ "method": "POST",
+ "url": "https://circleci.com"
+ },
+ "trigger": "DAY-2",
+ "requiredApproval": false
+ }
+]
+```
+
+
+
+Mapping - (Should be saved as `invocations.json`)
+
+```json
+{
+ "enabled": ".action == \"trigger_circle_ci_pipeline\"",
+ "url": "(env.CIRCLE_CI_URL // \"https://circleci.com\") as $baseUrl | .payload.entity.properties.project_slug | @uri as $path | $baseUrl + \"/api/v2/project/\" + $path + \"/pipeline\"",
+ "headers": {
+ "Circle-Token": "env.CIRCLE_CI_TOKEN"
+ },
+ "body": {
+ "branch": ".payload.properties.branch // \"main\"",
+ "parameters": ".payload.action.invocationMethod as $invocationMethod | .payload.properties | to_entries | map({(.key): (.value | tostring)}) | add | if $invocationMethod.omitUserInputs then {} else . end"
+ }
+ }
+```
+
+
+**Port agent installation for CircleCI example**:
+
+```sh
+helm repo add port-labs https://port-labs.github.io/helm-charts
+
+helm repo update
+
+helm install my-port-agent port-labs/port-agent \
+ --create-namespace --namespace port-agent \
+ --set env.normal.PORT_ORG_ID=YOUR_ORG_ID \
+ --set env.normal.KAFKA_CONSUMER_GROUP_ID=YOUR_KAFKA_CONSUMER_GROUP \
+ --set env.secret.KAFKA_CONSUMER_USERNAME=YOUR_KAFKA_USERNAME \
+ --set env.secret.KAFKA_CONSUMER_PASSWORD=YOUR_KAFKA_PASSWORD
+ --set env.normal.KAFKA_CONSUMER_BROKERS=PORT_KAFKA_BROKERS \
+ --set env.normal.STREAMER_NAME=KAFKA \
+ --set env.normal.KAFKA_CONSUMER_AUTHENTICATION_MECHANISM=SCRAM-SHA-512 \
+ --set env.normal.KAFKA_CONSUMER_AUTO_OFFSET_RESET=earliest \
+ --set env.normal.KAFKA_CONSUMER_SECURITY_PROTOCOL=SASL_SSL \
+ --set env.secret.CIRCLE_CI_TOKEN=YOUR_CIRCLE_CI_PERSONAL_TOKEN \
+ --set-file controlThePayloadConfig=./invocations.json
+```
diff --git a/app/control_the_payload_config.json b/app/control_the_payload_config.json
index 9d2a940..af87190 100644
--- a/app/control_the_payload_config.json
+++ b/app/control_the_payload_config.json
@@ -1,10 +1,10 @@
[
{
"enabled": ".payload.action.invocationMethod.type == \"GITLAB\"",
- "url": "($ENV.GITLAB_URL // \"https://gitlab.com\") as $baseUrl | (.payload.action.invocationMethod.groupName + \"/\" +.payload.action.invocationMethod.projectName) | @uri as $path | $baseUrl + \"/api/v4/projects/\" + $path + \"/trigger/pipeline\"",
+ "url": "(env.GITLAB_URL // \"https://gitlab.com/\") as $baseUrl | (.payload.action.invocationMethod.groupName + \"/\" +.payload.action.invocationMethod.projectName) | @uri as $path | $baseUrl + \"api/v4/projects/\" + $path + \"/trigger/pipeline\"",
"body": {
"ref": ".payload.properties.ref // .payload.action.invocationMethod.defaultRef // \"main\"",
- "token": ".payload.action.invocationMethod.groupName as $gitlab_group | .payload.action.invocationMethod.projectName as $gitlab_project | $ENV[($gitlab_group + \"_\" + ($gitlab_project | gsub(\"/\"; \"_\")))]",
+ "token": ".payload.action.invocationMethod.groupName as $gitlab_group | .payload.action.invocationMethod.projectName as $gitlab_project | env[($gitlab_group | gsub(\"/\"; \"_\")) + \"_\" + $gitlab_project]",
"variables": ".payload.action.invocationMethod as $invocationMethod | .payload.properties | to_entries | map({(.key): (.value | tostring)}) | add | if $invocationMethod.omitUserInputs then {} else . end",
"port_payload": "if .payload.action.invocationMethod.omitPayload then {} else . end"
}