Skip to content

Commit

Permalink
Add mtls and opentelemetry samples (#5)
Browse files Browse the repository at this point in the history
* add mtls and opentelemetry samples

* Standardize workflow script & file name

* Add prettier to match sdk-node

* Standardize package.json
  • Loading branch information
lorensr authored Sep 12, 2021
1 parent fdafaf7 commit f298c14
Show file tree
Hide file tree
Showing 17 changed files with 392 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"printWidth": 120,
"singleQuote": true
}
30 changes: 30 additions & 0 deletions hello-world-mtls/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "hello-world-mtls",
"version": "0.1.0",
"private": true,
"scripts": {
"build": "tsc --build",
"build.watch": "tsc --build --watch",
"start": "ts-node src/worker.ts",
"start.watch": "nodemon src/worker.ts",
"workflow": "ts-node src/execute-workflow.ts"
},
"dependencies": {
"temporalio": "^0.4.1"
},
"devDependencies": {
"@tsconfig/node16": "^1.0.0",
"nodemon": "^2.0.12",
"ts-node": "^10.2.1",
"typescript": "^4.4.2"
},
"nodemonConfig": {
"watch": [
"src"
],
"ext": "ts",
"execMap": {
"ts": "ts-node"
}
}
}
3 changes: 3 additions & 0 deletions hello-world-mtls/src/activities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export async function greet(name: string): Promise<string> {
return `Hello, ${name}!`;
}
48 changes: 48 additions & 0 deletions hello-world-mtls/src/execute-workflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import fs from 'fs';
import { Connection, WorkflowClient } from '@temporalio/client';
import { Example } from './interfaces/workflows';
import { getEnv, Env } from './mtls-env';

/**
* Schedule a Workflow connecting with mTLS, configuration is provided via environment variables.
* Note that serverNameOverride and serverRootCACertificate are optional.
*/
async function run({
address,
namespace,
clientCertPath,
clientKeyPath,
serverNameOverride,
serverRootCACertificatePath,
taskQueue,
}: Env) {
let serverRootCACertificate: Buffer | undefined = undefined;
if (serverRootCACertificatePath) {
serverRootCACertificate = fs.readFileSync(serverRootCACertificatePath);
}
const connection = new Connection({
address,
tls: {
serverNameOverride,
serverRootCACertificate,
clientCertPair: {
crt: fs.readFileSync(clientCertPath),
key: fs.readFileSync(clientKeyPath),
},
},
});
await connection.untilReady();
const client = new WorkflowClient(connection.service, { namespace });
// Create a typed client using the Example Workflow interface,
const workflow = client.stub<Example>('example', { taskQueue });
const result = await workflow.execute('Temporal');
console.log(result); // Hello, Temporal!
}

run(getEnv()).then(
() => process.exit(0),
(err) => {
console.error(err);
process.exit(1);
}
);
7 changes: 7 additions & 0 deletions hello-world-mtls/src/interfaces/workflows.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Workflow } from '@temporalio/workflow';

// Extend the generic Workflow interface to check that Example is a valid workflow interface
// Workflow interfaces are useful for generating type safe workflow clients
export interface Example extends Workflow {
main(name: string): Promise<string>;
}
31 changes: 31 additions & 0 deletions hello-world-mtls/src/mtls-env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Helpers for configuring the mTLS client and worker samples

function requiredEnv(name: string): string {
const value = process.env[name];
if (!value) {
throw new ReferenceError(`${name} environment variable is not defined`);
}
return value;
}

export interface Env {
address: string;
namespace: string;
clientCertPath: string;
clientKeyPath: string;
serverNameOverride?: string;
serverRootCACertificatePath?: string;
taskQueue: string;
}

export function getEnv(): Env {
return {
address: requiredEnv('TEMPORAL_ADDRESS'),
namespace: requiredEnv('TEMPORAL_NAMESPACE'),
clientCertPath: requiredEnv('TEMPORAL_CLIENT_CERT_PATH'),
clientKeyPath: requiredEnv('TEMPORAL_CLIENT_KEY_PATH'),
serverNameOverride: process.env.TEMPORAL_SERVER_NAME_OVERRIDE,
serverRootCACertificatePath: process.env.TEMPORAL_SERVER_ROOT_CA_CERT_PATH,
taskQueue: process.env.TEMPORAL_TASK_QUEUE || 'tutorial',
};
}
51 changes: 51 additions & 0 deletions hello-world-mtls/src/worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import fs from 'fs';
import { Worker, Core } from '@temporalio/worker';
import { getEnv, Env } from './mtls-env';

