Skip to content

Commit

Permalink
Refactor getters and pass resource into call and add codecov (#11)
Browse files Browse the repository at this point in the history
* Refactor getter and pass resource into call

Signed-off-by: Peter Zhu <[email protected]>

* Fix existing tests

Signed-off-by: Peter Zhu <[email protected]>

* More tweaks

Signed-off-by: Peter Zhu <[email protected]>

* Add print-to-console test

Signed-off-by: Peter Zhu <[email protected]>

* Add tests for verify-resource and create-issue-comment call

Signed-off-by: Peter Zhu <[email protected]>

* Add octokit test on octokitAuth utility

Signed-off-by: Peter Zhu <[email protected]>

* Update Readme

Signed-off-by: Peter Zhu <[email protected]>

* Update codecov

Signed-off-by: Peter Zhu <[email protected]>

* Add codecov step

Signed-off-by: Peter Zhu <[email protected]>

* Add codecov step

Signed-off-by: Peter Zhu <[email protected]>

* Rename verifyOrgRepo to verifyResourceConfig for later extension

Signed-off-by: Peter Zhu <[email protected]>

---------

Signed-off-by: Peter Zhu <[email protected]>
  • Loading branch information
peterzhuamazon authored Sep 30, 2024
1 parent f194866 commit bab72ee
Show file tree
Hide file tree
Showing 37 changed files with 600 additions and 263 deletions.
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* @getsaurabh02 @peterzhuamazon @gaiksaya @prudhvigodithi
* @getsaurabh02 @peterzhuamazon @gaiksaya @prudhvigodithi @nhtruong
6 changes: 4 additions & 2 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,7 @@ jobs:
run: echo "Please run 'npm run format' before commiting the code!"
- name: Run build
run: npm run build
- name: Run Test
run: npm test
- name: Upload results to Codecov
uses: codecov/codecov-action@v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
node_modules
npm-debug.log
*.pem
*.swp*
!mock-cert.pem
.env*
coverage
Expand Down
1 change: 1 addition & 0 deletions MAINTAINERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This document contains a list of maintainers in this repo. See [opensearch-proje
| Peter Zhu | [peterzhuamazon](https://github.com/peterzhuamazon) | Amazon |
| Prudhvi Godithi | [prudhvigodithi](https://github.com/prudhvigodithi) | Amazon |
| Sayali Gaikawad | [gaiksaya](https://github.com/gaiksaya) | Amazon |
| Theo Truong | [nhtruong](https://github.com/nhtruong) | Amazon |

## Emeritus

Expand Down
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,41 @@ To create a service, you need two configuration files:

### Start the Service

Once you have created the resource and operation configuration files, follow these steps to start the service:
Before starting the service, create a `.env` file to connect it to your GitHub App by copying the `.env.example` file from this repository to `.env`.

Once you have created the `.env` file, resource / operation configuration files, follow these steps to start the service:

1. Set the `RESOURCE_CONFIG` environment variable to the path of the resource configuration YAML file.
1. Set the `OPERATION_CONFIG` environment variable to the path of the operation configuration YAML file.
1. Run the service using the following command:
1. Update the `INSTALLATION_ID` variable in `.env` file. ([How to find installation id of your GitHub App](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app-installation#using-octokitjs-to-authenticate-with-an-installation-id))
1. Update your GitHub App settings on the GitHub website by defining the app's permissions and specifying the events to monitor.
1. Run the service using the following command.
1. (First Time Only) If you have created a new `.env` file, you will be directed to `http://localhost:3000` as seen in the console message. Open this URL in your browser and follow the instructions to set up the necessary information.

```bash
RESOURCE_CONFIG=configs/resources/sample-resource.yml OPERATION_CONFIG=configs/operations/sample-operation.yml npm start
RESOURCE_CONFIG=configs/resources/sample-resource.yml \
OPERATION_CONFIG=configs/operations/sample-operation.yml \
npm run dev
```

**Note**: You should run `npm run start` instead in production to run prettier / eslint / jest before starting the service.

When you run the above command, the following takes place:

1. The app starts a `Service` instance based on the specified configurations.
1. Retrieves the [GitHub Context](https://probot.github.io/api/latest/classes/context.Context.html) (or any other defined context) for all the resources listed in the resource config file.
1. Registers and listens for events, executes the `Tasks` defined in the operation config. These tasks will be executed sequentially when the corresponding events occur.


#### List of Environment Variables (You can use them directly in the startup command, export them, or add them to the `.env` file):
| Name | Type | Default | Description | Example |
|-----------------------------|---------|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------|
| RESOURCE_CONFIG | String | '' | Path to resource config yaml file. | 'configs/resources/sample-resource.yml' |
| OPERATION_CONFIG | String | '' | Path to operation config yaml file. | 'configs/operations/sample-operation.yml' |
| INSTALLATION_ID | String | '' | Installation Id of your GitHub App, must install the App to repositories before retrieving the id. | '1234567890' |
| ADDITIONAL_RESOURCE_CONTEXT | Boolean | false | Setting true will let each resource defined in RESOURCE_CONFIG to call GitHub Rest API and GraphQL for more detailed context (ex: node_id). Increase startup time. | true / false |
| SERVICE_NAME | String | 'default' | Set Service Name | 'My Service' |'

## Code of Conduct

This project has adopted [the Open Source Code of Conduct](CODE_OF_CONDUCT.md).
Expand Down
7 changes: 4 additions & 3 deletions configs/operations/sample-operation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ tasks:
- name: Print a message to the console
call: print-to-console@default
args:
text: Hello World!
- call: create-issue-comment@createIssueCommentTagUser
text: This is a sample output message!
- name: Print Hello World to the console
call: print-to-console@printToConsoleHelloWorld
- call: create-issue-comment@default
args:
text: This comment is created by Hello World Operation
tagUser: peterzhuamazon
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@
"automation-app"
],
"scripts": {
"build": "npm run clean && npm run format && npm run lint && npm run compile",
"build": "npm run format-dryrun && npm run clean && npm run lint && npm run compile",
"postbuild": "npm run test",
"compile": "tsc",
"dev": "npm run clean && npm run compile && probot run ./bin/app.js",
"start": "npm run build && probot run ./bin/app.js",
"clean": "rm -rf ./bin/*",
"format": "prettier --write \"src/**/*.{js,ts}\" \"test/**/*.ts\" \"configs/**/*.{yaml,yml}\"",
"format-dryrun": "prettier --check \"src/**/*.{js,ts}\" \"test/**/*.ts\" \"configs/**/*.{yaml,yml}\"",
"format": "prettier --write src/**/*.ts test/**/*.ts configs/**/*.yml",
"format-dryrun": "prettier --check src/**/*.ts test/**/*.ts configs/**/*.yml",
"lint": "eslint --fix \"src/**/*.ts\" --ignore-pattern \"**/*.d.ts\"",
"test": "jest"
"test": "jest --coverage"
},
"dependencies": {
"@aws-sdk/client-opensearch": "^3.658.1",
Expand Down
21 changes: 15 additions & 6 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,27 @@
import { Probot } from 'probot';
import { Service } from './service/service';

// TODO: Move Probot to the workflow folder and make it server
export default async (app: Probot) => {
app.log.info('OpenSearch Automation App is starting now......');

const srvObj = new Service('Hello World Service');
const resourceConfig: string = process.env.RESOURCE_CONFIG || 'configs/resources/sample-resource.yml';
const processConfig: string = process.env.OPERATION_CONFIG || 'configs/operations/sample-operation.yml';
// Env Vars
const resourceConfig: string = process.env.RESOURCE_CONFIG || '';
const processConfig: string = process.env.OPERATION_CONFIG || '';
const additionalResourceContext: boolean = Boolean(process.env.ADDITIONAL_RESOURCE_CONTEXT) || false;
const serviceName: string = process.env.SERVICE_NAME || 'default';

// Start service
const srvObj = new Service(serviceName);

if (resourceConfig === '' || processConfig === '') {
throw new Error(`Invalid config path: RESOURCE_CONFIG=${resourceConfig} or OPERATION_CONFIG=${processConfig}`);
throw new Error(`Empty config path: RESOURCE_CONFIG='${resourceConfig}' or OPERATION_CONFIG='${processConfig}'`);
}

if (additionalResourceContext) {
app.log.info('Start requesting additional resource context now, take a while......');
}
await srvObj.initService(app, resourceConfig, processConfig);

await srvObj.initService(app, resourceConfig, processConfig, additionalResourceContext);

app.log.info('All objects initialized, start listening events......');
};
15 changes: 6 additions & 9 deletions src/call/create-issue-comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,15 @@

import { Probot } from 'probot';
import dedent from 'dedent';
import { Resource } from '../service/resource/resource';
import { validateResourceConfig } from '../utility/verification/verify-resource';

export interface CreateIssueCommentParams {
text: string;
tagUser?: string;
}

export default async function createIssueComment(app: Probot, context: any, { text }: CreateIssueCommentParams): Promise<void> {
const comment = context.issue({ body: dedent`${text}` });
context.octokit.issues.createComment(comment);
}

export async function createIssueCommentTagUser(app: Probot, context: any, { text, tagUser }: CreateIssueCommentParams): Promise<void> {
const comment = context.issue({ body: dedent`@${tagUser}: ${text}` });
context.octokit.issues.createComment(comment);
export default async function createIssueComment(app: Probot, context: any, resource: Resource, { text }: CreateIssueCommentParams): Promise<void> {
if (!(await validateResourceConfig(app, context, resource))) return;
const comment = await context.issue({ body: dedent`${text}` });
await context.octokit.issues.createComment(comment);
}
6 changes: 5 additions & 1 deletion src/call/github-merged-pulls-monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@
// Description : Monitors the CI workflows of merged pull requests, providing metrics that give an overview of whether pull requests were merged without passing CI checks.

import { Probot } from 'probot';
import { Resource } from '../service/resource/resource';
import { OpensearchClient } from '../utility/opensearch/opensearch-client';
import { validateResourceConfig } from '../utility/verification/verify-resource';

export default async function githubMergedPullsMonitor(app: Probot, context: any, resource: Resource): Promise<void> {
if (!(await validateResourceConfig(app, context, resource))) return;

export default async function githubMergedPullsMonitor(app: Probot, context: any): Promise<void> {
const pr = context.payload.pull_request;

if (!pr.merged) {
Expand Down
6 changes: 5 additions & 1 deletion src/call/github-workflow-runs-monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@
// - events : The list of events to monitor and index, from https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows.

import { Probot } from 'probot';
import { Resource } from '../service/resource/resource';
import { OpensearchClient } from '../utility/opensearch/opensearch-client';
import { validateResourceConfig } from '../utility/verification/verify-resource';

interface WorkflowRunMonitorArgs {
events: string[];
}

export default async function githubWorkflowRunsMonitor(app: Probot, context: any, { events }: WorkflowRunMonitorArgs): Promise<void> {
export default async function githubWorkflowRunsMonitor(app: Probot, context: any, resource: Resource, { events }: WorkflowRunMonitorArgs): Promise<void> {
if (!(await validateResourceConfig(app, context, resource))) return;

const job = context.payload.workflow_run;

if (!events.includes(job?.event)) {
Expand Down
10 changes: 9 additions & 1 deletion src/call/print-to-console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,19 @@
// - text : (string) the text string to be printed out

import { Probot } from 'probot';
import { Resource } from '../service/resource/resource';
import { validateResourceConfig } from '../utility/verification/verify-resource';

export interface PrintToConsoleParams {
text: string;
}

export default async function printToConsole(app: Probot, context: any, { text }: PrintToConsoleParams): Promise<void> {
export default async function printToConsole(app: Probot, context: any, resource: Resource, { text }: PrintToConsoleParams): Promise<void> {
if (!(await validateResourceConfig(app, context, resource))) return;
app.log.info(text);
}

export async function printToConsoleHelloWorld(app: Probot, context: any, resource: Resource): Promise<void> {
if (!(await validateResourceConfig(app, context, resource))) return;
app.log.info('Hello World');
}
12 changes: 7 additions & 5 deletions src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ import { readFileSync, realpathSync } from 'fs';
import { ResourceData, OperationData } from './types';

export abstract class Config {
protected configType: string;
protected _configType: string;

protected configData: ResourceData | OperationData;

protected configSchema: any;
protected _configData: ResourceData | OperationData;

constructor(configType: string) {
this.configType = configType;
this._configType = configType;
}

protected get configData(): ResourceData | OperationData {
return this._configData;
}

protected static readConfig(filePath: string): any {
Expand Down
11 changes: 5 additions & 6 deletions src/config/operation-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { OperationData, TaskData } from './types';
import { Config } from './config';

export class OperationConfig extends Config {
private static configSchema = {
private static readonly _configSchema = {
type: 'object',
properties: {
name: {
Expand Down Expand Up @@ -62,15 +62,14 @@ export class OperationConfig extends Config {

constructor(configPath: string) {
super('OperationConfig');
this.configData = OperationConfig.readConfig(configPath);
this.configSchema = OperationConfig.configSchema;
OperationConfig.validateConfig(this.configData, this.configSchema);
this._configData = OperationConfig.readConfig(configPath);
OperationConfig.validateConfig(this.configData, OperationConfig._configSchema);
}

private static async _initTasks(taskDataArray: TaskData[]): Promise<Task[]> {
const taskObjArray = taskDataArray.map((taskData) => {
const taskObj = new Task(taskData.call, taskData.args, taskData.name);
console.log(`Setup Task: ${taskObj.getName()}`);
console.log(`Setup Task: ${taskObj.name}`);
return taskObj;
});
return taskObjArray;
Expand All @@ -82,7 +81,7 @@ export class OperationConfig extends Config {
(this.configData as OperationData).events,
await OperationConfig._initTasks((this.configData as OperationData).tasks),
);
console.log(`Setup Operation: ${opObj.getName()}`);
console.log(`Setup Operation: ${opObj.name}`);
return opObj;
}
}
24 changes: 13 additions & 11 deletions src/config/resource-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ import { Repository } from '../service/resource/repository';
import { Config } from './config';

export class ResourceConfig extends Config {
private octokit: ProbotOctokit;
private readonly _octokit: ProbotOctokit;

private static configSchema = {
private readonly _additionalResourceContext;

private static readonly _configSchema = {
type: 'object',
properties: {
organizations: {
Expand Down Expand Up @@ -77,12 +79,12 @@ export class ResourceConfig extends Config {
required: ['organizations'],
};

constructor(octokit: ProbotOctokit, configPath: string) {
constructor(octokit: ProbotOctokit, configPath: string, additionalResourceContext: boolean) {
super('ResourceConfig');
this.configData = ResourceConfig.readConfig(configPath);
this.configSchema = ResourceConfig.configSchema;
ResourceConfig.validateConfig(this.configData, this.configSchema);
this.octokit = octokit;
this._configData = ResourceConfig.readConfig(configPath);
this._octokit = octokit;
this._additionalResourceContext = additionalResourceContext;
ResourceConfig.validateConfig(this.configData, ResourceConfig._configSchema);
}

private async _initProjects(orgData: OrganizationData): Promise<Map<number, Project>> {
Expand All @@ -91,13 +93,13 @@ export class ResourceConfig extends Config {
await Promise.all(
orgData.projects.map(async (projData) => {
const projObj = new Project(orgData.name, projData.number);
await projObj.setContext(this.octokit);
if (this._additionalResourceContext) await projObj.setContext(this._octokit);

if (projData.fields) {
await Promise.all(
projData.fields.map(async (projFieldData) => {
const projFieldObj = new ProjectField(orgData.name, projData.number, projFieldData.name);
await projFieldObj.setContext(this.octokit, projObj.getNodeId());
if (this._additionalResourceContext) await projFieldObj.setContext(this._octokit, projObj.nodeId);
projObj.addField(projFieldObj);
}),
);
Expand All @@ -115,7 +117,7 @@ export class ResourceConfig extends Config {
await Promise.all(
orgData.repositories.map(async (repoData) => {
const repoObj = new Repository(orgData.name, repoData.name);
await repoObj.setContext(this.octokit);
if (this._additionalResourceContext) await repoObj.setContext(this._octokit);
repoObjMap.set(repoData.name, repoObj);
}),
);
Expand All @@ -131,7 +133,7 @@ export class ResourceConfig extends Config {
const repoObjMap = orgData.repositories ? await this._initRepositories(orgData) : new Map<string, Repository>();

const orgObj = new Organization(orgData.name, projObjMap, repoObjMap);
await orgObj.setContext(this.octokit);
if (this._additionalResourceContext) await orgObj.setContext(this._octokit);

orgObjMap.set(orgData.name, orgObj);
}),
Expand Down
2 changes: 1 addition & 1 deletion src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export interface OperationData {
export interface TaskData {
name?: string;
call: string;
args: TaskArgData;
args?: TaskArgData;
}

export interface TaskArgData {
Expand Down
24 changes: 12 additions & 12 deletions src/service/operation/operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,27 @@
import { Task } from './task';

export class Operation {
private name: string; // uid
private readonly _name: string; // uid

private events: string[];
private readonly _events: string[];

private tasks: Task[];
private readonly _tasks: Task[];

constructor(name: string, events: string[], tasks: Task[]) {
this.name = name;
this.events = events;
this.tasks = tasks;
this._name = name;
this._events = events;
this._tasks = tasks;
}

public getName(): string {
return this.name;
public get name(): string {
return this._name;
}

public getEvents(): string[] {
return this.events;
public get events(): string[] {
return this._events;
}

public getTasks(): Task[] {
return this.tasks;
public get tasks(): Task[] {
return this._tasks;
}
}
Loading

0 comments on commit bab72ee

Please sign in to comment.