Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Semaphore CI provider #1485

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
157 changes: 157 additions & 0 deletions src/ci_providers/provider_semaphore.ts
Original file line number Diff line number Diff line change
@@ -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(

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

View check run for this annotation

Codecov / codecov/patch

src/ci_providers/provider_semaphore.ts#L103

Added line #L103 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 119 in src/ci_providers/provider_semaphore.ts

View check run for this annotation

Codecov / codecov/patch

src/ci_providers/provider_semaphore.ts#L119

Added line #L119 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)
})
})