Skip to content

Commit

Permalink
[gate evaluate] Add options.pull_request_sha to the `datadog-ci gat…
Browse files Browse the repository at this point in the history
…e evaluate` payload (#1361)
  • Loading branch information
juan-fernandez authored Jul 4, 2024
1 parent 1db7594 commit ec62175
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 11 deletions.
39 changes: 39 additions & 0 deletions src/commands/gate/__tests__/evaluate.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import fs from 'fs'

import type {AxiosResponse, InternalAxiosRequestConfig} from 'axios'

import {createCommand} from '../../../helpers/__tests__/fixtures'
Expand Down Expand Up @@ -350,6 +352,43 @@ describe('evaluate', () => {
expect(response).toBe(1)
})
})

test('should include head SHA if it is a pull_request or pull_request_target event', async () => {
const HEAD_SHA = '1234567'
const fakeWebhookPayloadPath = './event.json'
const oldEnvGitHubActions = process.env.GITHUB_ACTIONS
const oldEnvGitHubEventPath = process.env.GITHUB_EVENT_PATH
const oldEnvGitHubBaseRef = process.env.GITHUB_BASE_REF
process.env.GITHUB_ACTIONS = '1'
process.env.GITHUB_EVENT_PATH = fakeWebhookPayloadPath
process.env.GITHUB_BASE_REF = 'main' // only set in pull_request and pull_request_target events

fs.writeFileSync(
fakeWebhookPayloadPath,
JSON.stringify({
pull_request: {
head: {
sha: HEAD_SHA,
},
},
})
)

const write = jest.fn()
const command = createCommand(GateEvaluateCommand, {stderr: {write}} as any)
command['timeoutInSeconds'] = 1
command['failIfUnavailable'] = true
command['getApiHelper'] = jest.fn().mockReturnValue(api)
jest.spyOn(api, 'evaluateGateRules').mockResolvedValueOnce(waitMockResponse(100))

await command.execute()
expect((api.evaluateGateRules as jest.Mock).mock.calls[0][0].options.pull_request_sha).toEqual(HEAD_SHA)

process.env.GITHUB_ACTIONS = oldEnvGitHubActions
process.env.GITHUB_EVENT_PATH = oldEnvGitHubEventPath
process.env.GITHUB_BASE_REF = oldEnvGitHubBaseRef
fs.unlinkSync(fakeWebhookPayloadPath)
})
})
})

Expand Down
1 change: 1 addition & 0 deletions src/commands/gate/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const evaluateGateRules = (
no_wait: evaluateRequest.options.noWait,
dry_run: evaluateRequest.options.dryRun,
is_last_retry: evaluateRequest.options.isLastRetry,
pull_request_sha: evaluateRequest.options.pull_request_sha,
},
},
},
Expand Down
28 changes: 22 additions & 6 deletions src/commands/gate/evaluate.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type {APIHelper, EvaluationResponse, EvaluationResponsePayload, Payload, PayloadOptions} from './interfaces'
import type {AxiosResponse} from 'axios'

import chalk from 'chalk'
Expand All @@ -7,13 +8,13 @@ import {v4 as uuidv4} from 'uuid'
import {getCISpanTags} from '../../helpers/ci'
import {getGitMetadata} from '../../helpers/git/format-git-span-data'
import {SpanTags} from '../../helpers/interfaces'
import {Logger, LogLevel} from '../../helpers/logger'
import {retryRequest} from '../../helpers/retry'
import {parseTags} from '../../helpers/tags'
import {GIT_HEAD_SHA, GIT_BASE_REF, parseTags} from '../../helpers/tags'
import {getUserGitSpanTags} from '../../helpers/user-provided-git'
import * as validation from '../../helpers/validation'

