Skip to content

Commit

Permalink
feat: Implement git tagging module
Browse files Browse the repository at this point in the history
  • Loading branch information
fmarek-kindred committed Jul 3, 2024
1 parent dfbad4b commit d487f4f
Show file tree
Hide file tree
Showing 9 changed files with 304 additions and 47 deletions.
4 changes: 2 additions & 2 deletions post-test-actions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"copy.templates": "cd src/teams && copyfiles *.json ../../dist/src/teams ",
"build": "npm run clean && npx tsc && npm run copy.templates",
"test": "npm run build && c8 --reporter=html --reporter=text mocha --timeout 10000 --recursive dist/test/ --loader=esmock",
"dev.start": "set -o allexport; source .env ; set +o allexport; npm run build && node $PIT_POST_TEST_NODE_OPTIONS dist/index.js",
"start": "node $PIT_POST_TEST_NODE_OPTIONS dist/index.js"
"dev.start": "set -o allexport; source .env ; set +o allexport; npm run build && node $PIT_POST_TEST_NODE_OPTIONS dist/src/index.js",
"start": "node $PIT_POST_TEST_NODE_OPTIONS dist/src/index.js"
},
"dependencies": {
"node-fetch": "^3.3.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,10 @@ else
else
# ref must just be sha
git clone -q $repo $dir
echo $(cd $dir; git checkout -b "branch_$ref" $ref)
$(cd $dir; git checkout -q -b "branch_$ref" $ref)
fi
fi

echo $(cd $dir; echo ''; git log --oneline -3)

COMMIT_SHA=$(cd $dir && git log --pretty=format:"%h" -1)

echo "COMMIT_SHA=${COMMIT_SHA}"
73 changes: 73 additions & 0 deletions post-test-actions/scripts/git-tags.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/bin/bash

dryRun=$1
action=$2
dir=$3
tagName=$4

if [ "$dryRun" != "false" ];
then
dryRun="true"
fi

if [ "$action" == "" ];
then
echo "Missing the second parameter: action"
exit 1
fi

if [ "$dir" == "" ];
then
echo "Missing the third parameter: project directory"
exit 1
fi

if [ "$action" != "read-tags" ];
then
if [ "$action" != "delete-tag" ];
then
if [ "$action" != "add-tag" ];
then
echo "Unknown action. The 'action' must be one of 'read-tags', 'delete-tag', 'add-tag'. The current value is ${action}"
exit 1
fi
fi
fi

if [ "$action" == "read-tags" ];
then
cd $dir
git show-ref --tags
fi

if [ "$action" == "delete-tag" ];
then
if [ "$tagName" == "" ];
then
echo "Missing the third parameter: tag name. Cannot delete tag."
exit 1
fi
cd $dir
git tag --delete $tagName

if [ "$dryRun" == "false" ];
then
git push --delete origin $tagName
else
echo "DRY RUN mode is ON. The tag ${tagName} will not be delted from remote."
echo "DRY RUN. git push --delete origin $tagName"
fi
fi

if [ "$action" == "add-tag" ];
then
cd $dir
git tag $tagName
if [ "$dryRun" == "false" ];
then
git push origin $tagName
else
echo "DRY RUN mode is ON. The tag ${tagName} will not be pushed to remote."
echo "DRY RUN. git push origin $tagName"
fi
fi
6 changes: 3 additions & 3 deletions post-test-actions/src/bootrstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as fs from "fs"
import { Config } from "./config.js"
import { logger } from "./logger.js"
import { PublisherConfig } from "./teams/config.js"
import { GitConfig } from "./git-tag/config.js"

export const readParams = (): Config => {
logger.debug("readParams()... ARGS \n%s", JSON.stringify(process.argv, null, 2))
Expand Down Expand Up @@ -42,7 +43,8 @@ export const readParams = (): Config => {
params.get(Config.PARAM_WORKSPACE),
exitCode,
params.get(Config.PARAM_APP_ROOT),
!params.has(PublisherConfig.PARAM_WEBHOOK) ? undefined : new PublisherConfig(params.get(PublisherConfig.PARAM_WEBHOOK))
!params.has(PublisherConfig.PARAM_WEBHOOK) ? undefined : new PublisherConfig(params.get(PublisherConfig.PARAM_WEBHOOK)),
!params.has(GitConfig.PARAM_GIT_REPOSTIRY_URL) ? undefined : new GitConfig(params.get(GitConfig.PARAM_GIT_REPOSTIRY_URL), params.get(GitConfig.PARAM_GIT_REF))
)

return config
Expand Down Expand Up @@ -77,9 +79,7 @@ export const readParameterValue: ValueReader = (params: Map<string, string>, par

const doesDirectoryExist = async (filePath: string): Promise<boolean> => {
try {
logger.debug("isDirectoryExists(): checking if dir exists: %s", filePath)
await fs.promises.access(filePath, fs.constants.F_OK)
logger.debug("isDirectoryExists(): checking if dir exists: %s. Yes", filePath)
return true
} catch (e) {
logger.warn("isDirectoryExists(): There is no %s or it is not accessible.", filePath, { cause: e })
Expand Down
2 changes: 2 additions & 0 deletions post-test-actions/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { GitConfig } from "./git-tag/config.js"
import { PublisherConfig } from "./teams/config.js"

export enum ActionType {
Expand Down Expand Up @@ -33,5 +34,6 @@ export class Config {
readonly exitCode: number,
readonly appRootDir: string,
readonly teamsConfig?: PublisherConfig,
readonly gitConfig?: GitConfig
) {}
}
6 changes: 6 additions & 0 deletions post-test-actions/src/git-tag/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class GitConfig {
static PARAM_GIT_REPOSTIRY_URL = "--git-repository-url"
static PARAM_GIT_REF = "--git-ref"

constructor(readonly gitRepoUrl: string, readonly gitRef: string) {}
}
106 changes: 106 additions & 0 deletions post-test-actions/src/git-tag/core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import * as NodeShell from "node:child_process"

import { ActionType, Config } from "../config.js"
import { logger } from "../logger.js"
import { TestReport } from "../report/schema-v1.js"
import { didTestFail } from "../report/utils.js"

type TagInfo = { tag: string, sha: string }

export class GitTaggingService {
constructor(
private readonly config: Config) {}

private readCommitSha = (workDir: string): string => {
const script = `${this.config.appRootDir}/scripts/git-co.sh`
let commitSha: string
try {
commitSha = NodeShell
.execSync(`${this.config.appRootDir}/scripts/git-co.sh ${this.config.gitConfig.gitRepoUrl} ${this.config.gitConfig.gitRef} ${workDir}`)
.toString()
.trim()
} catch (e) {
throw new Error(`Error invoking script ${script}`, { cause: e })
}

if (commitSha.indexOf("COMMIT_SHA=") !== 0) {
throw new Error(`Git checkout might have failed. Unexpected data returned from: "${script}"`)
}

commitSha = commitSha.substring(11)
return commitSha
}

private getCurrentTags = (workDir: string): Array<TagInfo> => {
// This comes in the format of "long-sha long-tag-name"
const listOfTags = NodeShell
.execSync(`${ this.config.appRootDir }/scripts/git-tags.sh ${ this.config.dryRun } read-tags ${ workDir }`)
.toString()
.trim()

logger.info("getCurrentTags(): \n%s", listOfTags)

// Convert into the list of "short sha, short tag name"
const formattedList = listOfTags
.split("\n")
.map(v => v.trim())
.filter(v => v.length > 0)
.map(v => {
const parsed = v.split(" ").map(a => a.trim()).filter(a => a.length > 0)
return {
sha: parsed[0].substring(0, 7),
tag: parsed[1].substring("refs/tags/".length)
}
})

return formattedList
}

private deleteOldDptTags = (workDir: string, tags: Array<TagInfo>, commitSha: string) => {
const currentTags = tags.filter(t => t.sha === commitSha)
logger.info("deleteOldDptTags(): %s", JSON.stringify(currentTags, null, 2))

if (currentTags.length == 0) return

// we found some tags at the current commit sha
for (let tagInfo of currentTags) {
if (tagInfo.tag == `dpt-fail-${commitSha}` || tagInfo.tag == `dpt-pass-${ commitSha }`) {
// we found DPT tag at the current commit sha, lets remove it
logger.info("deleting tag: %s at %s", tagInfo.tag, tagInfo.sha)
NodeShell.execSync(`${ this.config.appRootDir }/scripts/git-tags.sh ${ this.config.dryRun } delete-tag ${ workDir } ${ tagInfo.tag }`)
}
}
}

executeActions = async (report: TestReport) => {
logger.info("")
logger.info("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -")
logger.info("Git tagging module is processing the report: \"%s\"", report.name)
logger.info("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -")

const appRootDir = this.config.appRootDir

const processWhenFailed = this.config.testFailActions.includes(ActionType.TAG_GIT_REPOSITORY)
const processWhenPassed = this.config.testPassActions.includes(ActionType.TAG_GIT_REPOSITORY)
if (!processWhenFailed && !processWhenPassed) {
logger.info("No action is required from git-tag/GitTaggingService module")
return
}

const workDir = `${ appRootDir }/${ Date.now() }`
const commitSha = this.readCommitSha(workDir)
const tagsList = this.getCurrentTags(workDir)

this.deleteOldDptTags(workDir, tagsList, commitSha)

// ok, now the repo is clean from the previous runs. Lets just tag it based on test results

const isFailure = didTestFail(report)
const newTagName = `dpt-${ isFailure ? 'fail' : 'pass' }-${ commitSha }`

if (isFailure && processWhenFailed || !isFailure && processWhenPassed) {
logger.info("Tagging the repository with tag %s", newTagName)
NodeShell.execSync(`${ this.config.appRootDir }/scripts/git-tags.sh ${ this.config.dryRun } add-tag ${ workDir } ${ newTagName }`)
}
}
}
90 changes: 70 additions & 20 deletions post-test-actions/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { logger } from "./logger.js"

import * as fs from "fs"
import * as NodeShell from "node:child_process"

import { logger } from "./logger.js"
import { TestOutcomeType, TestReport } from "./report/schema-v1.js"
import { TeamsPublisher } from "./teams/core.js"
import { readParams } from "./bootrstrap.js"
import { didTestFail } from "./report/utils.js"
import { GitTaggingService } from "./git-tag/core.js"
import { Config } from "./config.js"

type ReportInfo = { report: TestReport, isFailed: boolean }

const main = async () => {
const config = readParams()

if (config.testPassActions.length == 0 && config.testFailActions.length == 0) {
logger.warn("No actions where configured. Program will exist now.")
logger.warn("No actions where configured. Program will exit now.")
return
}

Expand All @@ -24,33 +28,79 @@ const main = async () => {
process.exit(1)
}
const lookupResults = lookupResultsRaw.toString().split("\n").map(v => v.trim()).filter(v => v.length > 0)
// process each report file....

let hadFailures = false
const reports = loadReports(lookupResults)

for (let lookupResult of lookupResults) {
logger.info("Processing: %s", lookupResult)
const reportContent = fs.readFileSync(lookupResult.toString().trim(), "utf8")
let report: TestReport
let failedReports = 0
for (let reportInfo of reports) {
logger.info("Processing report: \"%s\"", reportInfo.report.name)
if (reportInfo.isFailed) failedReports++
try {
report = JSON.parse(reportContent) as TestReport
await processReport(config, reportInfo)
} catch (e) {
throw Error("Unable to parse report content as JSON", { cause: e })
logger.error("Unable to process report: \"%s\". Error: %s", reportInfo.report.name, e.message)
if (e.cause) logger.error(e.cause)
if (e.stack) logger.error("Stack:\n%s", e.stack)
}
}

hadFailures = hadFailures || didTestFail(report)
if (failedReports > 0) {
logger.info("Some of the analysed tests had %s outcome. The process will exit with code: %s", TestOutcomeType.FAIL, config.exitCode)
process.exit(config.exitCode)
}
}

if (config.teamsConfig) {
const teams = TeamsPublisher.init(config.teamsConfig, config.appRootDir, config.dryRun)
teams.executeActions(config, report)
} else {
logger.warn("Teams publisher module is not active.")
/**
* Loads reports and sorts them by outcome where failed reports are at the bottom of the list
*/
const loadReports = (paths: Array<string>): Array<ReportInfo> => {
const allReports = new Array<ReportInfo>()
const failedReports = new Array<ReportInfo>()
for (let path of paths) {
logger.info("Loading report from: %s", path)
try {
const report = loadReport(path)
if (didTestFail(report)) {
failedReports.push({ report, isFailed: true } )
} else {
allReports.push({ report, isFailed: false })
}
} catch (e) {
logger.error(`Unable to load report from: ${ path }`, { cause: e })
}
}

if (hadFailures) {
logger.info("Some of the analysed tests had %s outcome. The process will exist with code: %s", TestOutcomeType.FAIL, config.exitCode)
process.exit(config.exitCode)
for (let r of failedReports) allReports.push(r)

return allReports
}

const loadReport = (pathToReport: string): TestReport => {
const reportContent = fs.readFileSync(pathToReport, "utf8")
try {
return JSON.parse(reportContent) as TestReport
} catch (e) {
throw Error("Unable to parse report content as JSON", { cause: e })
}
}

const processReport = async (config: Config, reportInfo: ReportInfo) => {
if (config.teamsConfig) {
const teams = TeamsPublisher.init(config.teamsConfig, config.appRootDir, config.dryRun)
await teams.executeActions(config, reportInfo.report)
} else {
logger.info("")
logger.info("Teams publisher module is not active.")
logger.info("")
}

if (config.gitConfig) {
const taggingService = new GitTaggingService(config)
await taggingService.executeActions(reportInfo.report)
} else {
logger.info("")
logger.info("Git tagging module is not active.")
logger.info("")
}
}

Expand Down
Loading

0 comments on commit d487f4f

Please sign in to comment.