Skip to content

Commit

Permalink
Merge pull request #8 from port-labs/PORT-5167-port-agent-control-the…
Browse files Browse the repository at this point in the history
…-payload-fixes

fixed default mapping and added Control the payload of your self-serv…
  • Loading branch information
yairsimantov20 authored Nov 16, 2023
2 parents fe336ba + a7f687a commit 80195bd
Show file tree
Hide file tree
Showing 2 changed files with 345 additions and 2 deletions.
343 changes: 343 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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

<details>
<summary>Example for incoming event</summary>

```json
{
"action": "action_identifier",
"resourceType": "run",
"status": "TRIGGERED",
"trigger": {
"by": {
"orgId": "org_XXX",
"userId": "auth0|XXXXX",
"user": {
"email": "[email protected]",
"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": []
}
}
```

</details>

### Examples

#### Terraform Cloud

Create the following blueprint, action and mapping to trigger a Terraform Cloud run.

<details>
<summary>Blueprint</summary>

```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": {}
}
```
</details>

<details>
<summary>Action</summary>

```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
}
]
```
</details>

<details>
<summary>Mapping - (Should be saved as `invocations.json`)</summary>

```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"
}
}
}
}
}
}
```
</details>

**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.

<details>
<summary>Blueprint</summary>

```json
{
"identifier": "circle_ci_project",
"title": "CircleCI Project",
"icon": "CircleCI",
"schema": {
"properties": {
"project_slug": {
"title": "Slug",
"type": "string"
}
},
"required": [
"project_slug"
]
},
"mirrorProperties": {},
"calculationProperties": {},
"relations": {}
}
```
</details>

<details>
<summary>Action</summary>

```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
}
]
```
</details>

<details>
<summary>Mapping - (Should be saved as `invocations.json`)</summary>

```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"
}
}
```
</details>

**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
```
4 changes: 2 additions & 2 deletions app/control_the_payload_config.json
Original file line number Diff line number Diff line change
@@ -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"
}
Expand Down

0 comments on commit 80195bd

Please sign in to comment.