import {apiConstructor} from './api'
import {APIHelper, EvaluationResponse, EvaluationResponsePayload, Payload} from './interfaces'
import {
renderEvaluationResponse,
renderGateEvaluationInput,
Expand Down Expand Up @@ -77,15 +78,33 @@ export class GateEvaluateCommand extends Command {
private userScope = Option.Array('--scope')
private tags = Option.Array('--tags')

private logger: Logger = new Logger((s: string) => this.context.stdout.write(s), LogLevel.INFO)

private config = {
apiKey: process.env.DD_API_KEY,
appKey: process.env.DD_APP_KEY,
envVarTags: process.env.DD_TAGS,
}

public async execute() {
const options: PayloadOptions = {
dryRun: this.dryRun,
noWait: this.noWait,
}

const api = this.getApiHelper()
const spanTags = await this.getSpanTags()
const headRef = spanTags[GIT_BASE_REF]

if (headRef) {
const headSha = spanTags[GIT_HEAD_SHA]
if (headSha) {
options.pull_request_sha = headSha
} else {
this.logger.warn('Detected a pull request run but HEAD commit SHA could not be extracted.')
}
}

const userScope = this.userScope ? parseScope(this.userScope) : {}

const startTimeMs = new Date().getTime()
Expand All @@ -94,10 +113,7 @@ export class GateEvaluateCommand extends Command {
spanTags,
userScope,
startTimeMs,
options: {
dryRun: this.dryRun,
noWait: this.noWait,
},
options,
}

return this.evaluateRules(api, payload)
Expand Down
6 changes: 3 additions & 3 deletions src/commands/gate/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {Writable} from 'stream'

import type {AxiosPromise} from 'axios'
import type {Writable} from 'stream'

import {SpanTags} from '../../helpers/interfaces'
import type {SpanTags} from '../../helpers/interfaces'

export interface Payload {
requestId: string
Expand All @@ -16,6 +15,7 @@ export interface PayloadOptions {
dryRun: boolean
noWait: boolean
isLastRetry?: boolean
pull_request_sha?: string
}

export interface EvaluationResponsePayload {
Expand Down
21 changes: 20 additions & 1 deletion src/helpers/ci.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,17 @@ import {
GIT_REPOSITORY_URL,
GIT_SHA,
GIT_TAG,
GIT_HEAD_SHA,
GIT_BASE_REF,
} from './tags'
import {getUserCISpanTags, getUserGitSpanTags} from './user-provided-git'
import {normalizeRef, removeEmptyValues, removeUndefinedValues, filterSensitiveInfoFromRepository} from './utils'
import {
normalizeRef,
removeEmptyValues,
removeUndefinedValues,
filterSensitiveInfoFromRepository,
getGitHeadShaFromGitHubWebhookPayload,
} from './utils'

export const CI_ENGINES = {
APPVEYOR: 'appveyor',
Expand Down Expand Up @@ -218,6 +226,7 @@ export const getCISpanTags = (): SpanTags | undefined => {
GITHUB_REPOSITORY,
GITHUB_SERVER_URL,
GITHUB_RUN_ATTEMPT,
GITHUB_BASE_REF,
} = env
const repositoryUrl = `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git`
let pipelineURL = `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}`
Expand Down Expand Up @@ -249,6 +258,16 @@ export const getCISpanTags = (): SpanTags | undefined => {
GITHUB_RUN_ATTEMPT,
}),
}

if (GITHUB_BASE_REF) {
// GITHUB_BASE_REF is defined if it's a pull_request or pull_request_target trigger
tags[GIT_BASE_REF] = GITHUB_BASE_REF
const headSha = getGitHeadShaFromGitHubWebhookPayload()

if (headSha) {
tags[GIT_HEAD_SHA] = headSha
}
}
}

if (env.JENKINS_URL) {
Expand Down
4 changes: 4 additions & 0 deletions src/helpers/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ import {
GIT_REPOSITORY_URL,
GIT_SHA,
GIT_TAG,
GIT_HEAD_SHA,
SERVICE,
GIT_BASE_REF,
} from './tags'

export interface Metadata {
Expand Down Expand Up @@ -94,6 +96,8 @@ export type SpanTag =
| typeof CI_NODE_NAME
| typeof CI_NODE_LABELS
| typeof SERVICE
| typeof GIT_HEAD_SHA
| typeof GIT_BASE_REF

export type SpanTags = Partial<Record<SpanTag, string>>

Expand Down
2 changes: 2 additions & 0 deletions src/helpers/tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export const GIT_COMMIT_COMMITTER_NAME = 'git.commit.committer.name'
export const GIT_COMMIT_MESSAGE = 'git.commit.message'
export const GIT_SHA = 'git.commit.sha'
export const GIT_TAG = 'git.tag'
export const GIT_HEAD_SHA = 'git.commit.head_sha'
export const GIT_BASE_REF = 'git.commit.base_ref'

// General
export const SPAN_TYPE = 'span.type'
Expand Down
23 changes: 22 additions & 1 deletion src/helpers/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {exec} from 'child_process'
import fs, {existsSync} from 'fs'
import fs, {existsSync, readFileSync} from 'fs'
import {promisify} from 'util'

import type {SpanTag, SpanTags} from './interfaces'
Expand Down Expand Up @@ -412,3 +412,24 @@ export const execute = (cmd: string, cwd?: string): Promise<{stderr: string; std
cwd,
maxBuffer: 5 * 1024 * 5000,
})

type GitHubWebhookPayload = {
pull_request: {
head: {
sha: string
}
}
}

export const getGitHeadShaFromGitHubWebhookPayload = () => {
if (!process.env.GITHUB_EVENT_PATH) {
return ''
}
try {
const parsedContents = JSON.parse(readFileSync(process.env.GITHUB_EVENT_PATH, 'utf8')) as GitHubWebhookPayload

return parsedContents.pull_request.head.sha
} catch (e) {
return ''
}
}

0 comments on commit ec62175

Please sign in to comment.