From 14c72324fc113f5eafadf6ff3dac681f0fee1f42 Mon Sep 17 00:00:00 2001 From: Tomas Thor Jonsson Date: Tue, 19 Mar 2024 08:44:43 +0100 Subject: [PATCH] feat: add Semaphore CI provider --- src/ci_providers/index.ts | 2 + src/ci_providers/provider_semaphore.ts | 157 +++++++++++++++++++++ test/providers/provider_semaphore.test.ts | 159 ++++++++++++++++++++++ 3 files changed, 318 insertions(+) create mode 100644 src/ci_providers/provider_semaphore.ts create mode 100644 test/providers/provider_semaphore.test.ts diff --git a/src/ci_providers/index.ts b/src/ci_providers/index.ts index a757a896c..04a9a0ff8 100644 --- a/src/ci_providers/index.ts +++ b/src/ci_providers/index.ts @@ -14,6 +14,7 @@ import * as providerGitLabci from './provider_gitlabci' import * as providerHerokuci from './provider_herokuci' import * as providerJenkinsci from './provider_jenkinsci' import * as providerLocal from './provider_local' +import * as providerSemaphore from './provider_semaphore' import * as providerTeamCity from './provider_teamcity' import * as providerTravisci from './provider_travisci' import * as providerWercker from './provider_wercker' @@ -34,6 +35,7 @@ const providerList: IProvider[] = [ providerGitLabci, providerHerokuci, providerJenkinsci, + providerSemaphore, providerTeamCity, providerTravisci, providerWercker, diff --git a/src/ci_providers/provider_semaphore.ts b/src/ci_providers/provider_semaphore.ts new file mode 100644 index 000000000..4c5e96150 --- /dev/null +++ b/src/ci_providers/provider_semaphore.ts @@ -0,0 +1,157 @@ +/** + * https://docs.semaphoreci.com/ci-cd-environment/environment-variables/ + */ +import { IServiceParams, UploaderEnvs, UploaderInputs } from '../types' + +/** + * Detects if this CI provider is being used + * + * @param {*} envs an object of environment variable key/value pairs + * @returns boolean + */ + +export function detect(envs: UploaderEnvs): boolean { + return Boolean(envs.CI) && Boolean(envs.SEMAPHORE) +} + +/** + * Determine the build number, based on args and envs + * + * @param {args: {}, envs: {}} inputs an object of arguments and environment variable key/value pairs + * @returns {string} + */ +function _getBuild(inputs: UploaderInputs): string { + const { args, envs } = inputs + return args.build || envs.SEMAPHORE_WORKFLOW_NUMBER || '' +} + +/** + * Determine the build URL for use in the Codecov UI + * + * @param {args: {}, envs: {}} inputs an object of arguments and environment variable key/value pairs + * @returns {string} + */ +function _getBuildURL(inputs: UploaderInputs): string { + const { envs } = inputs + if (envs.SEMAPHORE_ORGANIZATION_URL && envs.SEMAPHORE_WORKFLOW_ID) { + return `${envs.SEMAPHORE_ORGANIZATION_URL}/workflows/${envs.SEMAPHORE_WORKFLOW_ID}` + } + return '' +} + +/** + * Determine the branch of the repository, based on args and envs + * + * @param {args: {}, envs: {}} inputs an object of arguments and environment variable key/value pairs + * @returns {string} + */ +function _getBranch(inputs: UploaderInputs): string { + const { args, envs } = inputs + return args.branch || envs.SEMAPHORE_GIT_PR_BRANCH || envs.SEMAPHORE_GIT_BRANCH || '' +} + +/** + * Determine the job number, based on args or envs + * + * @param {*} envs an object of environment variable key/value pairs + * @returns {string} + */ +function _getJob(envs: UploaderEnvs): string { + return envs.SEMAPHORE_WORKFLOW_ID || '' +} + +/** + * Determine the PR number, based on args and envs + * + * @param {args: {}, envs: {}} inputs an object of arguments and environment variable key/value pairs + * @returns {string} + */ +function _getPR(inputs: UploaderInputs): string { + const { args, envs } = inputs + return args.pr || envs.SEMAPHORE_GIT_PR_NUMBER || '' +} + +/** + * The CI service name that gets sent to the Codecov uploader as part of the query string + * + * @returns {string} + */ +function _getService(): string { + return 'semaphore' +} + +/** + * The CI Service name that gets displayed when running the uploader + * + * @returns + */ +export function getServiceName(): string { + return 'Semaphore CI' +} +/** + * Determine the commit SHA that is being uploaded, based on args or envs + * + * @param {args: {}, envs: {}} inputs an object of arguments and environment variable key/value pairs + * @returns {string} + */ +function _getSHA(inputs: UploaderInputs): string { + const { args, envs } = inputs + try { + // when running on a PR, SEMAPHORE_GIT_SHA is the PR's merge commit + return args.sha || envs.SEMAPHORE_GIT_PR_SHA || envs.SEMAPHORE_GIT_SHA || '' + } catch (error) { + throw new Error( + `There was an error getting the commit SHA from git: ${error}`, + ) + } +} +/** + * Determine the slug (org/repo) based on args or envs + * + * @param {args: {}, envs: {}} inputs an object of arguments and environment variable key/value pairs + * @returns {string} + */ +function _getSlug(inputs: UploaderInputs): string { + const { args, envs } = inputs + try { + return args.slug || envs.SEMAPHORE_GIT_PR_SLUG || envs.SEMAPHORE_GIT_REPO_SLUG || '' + } catch (error) { + throw new Error(`There was an error getting the slug from git: ${error}`) + } +} +/** + * Generates and return the serviceParams object + * + * @param {args: {}, envs: {}} inputs an object of arguments and environment variable key/value pairs + * @returns {{ branch: string, build: string, buildURL: string, commit: string, job: string, pr: string, service: string, slug: string }} + */ +export async function getServiceParams(inputs: UploaderInputs): Promise { + return { + branch: _getBranch(inputs), + build: _getBuild(inputs), + buildURL: _getBuildURL(inputs), + commit: _getSHA(inputs), + job: _getJob(inputs.envs), + pr: _getPR(inputs), + service: _getService(), + slug: _getSlug(inputs), + } +} + +/** + * Returns all the environment variables used by the provider + * + * @returns [{string}] + */ +export function getEnvVarNames(): string[] { + return [ + 'CI', + 'SEMAPHORE', + 'SEMAPHORE_WORKFLOW_NUMBER', + 'SEMAPHORE_ORGANIZATION_URL', + 'SEMAPHORE_WORKFLOW_ID', + 'SEMAPHORE_GIT_PR_BRANCH', + 'SEMAPHORE_GIT_BRANCH', + 'SEMAPHORE_GIT_PR_NUMBER', + ] +} diff --git a/test/providers/provider_semaphore.test.ts b/test/providers/provider_semaphore.test.ts new file mode 100644 index 000000000..5858d313c --- /dev/null +++ b/test/providers/provider_semaphore.test.ts @@ -0,0 +1,159 @@ +import td from 'testdouble' +import * as providerSemaphore from '../../src/ci_providers/provider_semaphore' +import { IServiceParams, UploaderInputs } from '../../src/types' +import { createEmptyArgs } from '../test_helpers' + +describe('Semaphore Params', () => { + afterEach(() => { + td.reset() + }) + + describe('detect()', () => { + it('does not run without Semaphore env variable', () => { + const inputs: UploaderInputs = { + args: { ...createEmptyArgs() }, + envs: {}, + } + let detected = providerSemaphore.detect(inputs.envs) + expect(detected).toBeFalsy() + + inputs.envs['CI'] = 'true' + detected = providerSemaphore.detect(inputs.envs) + expect(detected).toBeFalsy() + }) + + it('does run with Semaphore env variable', () => { + const inputs: UploaderInputs = { + args: { ...createEmptyArgs() }, + envs: { + CI: 'true', + SEMAPHORE: 'true', + }, + } + const detected = providerSemaphore.detect(inputs.envs) + expect(detected).toBeTruthy() + }) + }) + + // This should test that the provider outputs proper default values + it('gets the correct params on no env variables', async () => { + const inputs: UploaderInputs = { + args: { ...createEmptyArgs() }, + envs: { + CI: 'true', + SEMAPHORE: 'true', + }, + } + const expected: IServiceParams = { + branch: '', + build: '', + buildURL: '', + commit: '', + job: '', + pr: '', + service: 'semaphore', + slug: '', + } + const params = await providerSemaphore.getServiceParams(inputs) + expect(params).toMatchObject(expected) + }) + + // This should test that the provider outputs proper parameters when a push event is created + it('gets the correct params on push', async () => { + const inputs: UploaderInputs = { + args: { ...createEmptyArgs() }, + envs: { + CI: 'true', + SEMAPHORE: 'true', + SEMAPHORE_GIT_BRANCH: 'master', + SEMAPHORE_GIT_REPO_SLUG: 'org/user', + SEMAPHORE_GIT_SHA: 'testingsha', + SEMAPHORE_ORGANIZATION_URL: 'https://example.semaphoreci.com', + SEMAPHORE_WORKFLOW_ID: '03d9de4c-c798-4df5-bbd5-786db9d4d309', + SEMAPHORE_WORKFLOW_NUMBER: '1', + }, + } + const expected: IServiceParams = { + branch: 'master', + build: '1', + buildURL: 'https://example.semaphoreci.com/workflows/03d9de4c-c798-4df5-bbd5-786db9d4d309', + commit: 'testingsha', + job: '03d9de4c-c798-4df5-bbd5-786db9d4d309', + pr: '', + service: 'semaphore', + slug: 'org/user', + } + const params = await providerSemaphore.getServiceParams(inputs) + expect(params).toMatchObject(expected) + }) + // + // This should test that the provider outputs proper parameters when a pull request event is created + it('gets the correct params on pr', async () => { + const inputs: UploaderInputs = { + args: { ...createEmptyArgs() }, + envs: { + CI: 'true', + SEMAPHORE: 'true', + SEMAPHORE_GIT_BRANCH: 'master', + SEMAPHORE_GIT_PR_BRANCH: 'feature-branch', + SEMAPHORE_GIT_PR_NUMBER: '1234', + SEMAPHORE_GIT_PR_SHA: 'prsha', + SEMAPHORE_GIT_PR_SLUG: 'org/user', + SEMAPHORE_GIT_SHA: 'testingsha', + SEMAPHORE_ORGANIZATION_URL: 'https://example.semaphoreci.com', + SEMAPHORE_WORKFLOW_ID: '03d9de4c-c798-4df5-bbd5-786db9d4d309', + SEMAPHORE_WORKFLOW_NUMBER: '1', + }, + } + const expected: IServiceParams = { + branch: 'feature-branch', + build: '1', + buildURL: 'https://example.semaphoreci.com/workflows/03d9de4c-c798-4df5-bbd5-786db9d4d309', + commit: 'prsha', + job: '03d9de4c-c798-4df5-bbd5-786db9d4d309', + pr: '1234', + service: 'semaphore', + slug: 'org/user', + } + const params = await providerSemaphore.getServiceParams(inputs) + expect(expected).toBeTruthy() + }) + + // This should test that the provider outputs proper parameters when given overrides + it('gets the correct params on overrides', async () => { + const inputs: UploaderInputs = { + args: {...createEmptyArgs(), ...{ + branch: 'overwrite-feature-branch', + build: '3', + pr: '4', + sha: 'overwriteSha', + slug: 'overwriteOwner/overwriteRepo', + }}, + envs: { + CI: 'true', + SEMAPHORE: 'true', + SEMAPHORE_GIT_BRANCH: 'master', + SEMAPHORE_GIT_PR_BRANCH: 'feature-branch', + SEMAPHORE_GIT_PR_NUMBER: '1234', + SEMAPHORE_GIT_PR_SHA: 'prsha', + SEMAPHORE_GIT_PR_SLUG: 'org/user', + SEMAPHORE_GIT_SHA: 'testingsha', + SEMAPHORE_ORGANIZATION_URL: 'https://example.semaphoreci.com', + SEMAPHORE_WORKFLOW_ID: '03d9de4c-c798-4df5-bbd5-786db9d4d309', + SEMAPHORE_WORKFLOW_NUMBER: '1', + }, + } + const expected: IServiceParams = { + branch: 'overwrite-feature-branch', + build: '3', + buildURL: 'https://example.semaphoreci.com/workflows/03d9de4c-c798-4df5-bbd5-786db9d4d309', + commit: 'overwriteSha', + job: '03d9de4c-c798-4df5-bbd5-786db9d4d309', + pr: '4', + service: 'semaphore', + slug: 'overwriteOwner/overwriteRepo', + } + const params = await providerSemaphore.getServiceParams(inputs) + expect(params).toMatchObject(expected) + }) +})