From cf7e3a79123f91378a04ad28dd9b6e971216aa53 Mon Sep 17 00:00:00 2001 From: "Michael D. Stemle, Jr." Date: Fri, 15 Mar 2024 11:44:46 -0400 Subject: [PATCH 1/5] Adding the version to the Action title. --- .github/workflows/v2-automated-testing.yml | 2 +- .github/workflows/v2.1-automated-testing.yml | 2 +- .github/workflows/v2.2-automated-testing.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/v2-automated-testing.yml b/.github/workflows/v2-automated-testing.yml index f3b8850..3edd262 100644 --- a/.github/workflows/v2-automated-testing.yml +++ b/.github/workflows/v2-automated-testing.yml @@ -1,5 +1,5 @@ --- -name: DataDog Service Catalog +name: DataDog Service Catalog v2 on: push: workflow_dispatch: diff --git a/.github/workflows/v2.1-automated-testing.yml b/.github/workflows/v2.1-automated-testing.yml index 37f6e45..ea62568 100644 --- a/.github/workflows/v2.1-automated-testing.yml +++ b/.github/workflows/v2.1-automated-testing.yml @@ -1,5 +1,5 @@ --- -name: DataDog Service Catalog +name: DataDog Service Catalog v2.1 on: push: workflow_dispatch: diff --git a/.github/workflows/v2.2-automated-testing.yml b/.github/workflows/v2.2-automated-testing.yml index a8db6a0..7ecb7ef 100644 --- a/.github/workflows/v2.2-automated-testing.yml +++ b/.github/workflows/v2.2-automated-testing.yml @@ -1,5 +1,5 @@ --- -name: DataDog Service Catalog +name: DataDog Service Catalog v2.2 on: push: workflow_dispatch: From ec1b06ed22d29a402df0cd3adb7ffb2467273db3 Mon Sep 17 00:00:00 2001 From: "Michael D. Stemle, Jr." Date: Fri, 29 Mar 2024 13:30:16 -0400 Subject: [PATCH 2/5] Got the v2 schema module ready to go, and it's working! --- .../convenienceMappings.test.cjs.snap | 26 +++ __tests__/lib/convenienceMappings.test.cjs | 36 +++++ __tests__/lib/schemaVersions.test.cjs | 18 +++ .../__snapshots__/v2.test.cjs.snap | 144 +++++++++++++++++ __tests__/lib/schemaVersions/v2.test.cjs | 149 ++++++++++++++++++ lib/convenienceMappings.cjs | 22 +++ lib/schemaVersions.cjs | 12 ++ lib/schemaVersions/v2.cjs | 126 +++++++++++++++ 8 files changed, 533 insertions(+) create mode 100644 __tests__/lib/__snapshots__/convenienceMappings.test.cjs.snap create mode 100644 __tests__/lib/convenienceMappings.test.cjs create mode 100644 __tests__/lib/schemaVersions.test.cjs create mode 100644 __tests__/lib/schemaVersions/__snapshots__/v2.test.cjs.snap create mode 100644 __tests__/lib/schemaVersions/v2.test.cjs create mode 100644 lib/convenienceMappings.cjs create mode 100644 lib/schemaVersions.cjs create mode 100644 lib/schemaVersions/v2.cjs diff --git a/__tests__/lib/__snapshots__/convenienceMappings.test.cjs.snap b/__tests__/lib/__snapshots__/convenienceMappings.test.cjs.snap new file mode 100644 index 0000000..339e988 --- /dev/null +++ b/__tests__/lib/__snapshots__/convenienceMappings.test.cjs.snap @@ -0,0 +1,26 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`lib/convenienceMappings.cjs #getConvenienceFieldValues() - full set 1`] = ` +{ + "email": "team-name-here@fakeemaildomainthatdoesntexist.com", + "repo": "https://github.com/arcxp/datadog-service-catalog-metadata-provider", + "slack": "https://fakeorg.slack.com/archives/A0000000000", + "slack-support-channel": "https://fakeorg.slack.com/archives/A0000000001", +} +`; + +exports[`lib/convenienceMappings.cjs #getConvenienceFieldValues() - full set 2`] = ` +{ + "repo": "https://github.com/arcxp/datadog-service-catalog-metadata-provider", + "slack": "https://fakeorg.slack.com/archives/A0000000000", + "slack-support-channel": "https://fakeorg.slack.com/archives/A0000000001", +} +`; + +exports[`lib/convenienceMappings.cjs #getConvenienceFieldValues() - partial set 1`] = ` +{ + "repo": "https://github.com/arcxp/datadog-service-catalog-metadata-provider", + "slack": "https://fakeorg.slack.com/archives/A0000000000", + "slack-support-channel": "https://fakeorg.slack.com/archives/A0000000001", +} +`; diff --git a/__tests__/lib/convenienceMappings.test.cjs b/__tests__/lib/convenienceMappings.test.cjs new file mode 100644 index 0000000..fd95503 --- /dev/null +++ b/__tests__/lib/convenienceMappings.test.cjs @@ -0,0 +1,36 @@ +const YAML = require('yaml') +const core = require('@actions/core') +const subject = require('../../lib/convenienceMappings.cjs') + +describe('lib/convenienceMappings.cjs', () => { + test('#getConvenienceFieldValues() - full set', () => { + const testInput = ` +--- +email: 'team-name-here@fakeemaildomainthatdoesntexist.com' +slack: 'https://fakeorg.slack.com/archives/A0000000000' +slack-support-channel: 'https://fakeorg.slack.com/archives/A0000000001' +repo: https://github.com/arcxp/datadog-service-catalog-metadata-provider +` + // Full set of fields + core.__setInputsObject(YAML.parse(testInput)) + const inputs = subject.getConvenienceFieldValues(core) + expect(inputs).toMatchSnapshot() + expect(inputs.email).toEqual( + 'team-name-here@fakeemaildomainthatdoesntexist.com', + ) + }) + + test('#getConvenienceFieldValues() - partial set', () => { + const testInput = ` +--- +slack: 'https://fakeorg.slack.com/archives/A0000000000' +slack-support-channel: 'https://fakeorg.slack.com/archives/A0000000001' +repo: https://github.com/arcxp/datadog-service-catalog-metadata-provider +` + // Full set of fields + core.__setInputsObject(YAML.parse(testInput)) + const inputs = subject.getConvenienceFieldValues(core) + expect(inputs).toMatchSnapshot() + expect(inputs.email).toBeUndefined() + }) +}) diff --git a/__tests__/lib/schemaVersions.test.cjs b/__tests__/lib/schemaVersions.test.cjs new file mode 100644 index 0000000..cd60840 --- /dev/null +++ b/__tests__/lib/schemaVersions.test.cjs @@ -0,0 +1,18 @@ +const subject = require('../../lib/schemaVersions.cjs') + +describe('lib/schemaVersions.cjs#inputMapperByVersion()', () => { + test('v2 mapper', () => { + const mapper = subject.inputMapperByVersion('v2') + + expect(mapper).toBeInstanceOf(Function) + expect(mapper).not.toBe(subject._test.defaultMapper) + }) + + test('default mapper', () => { + const mapper = subject.inputMapperByVersion('blah') + + expect(mapper).toBeInstanceOf(Function) + expect(mapper).toBe(subject._test.defaultMapper) + expect(() => mapper()).toThrow(/Invalid\sschema\sversion/) + }) +}) diff --git a/__tests__/lib/schemaVersions/__snapshots__/v2.test.cjs.snap b/__tests__/lib/schemaVersions/__snapshots__/v2.test.cjs.snap new file mode 100644 index 0000000..3f25e5a --- /dev/null +++ b/__tests__/lib/schemaVersions/__snapshots__/v2.test.cjs.snap @@ -0,0 +1,144 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`lib/schemaVersions/v2.cjs#mapInputs() #mapInputs() - Merging in some convenience fields 1`] = ` +{ + "contacts": [ + { + "contact": "dba-team-name-here@fakeemaildomainthatdoesntexist.com", + "name": "DBA Team Email Alias", + "type": "email", + }, + { + "contact": "atotallydifferentemail@fakeemaildomainthatdoesntexist.com", + "name": "Primary Email", + "type": "email", + }, + { + "contact": "https://fakeorg.slack.com/archives/A0000000000", + "name": "Primary Slack Channel", + "type": "slack", + }, + { + "contact": "https://fakeorg.slack.com/archives/A0000000001", + "name": "Support Channel", + "type": "slack", + }, + ], + "integrations": { + "opsgenie": { + "service-url": "https://www.opsgenies.com/service/123e4567-e89b-12d3-a456-426614174000", + }, + "pagerduty": "https://my-org.pagerduty.com/service-directory/PMyService", + }, + "repos": [ + { + "name": "Primary Repository", + "url": "https://github.com/arcxp/datadog-service-catalog-metadata-provider", + }, + ], + "schema-version": "v2", + "tags": [ + "application:GitHub Action Config Test", + ], + "team": "Team Name Here", +} +`; + +exports[`lib/schemaVersions/v2.cjs#mapSchemaFields() #mapSchemaFields() - Full schema fields set 1`] = ` +{ + "contacts": [ + { + "contact": "dba-team-name-here@fakeemaildomainthatdoesntexist.com", + "name": "DBA Team Email Alias", + "type": "email", + }, + ], + "dd-service": "schemaVersions-v2-test", + "docs": [ + { + "name": "GitHub Actions!", + "provider": "Github", + "url": "https://github.com/features/actions", + }, + ], + "integrations": { + "opsgenie": { + "region": "US", + "service-url": "https://yourorghere.app.opsgenie.com/service/00000000-0000-0000-0000-000000000000", + }, + "pagerduty": "https://my-org.pagerduty.com/service-directory/PMyService", + }, + "links": [ + { + "name": "AMI Version Status Dashboard", + "type": "dashboard", + "url": "https://thisisanentirelyfakeurl.seriouslythisisafakehostname.com/dashboard", + }, + ], + "repos": [ + { + "name": "@actions/toolkit", + "provider": "Github", + "url": "https://github.com/actions/toolkit", + }, + ], + "schema-version": "v2", + "tags": [ + "application:GitHub Action Config Test", + "env:prod", + "infrastructure:serverless", + "language:nodejs", + "other:value", + ], + "team": "Team Name Here", +} +`; + +exports[`lib/schemaVersions/v2.cjs#mapSchemaFields() #mapSchemaFields() - Partial schema fields set 1`] = ` +{ + "contacts": [ + { + "contact": "dba-team-name-here@fakeemaildomainthatdoesntexist.com", + "name": "DBA Team Email Alias", + "type": "email", + }, + ], + "docs": [ + { + "name": "GitHub Actions!", + "provider": "Github", + "url": "https://github.com/features/actions", + }, + ], + "integrations": { + "opsgenie": { + "region": "US", + "service-url": "https://yourorghere.app.opsgenie.com/service/00000000-0000-0000-0000-000000000000", + }, + "pagerduty": "https://my-org.pagerduty.com/service-directory/PMyService", + }, + "links": [ + { + "name": "AMI Version Status Dashboard", + "type": "dashboard", + "url": "https://thisisanentirelyfakeurl.seriouslythisisafakehostname.com/dashboard", + }, + ], + "repos": [ + { + "name": "@actions/toolkit", + "provider": "Github", + "url": "https://github.com/actions/toolkit", + }, + ], + "schema-version": "v2", + "tags": [ + "application:GitHub Action Config Test", + "env:prod", + "infrastructure:serverless", + "language:nodejs", + "other:value", + ], + "team": "Team Name Here", +} +`; diff --git a/__tests__/lib/schemaVersions/v2.test.cjs b/__tests__/lib/schemaVersions/v2.test.cjs new file mode 100644 index 0000000..4000ebe --- /dev/null +++ b/__tests__/lib/schemaVersions/v2.test.cjs @@ -0,0 +1,149 @@ +const YAML = require('yaml') +const core = require('@actions/core') +const subject = require('../../../lib/schemaVersions/v2.cjs') + +describe('lib/schemaVersions/v2.cjs#mapSchemaFields()', () => { + test('#mapSchemaFields() - Full schema fields set', () => { + const testInput = ` +--- +schema-version: something-crazy-that-is-ignored +service-name: schemaVersions-v2-test +team: Team Name Here +tags: | + - 'application:GitHub Action Config Test' + - env:prod + - infrastructure:serverless + - language:nodejs + - other : value +repos: | + - url: https://github.com/actions/toolkit + provider: Github + name: "@actions/toolkit" +links: | + - name: AMI Version Status Dashboard + url: https://thisisanentirelyfakeurl.seriouslythisisafakehostname.com/dashboard + type: dashboard +docs: | + - name: GitHub Actions! + url: https://github.com/features/actions + provider: Github +integrations: | + opsgenie: + service-url: https://yourorghere.app.opsgenie.com/service/00000000-0000-0000-0000-000000000000 + region: US + pagerduty: https://my-org.pagerduty.com/service-directory/PMyService +contacts: | + - name: DBA Team Email Alias + type: email + contact: dba-team-name-here@fakeemaildomainthatdoesntexist.com + ` + // Full set of fields + core.__setInputsObject(YAML.parse(testInput)) + const inputs = subject._test.mapSchemaFields(core) + expect(inputs).toMatchSnapshot() + expect(inputs['dd-service']).toEqual('schemaVersions-v2-test') + }) + + test('#mapSchemaFields() - Partial schema fields set', () => { + const testInput = ` +--- +schema-version: something-crazy-that-is-ignored +team: Team Name Here +tags: | + - 'application:GitHub Action Config Test' + - env:prod + - infrastructure:serverless + - language:nodejs + - other : value +repos: | + - url: https://github.com/actions/toolkit + provider: Github + name: "@actions/toolkit" +links: | + - name: AMI Version Status Dashboard + url: https://thisisanentirelyfakeurl.seriouslythisisafakehostname.com/dashboard + type: dashboard +docs: | + - name: GitHub Actions! + url: https://github.com/features/actions + provider: Github +integrations: | + opsgenie: + service-url: https://yourorghere.app.opsgenie.com/service/00000000-0000-0000-0000-000000000000 + region: US + pagerduty: https://my-org.pagerduty.com/service-directory/PMyService +contacts: | + - name: DBA Team Email Alias + type: email + contact: dba-team-name-here@fakeemaildomainthatdoesntexist.com + ` + // Full set of fields + core.__setInputsObject(YAML.parse(testInput)) + const inputs = subject._test.mapSchemaFields(core) + expect(inputs).toMatchSnapshot() + expect(inputs['dd-service']).toBeUndefined() + }) +}) + +describe('lib/schemaVersions/v2.cjs#mapInputs()', () => { + test('#mapInputs() - Merging in some convenience fields', () => { + const testInput = ` +--- +schema-version: something-crazy-that-is-ignored +team: Team Name Here +email: atotallydifferentemail@fakeemaildomainthatdoesntexist.com +slack: 'https://fakeorg.slack.com/archives/A0000000000' +slack-support-channel: 'https://fakeorg.slack.com/archives/A0000000001' +repo: https://github.com/arcxp/datadog-service-catalog-metadata-provider +tags: | + - 'application:GitHub Action Config Test' +contacts: | + - name: DBA Team Email Alias + type: email + contact: dba-team-name-here@fakeemaildomainthatdoesntexist.com +pagerduty: https://my-org.pagerduty.com/service-directory/PMyService +opsgenie: https://www.opsgenies.com/service/123e4567-e89b-12d3-a456-426614174000 + ` + // Full set of fields + core.__setInputsObject(YAML.parse(testInput)) + const inputs = subject.mapInputs(core) + expect(inputs).toMatchSnapshot() + expect(inputs.contacts).toEqual([ + { + contact: 'dba-team-name-here@fakeemaildomainthatdoesntexist.com', + name: 'DBA Team Email Alias', + type: 'email', + }, + { + contact: 'atotallydifferentemail@fakeemaildomainthatdoesntexist.com', + name: 'Primary Email', + type: 'email', + }, + { + name: 'Primary Slack Channel', + contact: 'https://fakeorg.slack.com/archives/A0000000000', + type: 'slack', + }, + { + name: 'Support Channel', + contact: 'https://fakeorg.slack.com/archives/A0000000001', + type: 'slack', + }, + ]) + + expect(inputs.repos).toEqual([ + { + url: 'https://github.com/arcxp/datadog-service-catalog-metadata-provider', + name: 'Primary Repository', + }, + ]) + + expect(inputs.integrations).toEqual({ + pagerduty: 'https://my-org.pagerduty.com/service-directory/PMyService', + opsgenie: { + 'service-url': + 'https://www.opsgenies.com/service/123e4567-e89b-12d3-a456-426614174000', + }, + }) + }) +}) diff --git a/lib/convenienceMappings.cjs b/lib/convenienceMappings.cjs new file mode 100644 index 0000000..82bebf5 --- /dev/null +++ b/lib/convenienceMappings.cjs @@ -0,0 +1,22 @@ +const _ = require('lodash') + +/** + * Construct an object of convenience field values, and return it so that it can be incorporated into the main document. + * @param {object} core - The `core` object from the GitHub Actions toolkit. + * @returns {object} - Returns an object of convenience field values, by the convenience field value name. It should be noted that only fields with values are returned. + **/ +const getConvenienceFieldValues = (core) => + _.omitBy( + { + // These fields map into `contacts` in the registry document. + email: core.getInput('email'), + slack: core.getInput('slack'), + 'slack-support-channel': core.getInput('slack-support-channel'), + repo: core.getInput('repo'), + opsgenie: core.getInput('opsgenie'), + pagerduty: core.getInput('pagerduty'), + }, + _.isNil, + ) + +module.exports = { getConvenienceFieldValues } diff --git a/lib/schemaVersions.cjs b/lib/schemaVersions.cjs new file mode 100644 index 0000000..1ce04dd --- /dev/null +++ b/lib/schemaVersions.cjs @@ -0,0 +1,12 @@ +const mapperMatrix = { + v2: require('./schemaVersions/v2.cjs').mapInputs, +} + +const defaultMapper = () => { + throw Error('Invalid schema version') +} + +const inputMapperByVersion = (version) => + mapperMatrix?.[version] ?? defaultMapper + +module.exports = { inputMapperByVersion, _test: { defaultMapper } } diff --git a/lib/schemaVersions/v2.cjs b/lib/schemaVersions/v2.cjs new file mode 100644 index 0000000..b8931af --- /dev/null +++ b/lib/schemaVersions/v2.cjs @@ -0,0 +1,126 @@ +/** + * @file This file maps the GitHub Action's inputs to the v2 schema. + * @module lib/schemaVersions/v2 + * @author Mike Stemle + **/ + +const _ = require('lodash') + +const { + parseYamlExpectArray, + parseYamlExpectObject, + parseYamlExpectDatadogTags, + isNothing, + combineValues, +} = require('../input-expander.cjs') + +const { getConvenienceFieldValues } = require('../convenienceMappings.cjs') + +/** + * This is function maps the inputs to the output schema version for the API. + * @param {object} core - The GitHub Actions core object. + * @returns {Object} - Returns an object containing all of the mapped fields. + * @private + * @function + **/ +const mapSchemaFields = (core) => + _.omitBy( + { + // Schema version is intentionally overridden because the schema version mapping + // logic has already discovered that `v2` is the value in order to get this far. + // No need to bother looking up something we already know. + 'schema-version': 'v2', + 'dd-service': core.getInput('service-name'), + team: core.getInput('team'), + contacts: parseYamlExpectArray(core.getInput('contacts')), + links: parseYamlExpectArray(core.getInput('links')), + repos: parseYamlExpectArray(core.getInput('repos')), + docs: parseYamlExpectArray(core.getInput('docs')), + tags: parseYamlExpectDatadogTags(core.getInput('tags')), + integrations: parseYamlExpectObject(core.getInput('integrations')), + }, + isNothing, + ) + +/** + * Map all relevant fields for this schema version. + * @param {object} core - The GitHub Actions core object. + * @returns {Object} - Returns an object containing all of the mapped fields. + * @public + * @function + **/ +const mapInputs = (core) => { + const convenienceValues = getConvenienceFieldValues(core) + + // This is just a shortcut because I don't want to have a big, repetitive list of + // conditionals. + const hasValuesForConvenienceKeys = (listOfKeys) => { + for (const key of listOfKeys) { + if (!!convenienceValues[key]) return true + } + + return false + } + + // Grab the schema fields + let mappedInputs = mapSchemaFields(core) + + if ( + hasValuesForConvenienceKeys(['email', 'slack', 'slack-support-channel']) + ) { + const contactAdditions = _.filter( + [ + !!convenienceValues['email'] + ? { + name: 'Primary Email', + contact: convenienceValues['email'], + type: 'email', + } + : undefined, + !!convenienceValues['slack'] + ? { + name: 'Primary Slack Channel', + contact: convenienceValues['slack'], + type: 'slack', + } + : undefined, + !!convenienceValues['slack-support-channel'] + ? { + name: 'Support Channel', + contact: convenienceValues['slack-support-channel'], + type: 'slack', + } + : undefined, + ], + (x) => !_.isNil(x), + ) + mappedInputs = combineValues(contactAdditions, 'contacts', mappedInputs) + } + + if (hasValuesForConvenienceKeys(['repo'])) { + mappedInputs = combineValues( + [{ url: convenienceValues['repo'], name: 'Primary Repository' }], + 'repos', + mappedInputs, + ) + } + + if (hasValuesForConvenienceKeys(['pagerduty', 'opsgenie'])) { + mappedInputs.integrations ||= {} + } + if (!!convenienceValues['pagerduty']) { + mappedInputs.integrations.pagerduty = convenienceValues['pagerduty'] + } + if (!!convenienceValues['opsgenie']) { + mappedInputs.integrations.opsgenie = { + 'service-url': convenienceValues['opsgenie'], + } + } + + return mappedInputs +} + +module.exports = { + _test: { mapSchemaFields }, + mapInputs, +} From 74efe448a00bb53df5fcc8f9ab456a130af2f1d0 Mon Sep 17 00:00:00 2001 From: "Michael D. Stemle, Jr." Date: Fri, 29 Mar 2024 20:31:04 -0400 Subject: [PATCH 3/5] Major update to modularize the version mappers! - Each schema version now has a separate module and a separate test - There are now different workflow-based tests per schema version - Got to remove a bunch of code which was more complex than needed - We're ready to support additional schema versions now --- .github/workflows/v2-automated-testing.yml | 3 - .github/workflows/v2.2-automated-testing.yml | 2 +- .../convenienceMappings.test.cjs.snap | 8 - .../fieldMappings-schema.test.cjs.snap | 124 ----- .../__snapshots__/fieldMappings.test.cjs.snap | 124 ----- .../input-to-registry-document.test.cjs.snap | 24 +- .../lib/fieldMappings-convenience.test.cjs | 251 ---------- __tests__/lib/fieldMappings-schema.test.cjs | 440 ------------------ __tests__/lib/fieldMappings.test.cjs | 37 -- __tests__/lib/input-expander.test.cjs | 149 +++++- .../lib/input-to-registry-document.test.cjs | 17 +- __tests__/lib/schemaVersions.test.cjs | 13 +- .../__snapshots__/v2.1.test.cjs.snap | 147 ++++++ .../__snapshots__/v2.2.test.cjs.snap | 152 ++++++ .../__snapshots__/v2.test.cjs.snap | 18 + __tests__/lib/schemaVersions/v2.1.test.cjs | 155 ++++++ __tests__/lib/schemaVersions/v2.2.test.cjs | 159 +++++++ __tests__/lib/schemaVersions/v2.test.cjs | 9 + __tests__/self-workflow-validation.test.cjs | 19 +- dist/index.cjs | 8 +- lib/fieldMappings.cjs | 359 -------------- lib/input-expander.cjs | 134 ++++-- lib/input-to-registry-document.cjs | 66 +-- lib/schemaVersions.cjs | 2 + lib/schemaVersions/v2.1.cjs | 138 ++++++ lib/schemaVersions/v2.2.cjs | 140 ++++++ lib/schemaVersions/v2.cjs | 2 + 27 files changed, 1231 insertions(+), 1469 deletions(-) delete mode 100644 __tests__/lib/__snapshots__/fieldMappings-schema.test.cjs.snap delete mode 100644 __tests__/lib/__snapshots__/fieldMappings.test.cjs.snap delete mode 100644 __tests__/lib/fieldMappings-convenience.test.cjs delete mode 100644 __tests__/lib/fieldMappings-schema.test.cjs delete mode 100644 __tests__/lib/fieldMappings.test.cjs create mode 100644 __tests__/lib/schemaVersions/__snapshots__/v2.1.test.cjs.snap create mode 100644 __tests__/lib/schemaVersions/__snapshots__/v2.2.test.cjs.snap create mode 100644 __tests__/lib/schemaVersions/v2.1.test.cjs create mode 100644 __tests__/lib/schemaVersions/v2.2.test.cjs delete mode 100644 lib/fieldMappings.cjs create mode 100644 lib/schemaVersions/v2.1.cjs create mode 100644 lib/schemaVersions/v2.2.cjs diff --git a/.github/workflows/v2-automated-testing.yml b/.github/workflows/v2-automated-testing.yml index 3edd262..b52f6c0 100644 --- a/.github/workflows/v2-automated-testing.yml +++ b/.github/workflows/v2-automated-testing.yml @@ -57,19 +57,16 @@ jobs: links: | - url: https://github.com/actions/toolkit type: repo - provider: Github name: '@actions/toolkit' - name: AMI Version Status Dashboard url: https://thisisanentirelyfakeurl.seriouslythisisafakehostname.com/dashboard type: dashboard - name: GitHub Actions! url: https://github.com/features/actions - provider: Github Docs type: doc - name: Some Runbook url: https://thisisanentirelyfakeurl.seriouslythisisafakehostname.com/runbook type: runbook - provider: Confluence docs: | - name: Some Docs url: https://thisisanentirelyfakeurl.seriouslythisisafakehostname.com/docs diff --git a/.github/workflows/v2.2-automated-testing.yml b/.github/workflows/v2.2-automated-testing.yml index 7ecb7ef..e5020ff 100644 --- a/.github/workflows/v2.2-automated-testing.yml +++ b/.github/workflows/v2.2-automated-testing.yml @@ -54,7 +54,7 @@ jobs: - language:nodejs - updated:${{ steps.date.outputs.date }} - internet_accessible: false - - schema-version: 2.2 + - schema-version: v2.2 links: | - url: https://github.com/actions/toolkit type: repo diff --git a/__tests__/lib/__snapshots__/convenienceMappings.test.cjs.snap b/__tests__/lib/__snapshots__/convenienceMappings.test.cjs.snap index 339e988..1100677 100644 --- a/__tests__/lib/__snapshots__/convenienceMappings.test.cjs.snap +++ b/__tests__/lib/__snapshots__/convenienceMappings.test.cjs.snap @@ -9,14 +9,6 @@ exports[`lib/convenienceMappings.cjs #getConvenienceFieldValues() - full set 1`] } `; -exports[`lib/convenienceMappings.cjs #getConvenienceFieldValues() - full set 2`] = ` -{ - "repo": "https://github.com/arcxp/datadog-service-catalog-metadata-provider", - "slack": "https://fakeorg.slack.com/archives/A0000000000", - "slack-support-channel": "https://fakeorg.slack.com/archives/A0000000001", -} -`; - exports[`lib/convenienceMappings.cjs #getConvenienceFieldValues() - partial set 1`] = ` { "repo": "https://github.com/arcxp/datadog-service-catalog-metadata-provider", diff --git a/__tests__/lib/__snapshots__/fieldMappings-schema.test.cjs.snap b/__tests__/lib/__snapshots__/fieldMappings-schema.test.cjs.snap deleted file mode 100644 index 02cb247..0000000 --- a/__tests__/lib/__snapshots__/fieldMappings-schema.test.cjs.snap +++ /dev/null @@ -1,124 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`constants convenienceFields 1`] = ` -[ - "email", - "slack", - "repo", - "opsgenie", - "pagerduty", - "slack-support-channel", -] -`; - -exports[`constants mappings 1`] = ` -{ - "application": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "ci-pipeline-fingerprints": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "contacts": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "description": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "docs": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "extensions": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "integrations": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "languages": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "lifecycle": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "links": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "repos": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "schema-version": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "service-name": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "tags": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "team": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "tier": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "type": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, -} -`; - -exports[`constants schemaFields 1`] = ` -[ - "schema-version", - "service-name", - "team", - "application", - "description", - "tier", - "lifecycle", - "type", - "languages", - "contacts", - "links", - "tags", - "integrations", - "docs", - "repos", - "ci-pipeline-fingerprints", - "extensions", -] -`; diff --git a/__tests__/lib/__snapshots__/fieldMappings.test.cjs.snap b/__tests__/lib/__snapshots__/fieldMappings.test.cjs.snap deleted file mode 100644 index 02cb247..0000000 --- a/__tests__/lib/__snapshots__/fieldMappings.test.cjs.snap +++ /dev/null @@ -1,124 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`constants convenienceFields 1`] = ` -[ - "email", - "slack", - "repo", - "opsgenie", - "pagerduty", - "slack-support-channel", -] -`; - -exports[`constants mappings 1`] = ` -{ - "application": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "ci-pipeline-fingerprints": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "contacts": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "description": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "docs": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "extensions": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "integrations": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "languages": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "lifecycle": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "links": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "repos": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "schema-version": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "service-name": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "tags": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "team": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "tier": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, - "type": { - "v2": [Function], - "v2.1": [Function], - "v2.2": [Function], - }, -} -`; - -exports[`constants schemaFields 1`] = ` -[ - "schema-version", - "service-name", - "team", - "application", - "description", - "tier", - "lifecycle", - "type", - "languages", - "contacts", - "links", - "tags", - "integrations", - "docs", - "repos", - "ci-pipeline-fingerprints", - "extensions", -] -`; diff --git a/__tests__/lib/__snapshots__/input-to-registry-document.test.cjs.snap b/__tests__/lib/__snapshots__/input-to-registry-document.test.cjs.snap index 6d3268e..f09b68b 100644 --- a/__tests__/lib/__snapshots__/input-to-registry-document.test.cjs.snap +++ b/__tests__/lib/__snapshots__/input-to-registry-document.test.cjs.snap @@ -10,10 +10,12 @@ exports[`input-to-registry-document.js - schema v2 #inputsToRegistryDocument() - }, { "contact": "team-name-here@fakeemaildomainthatdoesntexist.com", + "name": "Primary Email", "type": "email", }, { "contact": "https://fakeorg.slack.com/archives/A0000000000", + "name": "Support Channel", "type": "slack", }, ], @@ -46,7 +48,7 @@ exports[`input-to-registry-document.js - schema v2 #inputsToRegistryDocument() - "url": "https://github.com/actions/toolkit", }, { - "name": "Repo", + "name": "Primary Repository", "url": "https://github.com/arcxp/datadog-service-catalog-metadata-provider", }, ], @@ -67,10 +69,12 @@ exports[`input-to-registry-document.js - schema v2 #inputsToRegistryDocument() - "contacts": [ { "contact": "team-name-here@fakeemaildomainthatdoesntexist.com", + "name": "Primary Email", "type": "email", }, { "contact": "https://fakeorg.slack.com/archives/A0000000000", + "name": "Support Channel", "type": "slack", }, ], @@ -83,7 +87,7 @@ exports[`input-to-registry-document.js - schema v2 #inputsToRegistryDocument() - }, "repos": [ { - "name": "Repo", + "name": "Primary Repository", "url": "https://github.com/arcxp/datadog-service-catalog-metadata-provider", }, ], @@ -103,10 +107,12 @@ exports[`input-to-registry-document.js - schema v2 #inputsToRegistryDocument() - "contacts": [ { "contact": "team-name-here@fakeemaildomainthatdoesntexist.com", + "name": "Primary Email", "type": "email", }, { "contact": "https://fakeorg.slack.com/archives/A0000000000", + "name": "Support Channel", "type": "slack", }, ], @@ -138,7 +144,7 @@ exports[`input-to-registry-document.js - schema v2 #inputsToRegistryDocument() - "url": "https://github.com/actions/toolkit", }, { - "name": "Repo", + "name": "Primary Repository", "url": "https://github.com/arcxp/datadog-service-catalog-metadata-provider", }, ], @@ -164,10 +170,12 @@ exports[`input-to-registry-document.js - schema v2.1 #inputsToRegistryDocument() }, { "contact": "team-name-here@fakeemaildomainthatdoesntexist.com", + "name": "Primary Email", "type": "email", }, { "contact": "https://fakeorg.slack.com/archives/A0000000000", + "name": "Support Channel", "type": "slack", }, ], @@ -198,7 +206,7 @@ exports[`input-to-registry-document.js - schema v2.1 #inputsToRegistryDocument() "url": "https://github.com/features/actions", }, { - "name": "Repo", + "name": "Primary Repository", "type": "repo", "url": "https://github.com/arcxp/datadog-service-catalog-metadata-provider", }, @@ -220,10 +228,12 @@ exports[`input-to-registry-document.js - schema v2.1 #inputsToRegistryDocument() "contacts": [ { "contact": "team-name-here@fakeemaildomainthatdoesntexist.com", + "name": "Primary Email", "type": "email", }, { "contact": "https://fakeorg.slack.com/archives/A0000000000", + "name": "Support Channel", "type": "slack", }, ], @@ -236,7 +246,7 @@ exports[`input-to-registry-document.js - schema v2.1 #inputsToRegistryDocument() }, "links": [ { - "name": "Repo", + "name": "Primary Repository", "type": "repo", "url": "https://github.com/arcxp/datadog-service-catalog-metadata-provider", }, @@ -257,10 +267,12 @@ exports[`input-to-registry-document.js - schema v2.1 #inputsToRegistryDocument() "contacts": [ { "contact": "team-name-here@fakeemaildomainthatdoesntexist.com", + "name": "Primary Email", "type": "email", }, { "contact": "https://fakeorg.slack.com/archives/A0000000000", + "name": "Support Channel", "type": "slack", }, ], @@ -291,7 +303,7 @@ exports[`input-to-registry-document.js - schema v2.1 #inputsToRegistryDocument() "url": "https://github.com/features/actions", }, { - "name": "Repo", + "name": "Primary Repository", "type": "repo", "url": "https://github.com/arcxp/datadog-service-catalog-metadata-provider", }, diff --git a/__tests__/lib/fieldMappings-convenience.test.cjs b/__tests__/lib/fieldMappings-convenience.test.cjs deleted file mode 100644 index 1441296..0000000 --- a/__tests__/lib/fieldMappings-convenience.test.cjs +++ /dev/null @@ -1,251 +0,0 @@ -/** - * @fileoverview This test covers all of the field mappings across versions. - * @jest-environment node - * @group ci - * @author Mike Stemle - **/ - -const core = require('@actions/core') -const { - mappings, - convenienceFields, - schemaFields, - mapField, -} = require('../../lib/fieldMappings') - -describe.each([ - { - version: 'v2', - field: 'email', - input: 'testing@manchicken.io', - doc: {}, - value: { - contacts: [{ type: 'email', contact: 'testing@manchicken.io' }], - }, - }, - { - version: 'v2', - field: 'email', - input: 'testing@manchicken.io', - doc: { - contacts: [ - { - name: 'test', - type: 'slack', - contact: 'https://my-org.slack.com/archives/my-channel', - }, - ], - }, - value: { - contacts: [ - { - name: 'test', - type: 'slack', - contact: 'https://my-org.slack.com/archives/my-channel', - }, - { type: 'email', contact: 'testing@manchicken.io' }, - ], - }, - }, - { - version: 'v2.1', - field: 'email', - input: 'testing@manchicken.io', - doc: { - contacts: [ - { - name: 'test', - type: 'slack', - contact: 'https://my-org.slack.com/archives/my-channel', - }, - ], - }, - value: { - contacts: [ - { - name: 'test', - type: 'slack', - contact: 'https://my-org.slack.com/archives/my-channel', - }, - { type: 'email', contact: 'testing@manchicken.io' }, - ], - }, - }, - { - version: 'v2', - field: 'slack', - input: 'https://my-org.slack.com/archives/my-channel', - doc: {}, - value: { - contacts: [ - { - type: 'slack', - contact: 'https://my-org.slack.com/archives/my-channel', - }, - ], - }, - }, - { - version: 'v2', - field: 'repo', - input: 'https://github.com/arcxp/datadog-service-catalog-metadata-provider', - doc: { - contacts: [{ type: 'email', contact: 'testing@manchicken.io' }], - }, - value: { - contacts: [{ type: 'email', contact: 'testing@manchicken.io' }], - repos: [ - { - name: 'Repo', - url: 'https://github.com/arcxp/datadog-service-catalog-metadata-provider', - }, - ], - }, - }, - { - version: 'v2.1', - field: 'repo', - input: 'https://github.com/arcxp/datadog-service-catalog-metadata-provider', - doc: { - contacts: [{ type: 'email', contact: 'testing@manchicken.io' }], - }, - value: { - contacts: [{ type: 'email', contact: 'testing@manchicken.io' }], - links: [ - { - name: 'Repo', - type: 'repo', - url: 'https://github.com/arcxp/datadog-service-catalog-metadata-provider', - }, - ], - }, - }, - { - version: 'v2', - field: 'slack', - input: 'https://my-org.slack.com/archives/my-channel', - doc: { - contacts: [{ type: 'email', contact: 'testing@manchicken.io' }], - }, - value: { - contacts: [ - { type: 'email', contact: 'testing@manchicken.io' }, - { - type: 'slack', - contact: 'https://my-org.slack.com/archives/my-channel', - }, - ], - }, - }, - { - version: 'v2.1', - field: 'slack', - input: 'https://my-org.slack.com/archives/my-channel', - doc: { - contacts: [{ type: 'email', contact: 'testing@manchicken.io' }], - }, - value: { - contacts: [ - { type: 'email', contact: 'testing@manchicken.io' }, - { - type: 'slack', - contact: 'https://my-org.slack.com/archives/my-channel', - }, - ], - }, - }, - { - version: 'v2', - field: 'opsgenie', - input: - 'https://www.opsgenies.com/service/123e4567-e89b-12d3-a456-426614174000', - doc: { - contacts: [{ type: 'email', contact: 'testing@manchicken.io' }], - }, - value: { - contacts: [{ type: 'email', contact: 'testing@manchicken.io' }], - integrations: { - opsgenie: { - 'service-url': - 'https://www.opsgenies.com/service/123e4567-e89b-12d3-a456-426614174000', - }, - }, - }, - }, - { - version: 'v2.1', - field: 'opsgenie', - input: - 'https://www.opsgenies.com/service/123e4567-e89b-12d3-a456-426614174000', - doc: { - contacts: [{ type: 'email', contact: 'testing@manchicken.io' }], - }, - value: { - contacts: [{ type: 'email', contact: 'testing@manchicken.io' }], - integrations: { - opsgenie: { - 'service-url': - 'https://www.opsgenies.com/service/123e4567-e89b-12d3-a456-426614174000', - }, - }, - }, - }, - { - version: 'v2', - field: 'pagerduty', - input: 'https://my-org.pagerduty.com/service-directory/PMyService', - doc: { - integrations: { - opsgenie: { - 'service-url': - 'https://www.opsgenies.com/service/123e4567-e89b-12d3-a456-426614174000', - }, - }, - }, - value: { - integrations: { - opsgenie: { - 'service-url': - 'https://www.opsgenies.com/service/123e4567-e89b-12d3-a456-426614174000', - }, - pagerduty: 'https://my-org.pagerduty.com/service-directory/PMyService', - }, - }, - }, - { - version: 'v2.1', - field: 'pagerduty', - input: 'https://my-org.pagerduty.com/service-directory/PMyService', - doc: { - integrations: { - opsgenie: { - 'service-url': - 'https://www.opsgenies.com/service/123e4567-e89b-12d3-a456-426614174000', - }, - }, - }, - value: { - integrations: { - opsgenie: { - 'service-url': - 'https://www.opsgenies.com/service/123e4567-e89b-12d3-a456-426614174000', - }, - pagerduty: { - 'service-url': - 'https://my-org.pagerduty.com/service-directory/PMyService', - }, - }, - }, - }, -])('$field:$version', ({ version, field, input, doc, value }) => { - afterAll(() => { - core.setFailed.mockClear() - core.setFailed.mockReset() - }) - beforeEach(() => { - core.setFailed.mockClear() - }) - test('mapping', () => { - expect(mapField(field, version)(input, doc)).toEqual(value) - }) -}) diff --git a/__tests__/lib/fieldMappings-schema.test.cjs b/__tests__/lib/fieldMappings-schema.test.cjs deleted file mode 100644 index 993736d..0000000 --- a/__tests__/lib/fieldMappings-schema.test.cjs +++ /dev/null @@ -1,440 +0,0 @@ -/** - * @fileoverview This test covers all of the field mappings across versions. - * @jest-environment node - * @group ci - * @author Mike Stemle - **/ - -const core = require('@actions/core') -const { - mappings, - convenienceFields, - schemaFields, - mapField, -} = require('../../lib/fieldMappings') - -describe.each([ - { - version: 'v2', - field: 'schema-version', - input: 'v2', - value: { 'schema-version': 'v2' }, - }, - { - version: 'v2.1', - field: 'schema-version', - input: 'v2.1', - value: { 'schema-version': 'v2.1' }, - }, - { - version: 'v2', - field: 'service-name', - input: 'test-service', - value: { 'dd-service': 'test-service' }, - }, - { - version: 'v2.1', - field: 'service-name', - input: 'test-service', - value: { 'dd-service': 'test-service' }, - }, - { - version: 'v2', - field: 'team', - input: 'app-team-name', - value: { team: 'app-team-name' }, - }, - { - version: 'v2.1', - field: 'team', - input: 'app-team-name', - value: { team: 'app-team-name' }, - }, - { - version: 'v2', - field: 'contacts', - input: ` -- name: Testy McTester - type: email - contact: testy@mctester.com -`, - value: { - contacts: [ - { - name: 'Testy McTester', - type: 'email', - contact: 'testy@mctester.com', - }, - ], - }, - }, - { - version: 'v2.1', - field: 'contacts', - input: ` -- name: Testy McTester - type: email - contact: testy@mctester.com -`, - value: { - contacts: [ - { - name: 'Testy McTester', - type: 'email', - contact: 'testy@mctester.com', - }, - ], - }, - }, - { - version: 'v2', - field: 'tags', - input: ` -- plain:nospaces -- compat1: space-in-value -- compat2 : space-in-both -- number_value : 42 -`, - value: { - tags: [ - 'plain:nospaces', - 'compat1:space-in-value', - 'compat2:space-in-both', - 'number_value:42', - ], - }, - }, - { - version: 'v2.1', - field: 'tags', - input: ` -- plain:nospaces -- compat1: space-in-value -- compat2 : space-in-both -`, - value: { - tags: [ - 'plain:nospaces', - 'compat1:space-in-value', - 'compat2:space-in-both', - ], - }, - }, - { - version: 'v2', - field: 'links', - input: ` -- name: 'first-link' - type: url - url: https://manchicken.io/testing -`, - value: { - links: [ - { - name: 'first-link', - type: 'url', - url: 'https://manchicken.io/testing', - }, - ], - }, - }, - { - version: 'v2', - field: 'links', - input: ` -- name: 'first-link' - type: url - url: https://manchicken.io/testing - provider: Website -`, - value: { - links: [ - { - name: 'first-link', - type: 'url', - url: 'https://manchicken.io/testing', - }, - ], - }, - }, - { - version: 'v2.1', - field: 'links', - input: ` -- name: 'first-link' - type: url - url: https://manchicken.io/testing - provider: Website -`, - value: { - links: [ - { - name: 'first-link', - type: 'url', - url: 'https://manchicken.io/testing', - provider: 'Website', - }, - ], - }, - }, - { - version: 'v2', - field: 'integrations', - input: ` -pagerduty: "https://my-org.pagerduty.com/service-directory/PMyService" -`, - value: { - integrations: { - pagerduty: 'https://my-org.pagerduty.com/service-directory/PMyService', - }, - }, - }, - { - version: 'v2', - field: 'integrations', - input: ` -opsgenie: - service-url: "https://www.opsgenies.com/service/123e4567-e89b-12d3-a456-426614174000" - region: US -`, - value: { - integrations: { - opsgenie: { - 'service-url': - 'https://www.opsgenies.com/service/123e4567-e89b-12d3-a456-426614174000', - region: 'US', - }, - }, - }, - }, - { - version: 'v2.1', - field: 'integrations', - input: ` -pagerduty: - service-url: "https://my-org.pagerduty.com/service-directory/PMyService" -`, - value: { - integrations: { - pagerduty: { - 'service-url': - 'https://my-org.pagerduty.com/service-directory/PMyService', - }, - }, - }, - }, - { - version: 'v2.1', - field: 'integrations', - input: ` -opsgenie: - service-url: "https://www.opsgenies.com/service/123e4567-e89b-12d3-a456-426614174000" - region: US -`, - value: { - integrations: { - opsgenie: { - 'service-url': - 'https://www.opsgenies.com/service/123e4567-e89b-12d3-a456-426614174000', - region: 'US', - }, - }, - }, - }, - { - version: 'v2', - field: 'application', - input: 'app-name', - value: { - error: - 'Sorry, but the «application» field is not avaiable in version v2 of the Datadog Service Catalog schema; this field is only available in version(s): v2.1', - }, - }, - { - version: 'v2.1', - field: 'application', - input: 'app-name', - value: { application: 'app-name' }, - }, - { - version: 'v2', - field: 'description', - input: 'some description', - value: { - error: - 'Sorry, but the «description» field is not avaiable in version v2 of the Datadog Service Catalog schema; this field is only available in version(s): v2.1', - }, - }, - { - version: 'v2.1', - field: 'description', - input: 'some description', - value: { description: 'some description' }, - }, - { - version: 'v2', - field: 'tier', - input: 'high', - value: { - error: - 'Sorry, but the «tier» field is not avaiable in version v2 of the Datadog Service Catalog schema; this field is only available in version(s): v2.1', - }, - }, - { - version: 'v2.1', - field: 'tier', - input: 'high', - value: { tier: 'high' }, - }, - { - version: 'v2', - field: 'lifecycle', - input: 'production', - value: { - error: - 'Sorry, but the «lifecycle» field is not avaiable in version v2 of the Datadog Service Catalog schema; this field is only available in version(s): v2.1', - }, - }, - { - version: 'v2.1', - field: 'lifecycle', - input: 'production', - value: { lifecycle: 'production' }, - }, - { - version: 'v2', - field: 'docs', - input: ` -- name: 'first-doc' - provider: jira - url: https://my-org.atlassian.net/wiki/spaces/PROJ/pages/1234567890 -`, - value: { - docs: [ - { - name: 'first-doc', - provider: 'jira', - url: 'https://my-org.atlassian.net/wiki/spaces/PROJ/pages/1234567890', - }, - ], - }, - }, - { - version: 'v2.1', - field: 'docs', - input: ` -- name: 'first-doc' - provider: jira - url: https://my-org.atlassian.net/wiki/spaces/PROJ/pages/1234567890 -`, - value: { - error: - 'Sorry, but the «docs» field is not avaiable in version v2.1 of the Datadog Service Catalog schema; this field is only available in version(s): v2', - }, - }, - { - version: 'v2', - field: 'repos', - input: ` -- name: 'first-repo' - provider: github - url: https://github.com/arcxp/datadog-service-catalog-metadata-provider -`, - value: { - repos: [ - { - name: 'first-repo', - provider: 'github', - url: 'https://github.com/arcxp/datadog-service-catalog-metadata-provider', - }, - ], - }, - }, - { - version: 'v2.1', - field: 'repos', - input: ` -- name: 'first-repo' - provider: github - url: https://github.com/arcxp/datadog-service-catalog-metadata-provider -`, - value: { - error: - 'Sorry, but the «repos» field is not avaiable in version v2.1 of the Datadog Service Catalog schema; this field is only available in version(s): v2', - }, - }, - { - version: 'v2.2', - field: 'extensions', - input: ` -shopist.com/release-scheduler: - release-manager: - slack: "release-train-shopist" - schedule: "* * * * *" - env: - - name: "staging" - ci_pipeline: "//domains/examples/apps/hello-joe/config/k8s:release-staging" - branch: "hello-joe/staging" - schedule: "* * * * 1" -`, - value: { - extensions: { - 'shopist.com/release-scheduler': { - 'release-manager': { - slack: 'release-train-shopist', - schedule: '* * * * *', - env: [ - { - name: 'staging', - ci_pipeline: - '//domains/examples/apps/hello-joe/config/k8s:release-staging', - branch: 'hello-joe/staging', - schedule: '* * * * 1', - }, - ], - }, - }, - }, - }, - }, -])('$field:$version', ({ version, field, input, value }) => { - afterAll(() => { - core.setFailed.mockClear() - core.setFailed.mockReset() - }) - beforeEach(() => { - core.setFailed.mockClear() - }) - test('mapping', () => { - if (value?.error) { - expect(mapField(field, version)(input)).toBeUndefined() - return - } - expect(mapField(field, version)(input)).toEqual(value) - }) - - test('failures', () => { - if (!value?.error) { - expect(core.setFailed).not.toHaveBeenCalled() - return - } - - mapField(field, version)(input) - expect(core.setFailed).toHaveBeenCalledTimes(1) - expect(core.setFailed).toHaveBeenLastCalledWith(value.error) - }) -}) - -describe('constants', () => { - test('mappings', () => { - expect(mappings).toMatchSnapshot() - }) - - test('convenienceFields', () => { - expect(convenienceFields).toMatchSnapshot() - }) - - test('schemaFields', () => { - expect(schemaFields).toMatchSnapshot() - }) -}) diff --git a/__tests__/lib/fieldMappings.test.cjs b/__tests__/lib/fieldMappings.test.cjs deleted file mode 100644 index 8e3e16b..0000000 --- a/__tests__/lib/fieldMappings.test.cjs +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @fileoverview This test covers all of the field mappings across versions. - * @jest-environment node - * @group ci - * @author Mike Stemle - **/ - -const core = require('@actions/core') -const { - mappings, - convenienceFields, - schemaFields, - mapField, -} = require('../../lib/fieldMappings') - -describe('constants', () => { - test('mappings', () => { - expect(mappings).toMatchSnapshot() - }) - - test('convenienceFields', () => { - expect(convenienceFields).toMatchSnapshot() - }) - - test('schemaFields', () => { - expect(schemaFields).toMatchSnapshot() - }) - - test('mapField for invalid field', () => { - core.setFailed.mockReset() - core.setFailed.mockClear() - mapField('v2', 'invalid-field', 'test')('foo:bar') - expect(core.setFailed).toHaveBeenCalledTimes(1) - core.setFailed.mockReset() - core.setFailed.mockClear() - }) -}) diff --git a/__tests__/lib/input-expander.test.cjs b/__tests__/lib/input-expander.test.cjs index b266ea1..e135cb9 100644 --- a/__tests__/lib/input-expander.test.cjs +++ b/__tests__/lib/input-expander.test.cjs @@ -1,6 +1,19 @@ -const { expandObjectInputs } = require('../../lib/input-expander') +const { + logPass, + parseSafely, + FailedParse, + expandObjectInputs, + parseYamlExpectArray, + parseYamlExpectObject, + parseYamlExpectDatadogTags, + isNothing, + combineValues, +} = require('../../lib/input-expander') -describe('input-expander.js', () => { +describe('input-expander.cjs - parsers', () => { + test('#parseSafely()', () => { + expect(parseSafely('---\n- \nnull\n')).toBe(FailedParse) + }) test('#expandObjectInputs() - empty input', () => { // Let's try empty strings const testText = '' @@ -65,12 +78,134 @@ nested-yaml: }, }) }) + test('#parseYamlExpectArray() - single element', () => { + const yamlSingleElement = 'foo' + expect(parseYamlExpectArray(yamlSingleElement)).toEqual(['foo']) + }) + + test('#parseYamlExpectArray() - multiple elements', () => { + const yamlMultiElement = ` +- foo +- bar +` + expect(parseYamlExpectArray(yamlMultiElement)).toEqual(['foo', 'bar']) + }) + + test('#parseYamlExpectObject() - empty', () => { + expect(parseYamlExpectObject(null)).toEqual({}) + }) + + test('#parseYamlExpectObject() - single element', () => { + const yamlSingleElement = 'foo' + expect(parseYamlExpectObject(yamlSingleElement)).toEqual({ value: 'foo' }) + }) + + test('#parseYamlExpectObject() - multiple elements', () => { + const yamlMultiElement = ` +foo: bar +a: b +` + expect(parseYamlExpectObject(yamlMultiElement)).toEqual({ + foo: 'bar', + a: 'b', + }) + }) + + test('#parseYamlExpectDatadogTags() - mix-and-match', () => { + const input = ` +- plain:nospaces +- compat1: space-in-value +- compat2 : space-in-both +- number_value : 42 +` + const expectedTags = [ + 'plain:nospaces', + 'compat1:space-in-value', + 'compat2:space-in-both', + 'number_value:42', + ] + + expect(parseYamlExpectDatadogTags(input)).toEqual(expectedTags) + }) }) -describe('Making sure that inputs match the schema.', () => { - // Do some tests which will validate the schema against the parsed - // input object. - test('#expandObjectInputs() - small schema validation', () => { - // Let's just try to expand some inputs. +describe('input-expander.cjs - utilities', () => { + test.each([ + { value: {}, outcome: true }, + { value: [], outcome: true }, + { value: null, outcome: true }, + { value: undefined, outcome: true }, + { value: [0], outcome: false }, + { value: new Date(), outcome: false }, + { value: 0, outcome: false }, + { value: 1, outcome: false }, + { value: { a: undefined }, outcome: false }, + ])('#isNothing($value) should be $outcome', ({ value, outcome }) => { + expect(isNothing(value)).toBe(outcome) }) + + test.each([ + { + label: 'no-op for undefined value', + value: undefined, + key: 'b', + target: { a: [1] }, + expected: { a: [1] }, + }, + { + label: 'no-op for empty array', + value: [], + key: 'b', + target: { a: [1] }, + expected: { a: [1] }, + }, + { + label: 'add new key to object', + value: 1, + key: 'b', + target: { a: [1] }, + expected: { a: [1], b: 1 }, + }, + { + label: 'merge array on existing key', + value: [1], + key: 'a', + target: { a: [1] }, + expected: { a: [1, 1] }, + }, + { + label: 'merge object on existing key', + value: { b: 1 }, + key: 'val', + target: { val: { a: 1 } }, + expected: { val: { a: 1, b: 1 } }, + }, + ])( + '#combineValues($value, $key, ...) - $label', + ({ value, key, target, expected }) => { + expect(combineValues(value, key, target)).toEqual(expected) + }, + ) + + test.each([ + { + label: 'Not an array', + value: 1, + key: 'foo', + target: { foo: [0] }, + match: /^Value.*?Array.*?$/, + }, + { + label: 'Not an object', + value: 1, + key: 'foo', + target: { foo: { a: 0 } }, + match: /^Value.*?Object.*?$/, + }, + ])( + '#combineValues($value, $key, ...) - Error case: $label', + ({ value, key, target, match }) => { + expect(() => combineValues(value, key, target)).toThrow(match) + }, + ) }) diff --git a/__tests__/lib/input-to-registry-document.test.cjs b/__tests__/lib/input-to-registry-document.test.cjs index ad90b4c..b1b1981 100644 --- a/__tests__/lib/input-to-registry-document.test.cjs +++ b/__tests__/lib/input-to-registry-document.test.cjs @@ -11,6 +11,7 @@ const core = require('@actions/core') const Ajv = require('ajv') const ddSchema_v2 = require('../data/datadog-service-catalog-schema-v2.json') const ddSchema_v2_1 = require('../data/datadog-service-catalog-schema-v2.1.json') +const ddSchema_v2_2 = require('../data/datadog-service-catalog-schema-v2.2.json') const validate_v2 = new Ajv({ strict: false, validateFormats: false }).compile( ddSchema_v2, ) @@ -18,6 +19,10 @@ const validate_v2_1 = new Ajv({ strict: false, validateFormats: false, }).compile(ddSchema_v2_1) +const validate_v2_2 = new Ajv({ + strict: false, + validateFormats: false, +}).compile(ddSchema_v2_2) const { inputsToRegistryDocument, @@ -69,7 +74,7 @@ contacts: | ` core.__setInputsObject(YAML.parse(testInput)) - const inputs = await inputsToRegistryDocument() + const inputs = inputsToRegistryDocument() expect(validate_v2(inputs)).toStrictEqual(true) if (validate_v2.errors) { console.log(validate_v2.errors) @@ -115,7 +120,7 @@ integrations: | ` core.__setInputsObject(YAML.parse(testInput)) - const inputs = await inputsToRegistryDocument() + const inputs = inputsToRegistryDocument() expect(validate_v2(inputs)).toStrictEqual(true) if (validate_v2.errors) { console.log(validate_v2.errors) @@ -147,7 +152,7 @@ integrations: | ` core.__setInputsObject(YAML.parse(testInput)) - const inputs = await inputsToRegistryDocument() + const inputs = inputsToRegistryDocument() expect(validate_v2(inputs)).toStrictEqual(true) if (!validate_v2(inputs)) { console.log(validate_v2_1.errors) @@ -204,7 +209,7 @@ contacts: | ` core.__setInputsObject(YAML.parse(testInput)) - const inputs = await inputsToRegistryDocument() + const inputs = inputsToRegistryDocument() if (!validate_v2_1(inputs)) { console.log(validate_v2_1.errors) } @@ -253,7 +258,7 @@ integrations: | ` core.__setInputsObject(YAML.parse(testInput)) - const inputs = await inputsToRegistryDocument() + const inputs = inputsToRegistryDocument() if (!validate_v2_1(inputs)) { console.log(validate_v2_1.errors) } @@ -285,7 +290,7 @@ integrations: | ` core.__setInputsObject(YAML.parse(testInput)) - const inputs = await inputsToRegistryDocument() + const inputs = inputsToRegistryDocument() if (!validate_v2_1(inputs)) { console.log(validate_v2_1.errors) } diff --git a/__tests__/lib/schemaVersions.test.cjs b/__tests__/lib/schemaVersions.test.cjs index cd60840..a64a335 100644 --- a/__tests__/lib/schemaVersions.test.cjs +++ b/__tests__/lib/schemaVersions.test.cjs @@ -1,12 +1,15 @@ const subject = require('../../lib/schemaVersions.cjs') describe('lib/schemaVersions.cjs#inputMapperByVersion()', () => { - test('v2 mapper', () => { - const mapper = subject.inputMapperByVersion('v2') + test.each([{ version: 'v2' }, { version: 'v2.1' }, { version: 'v2.2' }])( + '$version mapper', + ({ version }) => { + const mapper = subject.inputMapperByVersion(version) - expect(mapper).toBeInstanceOf(Function) - expect(mapper).not.toBe(subject._test.defaultMapper) - }) + expect(mapper).toBeInstanceOf(Function) + expect(mapper).not.toBe(subject._test.defaultMapper) + }, + ) test('default mapper', () => { const mapper = subject.inputMapperByVersion('blah') diff --git a/__tests__/lib/schemaVersions/__snapshots__/v2.1.test.cjs.snap b/__tests__/lib/schemaVersions/__snapshots__/v2.1.test.cjs.snap new file mode 100644 index 0000000..b47d72a --- /dev/null +++ b/__tests__/lib/schemaVersions/__snapshots__/v2.1.test.cjs.snap @@ -0,0 +1,147 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`lib/schemaVersions/v2.1.cjs#mapInputs() #mapInputs() - Merging in some convenience fields 1`] = ` +{ + "contacts": [ + { + "contact": "dba-team-name-here@fakeemaildomainthatdoesntexist.com", + "name": "DBA Team Email Alias", + "type": "email", + }, + { + "contact": "atotallydifferentemail@fakeemaildomainthatdoesntexist.com", + "name": "Primary Email", + "type": "email", + }, + { + "contact": "https://fakeorg.slack.com/archives/A0000000000", + "name": "Primary Slack Channel", + "type": "slack", + }, + { + "contact": "https://fakeorg.slack.com/archives/A0000000001", + "name": "Support Channel", + "type": "slack", + }, + ], + "integrations": { + "opsgenie": { + "service-url": "https://www.opsgenies.com/service/123e4567-e89b-12d3-a456-426614174000", + }, + "pagerduty": { + "service-url": "https://my-org.pagerduty.com/service-directory/PMyService", + }, + }, + "links": [ + { + "name": "Primary Repository", + "type": "repo", + "url": "https://github.com/arcxp/datadog-service-catalog-metadata-provider", + }, + ], + "schema-version": "v2.1", + "tags": [ + "application:GitHub Action Config Test", + ], + "team": "Team Name Here", +} +`; + +exports[`lib/schemaVersions/v2.1.cjs#mapSchemaFields() #mapSchemaFields() - Full schema fields set 1`] = ` +{ + "application": "Schema Version v2.1 Test", + "contacts": [ + { + "contact": "dba-team-name-here@fakeemaildomainthatdoesntexist.com", + "name": "DBA Team Email Alias", + "type": "email", + }, + ], + "dd-service": "schemaVersions-v2.1-test", + "description": "This is just a test", + "integrations": { + "opsgenie": { + "region": "US", + "service-url": "https://yourorghere.app.opsgenie.com/service/00000000-0000-0000-0000-000000000000", + }, + "pagerduty": { + "service-url": "https://my-org.pagerduty.com/service-directory/PMyService", + }, + }, + "lifecycle": "production", + "links": [ + { + "name": "AMI Version Status Dashboard", + "type": "dashboard", + "url": "https://thisisanentirelyfakeurl.seriouslythisisafakehostname.com/dashboard", + }, + { + "name": "GitHub Actions!", + "type": "doc", + "url": "https://github.com/features/actions", + }, + { + "name": "@actions/toolkit", + "type": "repo", + "url": "https://github.com/actions/toolkit", + }, + ], + "schema-version": "v2.1", + "tags": [ + "application:GitHub Action Config Test", + "env:prod", + "infrastructure:serverless", + "language:nodejs", + "other:value", + ], + "team": "Team Name Here", + "tier": "p0", +} +`; + +exports[`lib/schemaVersions/v2.1.cjs#mapSchemaFields() #mapSchemaFields() - Partial schema fields set 1`] = ` +{ + "contacts": [ + { + "contact": "dba-team-name-here@fakeemaildomainthatdoesntexist.com", + "name": "DBA Team Email Alias", + "type": "email", + }, + ], + "integrations": { + "opsgenie": { + "region": "US", + "service-url": "https://yourorghere.app.opsgenie.com/service/00000000-0000-0000-0000-000000000000", + }, + "pagerduty": { + "service-url": "https://my-org.pagerduty.com/service-directory/PMyService", + }, + }, + "links": [ + { + "name": "AMI Version Status Dashboard", + "type": "dashboard", + "url": "https://thisisanentirelyfakeurl.seriouslythisisafakehostname.com/dashboard", + }, + { + "name": "GitHub Actions!", + "type": "doc", + "url": "https://github.com/features/actions", + }, + { + "name": "@actions/toolkit", + "type": "repo", + "url": "https://github.com/actions/toolkit", + }, + ], + "schema-version": "v2.1", + "tags": [ + "application:GitHub Action Config Test", + "env:prod", + "infrastructure:serverless", + "language:nodejs", + "other:value", + ], + "team": "Team Name Here", +} +`; diff --git a/__tests__/lib/schemaVersions/__snapshots__/v2.2.test.cjs.snap b/__tests__/lib/schemaVersions/__snapshots__/v2.2.test.cjs.snap new file mode 100644 index 0000000..e4a3ff5 --- /dev/null +++ b/__tests__/lib/schemaVersions/__snapshots__/v2.2.test.cjs.snap @@ -0,0 +1,152 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`lib/schemaVersions/v2.2.cjs#mapInputs() #mapInputs() - Merging in some convenience fields 1`] = ` +{ + "contacts": [ + { + "contact": "dba-team-name-here@fakeemaildomainthatdoesntexist.com", + "name": "DBA Team Email Alias", + "type": "email", + }, + { + "contact": "atotallydifferentemail@fakeemaildomainthatdoesntexist.com", + "name": "Primary Email", + "type": "email", + }, + { + "contact": "https://fakeorg.slack.com/archives/A0000000000", + "name": "Primary Slack Channel", + "type": "slack", + }, + { + "contact": "https://fakeorg.slack.com/archives/A0000000001", + "name": "Support Channel", + "type": "slack", + }, + ], + "integrations": { + "opsgenie": { + "service-url": "https://www.opsgenies.com/service/123e4567-e89b-12d3-a456-426614174000", + }, + "pagerduty": { + "service-url": "https://my-org.pagerduty.com/service-directory/PMyService", + }, + }, + "links": [ + { + "name": "Primary Repository", + "type": "repo", + "url": "https://github.com/arcxp/datadog-service-catalog-metadata-provider", + }, + ], + "schema-version": "v2.2", + "tags": [ + "application:GitHub Action Config Test", + ], + "team": "Team Name Here", +} +`; + +exports[`lib/schemaVersions/v2.2.cjs#mapSchemaFields() #mapSchemaFields() - Full schema fields set 1`] = ` +{ + "application": "Schema Version v2.2 Test", + "contacts": [ + { + "contact": "dba-team-name-here@fakeemaildomainthatdoesntexist.com", + "name": "DBA Team Email Alias", + "type": "email", + }, + ], + "dd-service": "schemaVersions-v2.2-test", + "description": "This is just a test", + "integrations": { + "opsgenie": { + "region": "US", + "service-url": "https://yourorghere.app.opsgenie.com/service/00000000-0000-0000-0000-000000000000", + }, + "pagerduty": { + "service-url": "https://my-org.pagerduty.com/service-directory/PMyService", + }, + }, + "languages": [ + "javascript", + "markdown", + ], + "lifecycle": "production", + "links": [ + { + "name": "AMI Version Status Dashboard", + "type": "dashboard", + "url": "https://thisisanentirelyfakeurl.seriouslythisisafakehostname.com/dashboard", + }, + { + "name": "GitHub Actions!", + "type": "doc", + "url": "https://github.com/features/actions", + }, + { + "name": "@actions/toolkit", + "type": "repo", + "url": "https://github.com/actions/toolkit", + }, + ], + "schema-version": "v2.2", + "tags": [ + "application:GitHub Action Config Test", + "env:prod", + "infrastructure:serverless", + "language:nodejs", + "other:value", + ], + "team": "Team Name Here", + "tier": "p0", + "type": "function", +} +`; + +exports[`lib/schemaVersions/v2.2.cjs#mapSchemaFields() #mapSchemaFields() - Partial schema fields set 1`] = ` +{ + "contacts": [ + { + "contact": "dba-team-name-here@fakeemaildomainthatdoesntexist.com", + "name": "DBA Team Email Alias", + "type": "email", + }, + ], + "integrations": { + "opsgenie": { + "region": "US", + "service-url": "https://yourorghere.app.opsgenie.com/service/00000000-0000-0000-0000-000000000000", + }, + "pagerduty": { + "service-url": "https://my-org.pagerduty.com/service-directory/PMyService", + }, + }, + "links": [ + { + "name": "AMI Version Status Dashboard", + "type": "dashboard", + "url": "https://thisisanentirelyfakeurl.seriouslythisisafakehostname.com/dashboard", + }, + { + "name": "GitHub Actions!", + "type": "doc", + "url": "https://github.com/features/actions", + }, + { + "name": "@actions/toolkit", + "type": "repo", + "url": "https://github.com/actions/toolkit", + }, + ], + "schema-version": "v2.2", + "tags": [ + "application:GitHub Action Config Test", + "env:prod", + "infrastructure:serverless", + "language:nodejs", + "other:value", + ], + "team": "Team Name Here", +} +`; diff --git a/__tests__/lib/schemaVersions/__snapshots__/v2.test.cjs.snap b/__tests__/lib/schemaVersions/__snapshots__/v2.test.cjs.snap index 3f25e5a..9c476be 100644 --- a/__tests__/lib/schemaVersions/__snapshots__/v2.test.cjs.snap +++ b/__tests__/lib/schemaVersions/__snapshots__/v2.test.cjs.snap @@ -24,6 +24,12 @@ exports[`lib/schemaVersions/v2.cjs#mapInputs() #mapInputs() - Merging in some co "type": "slack", }, ], + "extensions": [ + { + "bar": "bar", + "name": "foo", + }, + ], "integrations": { "opsgenie": { "service-url": "https://www.opsgenies.com/service/123e4567-e89b-12d3-a456-426614174000", @@ -61,6 +67,12 @@ exports[`lib/schemaVersions/v2.cjs#mapSchemaFields() #mapSchemaFields() - Full s "url": "https://github.com/features/actions", }, ], + "extensions": [ + { + "bar": "bar", + "name": "foo", + }, + ], "integrations": { "opsgenie": { "region": "US", @@ -110,6 +122,12 @@ exports[`lib/schemaVersions/v2.cjs#mapSchemaFields() #mapSchemaFields() - Partia "url": "https://github.com/features/actions", }, ], + "extensions": [ + { + "bar": "bar", + "name": "foo", + }, + ], "integrations": { "opsgenie": { "region": "US", diff --git a/__tests__/lib/schemaVersions/v2.1.test.cjs b/__tests__/lib/schemaVersions/v2.1.test.cjs new file mode 100644 index 0000000..d99af58 --- /dev/null +++ b/__tests__/lib/schemaVersions/v2.1.test.cjs @@ -0,0 +1,155 @@ +const YAML = require('yaml') +const core = require('@actions/core') +const subject = require('../../../lib/schemaVersions/v2.1.cjs') + +describe('lib/schemaVersions/v2.1.cjs#mapSchemaFields()', () => { + test('#mapSchemaFields() - Full schema fields set', () => { + const testInput = ` +--- +schema-version: something-crazy-that-is-ignored +service-name: schemaVersions-v2.1-test +team: Team Name Here +application: Schema Version v2.1 Test +description: This is just a test +tier: p0 +lifecycle: production +tags: | + - 'application:GitHub Action Config Test' + - env:prod + - infrastructure:serverless + - language:nodejs + - other : value +links: | + - name: AMI Version Status Dashboard + url: https://thisisanentirelyfakeurl.seriouslythisisafakehostname.com/dashboard + type: dashboard + - name: GitHub Actions! + url: https://github.com/features/actions + type: doc + - url: https://github.com/actions/toolkit + type: repo + name: "@actions/toolkit" +integrations: | + opsgenie: + service-url: https://yourorghere.app.opsgenie.com/service/00000000-0000-0000-0000-000000000000 + region: US + pagerduty: + service-url: https://my-org.pagerduty.com/service-directory/PMyService +contacts: | + - name: DBA Team Email Alias + type: email + contact: dba-team-name-here@fakeemaildomainthatdoesntexist.com + ` + // Full set of fields + core.__setInputsObject(YAML.parse(testInput)) + const inputs = subject._test.mapSchemaFields(core) + expect(inputs).toMatchSnapshot() + expect(inputs['dd-service']).toEqual('schemaVersions-v2.1-test') + }) + + test('#mapSchemaFields() - Partial schema fields set', () => { + const testInput = ` +--- +schema-version: something-crazy-that-is-ignored +team: Team Name Here +tags: | + - 'application:GitHub Action Config Test' + - env:prod + - infrastructure:serverless + - language:nodejs + - other : value +links: | + - name: AMI Version Status Dashboard + url: https://thisisanentirelyfakeurl.seriouslythisisafakehostname.com/dashboard + type: dashboard + - name: GitHub Actions! + url: https://github.com/features/actions + type: doc + - url: https://github.com/actions/toolkit + type: repo + name: "@actions/toolkit" +integrations: | + opsgenie: + service-url: https://yourorghere.app.opsgenie.com/service/00000000-0000-0000-0000-000000000000 + region: US + pagerduty: + service-url: https://my-org.pagerduty.com/service-directory/PMyService +contacts: | + - name: DBA Team Email Alias + type: email + contact: dba-team-name-here@fakeemaildomainthatdoesntexist.com + ` + // Full set of fields + core.__setInputsObject(YAML.parse(testInput)) + const inputs = subject._test.mapSchemaFields(core) + expect(inputs).toMatchSnapshot() + expect(inputs['dd-service']).toBeUndefined() + }) +}) + +describe('lib/schemaVersions/v2.1.cjs#mapInputs()', () => { + test('#mapInputs() - Merging in some convenience fields', () => { + const testInput = ` +--- +schema-version: something-crazy-that-is-ignored +team: Team Name Here +email: atotallydifferentemail@fakeemaildomainthatdoesntexist.com +slack: 'https://fakeorg.slack.com/archives/A0000000000' +slack-support-channel: 'https://fakeorg.slack.com/archives/A0000000001' +repo: https://github.com/arcxp/datadog-service-catalog-metadata-provider +tags: | + - 'application:GitHub Action Config Test' +contacts: | + - name: DBA Team Email Alias + type: email + contact: dba-team-name-here@fakeemaildomainthatdoesntexist.com +pagerduty: https://my-org.pagerduty.com/service-directory/PMyService +opsgenie: https://www.opsgenies.com/service/123e4567-e89b-12d3-a456-426614174000 + ` + // Full set of fields + core.__setInputsObject(YAML.parse(testInput)) + const inputs = subject.mapInputs(core) + expect(inputs).toMatchSnapshot() + expect(inputs.contacts).toEqual([ + { + contact: 'dba-team-name-here@fakeemaildomainthatdoesntexist.com', + name: 'DBA Team Email Alias', + type: 'email', + }, + { + contact: 'atotallydifferentemail@fakeemaildomainthatdoesntexist.com', + name: 'Primary Email', + type: 'email', + }, + { + name: 'Primary Slack Channel', + contact: 'https://fakeorg.slack.com/archives/A0000000000', + type: 'slack', + }, + { + name: 'Support Channel', + contact: 'https://fakeorg.slack.com/archives/A0000000001', + type: 'slack', + }, + ]) + + expect(inputs.links).toEqual([ + { + url: 'https://github.com/arcxp/datadog-service-catalog-metadata-provider', + name: 'Primary Repository', + type: 'repo', + }, + ]) + + expect(inputs.integrations).toEqual({ + pagerduty: { + 'service-url': + 'https://my-org.pagerduty.com/service-directory/PMyService', + }, + opsgenie: { + 'service-url': + 'https://www.opsgenies.com/service/123e4567-e89b-12d3-a456-426614174000', + }, + }) + }) +}) diff --git a/__tests__/lib/schemaVersions/v2.2.test.cjs b/__tests__/lib/schemaVersions/v2.2.test.cjs new file mode 100644 index 0000000..1d378ae --- /dev/null +++ b/__tests__/lib/schemaVersions/v2.2.test.cjs @@ -0,0 +1,159 @@ +const YAML = require('yaml') +const core = require('@actions/core') +const subject = require('../../../lib/schemaVersions/v2.2.cjs') + +describe('lib/schemaVersions/v2.2.cjs#mapSchemaFields()', () => { + test('#mapSchemaFields() - Full schema fields set', () => { + const testInput = ` +--- +schema-version: something-crazy-that-is-ignored +service-name: schemaVersions-v2.2-test +team: Team Name Here +application: Schema Version v2.2 Test +description: This is just a test +type: function +languages: | + - javascript + - markdown +tier: p0 +lifecycle: production +tags: | + - 'application:GitHub Action Config Test' + - env:prod + - infrastructure:serverless + - language:nodejs + - other : value +links: | + - name: AMI Version Status Dashboard + url: https://thisisanentirelyfakeurl.seriouslythisisafakehostname.com/dashboard + type: dashboard + - name: GitHub Actions! + url: https://github.com/features/actions + type: doc + - url: https://github.com/actions/toolkit + type: repo + name: "@actions/toolkit" +integrations: | + opsgenie: + service-url: https://yourorghere.app.opsgenie.com/service/00000000-0000-0000-0000-000000000000 + region: US + pagerduty: + service-url: https://my-org.pagerduty.com/service-directory/PMyService +contacts: | + - name: DBA Team Email Alias + type: email + contact: dba-team-name-here@fakeemaildomainthatdoesntexist.com + ` + // Full set of fields + core.__setInputsObject(YAML.parse(testInput)) + const inputs = subject._test.mapSchemaFields(core) + expect(inputs).toMatchSnapshot() + expect(inputs['dd-service']).toEqual('schemaVersions-v2.2-test') + }) + + test('#mapSchemaFields() - Partial schema fields set', () => { + const testInput = ` +--- +schema-version: something-crazy-that-is-ignored +team: Team Name Here +tags: | + - 'application:GitHub Action Config Test' + - env:prod + - infrastructure:serverless + - language:nodejs + - other : value +links: | + - name: AMI Version Status Dashboard + url: https://thisisanentirelyfakeurl.seriouslythisisafakehostname.com/dashboard + type: dashboard + - name: GitHub Actions! + url: https://github.com/features/actions + type: doc + - url: https://github.com/actions/toolkit + type: repo + name: "@actions/toolkit" +integrations: | + opsgenie: + service-url: https://yourorghere.app.opsgenie.com/service/00000000-0000-0000-0000-000000000000 + region: US + pagerduty: + service-url: https://my-org.pagerduty.com/service-directory/PMyService +contacts: | + - name: DBA Team Email Alias + type: email + contact: dba-team-name-here@fakeemaildomainthatdoesntexist.com + ` + // Full set of fields + core.__setInputsObject(YAML.parse(testInput)) + const inputs = subject._test.mapSchemaFields(core) + expect(inputs).toMatchSnapshot() + expect(inputs['dd-service']).toBeUndefined() + }) +}) + +describe('lib/schemaVersions/v2.2.cjs#mapInputs()', () => { + test('#mapInputs() - Merging in some convenience fields', () => { + const testInput = ` +--- +schema-version: something-crazy-that-is-ignored +team: Team Name Here +email: atotallydifferentemail@fakeemaildomainthatdoesntexist.com +slack: 'https://fakeorg.slack.com/archives/A0000000000' +slack-support-channel: 'https://fakeorg.slack.com/archives/A0000000001' +repo: https://github.com/arcxp/datadog-service-catalog-metadata-provider +tags: | + - 'application:GitHub Action Config Test' +contacts: | + - name: DBA Team Email Alias + type: email + contact: dba-team-name-here@fakeemaildomainthatdoesntexist.com +pagerduty: https://my-org.pagerduty.com/service-directory/PMyService +opsgenie: https://www.opsgenies.com/service/123e4567-e89b-12d3-a456-426614174000 + ` + // Full set of fields + core.__setInputsObject(YAML.parse(testInput)) + const inputs = subject.mapInputs(core) + expect(inputs).toMatchSnapshot() + expect(inputs.contacts).toEqual([ + { + contact: 'dba-team-name-here@fakeemaildomainthatdoesntexist.com', + name: 'DBA Team Email Alias', + type: 'email', + }, + { + contact: 'atotallydifferentemail@fakeemaildomainthatdoesntexist.com', + name: 'Primary Email', + type: 'email', + }, + { + name: 'Primary Slack Channel', + contact: 'https://fakeorg.slack.com/archives/A0000000000', + type: 'slack', + }, + { + name: 'Support Channel', + contact: 'https://fakeorg.slack.com/archives/A0000000001', + type: 'slack', + }, + ]) + + expect(inputs.links).toEqual([ + { + url: 'https://github.com/arcxp/datadog-service-catalog-metadata-provider', + name: 'Primary Repository', + type: 'repo', + }, + ]) + + expect(inputs.integrations).toEqual({ + pagerduty: { + 'service-url': + 'https://my-org.pagerduty.com/service-directory/PMyService', + }, + opsgenie: { + 'service-url': + 'https://www.opsgenies.com/service/123e4567-e89b-12d3-a456-426614174000', + }, + }) + }) +}) diff --git a/__tests__/lib/schemaVersions/v2.test.cjs b/__tests__/lib/schemaVersions/v2.test.cjs index 4000ebe..98ae1ea 100644 --- a/__tests__/lib/schemaVersions/v2.test.cjs +++ b/__tests__/lib/schemaVersions/v2.test.cjs @@ -36,6 +36,9 @@ contacts: | - name: DBA Team Email Alias type: email contact: dba-team-name-here@fakeemaildomainthatdoesntexist.com +extensions: | + - name: foo + bar: bar ` // Full set of fields core.__setInputsObject(YAML.parse(testInput)) @@ -76,6 +79,9 @@ contacts: | - name: DBA Team Email Alias type: email contact: dba-team-name-here@fakeemaildomainthatdoesntexist.com +extensions: | + - name: foo + bar: bar ` // Full set of fields core.__setInputsObject(YAML.parse(testInput)) @@ -103,6 +109,9 @@ contacts: | contact: dba-team-name-here@fakeemaildomainthatdoesntexist.com pagerduty: https://my-org.pagerduty.com/service-directory/PMyService opsgenie: https://www.opsgenies.com/service/123e4567-e89b-12d3-a456-426614174000 +extensions: | + - name: foo + bar: bar ` // Full set of fields core.__setInputsObject(YAML.parse(testInput)) diff --git a/__tests__/self-workflow-validation.test.cjs b/__tests__/self-workflow-validation.test.cjs index 51e736f..01a1380 100644 --- a/__tests__/self-workflow-validation.test.cjs +++ b/__tests__/self-workflow-validation.test.cjs @@ -36,6 +36,9 @@ const { const Ajv = require('ajv') describe('Validate for schema v2', () => { + beforeAll(() => { + core.__resetInputsObject() + }) const ddSchema_v2 = require('./data/datadog-service-catalog-schema-v2.json') const validate_v2 = new Ajv({ strict: false, @@ -52,9 +55,11 @@ describe('Validate for schema v2', () => { )?.with core.__setInputsObject(parsedWorkflow) - const serviceDefinition = await inputsToRegistryDocument() + const serviceDefinition = inputsToRegistryDocument() - console.log({ parsedWorkflow, serviceDefinition }) + console.log( + JSON.stringify({ parsedWorkflow, serviceDefinition }, undefined, 2), + ) const isValid = validate_v2(serviceDefinition) if (!isValid) { console.log(validate_v2.errors) @@ -65,6 +70,9 @@ describe('Validate for schema v2', () => { }) describe('Validate for schema v2.1', () => { + beforeAll(() => { + core.__resetInputsObject() + }) const ddSchema_v2_1 = require('./data/datadog-service-catalog-schema-v2.1.json') const validate_v2_1 = new Ajv({ strict: false, @@ -81,7 +89,7 @@ describe('Validate for schema v2.1', () => { )?.with core.__setInputsObject(parsedWorkflow) - const serviceDefinition = await inputsToRegistryDocument() + const serviceDefinition = inputsToRegistryDocument() console.log({ parsedWorkflow, serviceDefinition }) const isValid = validate_v2_1(serviceDefinition) @@ -94,6 +102,9 @@ describe('Validate for schema v2.1', () => { }) describe('Validate for schema v2.2', () => { + beforeAll(() => { + core.__resetInputsObject() + }) const ddSchema_v2_2 = require('./data/datadog-service-catalog-schema-v2.2.json') const validate_v2_2 = new Ajv({ strict: false, @@ -110,7 +121,7 @@ describe('Validate for schema v2.2', () => { )?.with core.__setInputsObject(parsedWorkflow) - const serviceDefinition = await inputsToRegistryDocument() + const serviceDefinition = inputsToRegistryDocument() console.log({ parsedWorkflow, serviceDefinition }) const isValid = validate_v2_2(serviceDefinition) diff --git a/dist/index.cjs b/dist/index.cjs index 779dae2..f3e1db7 100644 --- a/dist/index.cjs +++ b/dist/index.cjs @@ -831,7 +831,7 @@ var require_tunnel = __commonJS({ return target; } var debug; - if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) { + if (process.env['NODE_DEBUG'] && /\btunnel\b/.test(process.env['NODE_DEBUG'])) { debug = function() { var args = Array.prototype.slice.call(arguments); if (typeof args[0] === "string") { @@ -17678,10 +17678,10 @@ var require_oidc_utils = __commonJS({ return __awaiter(this, void 0, void 0, function* () { const httpclient = _OidcClient.createHttpClient(); const res = yield httpclient.getJson(id_token_url).catch((error) => { - throw new Error(`Failed to get ID Token. - + throw new Error(`Failed to get ID Token. + Error Code : ${error.statusCode} - + Error Message: ${error.message}`); }); const id_token = (_a = res.result) === null || _a === void 0 ? void 0 : _a.value; diff --git a/lib/fieldMappings.cjs b/lib/fieldMappings.cjs deleted file mode 100644 index 70df7e2..0000000 --- a/lib/fieldMappings.cjs +++ /dev/null @@ -1,359 +0,0 @@ -/** - * @file This file contains the mappings between the GitHub Actions inputs and the Datadog Service Catalog schema. - * @module lib/fieldMappings - * @author Mike Stemle - **/ - -const core = require('@actions/core') -const _ = require('lodash') -const { - expandObjectInputs, - forceArray, - forceObject, -} = require('./input-expander') - -/** - * This lets us use the same mapping function for multiple versions. - * @param {string[]} versions - The versions to use. - * @param {function} mapper - The mapping function. - * @returns {object} - The mapping object. - * @private - * @function - **/ -const useSharedMappings = (versions, mapper) => - Object.assign(...versions.map((x) => ({ [x]: mapper }))) - -/** - * This function takes an input name and a mapper function and returns a function which maps the input value to the registry document value. - * @param {string} input - The input name. - * @param {function} func - The mapping function. - * @returns {function} - The mapping function. - * @private - * @function - **/ -const mapToUsing = (input, func) => (value) => func(input, value) - -/** - * This function takes an input and a value and returns an object with the input as the key and the value as the value. - * @param {string} input - The input name. - * @param {any} value - The value. - * @returns {object} - The mapped object. - * @private - * @function - **/ -const passThru = (input, value) => ({ [input]: value }) - -/** - * This function takes an input and a YAML string and returns an object with the input as the key and the expanded YAML as the value. - * @param {string} input - The input name. - * @param {string} str - The YAML string. - * @returns {object} - The mapped object. - * @private - * @function - * @see expandObjectInputs - **/ -const simpleYamlParse = (input, str) => ({ [input]: expandObjectInputs(str) }) - -/** - * This function takes an input and a YAML string and returns an object with the input as the key and the expanded YAML as the value. The value of the object returned will _always_ be an array. - * @param {string} input - The input name. - * @param {string} str - The YAML string. - * @returns {object} - The mapped object. - * @private - * @function - * @see expandObjectInputs - * @see forceArray - **/ -const arrayYamlParse = (input, str) => ({ - [input]: forceArray(expandObjectInputs(str)), -}) - -const objectYamlParse = (input, str) => ({ - [input]: forceObject(expandObjectInputs(str)), -}) - -const versionCompatibilityError = - (field, chosenVersion, validVersions) => (_input) => - core.setFailed( - `Sorry, but the «${field}» field is not avaiable in version ${chosenVersion} of the Datadog Service Catalog schema; this field is only available in version(s): ${validVersions.join( - ',', - )}`, - ) - -/** - * This is the list of mappings which tracks which fields map to different versions in different ways. - * - Keyed by the GitHub Actions input name (action.yml) - * - Values are objects keyed with version tags - * - Values of those objects are the function which maps the input value to the registry document value. - * TODO: Add warnings for when folks try to use the wrong schema versions. - **/ -const mappings = { - 'schema-version': useSharedMappings( - ['v2', 'v2.1', 'v2.2'], - mapToUsing('schema-version', (_, value) => ({ - // We default to `v2` because later versions should specify the schema version. - 'schema-version': value ?? 'v2', - })), - ), - - 'service-name': useSharedMappings( - ['v2', 'v2.1', 'v2.2'], - mapToUsing('dd-service', passThru), - ), - - team: useSharedMappings(['v2', 'v2.1', 'v2.2'], mapToUsing('team', passThru)), - - // New in v2.1 - application: Object.assign( - { - v2: versionCompatibilityError('application', 'v2', ['v2.1']), - }, - useSharedMappings(['v2.1', 'v2.2'], mapToUsing('application', passThru)), - ), - - // New in v2.1 - description: Object.assign( - { - v2: versionCompatibilityError('description', 'v2', ['v2.1']), - }, - useSharedMappings(['v2.1', 'v2.2'], mapToUsing('description', passThru)), - ), - - // New in v2.1 - tier: Object.assign( - { - v2: versionCompatibilityError('tier', 'v2', ['v2.1']), - }, - useSharedMappings(['v2.1', 'v2.2'], mapToUsing('tier', passThru)), - ), - - // New in v2.1 - lifecycle: Object.assign( - { - v2: versionCompatibilityError('lifecycle', 'v2', ['v2.1']), - }, - useSharedMappings(['v2.1', 'v2.2'], mapToUsing('lifecycle', passThru)), - ), - - // New in v2.2 - type: { - v2: versionCompatibilityError('type', 'v2', ['v2.2']), - 'v2.1': versionCompatibilityError('type', 'v2.1', ['v2.2']), - 'v2.2': mapToUsing('type', passThru), - }, - - // New in v2.2 - languages: { - v2: versionCompatibilityError('languages', 'v2', ['v2.2']), - 'v2.1': versionCompatibilityError('languages', 'v2.1', ['v2.2']), - 'v2.2': mapToUsing('languages', arrayYamlParse), - }, - - contacts: useSharedMappings( - ['v2', 'v2.1', 'v2.2'], - mapToUsing('contacts', arrayYamlParse), - ), - - links: Object.assign( - { - v2: (input) => ({ - links: forceArray(expandObjectInputs(input)).map((x) => - // v2 doesn't have a provider field - _.omit(x, ['provider']), - ), - }), - }, - useSharedMappings(['v2.1', 'v2.2'], (input) => ({ - links: forceArray(expandObjectInputs(input)), - })), - ), - - // This tags setup is a little hairy, but the biggest thing - // to keep in mind is that we want a list of strings, made up - // of colon-separated values. Mercifully, this is the same - // for both v2 and v2.1. - tags: useSharedMappings(['v2', 'v2.1', 'v2.2'], (input) => ({ - tags: forceArray(expandObjectInputs(input)).map((entry) => - _.isPlainObject(entry) - ? _.join( - _.head(_.toPairs(entry)).map((x) => - // This check is so that we trim strings, but don't break - // numbers or boolean values. - typeof x === 'string' ? x.trim() : x, - ), - ':', - ) - : entry, - ), - })), - - integrations: useSharedMappings( - ['v2', 'v2.1', 'v2.2'], - mapToUsing('integrations', objectYamlParse), - ), - - docs: Object.assign( - { - v2: mapToUsing('docs', arrayYamlParse), - }, - useSharedMappings( - ['v2.1', 'v2.2'], - versionCompatibilityError('docs', 'v2.1', ['v2']), - ), - ), - - repos: Object.assign( - { - v2: mapToUsing('repos', arrayYamlParse), - }, - useSharedMappings( - ['v2.1', 'v2.2'], - versionCompatibilityError('repos', 'v2.1', ['v2']), - ), - ), - - 'ci-pipeline-fingerprints': { - v2: versionCompatibilityError('ci-pipeline-fingerprints', 'v2', ['v2.2']), - 'v2.1': versionCompatibilityError('ci-pipeline-fingerprints', 'v2.1', [ - 'v2.2', - ]), - 'v2.2': mapToUsing('ci-pipeline-fingerprints', arrayYamlParse), - }, - - extensions: useSharedMappings( - ['v2', 'v2.1', 'v2.2'], - mapToUsing('extensions', simpleYamlParse), - ), -} -Object.freeze(mappings) - -/** - * This is the list of fields which are part of the Datadog schema, in one version or another. - * @type {string[]} - **/ -const schemaFields = _.keys(mappings) -Object.freeze(schemaFields) - -const incorporateConvenienceMapping = (inputObj, doc, targetList) => { - const docCopy = !!doc ? _.cloneDeep(doc) : {} - docCopy?.[targetList] - ? docCopy[targetList].push(inputObj) - : (docCopy[targetList] = [inputObj]) - return docCopy -} - -const incorporateConvenienceMappingToObject = (inputObj, doc, targetObject) => { - const docCopy = !!doc ? _.cloneDeep(doc) : {} - docCopy?.[targetObject] - ? (docCopy[targetObject] = _.merge(docCopy[targetObject], inputObj)) - : (docCopy[targetObject] = { ...inputObj }) - return docCopy -} - -/** - * This is the list of fields which are convenience fields, which are mapped to other fields in the registry document. - * A key difference between this and `mappings` is that the mappers here take two arguments: the input value, as well as the document currently being produced. The function then returns a fresh copy of that document, mutated with the output of the mapper. This is a pure function, and does not mutate the document passed in. - * @type {Object} - **/ -const convenienceMappings = { - // These fields map into `contacts` in the registry document. - email: useSharedMappings(['v2', 'v2.1', 'v2.2'], (input, doc) => - incorporateConvenienceMapping( - { contact: input, type: 'email' }, - doc, - 'contacts', - ), - ), - - slack: useSharedMappings(['v2', 'v2.1', 'v2.2'], (input, doc) => - incorporateConvenienceMapping( - { contact: input, type: 'slack' }, - doc, - 'contacts', - ), - ), - - // These fields map into `repos` list in the registry document for v2, and into the `links` list in the registry document for v2.1. - repo: Object.assign( - { - v2: (input, doc) => - incorporateConvenienceMapping( - { name: 'Repo', url: input }, - doc, - 'repos', - ), - }, - useSharedMappings(['v2.1', 'v2.2'], (input, doc) => - incorporateConvenienceMapping( - { name: 'Repo', type: 'repo', url: input }, - doc, - 'links', - ), - ), - ), - - // These fields map into `integrations` in the registry document. - opsgenie: useSharedMappings(['v2', 'v2.1', 'v2.2'], (input, doc) => - incorporateConvenienceMappingToObject( - { opsgenie: { 'service-url': input } }, - doc, - 'integrations', - ), - ), - pagerduty: Object.assign( - { - v2: (input, doc) => - incorporateConvenienceMappingToObject( - { pagerduty: input }, - doc, - 'integrations', - ), - }, - useSharedMappings(['v2.1', 'v2.2'], (input, doc) => - incorporateConvenienceMappingToObject( - { pagerduty: { 'service-url': input } }, - doc, - 'integrations', - ), - ), - ), -} -convenienceMappings['slack-support-channel'] = convenienceMappings.slack -Object.freeze(convenienceMappings) - -core.debug({ mappings, convenienceMappings }) - -/** - * This is the list of fields which are convenience fields, which are mapped to other fields in the registry document. - * @type {string[]} - **/ -const convenienceFields = _.keys(convenienceMappings) -Object.freeze(convenienceFields) - -/** - * This is a convenience function which takes a field name and a version tag and returns a function which maps the input value to the registry document value. - * @param {string} field - The name of the field to map. - * @param {string} version - The version tag to map the field to. - * @returns {function} - A function which maps the input value to the registry document value. - * @example - * const mapField = require('./lib/fieldMappings') - * const mappedValue = mapField('team', 'v2.1')('my-team') - * `// mappedValue = { team: 'my-team' }` - * @public - * @function - **/ -const mapField = - (field, version) => - (input, doc = undefined) => - ( - mappings?.[field]?.[version] ?? - convenienceMappings?.[field]?.[version] ?? - ((_) => core.setFailed(`Unknown field: ${field}`)) - )(input, doc) - -module.exports = { - mappings, - convenienceFields, - schemaFields, - mapField, -} diff --git a/lib/input-expander.cjs b/lib/input-expander.cjs index eb21cfe..412ea56 100644 --- a/lib/input-expander.cjs +++ b/lib/input-expander.cjs @@ -13,8 +13,10 @@ // return x // } +const _ = require('lodash') const core = require('@actions/core') const YAML = require('yaml') + /** * As part of our recursive implementation, we need * to engage in a little bit of type inference. @@ -31,22 +33,6 @@ const parseSafely = (x) => { } } -/** - * As part of our recursive implementation, we need to be able to break down different types. This tells us if it's an Array. - * @returns {boolean} Whether or not the input is an array. - * @private - **/ -const isArray = Array.isArray - -/** - * As part of our recursive implementation, we need to be able to break down different types. This tells us if it's an Object. - * @param {Object} x - The input value. - * @returns {boolean} Whether or not the input is an object. - * @private - **/ -const isObject = (x) => x?.constructor === Object -// this is because `YAML.parse(1)` returns `null`. - /** * As part of our recursive implementation, we need to be able to break down different types. This tells us if it's a scalar or scalar equivalent. * @param {any} x - The input value. @@ -86,11 +72,11 @@ const _deserializeNestedStrings = (input) => { : deserializeNestedStrings(parseSafely(input)) } - if (isArray(input)) { + if (_.isArray(input)) { return input.map((x) => deserializeNestedStrings(x)) } - if (isObject(input)) { + if (_.isObject(input)) { return Object.assign( ...Object.keys(input).map((x) => ({ [x]: deserializeNestedStrings(input[x]), @@ -106,13 +92,7 @@ const _deserializeNestedStrings = (input) => { * @returns {Object} The input value with all object inputs expanded. **/ const expandObjectInputs = (str) => { - try { - return deserializeNestedStrings(str) - } catch (error) { - core.debug(`Input as <<${str}>> is not a valid YAML object.`) - core.error(error) - core.setFailed(error.message) - } + return !!str ? deserializeNestedStrings(str) : str } /** @@ -123,7 +103,7 @@ const expandObjectInputs = (str) => { * @function */ const forceArray = (input) => - Array.isArray(input) ? input : !!input ? [input] : [] + _.isArray(input) ? input : !!input ? [input] : [] /** * This function takes an input and forces it to be an object. @@ -132,10 +112,110 @@ const forceArray = (input) => * @public * @function */ -const forceObject = (input) => (typeof input === 'object' ? input : {}) +const forceObject = (input) => + _.isObject(input) ? input : _.isEmpty(input) ? {} : { value: input } + +/** + * This function takes an input and a YAML string and returns an array containing the values in the YAML string. The value of the object returned will _always_ be an array. + * @param {string} str - The YAML string. + * @returns {Array} - The mapped object. + * @public + * @function + * @see forceArray + **/ +const parseYamlExpectArray = (str) => forceArray(expandObjectInputs(str)) + +/** + * This function takes an input and a YAML string and returns an object with the input as the key and the expanded YAML as the value. The value of the object returned will _always_ be an Object. + * @param {string} str - The YAML string. + * @returns {object} - The mapped object. + * @public + * @function + * @see expandObjectInputs + * @see forceObject + **/ +const parseYamlExpectObject = (str) => forceObject(expandObjectInputs(str)) + +/** + * This function parses YAML and converts it to a list of Datadog-friendly tags. + * @param {string} str - The YAML string. + * @returns {object} - The mapped object. + * @public + * @function + **/ +const parseYamlExpectDatadogTags = (str) => + forceArray(expandObjectInputs(str)).map((entry) => + _.isPlainObject(entry) + ? _.join( + _.head(_.toPairs(entry)).map((x) => + // This check is so that we trim strings, but don't break + // numbers or boolean values. + typeof x === 'string' ? x.trim() : x, + ), + ':', + ) + : entry, + ) + +/** + * Does the value amount to nothing? This is intended to be used for omitting properties lacking user-supplied values. + * @param {any} testValue - The value to be tested + * @returns {bool} - A boolean value indicating whether the value is "nothing", and safe to disregard. + * @public + * @function + **/ +const isNothing = (testValue) => + !_.isDate(testValue) && (_.isObject(testValue) || _.isArray(testValue)) + ? _.isEmpty(testValue) + : _.isNil(testValue) + +/** + * Combine the value and the target value. **This function modifies the `target` input.** + * @param {any} value - The value you'd like to store + * @param {string} key - The key to store the value in the `target` with + * @param {Object} target - The target object into which the value shall be stored + * @returns {Object} - Returns the `target` with the value. + * @public + * @function + **/ +const combineValues = (value, key, target) => { + // Do nothing if there's no value + if (isNothing(value)) { + return target + } + + const assertType = (check, type, func) => { + if (!func(check)) { + throw new Error( + `Value «${JSON.stringify(check, undefined, 2)}» was expected to be of type «${type}», but it isn't.`, + ) + } + } + + const ref = target[key] + + if (_.isArray(ref)) { + assertType(value, 'Array', _.isArray) + target[key] = _.concat(ref, value) + } else if (_.isObject(ref)) { + assertType(value, 'Object', _.isObject) + target[key] = _.merge(ref, value) + } else { + target[key] = value + } + + return target +} module.exports = { + FailedParse, + parseSafely, expandObjectInputs, forceArray, forceObject, + parseYamlExpectArray, + parseYamlExpectObject, + parseYamlExpectDatadogTags, + isNothing, + combineValues, } diff --git a/lib/input-to-registry-document.cjs b/lib/input-to-registry-document.cjs index b3293dc..4af8373 100644 --- a/lib/input-to-registry-document.cjs +++ b/lib/input-to-registry-document.cjs @@ -8,17 +8,9 @@ * @author Mike Stemle **/ -const fs = require('fs') -const path = require('path') const core = require('@actions/core') -const _ = require('lodash') -const { - expandObjectInputs, - forceArray, - forceObject, -} = require('./input-expander') -const { mapField, convenienceFields, schemaFields } = require('./fieldMappings') +const { inputMapperByVersion } = require('./schemaVersions.cjs') /** * This function takes the inputs from the Action and converts them into a registry document for Datadog. @@ -26,59 +18,7 @@ const { mapField, convenienceFields, schemaFields } = require('./fieldMappings') * @public * @function */ -const inputsToRegistryDocument = async () => { - // This does the initial fetch of configs from the Action inputs. - // const configs = collectInputs(core, core.getInput('schema-version') ?? 'v2') - - const version = core.getInput('schema-version') ?? 'v2' - let configs = { 'schema-version': version } - - _.merge( - configs, - ...schemaFields - .filter((fieldName) => !!core.getInput(fieldName)) - .map((fieldName) => { - const mapping = mapField(fieldName, version)(core.getInput(fieldName)) - core.debug( - JSON.stringify( - { - [`INPUT:schema_field:${fieldName}`]: core.getInput(fieldName), - [`RESULT:schema_field:${fieldName}`]: mapping, - }, - undefined, - 2, - ), - ) - return mapping - }), - ) - - for (const fieldName of convenienceFields) { - const input = core.getInput(fieldName) - - if (_.isEmpty(input)) continue - - core.debug( - `Convenience field ${fieldName} for version ${version} has input: ${input}`, - ) - core.debug( - JSON.stringify( - { [`BEFORE:convenience_field:${fieldName}`]: configs }, - undefined, - 2, - ), - ) - configs = mapField(fieldName, version)(input, configs) - core.debug( - JSON.stringify( - { [`AFTER:convenience_field:${fieldName}`]: configs }, - undefined, - 2, - ), - ) - } - - return configs -} +const inputsToRegistryDocument = () => + inputMapperByVersion(core.getInput('schema-version') ?? 'v2')(core) module.exports = { inputsToRegistryDocument } diff --git a/lib/schemaVersions.cjs b/lib/schemaVersions.cjs index 1ce04dd..56ca8e4 100644 --- a/lib/schemaVersions.cjs +++ b/lib/schemaVersions.cjs @@ -1,5 +1,7 @@ const mapperMatrix = { v2: require('./schemaVersions/v2.cjs').mapInputs, + 'v2.1': require('./schemaVersions/v2.1.cjs').mapInputs, + 'v2.2': require('./schemaVersions/v2.2.cjs').mapInputs, } const defaultMapper = () => { diff --git a/lib/schemaVersions/v2.1.cjs b/lib/schemaVersions/v2.1.cjs new file mode 100644 index 0000000..c1a0706 --- /dev/null +++ b/lib/schemaVersions/v2.1.cjs @@ -0,0 +1,138 @@ +/** + * @file This file maps the GitHub Action's inputs to the v2 schema. + * @module lib/schemaVersions/v2 + * @author Mike Stemle + **/ + +const _ = require('lodash') + +const { + parseYamlExpectArray, + parseYamlExpectObject, + parseYamlExpectDatadogTags, + expandObjectInputs, + isNothing, + combineValues, +} = require('../input-expander.cjs') + +const { getConvenienceFieldValues } = require('../convenienceMappings.cjs') + +/** + * This is function maps the inputs to the output schema version for the API. + * @param {object} core - The GitHub Actions core object. + * @returns {Object} - Returns an object containing all of the mapped fields. + * @private + * @function + **/ +const mapSchemaFields = (core) => + _.omitBy( + { + // Schema version is intentionally overridden because the schema version mapping + // logic has already discovered that `v2` is the value in order to get this far. + // No need to bother looking up something we already know. + 'schema-version': 'v2.1', + 'dd-service': core.getInput('service-name'), + team: core.getInput('team'), + application: core.getInput('application'), + description: core.getInput('description'), + tier: core.getInput('tier'), + lifecycle: core.getInput('lifecycle'), + contacts: parseYamlExpectArray(core.getInput('contacts')), + links: parseYamlExpectArray(core.getInput('links')), + tags: parseYamlExpectDatadogTags(core.getInput('tags')), + integrations: parseYamlExpectObject(core.getInput('integrations')), + extensions: expandObjectInputs(core.getInput('extensions')), + }, + isNothing, + ) + +/** + * Map all relevant fields for this schema version. + * @param {object} core - The GitHub Actions core object. + * @returns {Object} - Returns an object containing all of the mapped fields. + * @public + * @function + **/ +const mapInputs = (core) => { + const convenienceValues = getConvenienceFieldValues(core) + + // This is just a shortcut because I don't want to have a big, repetitive list of + // conditionals. + const hasValuesForConvenienceKeys = (listOfKeys) => { + for (const key of listOfKeys) { + if (!!convenienceValues[key]) return true + } + + return false + } + + // Grab the schema fields + let mappedInputs = mapSchemaFields(core) + + if ( + hasValuesForConvenienceKeys(['email', 'slack', 'slack-support-channel']) + ) { + const contactAdditions = _.filter( + [ + !!convenienceValues['email'] + ? { + name: 'Primary Email', + contact: convenienceValues['email'], + type: 'email', + } + : undefined, + !!convenienceValues['slack'] + ? { + name: 'Primary Slack Channel', + contact: convenienceValues['slack'], + type: 'slack', + } + : undefined, + !!convenienceValues['slack-support-channel'] + ? { + name: 'Support Channel', + contact: convenienceValues['slack-support-channel'], + type: 'slack', + } + : undefined, + ], + (x) => !_.isNil(x), + ) + mappedInputs = combineValues(contactAdditions, 'contacts', mappedInputs) + } + + if (hasValuesForConvenienceKeys(['repo'])) { + mappedInputs = combineValues( + [ + { + url: convenienceValues['repo'], + name: 'Primary Repository', + type: 'repo', + }, + ], + 'links', + mappedInputs, + ) + } + + if (hasValuesForConvenienceKeys(['pagerduty', 'opsgenie'])) { + mappedInputs.integrations ||= {} + } + if (!!convenienceValues['pagerduty']) { + mappedInputs.integrations.pagerduty = { + 'service-url': convenienceValues['pagerduty'], + } + } + if (!!convenienceValues['opsgenie']) { + mappedInputs.integrations.opsgenie = { + 'service-url': convenienceValues['opsgenie'], + } + } + + return mappedInputs +} + +module.exports = { + _test: { mapSchemaFields }, + mapInputs, +} diff --git a/lib/schemaVersions/v2.2.cjs b/lib/schemaVersions/v2.2.cjs new file mode 100644 index 0000000..2833a02 --- /dev/null +++ b/lib/schemaVersions/v2.2.cjs @@ -0,0 +1,140 @@ +/** + * @file This file maps the GitHub Action's inputs to the v2 schema. + * @module lib/schemaVersions/v2 + * @author Mike Stemle + **/ + +const _ = require('lodash') + +const { + parseYamlExpectArray, + parseYamlExpectObject, + parseYamlExpectDatadogTags, + expandObjectInputs, + isNothing, + combineValues, +} = require('../input-expander.cjs') + +const { getConvenienceFieldValues } = require('../convenienceMappings.cjs') + +/** + * This is function maps the inputs to the output schema version for the API. + * @param {object} core - The GitHub Actions core object. + * @returns {Object} - Returns an object containing all of the mapped fields. + * @private + * @function + **/ +const mapSchemaFields = (core) => + _.omitBy( + { + // Schema version is intentionally overridden because the schema version mapping + // logic has already discovered that `v2` is the value in order to get this far. + // No need to bother looking up something we already know. + 'schema-version': 'v2.2', + 'dd-service': core.getInput('service-name'), + team: core.getInput('team'), + application: core.getInput('application'), + description: core.getInput('description'), + type: core.getInput('type'), + languages: parseYamlExpectArray(core.getInput('languages')), + tier: core.getInput('tier'), + lifecycle: core.getInput('lifecycle'), + contacts: parseYamlExpectArray(core.getInput('contacts')), + links: parseYamlExpectArray(core.getInput('links')), + tags: parseYamlExpectDatadogTags(core.getInput('tags')), + integrations: parseYamlExpectObject(core.getInput('integrations')), + extensions: expandObjectInputs(core.getInput('extensions')), + }, + isNothing, + ) + +/** + * Map all relevant fields for this schema version. + * @param {object} core - The GitHub Actions core object. + * @returns {Object} - Returns an object containing all of the mapped fields. + * @public + * @function + **/ +const mapInputs = (core) => { + const convenienceValues = getConvenienceFieldValues(core) + + // This is just a shortcut because I don't want to have a big, repetitive list of + // conditionals. + const hasValuesForConvenienceKeys = (listOfKeys) => { + for (const key of listOfKeys) { + if (!!convenienceValues[key]) return true + } + + return false + } + + // Grab the schema fields + let mappedInputs = mapSchemaFields(core) + + if ( + hasValuesForConvenienceKeys(['email', 'slack', 'slack-support-channel']) + ) { + const contactAdditions = _.filter( + [ + !!convenienceValues['email'] + ? { + name: 'Primary Email', + contact: convenienceValues['email'], + type: 'email', + } + : undefined, + !!convenienceValues['slack'] + ? { + name: 'Primary Slack Channel', + contact: convenienceValues['slack'], + type: 'slack', + } + : undefined, + !!convenienceValues['slack-support-channel'] + ? { + name: 'Support Channel', + contact: convenienceValues['slack-support-channel'], + type: 'slack', + } + : undefined, + ], + (x) => !_.isNil(x), + ) + mappedInputs = combineValues(contactAdditions, 'contacts', mappedInputs) + } + + if (hasValuesForConvenienceKeys(['repo'])) { + mappedInputs = combineValues( + [ + { + url: convenienceValues['repo'], + name: 'Primary Repository', + type: 'repo', + }, + ], + 'links', + mappedInputs, + ) + } + + if (hasValuesForConvenienceKeys(['pagerduty', 'opsgenie'])) { + mappedInputs.integrations ||= {} + } + if (!!convenienceValues['pagerduty']) { + mappedInputs.integrations.pagerduty = { + 'service-url': convenienceValues['pagerduty'], + } + } + if (!!convenienceValues['opsgenie']) { + mappedInputs.integrations.opsgenie = { + 'service-url': convenienceValues['opsgenie'], + } + } + + return mappedInputs +} + +module.exports = { + _test: { mapSchemaFields }, + mapInputs, +} diff --git a/lib/schemaVersions/v2.cjs b/lib/schemaVersions/v2.cjs index b8931af..0ed2048 100644 --- a/lib/schemaVersions/v2.cjs +++ b/lib/schemaVersions/v2.cjs @@ -10,6 +10,7 @@ const { parseYamlExpectArray, parseYamlExpectObject, parseYamlExpectDatadogTags, + expandObjectInputs, isNothing, combineValues, } = require('../input-expander.cjs') @@ -38,6 +39,7 @@ const mapSchemaFields = (core) => docs: parseYamlExpectArray(core.getInput('docs')), tags: parseYamlExpectDatadogTags(core.getInput('tags')), integrations: parseYamlExpectObject(core.getInput('integrations')), + extensions: expandObjectInputs(core.getInput('extensions')), }, isNothing, ) From 7a230c95cf42db79ad4e77cd096d4729a9aacf8b Mon Sep 17 00:00:00 2001 From: "Michael D. Stemle, Jr." Date: Fri, 29 Mar 2024 20:39:42 -0400 Subject: [PATCH 4/5] Cleaning up print statements in test. --- .../lib/input-to-registry-document.test.cjs | 12 +++++----- __tests__/self-workflow-validation.test.cjs | 24 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/__tests__/lib/input-to-registry-document.test.cjs b/__tests__/lib/input-to-registry-document.test.cjs index b1b1981..470dc98 100644 --- a/__tests__/lib/input-to-registry-document.test.cjs +++ b/__tests__/lib/input-to-registry-document.test.cjs @@ -77,7 +77,7 @@ contacts: | const inputs = inputsToRegistryDocument() expect(validate_v2(inputs)).toStrictEqual(true) if (validate_v2.errors) { - console.log(validate_v2.errors) + console.error(validate_v2.errors) } expect(validate_v2.errors).toBeNull() expect(inputs).toMatchSnapshot() @@ -123,7 +123,7 @@ integrations: | const inputs = inputsToRegistryDocument() expect(validate_v2(inputs)).toStrictEqual(true) if (validate_v2.errors) { - console.log(validate_v2.errors) + console.error(validate_v2.errors) } expect(validate_v2.errors).toBeNull() expect(inputs).toMatchSnapshot() @@ -155,7 +155,7 @@ integrations: | const inputs = inputsToRegistryDocument() expect(validate_v2(inputs)).toStrictEqual(true) if (!validate_v2(inputs)) { - console.log(validate_v2_1.errors) + console.error(validate_v2_1.errors) } expect(validate_v2.errors).toBeNull() expect(inputs).toMatchSnapshot() @@ -211,7 +211,7 @@ contacts: | core.__setInputsObject(YAML.parse(testInput)) const inputs = inputsToRegistryDocument() if (!validate_v2_1(inputs)) { - console.log(validate_v2_1.errors) + console.error(validate_v2_1.errors) } expect(validate_v2_1(inputs)).toStrictEqual(true) expect(validate_v2_1.errors).toBeNull() @@ -260,7 +260,7 @@ integrations: | core.__setInputsObject(YAML.parse(testInput)) const inputs = inputsToRegistryDocument() if (!validate_v2_1(inputs)) { - console.log(validate_v2_1.errors) + console.error(validate_v2_1.errors) } expect(validate_v2_1(inputs)).toStrictEqual(true) expect(validate_v2_1.errors).toBeNull() @@ -292,7 +292,7 @@ integrations: | core.__setInputsObject(YAML.parse(testInput)) const inputs = inputsToRegistryDocument() if (!validate_v2_1(inputs)) { - console.log(validate_v2_1.errors) + console.error(validate_v2_1.errors) } expect(validate_v2_1(inputs)).toStrictEqual(true) expect(validate_v2_1.errors).toBeNull() diff --git a/__tests__/self-workflow-validation.test.cjs b/__tests__/self-workflow-validation.test.cjs index 01a1380..4039e9a 100644 --- a/__tests__/self-workflow-validation.test.cjs +++ b/__tests__/self-workflow-validation.test.cjs @@ -10,7 +10,7 @@ process.env.GITHUB_REPOSITORY = const YAML = require('yaml') // Pulling this in here activates the mocking of the github module -const github = require('@actions/github') +// const github = require('@actions/github') // Need to use inputs for some of our parameters const core = require('@actions/core') @@ -57,13 +57,13 @@ describe('Validate for schema v2', () => { core.__setInputsObject(parsedWorkflow) const serviceDefinition = inputsToRegistryDocument() - console.log( - JSON.stringify({ parsedWorkflow, serviceDefinition }, undefined, 2), - ) const isValid = validate_v2(serviceDefinition) if (!isValid) { - console.log(validate_v2.errors) - console.log(validate_v2) + console.error( + JSON.stringify({ parsedWorkflow, serviceDefinition }, undefined, 2), + ) + console.error(validate_v2.errors) + console.error(validate_v2) } expect(isValid).toBeTruthy() }) @@ -91,11 +91,11 @@ describe('Validate for schema v2.1', () => { core.__setInputsObject(parsedWorkflow) const serviceDefinition = inputsToRegistryDocument() - console.log({ parsedWorkflow, serviceDefinition }) const isValid = validate_v2_1(serviceDefinition) if (!isValid) { - console.log(validate_v2_1.errors) - console.log(validate_v2_1) + console.error({ parsedWorkflow, serviceDefinition }) + console.error(validate_v2_1.errors) + console.error(validate_v2_1) } expect(isValid).toBeTruthy() }) @@ -123,11 +123,11 @@ describe('Validate for schema v2.2', () => { core.__setInputsObject(parsedWorkflow) const serviceDefinition = inputsToRegistryDocument() - console.log({ parsedWorkflow, serviceDefinition }) const isValid = validate_v2_2(serviceDefinition) if (!isValid) { - console.log(validate_v2_2.errors) - console.log(validate_v2_2) + console.error({ parsedWorkflow, serviceDefinition }) + console.error(validate_v2_2.errors) + console.error(validate_v2_2) } expect(isValid).toBeTruthy() }) From c482723b4f6e5d1801196fce72862ce3ee3fe435 Mon Sep 17 00:00:00 2001 From: "Michael D. Stemle, Jr." Date: Fri, 29 Mar 2024 20:49:14 -0400 Subject: [PATCH 5/5] Getting ready for a future release. --- CHANGELOG.md | 19 +++++++++++++++++++ package.json | 9 ++------- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebc5e85..0d8c2a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog], and this project adheres to [Semantic Versioning]. +## [3.0.0] - unreleased + +> [!WARNING] +> This version may include breaking changes! +> There have been efforts made to not break backwards compatibility for either the org rules file +> or the schema versions. That said, it is impossible to know how you have structured your files, +> so please exercise caution when upgrading to the `v3` tag. + +### Added + +- Better tests for the various schema versions + +### Changed + +- Moved all of the field mapping from a common module for all versions, to version-specific modules + - This will make it easier to add new schema version support without risking breakage to existing versions + - This makes it much easier to test convenience functionality independently from the schema fields +- So... many... tests... + ## [2.3.0] - 2024-02-07 ### Added diff --git a/package.json b/package.json index 02fdf5c..c428337 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@arcxp/datadog-service-catalog-metadata-provider", - "version": "2.3.0", + "version": "3.0.0", "type": "commonjs", "description": "This is a package which provides GitHub Actions with a workflow for providing the DataDog Service Catalog Provider with information that will register your service in the Service Catalog.", "main": "index.cjs", @@ -13,12 +13,7 @@ "type": "git", "url": "git+https://github.com/arcxp/datadog-service-catalog-metadata-provider.git" }, - "keywords": [ - "github", - "action", - "datadog", - "service-catalog" - ], + "keywords": ["github", "action", "datadog", "service-catalog"], "author": "Mike Stemle ", "license": "MIT", "bugs": {