Skip to content

Commit

Permalink
feat: add Semaphore CI provider
Browse files Browse the repository at this point in the history
  • Loading branch information
benregn committed Mar 19, 2024
1 parent 308e747 commit 82e616e
Show file tree
Hide file tree
Showing 3 changed files with 328 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/ci_providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -34,6 +35,7 @@ const providerList: IProvider[] = [
providerGitLabci,
providerHerokuci,
providerJenkinsci,
providerSemaphore,
providerTeamCity,
providerTravisci,
providerWercker,
Expand Down
167 changes: 167 additions & 0 deletions src/ci_providers/provider_semaphore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/**
* 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
try {
return args.branch || envs.SEMAPHORE_GIT_PR_BRANCH || envs.SEMAPHORE_GIT_BRANCH || ''
} catch (error) {
throw new Error(

Check warning on line 53 in src/ci_providers/provider_semaphore.ts

View check run for this annotation

Codecov / codecov/patch

src/ci_providers/provider_semaphore.ts#L53

Added line #L53 was not covered by tests
`There was an error getting the branch name from git: ${error}`,
)
}
}

/**
* 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
try {
return args.pr || envs.SEMAPHORE_GIT_PR_NUMBER || ''
} catch (error) {
throw new Error(`There was an error getting the pr number: ${error}`)

Check warning on line 80 in src/ci_providers/provider_semaphore.ts

View check run for this annotation

Codecov / codecov/patch

src/ci_providers/provider_semaphore.ts#L80

Added line #L80 was not covered by tests
}
}

/**
* 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(

Check warning on line 113 in src/ci_providers/provider_semaphore.ts

View check run for this annotation

Codecov / codecov/patch

src/ci_providers/provider_semaphore.ts#L113

Added line #L113 was not covered by tests
`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}`)

Check warning on line 129 in src/ci_providers/provider_semaphore.ts

View check run for this annotation

Codecov / codecov/patch

src/ci_providers/provider_semaphore.ts#L129

Added line #L129 was not covered by tests
}
}
/**
* 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<IServiceParams> {
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',
]
}
159 changes: 159 additions & 0 deletions test/providers/provider_semaphore.test.ts
Original file line number Diff line number Diff line change
@@ -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)
})
})

0 comments on commit 82e616e

Please sign in to comment.