-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
29 changed files
with
886 additions
and
0 deletions.
There are no files selected for viewing
81 changes: 81 additions & 0 deletions
81
examples/aws-lambda-salesforce-webhook/.github/workflows/terraform.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
name: deploy.indent-salesforce-webhook | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
pull_request: | ||
|
||
jobs: | ||
terraform: | ||
name: 'Terraform' | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v2 | ||
|
||
- name: Setup Terraform | ||
uses: hashicorp/setup-terraform@v1 | ||
|
||
- name: Configure AWS Credentials | ||
uses: aws-actions/configure-aws-credentials@v1 | ||
with: | ||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | ||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | ||
aws-session-token: ${{ secrets.AWS_SESSION_TOKEN }} # if you have/need it | ||
aws-region: ${{ secrets.AWS_REGION }} | ||
|
||
- name: Terraform Format | ||
id: fmt | ||
run: terraform fmt -check -diff | ||
|
||
- name: Build Webhook (terraform-aws-salesforce-webhook) | ||
run: cd terraform-aws-salesforce-webhook && npm run deploy:prepare && npm install && npm run build | ||
|
||
- name: Terraform Init | ||
id: init | ||
run: terraform init | ||
|
||
- name: Terraform Plan | ||
id: plan | ||
if: github.event_name == 'pull_request' | ||
run: terraform plan -input=false -no-color | ||
continue-on-error: true | ||
env: | ||
TF_VAR_indent_webhook_secret: ${{ secrets.SALESFORCE_WEBHOOK_SECRET }} | ||
TF_VAR_indent_pull_webhook_secret: ${{ secrets.SALESFORCE_PULL_WEBHOOK_SECRET }} | ||
TF_VAR_okta_domain: ${{ secrets.SALESFORCE_ACCOUNT}} | ||
TF_VAR_okta_token: ${{ secrets.SALESFORCE_ACCESS_TOKEN }} | ||
|
||
- uses: actions/[email protected] | ||
if: github.event_name == 'pull_request' | ||
env: | ||
PLAN: "terraform\n${{ steps.plan.outputs.stdout }}" | ||
with: | ||
github-token: ${{ secrets.GITHUB_TOKEN }} | ||
script: | | ||
const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\` | ||
#### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\` | ||
#### Terraform Plan 📖\`${{ steps.plan.outcome }}\` | ||
<details><summary>Show Plan</summary> | ||
\`\`\`${process.env.PLAN}\`\`\` | ||
</details> | ||
*Actor: @${{ github.actor }}, Event: \`${{ github.event_name }}\`*`; | ||
github.issues.createComment({ | ||
issue_number: context.issue.number, | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
body: output | ||
}) | ||
- name: Terraform Plan Status | ||
if: steps.plan.outcome == 'failure' | ||
run: exit 1 | ||
|
||
- name: Terraform Apply | ||
if: github.ref == 'refs/heads/main' && github.event_name == 'push' | ||
run: terraform apply -input=false -auto-approve | ||
env: | ||
TF_VAR_indent_webhook_secret: ${{ secrets.SALESFORCE_WEBHOOK_SECRET }} | ||
TF_VAR_indent_pull_webhook_secret: ${{ secrets.SALESFORCE_PULL_WEBHOOK_SECRET }} | ||
TF_VAR_salesforce_instance_url: ${{ secrets.SALESFORCE_INSTANCE_URL }} | ||
TF_VAR_salesforce_access_token: ${{ secrets.SALESFORCE_ACCESS_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
data | ||
dist | ||
lib | ||
.env | ||
node_modules | ||
*.tfstate | ||
.terraform* | ||
*.tfstate.* | ||
terraform/config/*.tfvars | ||
!terraform/config/example.tfvars | ||
yarn.lock | ||
package-lock.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Indent + Salesforce |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# terraform { | ||
# backend "s3" { | ||
# encrypt = true | ||
# bucket = "" | ||
# region = "us-west-2" | ||
# key = "indent/terraform.tfstate" | ||
# } | ||
# } | ||
|
||
module "salesforce-pull-webhook" { | ||
source = "./terraform-aws-salesforce-webhook/terraform" | ||
|
||
indent_webhook_secret = var.salesforce_pull_webhook_secret | ||
salesforce_instance_url = var.salesforce_instance_url | ||
salesforce_access_token = var.salesforce_access_token | ||
} | ||
|
||
module "salesforce-change-webhook" { | ||
source = "./terraform-aws-salesforce-webhook/terraform" | ||
|
||
indent_webhook_secret = var.salesforce_webhook_secret | ||
salesforce_instance_url = var.salesforce_instance_url | ||
salesforce_access_token = var.salesforce_access_token | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
output "pull_api_base_url" { | ||
value = module.salesforce-pull-webhook.api_base_url | ||
description = "The URL of the deployed Lambda" | ||
} | ||
|
||
output "api_base_url" { | ||
value = module.salesforce-change-webhook.api_base_url | ||
description = "The URL of the deployed Lambda" | ||
} |
10 changes: 10 additions & 0 deletions
10
examples/aws-lambda-salesforce-webhook/terraform-aws-salesforce-webhook/.gitignore
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
data | ||
dist | ||
lib | ||
.env | ||
node_modules | ||
*.tfstate | ||
.terraform | ||
*.tfstate.* | ||
terraform/config/*.tfvars | ||
!terraform/config/example.tfvars |
40 changes: 40 additions & 0 deletions
40
examples/aws-lambda-salesforce-webhook/terraform-aws-salesforce-webhook/package.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
{ | ||
"name": "@indent/terraform-aws-salesforce-webhook", | ||
"version": "0.0.0", | ||
"description": "A Node.js starter for Terraform on AWS with Indent and Okta.", | ||
"main": "index.js", | ||
"private": true, | ||
"scripts": { | ||
"build": "tsc", | ||
"clean:dist": "rm -rf dist", | ||
"clean:modules": "rm -rf node_modules", | ||
"clean:tf": "rm -rf terraform/.terraform && rm -rf terraform/terraform.tfstate*", | ||
"clean:all": "npm run clean:dist; npm run clean:tf; npm run clean:modules", | ||
"create:all": "npm run deploy:init; npm run deploy:prepare; npm run deploy:all", | ||
"deploy:init": "cd terraform; terraform init", | ||
"deploy:prepare": "npm install --production && ./scripts/build-layers.sh", | ||
"deploy:all": "npm run build && npm run tf:apply -auto-approve", | ||
"destroy:all": "npm run tf:destroy -auto-approve", | ||
"tf:plan": "cd terraform && terraform plan -var-file ./config/terraform.tfvars", | ||
"tf:apply": "cd terraform && terraform apply -compact-warnings -var-file ./config/terraform.tfvars", | ||
"tf:destroy": "cd terraform && terraform destroy -auto-approve -var-file ./config/terraform.tfvars" | ||
}, | ||
"author": "Indent Inc <[email protected]>", | ||
"license": "Apache-2.0", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/indentapis/integrations.git" | ||
}, | ||
"devDependencies": { | ||
"@types/aws-lambda": "^8.10.39", | ||
"@types/node": "^13.9.8", | ||
"@types/node-fetch": "^2.5.5", | ||
"typescript": "^3.8.3" | ||
}, | ||
"dependencies": { | ||
"@indent/runtime-aws-lambda": "canary", | ||
"@indent/webhook": "latest", | ||
"@indent/types": "latest", | ||
"ts-node": "^8.5.4" | ||
} | ||
} |
1 change: 1 addition & 0 deletions
1
examples/aws-lambda-salesforce-webhook/terraform-aws-salesforce-webhook/readme.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Terraform AWS + Salesforce Webhook |
17 changes: 17 additions & 0 deletions
17
...es/aws-lambda-salesforce-webhook/terraform-aws-salesforce-webhook/scripts/build-layers.sh
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
#!/usr/bin/env bash | ||
set -x | ||
set -e | ||
|
||
ROOT_DIR="$(pwd)" | ||
|
||
OUTPUT_DIR="$(pwd)/dist" | ||
|
||
LAYER_DIR=$OUTPUT_DIR/layers/nodejs | ||
|
||
mkdir -p $LAYER_DIR | ||
|
||
cp -LR node_modules $LAYER_DIR | ||
|
||
cd $OUTPUT_DIR/layers | ||
|
||
zip -q -r layers.zip nodejs |
6 changes: 6 additions & 0 deletions
6
examples/aws-lambda-salesforce-webhook/terraform-aws-salesforce-webhook/src/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { getLambdaHandler } from '@indent/runtime-aws' | ||
import { SalesforceIntegration } from './integration' | ||
|
||
export const handle = getLambdaHandler({ | ||
integrations: [new SalesforceIntegration()], | ||
}) |
148 changes: 148 additions & 0 deletions
148
...s/aws-lambda-salesforce-webhook/terraform-aws-salesforce-webhook/src/integration/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
import { | ||
ApplyUpdateRequest, | ||
BaseHttpIntegration, | ||
BaseHttpIntegrationOpts, | ||
FullIntegration, | ||
HealthCheckResponse, | ||
IntegrationInfoResponse, | ||
PullUpdateRequest, | ||
StatusCode, | ||
WriteRequest, | ||
} from '@indent/base-integration' | ||
import { | ||
ApplyUpdateResponse, | ||
PullUpdateResponse, | ||
Resource, | ||
} from '@indent/types' | ||
import jsforce from 'jsforce' | ||
import { | ||
SalesforceMembersResponse, | ||
SalesforceUserRolesResponse, | ||
} from './salesforce-types' | ||
|
||
const pkg = require('../package.json') | ||
const SALESFORCE_INSTANCE_URL = process.env.SALESFORCE_INSTANCE_URL | ||
const SALESFORCE_ACCESS_TOKEN = process.env.SALESFORCE_ACCESS_TOKEN | ||
|
||
export class SalesforceIntegration | ||
extends BaseHttpIntegration | ||
implements FullIntegration | ||
{ | ||
conn | ||
constructor(opts?: BaseHttpIntegrationOpts) { | ||
super(opts) | ||
if (opts) { | ||
this._name = opts.name | ||
} | ||
} | ||
|
||
HealthCheck(): HealthCheckResponse { | ||
return { status: { code: 0 } } | ||
} | ||
|
||
GetInfo(): IntegrationInfoResponse { | ||
return { | ||
name: ['indent-salesforce-webhook', this._name].filter(Boolean).join('#'), | ||
capabilities: ['ApplyUpdate', 'PullUpdate'], | ||
version: pkg.version, | ||
} | ||
} | ||
|
||
MatchApply(req: WriteRequest): boolean { | ||
return ( | ||
req.events.filter((e) => | ||
Boolean( | ||
e.resources?.filter((r) => | ||
r.kind?.toLowerCase().includes('salesforce.v1.userrole') | ||
).length | ||
) | ||
).length > 0 | ||
) | ||
} | ||
|
||
async ConnectSalesforce(): Promise<void> { | ||
this.conn = new jsforce.Connection({ | ||
instanceUrl: SALESFORCE_INSTANCE_URL, | ||
accessToken: SALESFORCE_ACCESS_TOKEN, | ||
}) | ||
} | ||
|
||
MatchPull(req) { | ||
return req.kinds | ||
.map((k) => k.toLowerCase()) | ||
.includes('salesforce.v1.userrole') | ||
} | ||
|
||
async PullUpdate(_req: PullUpdateRequest): Promise<PullUpdateResponse> { | ||
if (!this.conn) { | ||
this.ConnectSalesforce() | ||
} | ||
const userRole: SalesforceUserRolesResponse = await this.conn.query( | ||
'SELECT Id, Name FROM UserRole' | ||
) | ||
console.log(`debug userRole: ${JSON.stringify(userRole, null, 1)}`) | ||
|
||
const kind = 'salesforce.v1.UserRole' | ||
const timestamp = new Date().toISOString() | ||
const resources: Resource[] = userRole.records.map((r) => ({ | ||
id: r.Id, | ||
displayName: r.Name, | ||
kind, | ||
labels: { | ||
description: r.Name, | ||
timestamp, | ||
}, | ||
})) as Resource[] | ||
console.log(`debug resources: ${JSON.stringify(resources, null, 1)}`) | ||
|
||
return { | ||
resources, | ||
} | ||
} | ||
|
||
async ApplyUpdate(req: ApplyUpdateRequest): Promise<ApplyUpdateResponse> { | ||
if (!this.conn) { | ||
this.ConnectSalesforce() | ||
} | ||
const auditEvent = req.events.find((e) => /grant|revoke/.test(e.event)) | ||
const { event, resources } = auditEvent | ||
const grantee = getResourceByKind(resources, 'user') | ||
const granted = getResourceByKind(resources, 'salesforce.v1.userrole') | ||
let res = { status: { code: StatusCode.UNKNOWN, message: '' } } | ||
|
||
try { | ||
if (event === 'access/grant') { | ||
const result: SalesforceMembersResponse = await this.conn.query( | ||
`SELECT Id, Name, UserRole.Id, UserRole.Name FROM User where Id = '${grantee.id}'` | ||
) | ||
const role = result.records.map((u) => u.UserRole?.Id) | ||
if (!role || role[0] !== granted.id) { | ||
await this.conn.sobject('User').update({ | ||
Id: grantee.id, | ||
UserRoleId: granted.id, | ||
}) | ||
} | ||
|
||
res.status.code = StatusCode.OK | ||
} else if (event === 'access/revoke') { | ||
await this.conn.sobject('User').update({ | ||
Id: grantee.id, | ||
UserRoleId: null, | ||
}) | ||
res.status.code = StatusCode.OK | ||
} | ||
} catch (err) { | ||
res.status.code = StatusCode.INTERNAL | ||
res.status.message = err.message | ||
console.error('failed to update role and license') | ||
console.error(res.status.message) | ||
} | ||
return res | ||
} | ||
} | ||
|
||
function getResourceByKind(resources, kind) { | ||
return resources.find( | ||
(r) => r.kind && r.kind.toLowerCase().includes(kind.toLowerCase()) | ||
) | ||
} |
30 changes: 30 additions & 0 deletions
30
...a-salesforce-webhook/terraform-aws-salesforce-webhook/src/integration/salesforce-types.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
export type SalesforceMembersResponse = { | ||
totalSize: boolean | ||
done: boolean | ||
records: SalesforceMember[] | ||
} | ||
|
||
export type SalesforceUserRolesResponse = { | ||
totalSize: boolean | ||
done: boolean | ||
records: SalesforceRole[] | ||
} | ||
|
||
export type SalesforceMember = { | ||
attributes: { | ||
type: string | ||
url: string | ||
} | ||
Id: string | ||
Name: string | ||
UserRole: SalesforceRole | null | ||
} | ||
|
||
export type SalesforceRole = { | ||
attributes: { | ||
type: string | ||
url: string | ||
} | ||
Name: string | ||
Id: string | ||
} |
Oops, something went wrong.