From 8d7449d25d90e186c658ad235aa889db102f0573 Mon Sep 17 00:00:00 2001 From: "Michael D. Stemle, Jr" Date: Sun, 1 Oct 2023 21:19:40 -0400 Subject: [PATCH] Adding dd api governor (#58) * Adding a governor to prevent excessive Datadog calls - Fixed a couple of spots I missed in the CJS migration - Adding the Governor for HTTP posts to Datadog. It's quasi-configurable, but I haven't exposed the config yet. * Update CHANGELOG.md --- CHANGELOG.md | 4 ++ __tests__/lib/governors.test.cjs | 47 +++++++++++++++++++ ...pander.test.js => input-expander.test.cjs} | 0 index.cjs | 7 +++ jest.config.json | 3 +- lib/governors.cjs | 19 +++++++- 6 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 __tests__/lib/governors.test.cjs rename __tests__/lib/{input-expander.test.js => input-expander.test.cjs} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7a88ac..ee563b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning]. ## [Unreleased] +### Added + +- Added a limit of ten Datadog calls per Workflow step + ### Changed - Updated dependencies diff --git a/__tests__/lib/governors.test.cjs b/__tests__/lib/governors.test.cjs new file mode 100644 index 0000000..d4e2b7f --- /dev/null +++ b/__tests__/lib/governors.test.cjs @@ -0,0 +1,47 @@ +// Need to use inputs for some of our parameters +const core = require('@actions/core') + +const {DatadogPostGovernor } = require('../../lib/governors') + +describe('DatadogPostGovernor', () => { + test('init', () => { + DatadogPostGovernor.init() + expect(DatadogPostGovernor.state).toBe(0) + expect(DatadogPostGovernor.limit).toBe(10) + DatadogPostGovernor.init(42) + expect(DatadogPostGovernor.state).toBe(0) + expect(DatadogPostGovernor.limit).toBe(42) + }) + + test('increment', () => { + DatadogPostGovernor.init() + expect(DatadogPostGovernor.state).toBe(0) + expect(DatadogPostGovernor.limit).toBe(10) + + DatadogPostGovernor.increment() + expect(DatadogPostGovernor.state).toBe(1) + + DatadogPostGovernor.increment() + expect(DatadogPostGovernor.state).toBe(2) + }) + + test('enforcement', () => { + DatadogPostGovernor.init(3) + expect(DatadogPostGovernor.state).toBe(0) + expect(DatadogPostGovernor.limit).toBe(3) + + DatadogPostGovernor.increment() + expect(DatadogPostGovernor.state).toBe(1) + expect(core.setFailed).not.toHaveBeenCalled() + DatadogPostGovernor.increment() + expect(DatadogPostGovernor.state).toBe(2) + expect(core.setFailed).not.toHaveBeenCalled() + DatadogPostGovernor.increment() + expect(DatadogPostGovernor.state).toBe(3) + expect(core.setFailed).not.toHaveBeenCalled() + + DatadogPostGovernor.increment() + expect(DatadogPostGovernor.state).toBe(3) + expect(core.setFailed).toHaveBeenCalled() + }) +}) diff --git a/__tests__/lib/input-expander.test.js b/__tests__/lib/input-expander.test.cjs similarity index 100% rename from __tests__/lib/input-expander.test.js rename to __tests__/lib/input-expander.test.cjs diff --git a/index.cjs b/index.cjs index 7855620..71dd277 100644 --- a/index.cjs +++ b/index.cjs @@ -1,6 +1,7 @@ const core = require('@actions/core') const { HttpClient } = require('@actions/http-client') +const { DatadogPostGovernor } = require('./lib/governors') const { inputsToRegistryDocument } = require('./lib/input-to-registry-document') const { validateDatadogHostname } = require('./lib/input-validation') const { fetchAndApplyOrgRules } = require('./lib/org-rules') @@ -11,6 +12,8 @@ const { fetchAndApplyOrgRules } = require('./lib/org-rules') * @param {string} config - The config JSON string. **/ const registerWithDataDog = async (apiKey, appKey, ddHost, configJsonStr) => { + DatadogPostGovernor.increment() + core.debug(`JSON: ${configJsonStr}`) // Prep the auth const client = new HttpClient( @@ -42,6 +45,10 @@ const run = async (configs) => { // Extract the API key for DataDog const apiKey = core.getInput('datadog-key') const appKey = core.getInput('datadog-app-key') + + // Initialize the Post governor to help prevent excessive calls to Datadog + DatadogPostGovernor.init() + if (!apiKey || !appKey) { return core.setFailed( 'Both `datadog-key` and `datadog-app-key` are required.', diff --git a/jest.config.json b/jest.config.json index 8c93f7a..47b052f 100644 --- a/jest.config.json +++ b/jest.config.json @@ -2,7 +2,8 @@ "collectCoverage": true, "coverageDirectory": "coverage", "collectCoverageFrom": [ - "**/lib/**/*.js" + "**/lib/**/*.js", + "**/lib/**/*.cjs" ], "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.c?js$", "testPathIgnorePatterns": [ diff --git a/lib/governors.cjs b/lib/governors.cjs index fed53b6..29a467b 100644 --- a/lib/governors.cjs +++ b/lib/governors.cjs @@ -1,3 +1,20 @@ -const ddPostGovernor = { +const core = require('@actions/core') +const DatadogPostGovernor = { + state: undefined, + limit: undefined, + init: (limit = 10) => { + DatadogPostGovernor.limit = limit + DatadogPostGovernor.state = 0 + }, + increment: () => { + if (DatadogPostGovernor.state >= DatadogPostGovernor.limit) { + return core.setFailed(`Rate limit reached. Please do not exceed ${DatadogPostGovernor.limit} calls to Datadog in a single action.`) + } + DatadogPostGovernor.state += 1 + }, +} + +module.exports = { + DatadogPostGovernor }