/**
* Run a Worker with an mTLS connection, configuration is provided via environment variables.
* Note that serverNameOverride and serverRootCACertificate are optional.
*/
async function run({
address,
namespace,
clientCertPath,
clientKeyPath,
serverNameOverride,
serverRootCACertificatePath,
taskQueue,
}: Env) {
let serverRootCACertificate: Buffer | undefined = undefined;
if (serverRootCACertificatePath) {
serverRootCACertificate = fs.readFileSync(serverRootCACertificatePath);
}

await Core.install({
serverOptions: {
address,
namespace,
tls: {
serverNameOverride,
serverRootCACertificate,
// See docs for other TLS options
clientCertPair: {
crt: fs.readFileSync(clientCertPath),
key: fs.readFileSync(clientKeyPath),
},
},
},
});

const worker = await Worker.create({
workDir: __dirname,
taskQueue,
});
console.log('Worker connection succesfully established');
// Start accepting tasks on the `tutorial` queue
await worker.run();
}

run(getEnv()).catch((err) => {
console.error(err);
process.exit(1);
});
14 changes: 14 additions & 0 deletions hello-world-mtls/src/workflows/example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Context } from '@temporalio/workflow';
import * as activities from '../activities';

const { greet } = Context.configureActivities<typeof activities>({
type: 'remote',
startToCloseTimeout: '30 minutes',
});

// A workflow that simply calls an activity
async function main(name: string): Promise<string> {
return greet(name);
}

export const workflow = { main };
20 changes: 20 additions & 0 deletions hello-world-mtls/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"extends": "@tsconfig/node16/tsconfig.json",
"version": "4.4.2",
"compilerOptions": {
"emitDecoratorMetadata": false,
"experimentalDecorators": false,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"composite": true,
"rootDir": "./src",
"outDir": "./lib"
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules"
]
}
31 changes: 31 additions & 0 deletions interceptors-opentelemetry/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "interceptors-opentelemetry-sample",
"version": "0.1.0",
"private": true,
"scripts": {
"build": "tsc --build",
"build.watch": "tsc --build --watch",
"start": "ts-node src/worker.ts",
"start.watch": "nodemon src/worker.ts",
"workflow": "ts-node src/execute-workflow.ts"
},
"dependencies": {
"@temporalio/interceptors-opentelemetry": "^0.1.2",
"temporalio": "^0.4.1"
},
"devDependencies": {
"@tsconfig/node14": "^1.0.0",
"nodemon": "^2.0.12",
"ts-node": "^10.2.1",
"typescript": "^4.2.2"
},
"nodemonConfig": {
"watch": [
"src"
],
"ext": "ts",
"execMap": {
"ts": "ts-node"
}
}
}
3 changes: 3 additions & 0 deletions interceptors-opentelemetry/src/activities/greeter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export async function greet(name: string): Promise<string> {
return `Hello, ${name}!`;
}
30 changes: 30 additions & 0 deletions interceptors-opentelemetry/src/execute-workflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Connection, WorkflowClient } from '@temporalio/client';
import { OpenTelemetryWorkflowClientCallsInterceptor } from '@temporalio/interceptors-opentelemetry/lib/client';
import { Example } from './interfaces/workflows';
import { setupOpentelemetry } from './worker/setup';

async function run() {
const otel = await setupOpentelemetry();
// Connect to localhost with default ConnectionOptions,
// pass options to the Connection constructor to configure TLS and other settings.
const connection = new Connection({});
// Attach the OpenTelemetryWorkflowClientCallsInterceptor to the client.
const client = new WorkflowClient(connection.service, {
interceptors: {
calls: [() => new OpenTelemetryWorkflowClientCallsInterceptor()],
},
});
// Create a typed client using the Example Workflow interface,
// Workflow will be started in the "default" namespace unless specified otherwise.
const example = client.stub<Example>('example', {
taskQueue: 'interceptors-opentelemetry-example',
});
const result = await example.execute('Temporal');
console.log(result); // Hello, Temporal!
await otel.sdk.shutdown();
}

