From 2618db37ae9e0f301911bd5c1520dbd8016af83a Mon Sep 17 00:00:00 2001 From: agar Date: Thu, 11 Jun 2026 17:54:28 +0200 Subject: [PATCH] feat(cd): add terraform part --- CLAUDE.md | 80 +++++++++++ README.md | 134 +++++++++++------- .gitignore => function/.gitignore | 0 .gitleaks.toml => function/.gitleaks.toml | 0 function/README.md | 76 ++++++++++ cucumber.js => function/cucumber.js | 0 eslint.config.js => function/eslint.config.js | 0 .../package-lock.json | 0 package.json => function/package.json | 2 +- .../specs}/features/example.feature | 0 .../specs}/step_definitions/example.steps.ts | 0 .../src}/business/business-service.ts | 0 {src => function/src}/handler.ts | 0 tsconfig.json => function/tsconfig.json | 0 terraform/.gitignore | 2 + terraform/README.md | 15 ++ terraform/data_integration.tf | 3 + terraform/main.tf | 8 ++ terraform/resource_action.tf | 46 ++++++ 19 files changed, 315 insertions(+), 51 deletions(-) create mode 100644 CLAUDE.md rename .gitignore => function/.gitignore (100%) rename .gitleaks.toml => function/.gitleaks.toml (100%) create mode 100644 function/README.md rename cucumber.js => function/cucumber.js (100%) rename eslint.config.js => function/eslint.config.js (100%) rename package-lock.json => function/package-lock.json (100%) rename package.json => function/package.json (89%) rename {specs => function/specs}/features/example.feature (100%) rename {specs => function/specs}/step_definitions/example.steps.ts (100%) rename {src => function/src}/business/business-service.ts (100%) rename {src => function/src}/handler.ts (100%) rename tsconfig.json => function/tsconfig.json (100%) create mode 100644 terraform/.gitignore create mode 100644 terraform/README.md create mode 100644 terraform/data_integration.tf create mode 100644 terraform/main.tf create mode 100644 terraform/resource_action.tf diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..7d9dbc0 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,80 @@ +# CLAUDE.md — Repository Guide + +## Purpose + +This repository is a **GitHub template** for creating and deploying Genesys Functions (AWS Lambda-backed Data Actions on the Genesys Cloud platform). When using this template, always respect the conventions described below. + +## Repository Structure + +``` +.github/workflows/ # CI/CD pipelines +function/ # TypeScript source code for the Genesys Function +terraform/ # Terraform configuration to deploy the function to Genesys Cloud +``` + +## Architecture — `function/` + +The function code follows **hexagonal architecture** (ports & adapters): + +- `src/handler.ts` — the entry point (adapter). Receives the Genesys `Event` and `Context`, delegates to the domain layer, returns a structured HTTP-like response. +- `src/business/` — the domain layer (port). All business logic lives here, isolated from the infrastructure. `BusinessService` is the canonical starting point. + +**Rules:** +- Never put business logic in `handler.ts`. The handler only orchestrates: parse input → call service → format output. +- Never import Genesys-specific types or AWS SDK in `src/business/`. The domain must remain infrastructure-agnostic. +- New capabilities go in `src/business/` as dedicated services or adapters. + +## Secrets Management + +Genesys Functions receive credentials through the Lambda `context.clientContext` object, injected by the Genesys platform at runtime: + +```typescript +context.clientContext.gc_client_id +context.clientContext.gc_client_secret +context.clientContext.gc_aws_region +``` + +**Never hardcode credentials anywhere in the codebase.** The CI pipeline runs Gitleaks with custom rules that will block any commit containing `gc_client_id` or `gc_client_secret` values. Any secret needed at runtime must come through `clientContext`. + +For CI/CD secrets (e.g. Genesys provider credentials for Terraform), use **GitHub Actions Secrets** only — never commit `.tfvars` files (already gitignored). + +## CI/CD — `.github/workflows/ci.yml` + +The pipeline runs on every pull request targeting `main` and enforces this order: + +1. **Gitleaks** — secret scanning (blocks if any secret is detected) +2. **Install** — `npm ci` (clean install from lockfile) +3. **Test** — `npm test` (Cucumber BDD scenarios) +4. **Lint** — `npm run lint` (ESLint with TypeScript rules) +5. **Build** — `npm run build` (TypeScript compilation) + +All steps must pass before a PR can be merged. Do not skip or disable any step. + +## Testing + +Tests use **Cucumber.js** (BDD). Every business behaviour must have a corresponding `.feature` file under `specs/features/` and step definitions under `specs/step_definitions/`. + +- Test the domain layer (`BusinessService`) directly — do not test the handler. +- Do not mock the Genesys SDK inside unit tests; isolate via interfaces if needed. + +## Terraform — `terraform/` + +The Terraform configuration uses the `mypurecloud/genesyscloud` provider to deploy: + +- `data_integration.tf` — fetches the existing `Function Data Actions` integration by name. +- `resource_action.tf` — creates the `genesyscloud_integration_action` resource, wiring the deployed ZIP to the integration. +- `main.tf` — provider version pinning. + +Key parameters to update when creating a new function: +- Action `name` and `category` in `resource_action.tf` +- `contract_input` / `contract_output` schemas +- `function_config.handler` (must match the compiled output path) + +`.tfvars` files are gitignored — pass variables via environment variables or GitHub Actions Secrets in the CD pipeline. + +## General Best Practices + +- Keep Node.js version consistent across `package.json` `engines.node`, `ci.yml` matrix, and `resource_action.tf` `runtime`. +- Run `npm run zipnodev` to produce `function.zip` (production deps only + compiled `dist/`) before Terraform apply. +- All changes go through a PR — direct pushes to `main` are not allowed. +- TypeScript strict mode must remain enabled; do not weaken `tsconfig.json`. diff --git a/README.md b/README.md index 25d4034..a40049e 100644 --- a/README.md +++ b/README.md @@ -1,76 +1,110 @@ -# Lambda Function Template +# Genesys Function Template -A TypeScript template for AWS Lambda functions with business logic separation and Cucumber testing. +A GitHub template for building and deploying [Genesys Functions](https://help.mypurecloud.com/articles/about-genesys-functions/) — AWS Lambda-backed actions that integrate with Genesys Cloud Data Actions. -## Setup Instructions +## Overview + +This template provides a ready-to-use project structure with: + +- A **TypeScript function** following hexagonal architecture +- A **Terraform configuration** to deploy the function to Genesys Cloud +- A **CI/CD pipeline** that enforces quality gates on every pull request + +## Repository Structure + +``` +.github/workflows/ CI pipeline (secret scan, test, lint, build) +function/ TypeScript source code for the Genesys Function +terraform/ Terraform files to deploy the function to Genesys Cloud +``` + +## Getting Started ### Prerequisites -- Node.js 22 or higher -- npm -### Installation +- Node.js 22 +- Terraform +- A Genesys Cloud organisation with a `Function Data Actions` integration enabled + +### 1. Use this template -1. **Install dependencies:** - ```bash - npm install - ``` +Click **Use this template** on GitHub to create a new repository from this template. -### Project Structure +### 2. Develop the function +```bash +cd function +npm ci ``` -├── src/ -│ └── handler.ts # Main Lambda handler -├── business/ -│ └── example-service.ts # Business logic and services -├── specs/ -│ └── example.feature # Gherkin test specifications -├── dist/ # Compiled JavaScript output -├── package.json -├── tsconfig.json -└── README.md + +Edit `src/business/business-service.ts` to implement your business logic. The handler in `src/handler.ts` is the entry point — keep it thin and delegate everything to the business layer. + +### 3. Run tests and lint + +```bash +npm test # Cucumber BDD tests +npm run lint # ESLint +npm run build # TypeScript compilation ``` -### Development +### 4. Package the function -- **Build TypeScript:** `npm run build` -- **Run tests:** `npm run test` -- **Lint code:** `npm run lint` -- **Clean build:** `npm run clean` +```bash +npm run zipnodev +``` -### Local Testing +This produces `function.zip` containing the compiled `dist/` and production dependencies only. -Test the handler directly from command line: +### 5. Deploy with Terraform ```bash -# Run with default test data -npx ts-node src/handler.ts +cd ../terraform +terraform init +terraform apply +``` + +Provide your Genesys Cloud credentials as environment variables or via a `.tfvars` file (gitignored): -# Run with custom JSON data -npx ts-node src/handler.ts '{"name": "test", "value": 123}' +```hcl +# terraform.tfvars <-- never commit this file +genesyscloud_oauthclient_id = "..." +genesyscloud_oauthclient_secret = "..." +genesyscloud_region = "..." ``` -### Usage +## Function Architecture + +The function follows **hexagonal architecture**: + +``` +handler.ts ← entry point, adapts Genesys event/context to domain calls +business/ + business-service.ts ← domain logic, no infrastructure dependencies +``` + +The Genesys platform injects credentials at runtime via `context.clientContext` — no secrets are stored in the code or configuration files. + +## CI Pipeline -1. Implement your business logic in the `business/` folder -2. Update the handler in `src/handler.ts` to use your business services -3. Write Gherkin specifications in the `specs/` folder -4. Build and test before deployment +The pipeline runs on every pull request to `main` and enforces: -### CI/CD +| Step | Command | +|------|---------| +| Secret scan | Gitleaks (blocks on any detected secret) | +| Install | `npm ci` | +| Test | `npm test` | +| Lint | `npm run lint` | +| Build | `npm run build` | -This template includes a GitHub Actions CI workflow that automatically runs on pull requests to the main branch. The CI pipeline: -- Runs `npm test` to execute Cucumber BDD tests -- Runs `npm run lint` for code quality checks -- Runs `npm run build` to verify TypeScript compilation +All steps must pass before a PR can be merged. -### Dependencies +## Customising for Your Function -- **purecloud-platform-client-v2:** PureCloud SDK -- **@cucumber/cucumber:** BDD testing framework -- **TypeScript:** Type-safe JavaScript development +1. Update the action `name`, `category`, and contract schemas in `terraform/resource_action.tf`. +2. Implement your logic in `src/business/business-service.ts`. +3. Add BDD scenarios in `specs/features/` with matching step definitions in `specs/step_definitions/`. +4. Store any CI/CD secrets (Genesys provider credentials) as **GitHub Actions Secrets** — never commit them. -### Next Steps +## License -- [ ] Write comprehensive Gherkin tests in specs/ -- [ ] Create your business services in the business/ folder -- [ ] Update handler.ts with your specific business logic \ No newline at end of file +ISC diff --git a/.gitignore b/function/.gitignore similarity index 100% rename from .gitignore rename to function/.gitignore diff --git a/.gitleaks.toml b/function/.gitleaks.toml similarity index 100% rename from .gitleaks.toml rename to function/.gitleaks.toml diff --git a/function/README.md b/function/README.md new file mode 100644 index 0000000..25d4034 --- /dev/null +++ b/function/README.md @@ -0,0 +1,76 @@ +# Lambda Function Template + +A TypeScript template for AWS Lambda functions with business logic separation and Cucumber testing. + +## Setup Instructions + +### Prerequisites +- Node.js 22 or higher +- npm + +### Installation + +1. **Install dependencies:** + ```bash + npm install + ``` + +### Project Structure + +``` +├── src/ +│ └── handler.ts # Main Lambda handler +├── business/ +│ └── example-service.ts # Business logic and services +├── specs/ +│ └── example.feature # Gherkin test specifications +├── dist/ # Compiled JavaScript output +├── package.json +├── tsconfig.json +└── README.md +``` + +### Development + +- **Build TypeScript:** `npm run build` +- **Run tests:** `npm run test` +- **Lint code:** `npm run lint` +- **Clean build:** `npm run clean` + +### Local Testing + +Test the handler directly from command line: + +```bash +# Run with default test data +npx ts-node src/handler.ts + +# Run with custom JSON data +npx ts-node src/handler.ts '{"name": "test", "value": 123}' +``` + +### Usage + +1. Implement your business logic in the `business/` folder +2. Update the handler in `src/handler.ts` to use your business services +3. Write Gherkin specifications in the `specs/` folder +4. Build and test before deployment + +### CI/CD + +This template includes a GitHub Actions CI workflow that automatically runs on pull requests to the main branch. The CI pipeline: +- Runs `npm test` to execute Cucumber BDD tests +- Runs `npm run lint` for code quality checks +- Runs `npm run build` to verify TypeScript compilation + +### Dependencies + +- **purecloud-platform-client-v2:** PureCloud SDK +- **@cucumber/cucumber:** BDD testing framework +- **TypeScript:** Type-safe JavaScript development + +### Next Steps + +- [ ] Write comprehensive Gherkin tests in specs/ +- [ ] Create your business services in the business/ folder +- [ ] Update handler.ts with your specific business logic \ No newline at end of file diff --git a/cucumber.js b/function/cucumber.js similarity index 100% rename from cucumber.js rename to function/cucumber.js diff --git a/eslint.config.js b/function/eslint.config.js similarity index 100% rename from eslint.config.js rename to function/eslint.config.js diff --git a/package-lock.json b/function/package-lock.json similarity index 100% rename from package-lock.json rename to function/package-lock.json diff --git a/package.json b/function/package.json similarity index 89% rename from package.json rename to function/package.json index 4f0af36..33747ba 100644 --- a/package.json +++ b/function/package.json @@ -11,7 +11,7 @@ "test": "cucumber-js", "lint": "eslint src/**/*.ts", "clean": "rm -rf dist", - "zipnodev": "npm install --omit=dev && zip -r function_exporter.zip dist/ node_modules/" + "zipnodev": "npm install --omit=dev && zip -r function.zip dist/ node_modules/" }, "dependencies": { "purecloud-platform-client-v2": "*", diff --git a/specs/features/example.feature b/function/specs/features/example.feature similarity index 100% rename from specs/features/example.feature rename to function/specs/features/example.feature diff --git a/specs/step_definitions/example.steps.ts b/function/specs/step_definitions/example.steps.ts similarity index 100% rename from specs/step_definitions/example.steps.ts rename to function/specs/step_definitions/example.steps.ts diff --git a/src/business/business-service.ts b/function/src/business/business-service.ts similarity index 100% rename from src/business/business-service.ts rename to function/src/business/business-service.ts diff --git a/src/handler.ts b/function/src/handler.ts similarity index 100% rename from src/handler.ts rename to function/src/handler.ts diff --git a/tsconfig.json b/function/tsconfig.json similarity index 100% rename from tsconfig.json rename to function/tsconfig.json diff --git a/terraform/.gitignore b/terraform/.gitignore new file mode 100644 index 0000000..ba60094 --- /dev/null +++ b/terraform/.gitignore @@ -0,0 +1,2 @@ +*.tfvars +.terraform/* \ No newline at end of file diff --git a/terraform/README.md b/terraform/README.md new file mode 100644 index 0000000..56fa7a5 --- /dev/null +++ b/terraform/README.md @@ -0,0 +1,15 @@ +# Genesys Cloud prerequesites +To be executed, this terraform project need an oath with the following persmissions : +- integrations:actionFunction:edit +- integrations:actionFunction:view +- integrations:action:edit +- integrations:action:view +- integrations:action:add +- integrations:action:delete +- integrations:integration:view +- bridge:actions:view + +Terraform credentials need to be set as env variable with GENESYSCLOUD_REGION, GENESYSCLOUD_OAUTHCLIENT_ID and GENESYSCLOUD_OAUTHCLIENT_SECRET. + +# Terraform state management +State is managed in HCP Terraform through Terraform CLI. diff --git a/terraform/data_integration.tf b/terraform/data_integration.tf new file mode 100644 index 0000000..09e7e31 --- /dev/null +++ b/terraform/data_integration.tf @@ -0,0 +1,3 @@ +data "genesyscloud_integration" "integration" { + name = "Function Data Actions" +} diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..b68806f --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + genesyscloud = { + source = "mypurecloud/genesyscloud" + version = "~> 1.82.0" + } + } +} diff --git a/terraform/resource_action.tf b/terraform/resource_action.tf new file mode 100644 index 0000000..e2daff8 --- /dev/null +++ b/terraform/resource_action.tf @@ -0,0 +1,46 @@ + resource "genesyscloud_integration_action" "function_action" { + name = "Function Action" + category = "Function Data Actions" + integration_id = data.genesyscloud_integration.integration.id + config_timeout_seconds = 20 + + contract_input = jsonencode({ + type = "object" + additionalProperties = true + properties = { + dossier = { type = "string" }, + secret = { type = "string" } + } + }) + + contract_output = jsonencode({ + type = "object" + additionalProperties = true + properties = { + result = { type = "string" } + } + }) + + config_request { + request_type = "POST" + request_template = "$${input.rawRequest}" + } + + config_response { + translation_map = { + result = "$.body" + } + translation_map_defaults = { + result = "-1" + } + success_template = "{ \"result\": $${result}}" + } + + function_config { + description = "Function action" + handler = "dist/src/handler.handler" + runtime = "nodejs22.x" + timeout_seconds = 15 + file_path = "function.zip" + } +}