run().catch((err) => {
console.error(err);
process.exit(1);
});
7 changes: 7 additions & 0 deletions interceptors-opentelemetry/src/interfaces/workflows.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Workflow } from '@temporalio/workflow';

// Extend the generic Workflow interface to check that Example is a valid workflow interface
// Workflow interfaces are useful for generating type safe workflow clients
export interface Example extends Workflow {
main(name: string): Promise<string>;
}
44 changes: 44 additions & 0 deletions interceptors-opentelemetry/src/worker/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { DefaultLogger, Worker } from '@temporalio/worker';
import { OpenTelemetryDependencies } from '@temporalio/interceptors-opentelemetry/lib/workflow';
import {
OpenTelemetryActivityInboundInterceptor,
makeWorkflowExporter,
} from '@temporalio/interceptors-opentelemetry/lib/worker';
import { setupOpentelemetry } from './setup';

async function main() {
const otel = await setupOpentelemetry();

// Automatically locate and register Activities and Workflows relative to __dirname
// (assuming package was bootstrapped with `npm init @temporalio`).
// Worker connects to localhost by default and uses console error for logging.
// Customize the Worker by passing more options to create().
// create() tries to connect to the server and will throw if a connection could not be established.
// You may create multiple Workers in a single process in order to poll on multiple task queues.
// In order to configure the server connection parameters and other global options,
// use the Core.install() method to configure the Rust Core SDK singleton.
const worker = await Worker.create<{ dependencies: OpenTelemetryDependencies }>({
workDir: __dirname,
// Silence the Worker logs to better see the span output
logger: new DefaultLogger('WARNING'),
taskQueue: 'interceptors-opentelemetry-example',
dependencies: {
exporter: makeWorkflowExporter(otel.exporter),
},
// Registers opentelemetry interceptors for Workflow and Activity calls
interceptors: {
workflowModules: ['example'], // example contains both workflow and interceptors
activityInbound: [(_ctx) => new OpenTelemetryActivityInboundInterceptor()],
},
});
await worker.run();
await otel.sdk.shutdown();
}

main().then(
() => void process.exit(0),
(err) => {
console.error(err);
process.exit(1);
}
);
26 changes: 26 additions & 0 deletions interceptors-opentelemetry/src/worker/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ExportResult } from '@opentelemetry/core';
import { ConsoleSpanExporter, ReadableSpan } from '@opentelemetry/tracing';
import { NodeSDK } from '@opentelemetry/sdk-node';
import { SpanName } from '@temporalio/interceptors-opentelemetry/lib/workflow';

// Only print the spans generated by interceptors
export const instrumentedSpans = new Set<string>(Object.values(SpanName));

export class SpanExporter extends ConsoleSpanExporter {
public export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void {
const filtered = spans.filter(({ name }) => instrumentedSpans.has(name));
super.export(filtered, resultCallback);
}
}

export interface OpentelemtryEnv {
sdk: NodeSDK;
exporter: SpanExporter;
}

export async function setupOpentelemetry(): Promise<OpentelemtryEnv> {
const exporter = new SpanExporter();
const sdk = new NodeSDK({ traceExporter: exporter });
await sdk.start();
return { sdk, exporter };
}
23 changes: 23 additions & 0 deletions interceptors-opentelemetry/src/workflows/example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { WorkflowInterceptors } from '@temporalio/workflow';
import {
OpenTelemetryInboundInterceptor,
OpenTelemetryOutboundInterceptor,
registerOpentelemetryTracerProvider,
} from '@temporalio/interceptors-opentelemetry/lib/workflow';
import { Example } from '../interfaces/workflows';
import { greet } from '@activities/greeter';

// A workflow that simply calls an activity
async function main(name: string): Promise<string> {
return greet(name);
}

// Declare the workflow's type to be checked by the Typescript compiler
export const workflow: Example = { main };

export const interceptors: WorkflowInterceptors = {
inbound: [new OpenTelemetryInboundInterceptor()],
outbound: [new OpenTelemetryOutboundInterceptor()],
};

registerOpentelemetryTracerProvider();
Loading

0 comments on commit f298c14

Please sign in to comment.