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

feature(Improve actions testing) #240

Merged
merged 70 commits into from
Feb 3, 2025
Merged
Changes from 63 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
21ae007
getTag
bitmaskit Jan 6, 2025
a301095
refactor getEnterpriseConfigUrl
bitmaskit Jan 10, 2025
8451a84
added functionality for inner source
bitmaskit Jan 13, 2025
312957f
use sap-version
bitmaskit Jan 13, 2025
5b6130e
move if check
Jan 13, 2025
8beca52
distupdate
Jan 13, 2025
4c8eb96
distupdate
Jan 13, 2025
d822e99
distupdate
Jan 13, 2025
9f243cb
distupdate
Jan 13, 2025
3a17a1b
added loggin
bitmaskit Jan 16, 2025
c448325
distupdate
Jan 17, 2025
dc12da2
Merge branch 'main' into improve-actions-testing
bitmaskit Jan 21, 2025
e2da4a0
update
bitmaskit Jan 21, 2025
56338ca
use correct version
bitmaskit Jan 22, 2025
e82e59b
dist update
bitmaskit Jan 22, 2025
669f007
dist update
bitmaskit Jan 22, 2025
806b0be
dist update
bitmaskit Jan 22, 2025
221dda9
dist update
bitmaskit Jan 22, 2025
5f0f9ce
dist update
bitmaskit Jan 23, 2025
e75fad1
dist update
bitmaskit Jan 23, 2025
06497b6
dist update
bitmaskit Jan 23, 2025
cba0d7e
dist update 06497b6
bitmaskit Jan 23, 2025
fee7131
dist update
bitmaskit Jan 23, 2025
b0d5d77
dist update
bitmaskit Jan 23, 2025
0b7230d
dist update
bitmaskit Jan 23, 2025
bf93fdc
dist update
bitmaskit Jan 23, 2025
920c741
dist update
bitmaskit Jan 23, 2025
8ccc0ce
dist update
bitmaskit Jan 23, 2025
cfa7eac
dist update
bitmaskit Jan 23, 2025
9046076
dist update
bitmaskit Jan 23, 2025
698a801
dist update
bitmaskit Jan 23, 2025
ae9e3c3
dist update
bitmaskit Jan 23, 2025
1433bad
dist update
bitmaskit Jan 23, 2025
6e76d8a
dist update
bitmaskit Jan 23, 2025
47806fc
dist update
bitmaskit Jan 23, 2025
6b7c294
dist update
bitmaskit Jan 23, 2025
1e0cc33
dist update
bitmaskit Jan 23, 2025
34a3cbb
dist update
bitmaskit Jan 23, 2025
6088b94
dist update
bitmaskit Jan 23, 2025
9741d18
dist update
bitmaskit Jan 23, 2025
d8b82dc
dist update
bitmaskit Jan 24, 2025
56e70cd
dist update
bitmaskit Jan 24, 2025
70cd9f0
dist update
bitmaskit Jan 24, 2025
55e3a38
work on tests
bitmaskit Jan 24, 2025
54d7617
dist update
bitmaskit Jan 27, 2025
e32166f
dist update
bitmaskit Jan 27, 2025
065b6a3
dist update
bitmaskit Jan 27, 2025
43102e1
dist update
bitmaskit Jan 27, 2025
8b1c119
dist update
bitmaskit Jan 28, 2025
ddd5dd1
step name
Jan 28, 2025
0beeb9c
Merge branch 'improve-actions-testing' of https://github.com/SAP/proj…
Jan 28, 2025
3e6350f
dist update
Jan 28, 2025
6dc3334
update dist folder
bitmaskit Jan 28, 2025
2d9cf21
dist update
bitmaskit Jan 28, 2025
4edf32b
fix test
bitmaskit Jan 28, 2025
6581bce
dist update
bitmaskit Jan 28, 2025
95240c5
Merge branch 'main' into improve-actions-testing
bitmaskit Jan 28, 2025
0a263b7
update dist folder
bitmaskit Jan 28, 2025
fb21050
dist update
Jan 28, 2025
13c16ae
update dist folder
Jan 29, 2025
3610abb
dist update
bitmaskit Jan 29, 2025
874571e
dist:update
Jan 29, 2025
5ff0966
dist:update
Jan 29, 2025
2f59ddf
cleanup unnecessary code
bitmaskit Jan 29, 2025
227d659
update dist folder
bitmaskit Jan 29, 2025
bb5502f
dist update
bitmaskit Jan 29, 2025
9d838e9
fix bug in default config
Feb 3, 2025
c6f22ee
fix bug in default config
Feb 3, 2025
69b46c8
fix bug in default config
Feb 3, 2025
b04539f
fix bug in default config
Feb 3, 2025
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -4,3 +4,4 @@ go*linux-amd64.tar.gz
.idea/
reports/
**/.DS_store
pnpm-lock.yaml
581 changes: 397 additions & 184 deletions dist/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

142 changes: 142 additions & 0 deletions src/build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Format for inner source development versions (all parts required): 'devel:GH_OWNER:REPOSITORY:COMMITISH'
import { parseDevVersion } from './github'
import { debug, info, setFailed } from '@actions/core'
import { dirname, join } from 'path'
import fs from 'fs'
import { chdir, cwd } from 'process'
import { exec } from '@actions/exec'
import { extractZip } from '@actions/tool-cache'

export const GITHUB_WDF_SAP_SERVER_URL = 'https://github.wdf.sap.corp'

export async function buildPiperInnerSource (version: string, wdfGithubEnterpriseToken: string = ''): Promise<string> {
const { owner, repository, commitISH } = parseDevVersion(version)
const versionName = getVersionName(commitISH)

const path = `${process.cwd()}/${owner}-${repository}-${versionName}`
info(`path: ${path}`)
const piperPath = `${path}/sap-piper`
info(`piperPath: ${piperPath}`)

if (fs.existsSync(piperPath)) {
info(`piperPath exists: ${piperPath}`)
return piperPath
}

info(`Building Inner Source Piper from ${version}`)
const url = `${GITHUB_WDF_SAP_SERVER_URL}/${owner}/${repository}/archive/${commitISH}.zip`
info(`URL: ${url}`)

info(`Downloading Inner Source Piper from ${url} and saving to ${path}/source-code.zip`)
const zipFile = await downloadWithAuth(url, `${path}/source-code.zip`, wdfGithubEnterpriseToken)
.catch((err) => {
throw new Error(`Can't download Inner Source Piper: ${err}`)
})

info(`Listing cwd: ${cwd()}`)
listFilesAndFolders(cwd())

info(`Listing $path: ${path}`)
listFilesAndFolders(path)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this logging?


info(`Extracting Inner Source Piper from ${zipFile} to ${path}`)
await extractZip(zipFile, `${path}`).catch((err) => {
throw new Error(`Can't extract Inner Source Piper: ${err}`)
})
const wd = cwd()

const repositoryPath = join(path, fs.readdirSync(path).find((name: string) => {
return name.includes(repository)
}) ?? '')
info(`repositoryPath: ${repositoryPath}`)
chdir(repositoryPath)

const cgoEnabled = process.env.CGO_ENABLED
process.env.CGO_ENABLED = '0'
info(`Building Inner Source Piper from ${version}`)
await exec('go build -o ../sap-piper')
.catch((err) => {
throw new Error(`Can't build Inner Source Piper: ${err}`)
})

process.env.CGO_ENABLED = cgoEnabled

info('Changing directory back to working directory: ' + wd)
chdir(wd)
info('Removing repositoryPath: ' + repositoryPath)
fs.rmSync(repositoryPath, { recursive: true, force: true })

info(`Returning piperPath: ${piperPath}`)
return piperPath
}

async function downloadWithAuth (url: string, destination: string, wdfGithubToken: string): Promise<string> {
if (wdfGithubToken.length !== 0) {
info('WDF Github Token is set. ')
} else {
setFailed('WDF GitHub Token is not provided, please set the PIPER_WDF_GITHUB_TOKEN environment variable in Settings')
}
try {
info(`🔄 Trying to download with auth ${url} to ${destination}`)

// Ensure the parent directory exists
const dir = dirname(destination)
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true })
info(`📂 Created directory: ${dir}`)
}

const zipFile = await downloadZip(url, destination, wdfGithubToken).catch((err) => {
throw new Error(`Can't download with auth: ${err}`)
})
info(`✅ Downloaded successfully to ${zipFile}`)
return zipFile
} catch (error) {
setFailed(`❌ Download failed: ${error instanceof Error ? error.message : String(error)}`)
return ''
}
}

async function downloadZip (url: string, zipPath: string, token?: string): Promise<string> {
try {
info(`🔄 Downloading ZIP from ${url}`)

const headers: Record<string, string> = {
Accept: 'application/vnd.github.v3.raw'
}

if (typeof token === 'string' && token.trim() !== '') {
headers.Authorization = `Bearer ${token}`
}

const response = await fetch(url, { headers })

if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}

const buffer = await response.arrayBuffer()
fs.writeFileSync(zipPath, Buffer.from(buffer))

info(`✅ ZIP downloaded successfully to ${zipPath}`)
} catch (error) {
setFailed(`❌ Download failed: ${error instanceof Error ? error.message : String(error)}`)
}
return zipPath
}

export function listFilesAndFolders (dirPath: string): void {
const items = fs.readdirSync(dirPath)
items.forEach(item => {
const fullPath = join(dirPath, item)
const stats = fs.statSync(fullPath)
debug(stats.isDirectory() ? `📁 ${item}` : `📄 ${item} - ${stats.size} bytes`)
})
}

function getVersionName (commitISH: string): string {
if (!/^[0-9a-f]{7,40}$/.test(commitISH)) {
throw new Error('Can\'t resolve COMMITISH, use SHA or short SHA')
}
return commitISH.slice(0, 7)
}
135 changes: 119 additions & 16 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,149 @@
import * as path from 'path'
import * as fs from 'fs'
import { debug, exportVariable, info } from '@actions/core'
import { debug, exportVariable, getInput, info, type InputOptions } from '@actions/core'
import * as artifact from '@actions/artifact'
import { type UploadResponse } from '@actions/artifact'
import { executePiper } from './execute'
import { getHost } from './github'
import {
getHost,
GITHUB_COM_API_URL,
GITHUB_COM_SERVER_URL,
PIPER_OWNER,
PIPER_REPOSITORY
} from './github'
import {
ENTERPRISE_DEFAULTS_FILENAME,
ENTERPRISE_STAGE_CONFIG_FILENAME,
DEFAULT_CONFIG,
STAGE_CONFIG,
getEnterpriseConfigUrl
getEnterpriseConfigUrl,
onGitHubEnterprise
} from './enterprise'
import type { ActionConfiguration } from './piper'
import { internalActionVariables } from './piper'

export const CONFIG_DIR = '.pipeline'
export const ARTIFACT_NAME = 'Pipeline defaults'

export interface ActionConfiguration {
stepName: string
flags: string
piperVersion: string
piperOwner: string
piperRepo: string
sapPiperVersion: string
sapPiperOwner: string
sapPiperRepo: string
gitHubServer: string
gitHubApi: string
gitHubToken: string
gitHubEnterpriseServer: string
gitHubEnterpriseApi: string
gitHubEnterpriseToken: string
wdfGithubEnterpriseToken: string
dockerImage: string
dockerOptions: string
dockerEnvVars: string
sidecarImage: string
sidecarOptions: string
sidecarEnvVars: string
retrieveDefaultConfig: boolean
customDefaultsPaths: string
customStageConditionsPath: string
createCheckIfStepActiveMaps: boolean
exportPipelineEnvironment: boolean
}

export async function getActionConfig (options: InputOptions): Promise<ActionConfiguration> {
const getValue = (param: string, defaultValue?: string): string => {
let value: string = getInput(param, options)
if (value === '') {
// EnVs should be provided like this
// PIPER_ACTION_DOWNLOAD_URL
value = process.env[`PIPER_ACTION_${param.toUpperCase().replace(/-/g, '_')}`] ?? ''
if (value === '') return defaultValue ?? ''
}

debug(`${param}: ${value}`)
return value
}
let enterpriseHost: string = ''
let enterpriseApi: string = ''
if (onGitHubEnterprise()) {
if (process.env.GITHUB_SERVER_URL !== undefined) {
enterpriseHost = process.env.GITHUB_SERVER_URL
}
if (process.env.GITHUB_API_URL !== undefined) {
enterpriseApi = process.env.GITHUB_API_URL
}
}

let stepNameValue = getValue('step-name')
// TODO: remove command input
if (stepNameValue === undefined || stepNameValue === '') {
stepNameValue = getValue('command')
}

return {
stepName: stepNameValue,
flags: getValue('flags'),
piperVersion: getValue('piper-version'),
piperOwner: getValue('piper-owner', PIPER_OWNER),
piperRepo: getValue('piper-repository', PIPER_REPOSITORY),
sapPiperVersion: getValue('sap-piper-version'),
sapPiperOwner: getValue('sap-piper-owner'),
sapPiperRepo: getValue('sap-piper-repository'),
gitHubToken: getValue('github-token'),
gitHubServer: GITHUB_COM_SERVER_URL,
gitHubApi: GITHUB_COM_API_URL,
gitHubEnterpriseServer: enterpriseHost,
gitHubEnterpriseApi: enterpriseApi,
gitHubEnterpriseToken: getValue('github-enterprise-token'),
wdfGithubEnterpriseToken: getValue('wdf-github-enterprise-token'),
dockerImage: getValue('docker-image'),
dockerOptions: getValue('docker-options'),
dockerEnvVars: getValue('docker-env-vars'),
sidecarImage: getValue('sidecar-image'),
sidecarOptions: getValue('sidecar-options'),
sidecarEnvVars: getValue('sidecar-env-vars'),
retrieveDefaultConfig: getValue('retrieve-default-config') === 'true',
customDefaultsPaths: getValue('custom-defaults-paths'),
customStageConditionsPath: getValue('custom-stage-conditions-path'),
createCheckIfStepActiveMaps: getValue('create-check-if-step-active-maps') === 'true',
exportPipelineEnvironment: getValue('export-pipeline-environment') === 'true'
}
}

export async function getDefaultConfig (server: string, apiURL: string, version: string, token: string, owner: string, repository: string, customDefaultsPaths: string): Promise<number> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like getDefaultConfig function return value is not used. We can make the return type Promise<void> and use simple return instead of return 0

if (fs.existsSync(path.join(CONFIG_DIR, ENTERPRISE_DEFAULTS_FILENAME))) {
info('Defaults are present')
if (process.env.defaultsFlags !== undefined) {
debug(`Defaults flags: ${process.env.defaultsFlags}`)
} else {
debug('But no defaults flags available in the environment!')
}
return await Promise.resolve(0)
debug(process.env.defaultsFlags !== undefined
? `Defaults flags: ${process.env.defaultsFlags}`
: 'But no defaults flags available in the environment!')
return 0
}

try {
await restoreDefaultConfig()
info('Trying to restore defaults from artifact')
await restoreDefaultConfig() // this fails
info('Defaults restored from artifact')
return await Promise.resolve(0)
return 0
} catch (err: unknown) {
// throws an error with message containing 'Unable to find' if artifact does not exist
if (err instanceof Error && !err.message.includes('Unable to find')) throw err
// continue with downloading defaults and upload as artifact
info('Downloading defaults')
await downloadDefaultConfig(server, apiURL, version, token, owner, repository, customDefaultsPaths)
return await Promise.resolve(0)
return 0
}
}

export async function downloadDefaultConfig (server: string, apiURL: string, version: string, token: string, owner: string, repository: string, customDefaultsPaths: string): Promise<UploadResponse> {
let defaultsPaths: string[] = []

// version: devel:.....
if (version.startsWith('devel:')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add comment to clarify this condition:
// Since defaults file is located in release assets, we will take it from latest release

version = 'latest'
}
const enterpriseDefaultsURL = await getEnterpriseConfigUrl(DEFAULT_CONFIG, apiURL, version, token, owner, repository)
if (enterpriseDefaultsURL !== '') {
defaultsPaths = defaultsPaths.concat([enterpriseDefaultsURL])
@@ -75,10 +173,14 @@ export async function downloadDefaultConfig (server: string, apiURL: string, ver
return uploadResponse
}

// TODO configuration should be strictly typed
export function saveDefaultConfigs (defaultConfigs: any[]): string[] {
interface DefaultConfig {
filepath: string
content: string
}

export function saveDefaultConfigs (defaultConfigs: DefaultConfig[]): string[] {
if (!fs.existsSync(CONFIG_DIR)) {
fs.mkdirSync(CONFIG_DIR)
fs.mkdirSync(CONFIG_DIR, { recursive: true })
}

const defaultsPaths = []
@@ -150,6 +252,7 @@ export async function checkIfStepActive (stepName: string, stageName: string, ou
return result.exitCode
}

// ?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?? 😃

export async function restoreDefaultConfig (): Promise<void> {
const artifactClient = artifact.create()
const tempDir = path.join(CONFIG_DIR, 'defaults_temp')
6 changes: 2 additions & 4 deletions src/docker.ts
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import { dirname } from 'path'
import { debug, info } from '@actions/core'
import { exec } from '@actions/exec'
import { v4 as uuidv4 } from 'uuid'
import type { ActionConfiguration } from './piper'
import type { ActionConfiguration } from './config'
import { createNetwork, parseDockerEnvVars, removeNetwork, startSidecar } from './sidecar'
import { internalActionVariables } from './piper'

@@ -18,9 +18,7 @@ export async function runContainers (actionCfg: ActionConfiguration, ctxConfig:

export async function startContainer (actionCfg: ActionConfiguration, ctxConfig: any): Promise<void> {
const dockerImage = actionCfg.dockerImage !== '' ? actionCfg.dockerImage : ctxConfig.dockerImage
if (dockerImage === undefined || dockerImage === '') {
return
}
if (dockerImage === undefined || dockerImage === '') return

const piperPath = internalActionVariables.piperBinPath
const containerID = uuidv4()
68 changes: 68 additions & 0 deletions src/download.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import fs from 'fs'
import { debug, info } from '@actions/core'
import { downloadTool } from '@actions/tool-cache'
import { isEnterpriseStep } from './enterprise'
import {
getReleaseAssetUrl,
getTag,
GITHUB_COM_SERVER_URL
} from './github'
import { fetchRetry } from './fetch'

export async function downloadPiperBinary (
stepName: string, version: string, apiURL: string, token: string, owner: string, repo: string
): Promise<string> {
const isEnterprise = isEnterpriseStep(stepName)
if (isEnterprise && token === '') throw new Error('Token is not provided for enterprise step')
if (owner === '') throw new Error('owner is not provided')
if (repo === '') throw new Error('repository is not provided')

let binaryURL
const headers: any = {}
const piperBinaryName = await getPiperBinaryNameFromInputs(isEnterprise, version)
debug(`version: ${version}`)
if (token !== '') {
debug('Fetching binary from GitHub API')
headers.Accept = 'application/octet-stream'
headers.Authorization = `token ${token}`

const [binaryAssetURL, tag] = await getReleaseAssetUrl(piperBinaryName, version, apiURL, token, owner, repo)
debug(`downloadPiperBinary: binaryAssetURL: ${binaryAssetURL}, tag: ${tag}`)
binaryURL = binaryAssetURL
version = tag
} else {
debug('Fetching binary from URL')
binaryURL = await getPiperDownloadURL(piperBinaryName, version)
version = binaryURL.split('/').slice(-2)[0]
}
version = version.replace(/\./g, '_')
const piperPath = `${process.cwd()}/${version}/${piperBinaryName}`
if (fs.existsSync(piperPath)) {
return piperPath
}

info(`Downloading '${binaryURL}' as '${piperPath}'`)
await downloadTool(
binaryURL,
piperPath,
undefined,
headers
)

return piperPath
}
async function getPiperDownloadURL (piper: string, version?: string): Promise<string> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thnik we can safely turn version? into version, since there is no sense in expecting undefined here. Same applies to getTag function, where version: string | undefined can be turned into version: string

const tagURL = `${GITHUB_COM_SERVER_URL}/SAP/jenkins-library/releases/${getTag(version, false)}`
const response = await fetchRetry(tagURL, 'HEAD')
.catch(async (err) => {
throw new Error(`Can't get the tag: ${err}`)
})
return await Promise.resolve(response.url.replace(/tag/, 'download') + `/${piper}`)
}

async function getPiperBinaryNameFromInputs (isEnterpriseStep: boolean, version?: string): Promise<string> {
if (version === 'master') {

Check warning on line 64 in src/download.ts

In Solidarity / Inclusive Language

Match Found

Please consider an alternative to `master`. Possibilities include: `primary`, `main`, `leader`, `active`, `writer`
Raw output
/\b(?!masterdata|masterdata\w+\b)master/gi
info('using _master binaries is deprecated. Using latest release version instead.')
}
return isEnterpriseStep ? 'sap-piper' : 'piper'
}
39 changes: 26 additions & 13 deletions src/enterprise.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { GITHUB_COM_SERVER_URL, getReleaseAssetUrl } from './github'
import { debug } from '@actions/core'
import { listFilesAndFolders } from './build'

export const DEFAULT_CONFIG = 'DefaultConfig'
export const STAGE_CONFIG = 'StageConfig'
@@ -20,22 +22,33 @@ export function onGitHubEnterprise (): boolean {
}

export async function getEnterpriseConfigUrl (configType: string, apiURL: string, version: string, token: string, owner: string, repository: string): Promise<string> {
let assetname: string = ''
let filename: string = ''
debug('Getting enterprise config URL')
if (configType !== DEFAULT_CONFIG && configType !== STAGE_CONFIG) return ''

if (configType === DEFAULT_CONFIG) {
assetname = ENTERPRISE_DEFAULTS_FILENAME_ON_RELEASE
filename = ENTERPRISE_DEFAULTS_FILENAME
} else if (configType === STAGE_CONFIG) {
assetname = ENTERPRISE_STAGE_CONFIG_FILENAME
debug('initiating assetName and filename')
let assetName: string = ENTERPRISE_DEFAULTS_FILENAME_ON_RELEASE
let filename: string = ENTERPRISE_DEFAULTS_FILENAME

if (configType === STAGE_CONFIG) {
debug('configType is STAGE_CONFIG')
assetName = ENTERPRISE_STAGE_CONFIG_FILENAME
filename = ENTERPRISE_STAGE_CONFIG_FILENAME
} else {
return ''
}

// if version starts with devel: then it should use inner source Piper
if (version.startsWith('devel:')) {
debug(`version starts with "devel:" => ${version}`)
debug(`params: ${owner}, ${repository}, ${version}, ${filename}`)
listFilesAndFolders(process.cwd())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was it for debugging?

version = ''
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose it should be latest here as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

either way its gets turned to latest in the getTag function but I changed it

}
// get URL of defaults from the release (gh api, authenticated)
const [url] = await getReleaseAssetUrl(assetname, version, apiURL, token, owner, repository)
if (url !== '') return url
// fallback to get URL of defaults in the repository (unauthenticated)
return `${process.env.GITHUB_API_URL}/repos/${owner}/${repository}/contents/resources/${filename}`
const [url] = await getReleaseAssetUrl(assetName, version, apiURL, token, owner, repository)
if (url === '') {
// fallback to get URL of defaults in the repository (unauthenticated)
debug(`Fallback to get URL of defaults in the repository: ${process.env.GITHUB_API_URL}/repos/${owner}/${repository}/contents/resources/${filename}`)
return `${process.env.GITHUB_API_URL}/repos/${owner}/${repository}/contents/resources/${filename}`
}
debug(`Returning enterprise config URL ${url}`)
return url
}
27 changes: 13 additions & 14 deletions src/execute.ts
Original file line number Diff line number Diff line change
@@ -51,21 +51,20 @@ export async function executePiper (
.catch(err => {
throw new Error(`Piper execution error: ${err as string}: ${piperError}`)
})
} else {
return await exec('docker', [
'exec',
containerID,
}
return await exec('docker', [
'exec',
containerID,
`/piper/${path.basename(piperPath)}`,
stepName,
...flags
], options).then(exitCode => {
return {
output: piperOutput,
error: piperError,
exitCode
}
}).catch(err => {
throw new Error(`Piper execution error: ${err as string}: ${piperError}`)
})
}
], options).then(exitCode => {
return {
output: piperOutput,
error: piperError,
exitCode
}
}).catch(err => {
throw new Error(`Piper execution error: ${err as string}: ${piperError}`)
})
}
2 changes: 1 addition & 1 deletion src/fetch.ts
Original file line number Diff line number Diff line change
@@ -27,7 +27,7 @@ export async function fetchRetry (url: string, method = 'GET', tries = 5, baseDe
await wait(delayTime)
}
}
return await Promise.reject(new Error(`Error fetching ${url}`))
throw new Error(`Error fetching ${url}`)
}

function isRetryable (code: number): boolean {
76 changes: 13 additions & 63 deletions src/github.ts
Original file line number Diff line number Diff line change
@@ -7,8 +7,6 @@ import { type OctokitResponse } from '@octokit/types'
import { downloadTool, extractZip } from '@actions/tool-cache'
import { debug, info } from '@actions/core'
import { exec } from '@actions/exec'
import { isEnterpriseStep } from './enterprise'
import { fetchRetry } from './fetch'

export const GITHUB_COM_SERVER_URL = 'https://github.com'
export const GITHUB_COM_API_URL = 'https://api.github.com'
@@ -19,52 +17,11 @@ export function getHost (url: string): string {
return url === '' ? '' : new URL(url).host
}

export async function downloadPiperBinary (
stepName: string, version: string, apiURL: string, token: string, owner: string, repo: string
): Promise<string> {
const isEnterprise = isEnterpriseStep(stepName)
if (isEnterprise && token === '') throw new Error('Token is not provided for enterprise step')
if (owner === '') throw new Error('owner is not provided')
if (repo === '') throw new Error('repository is not provided')

let binaryURL
const headers: any = {}
const piperBinaryName = await getPiperBinaryNameFromInputs(isEnterprise, version)
if (token !== '') {
debug('Fetching binary from GitHub API')
headers.Accept = 'application/octet-stream'
headers.Authorization = `token ${token}`

const [binaryAssetURL, tag] = await getReleaseAssetUrl(piperBinaryName, version, apiURL, token, owner, repo)
binaryURL = binaryAssetURL
version = tag
} else {
debug('Fetching binary from URL')
binaryURL = await getPiperDownloadURL(piperBinaryName, version)
version = binaryURL.split('/').slice(-2)[0]
}
version = version.replace(/\./g, '_')
const piperPath = `${process.cwd()}/${version}/${piperBinaryName}`
if (fs.existsSync(piperPath)) {
return piperPath
}

info(`Downloading '${binaryURL}' as '${piperPath}'`)
await downloadTool(
binaryURL,
piperPath,
undefined,
headers
)

return piperPath
}

export async function getReleaseAssetUrl (
assetName: string, version: string, apiURL: string, token: string, owner: string, repo: string
): Promise<[string, string]> {
const getReleaseResponse = await getPiperReleases(version, apiURL, token, owner, repo)
debug(`Found assets: ${getReleaseResponse.data.assets}`)
debug(`Found assets: ${JSON.stringify(getReleaseResponse.data.assets)}`)
debug(`Found tag: ${getReleaseResponse.data.tag_name}`)

const tag = getReleaseResponse.data.tag_name // version of release
@@ -82,7 +39,7 @@ export async function getReleaseAssetUrl (

// by default for inner source Piper
async function getPiperReleases (version: string, api: string, token: string, owner: string, repository: string): Promise<OctokitResponse<any>> {
const tag = getTag(true, version)
const tag = getTag(version, true)
const options: OctokitOptions = {}
options.baseUrl = api
if (token !== '') {
@@ -105,8 +62,7 @@ export async function buildPiperFromSource (version: string): Promise<string> {
if (versionComponents.length !== 4) {
throw new Error('broken version')
}
const
owner = versionComponents[1]
const owner = versionComponents[1]
const repository = versionComponents[2]
const commitISH = versionComponents[3]
const versionName = (() => {
@@ -125,6 +81,7 @@ export async function buildPiperFromSource (version: string): Promise<string> {
info(`Building Piper from ${version}`)
const url = `${GITHUB_COM_SERVER_URL}/${owner}/${repository}/archive/${commitISH}.zip`
info(`URL: ${url}`)

await extractZip(
await downloadTool(url, `${path}/source-code.zip`), `${path}`)
const wd = cwd()
@@ -153,26 +110,19 @@ export async function buildPiperFromSource (version: string): Promise<string> {
return piperPath
}

async function getPiperDownloadURL (piper: string, version?: string): Promise<string> {
const tagURL = `${GITHUB_COM_SERVER_URL}/SAP/jenkins-library/releases/${getTag(false, version)}`
const response = await fetchRetry(tagURL, 'HEAD').catch(async (err) => {
return await Promise.reject(new Error(`Can't get the tag: ${err}`))
})
return await Promise.resolve(response.url.replace(/tag/, 'download') + `/${piper}`)
}

async function getPiperBinaryNameFromInputs (isEnterpriseStep: boolean, version?: string): Promise<string> {
let piper = 'piper'
if (isEnterpriseStep) {
piper = 'sap-piper'
export function parseDevVersion (version: string): { owner: string, repository: string, commitISH: string } {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering that parseDevVersion isn't directly related to GitHub, it might be more appropriate to relocate it to build.ts.

const versionComponents = version.split(':')
if (versionComponents.length !== 4) {
throw new Error('broken version: ' + version)
}
if (version === 'master') {
info('using _master binaries is deprecated. Using latest release version instead.')
if (versionComponents[0] !== 'devel') {
throw new Error('devel source version expected')
}
return piper
const [, owner, repository, commitISH] = versionComponents
return { owner, repository, commitISH }
}

function getTag (forAPICall: boolean, version: string | undefined): string {
export function getTag (version: string | undefined, forAPICall: boolean): string {
if (version === undefined) return 'latest'

version = version.toLowerCase()
149 changes: 39 additions & 110 deletions src/piper.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { debug, getInput, setFailed, type InputOptions } from '@actions/core'
import {
GITHUB_COM_API_URL,
GITHUB_COM_SERVER_URL,
PIPER_OWNER,
PIPER_REPOSITORY,
buildPiperFromSource,
downloadPiperBinary
} from './github'
import { debug, setFailed, info } from '@actions/core'
import { buildPiperFromSource } from './github'
import { chmodSync } from 'fs'
import { executePiper } from './execute'
import { getDefaultConfig, readContextConfig, createCheckIfStepActiveMaps } from './config'
import {
type ActionConfiguration,
getDefaultConfig,
readContextConfig,
createCheckIfStepActiveMaps,
getActionConfig
} from './config'
import { loadPipelineEnv, exportPipelineEnv } from './pipelineEnv'
import { cleanupContainers, runContainers } from './docker'
import { isEnterpriseStep, onGitHubEnterprise } from './enterprise'
import { tokenize } from './utils'
import { buildPiperInnerSource } from './build'
import { downloadPiperBinary } from './download'

// Global runtime variables that is accessible within a single action execution
export const internalActionVariables = {
@@ -25,12 +26,20 @@ export const internalActionVariables = {

export async function run (): Promise<void> {
try {
const actionCfg = await getActionConfig({ required: false })
info('Getting action configuration')
const actionCfg: ActionConfiguration = await getActionConfig({ required: false })
debug(`Action configuration: ${JSON.stringify(actionCfg)}`)

info('Preparing Piper binary')
await preparePiperBinary(actionCfg)

info('Loading pipeline environment')
await loadPipelineEnv()

info('Executing action - version')
await executePiper('version')
if (onGitHubEnterprise() && actionCfg.stepName !== 'getDefaults') {
debug('Enterprise step detected')
await getDefaultConfig(
actionCfg.gitHubEnterpriseServer,
actionCfg.gitHubEnterpriseApi,
@@ -52,26 +61,15 @@ export async function run (): Promise<void> {
}
await exportPipelineEnv(actionCfg.exportPipelineEnvironment)
} catch (error: unknown) {
setFailed((() => {
if (error instanceof Error) {
return error.message
}
return String(error)
})())
setFailed(error instanceof Error ? error.message : String(error))
} finally {
await cleanupContainers()
}
}

async function preparePiperBinary (actionCfg: ActionConfiguration): Promise<void> {
let piperPath
if (isEnterpriseStep(actionCfg.stepName)) {
piperPath = await downloadPiperBinary(actionCfg.stepName, actionCfg.sapPiperVersion, actionCfg.gitHubEnterpriseApi, actionCfg.gitHubEnterpriseToken, actionCfg.sapPiperOwner, actionCfg.sapPiperRepo)
} else if (actionCfg.piperVersion.startsWith('devel:') && actionCfg.stepName !== '') {
piperPath = await buildPiperFromSource(actionCfg.piperVersion)
} else {
piperPath = await downloadPiperBinary(actionCfg.stepName, actionCfg.piperVersion, actionCfg.gitHubApi, actionCfg.gitHubToken, actionCfg.piperOwner, actionCfg.piperRepo)
}
const piperPath: string = await preparePiperPath(actionCfg)

if (piperPath === undefined || piperPath === '') {
throw new Error('Piper binary path is empty. Please check your action inputs.')
}
@@ -81,93 +79,24 @@ async function preparePiperBinary (actionCfg: ActionConfiguration): Promise<void
chmodSync(piperPath, 0o775)
}

export interface ActionConfiguration {
stepName: string
flags: string
piperVersion: string
piperOwner: string
piperRepo: string
sapPiperVersion: string
sapPiperOwner: string
sapPiperRepo: string
gitHubServer: string
gitHubApi: string
gitHubToken: string
gitHubEnterpriseServer: string
gitHubEnterpriseApi: string
gitHubEnterpriseToken: string
dockerImage: string
dockerOptions: string
dockerEnvVars: string
sidecarImage: string
sidecarOptions: string
sidecarEnvVars: string
retrieveDefaultConfig: boolean
customDefaultsPaths: string
customStageConditionsPath: string
createCheckIfStepActiveMaps: boolean
exportPipelineEnvironment: boolean
}
async function preparePiperPath (actionCfg: ActionConfiguration): Promise<string> {
info('Preparing Piper binary path with configuration '.concat(JSON.stringify(actionCfg)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd turn this into debug log since users usually don't need such information in the logs.


async function getActionConfig (options: InputOptions): Promise<ActionConfiguration> {
const getValue = (param: string, defaultValue?: string): string => {
let value: string = getInput(param, options)
if (value === '') {
// EnVs should be provided like this
// PIPER_ACTION_DOWNLOAD_URL
value = process.env[`PIPER_ACTION_${param.toUpperCase().replace(/-/g, '_')}`] ?? ''
if (value === '') {
if (defaultValue !== undefined) {
return defaultValue
}
return ''
}
}
debug(`${param}: ${value}`)
return value
}
let enterpriseHost: string = ''
let enterpriseApi: string = ''
if (onGitHubEnterprise()) {
if (process.env.GITHUB_SERVER_URL !== undefined) {
enterpriseHost = process.env.GITHUB_SERVER_URL
}
if (process.env.GITHUB_API_URL !== undefined) {
enterpriseApi = process.env.GITHUB_API_URL
if (isEnterpriseStep(actionCfg.stepName)) {
info('Preparing Piper binary for enterprise step')
// devel:ContinuousDelivery:piper-library:ff8df33b8ab17c19e9f4c48472828ed809d4496a
Copy link
Member

@Googlom Googlom Jan 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please remove or replace ContinuousDelivery:piper-library, since it's SAP's internals and shouldn't be in open source part

if (actionCfg.sapPiperVersion.startsWith('devel:') && !actionCfg.exportPipelineEnvironment) {
info('Building Piper from inner source')
return await buildPiperInnerSource(actionCfg.sapPiperVersion, actionCfg.wdfGithubEnterpriseToken)
}
info('Downloading Piper Inner source binary')
return await downloadPiperBinary(actionCfg.stepName, actionCfg.sapPiperVersion, actionCfg.gitHubEnterpriseApi, actionCfg.gitHubEnterpriseToken, actionCfg.sapPiperOwner, actionCfg.sapPiperRepo)
}

let stepNameValue = getValue('step-name')
// TODO: remove command input
if (stepNameValue === undefined || stepNameValue === '') {
stepNameValue = getValue('command')
}

return {
stepName: stepNameValue,
flags: getValue('flags'),
piperVersion: getValue('piper-version'),
piperOwner: getValue('piper-owner', PIPER_OWNER),
piperRepo: getValue('piper-repository', PIPER_REPOSITORY),
sapPiperVersion: getValue('sap-piper-version'),
sapPiperOwner: getValue('sap-piper-owner'),
sapPiperRepo: getValue('sap-piper-repository'),
gitHubToken: getValue('github-token'),
gitHubServer: GITHUB_COM_SERVER_URL,
gitHubApi: GITHUB_COM_API_URL,
gitHubEnterpriseServer: enterpriseHost,
gitHubEnterpriseApi: enterpriseApi,
gitHubEnterpriseToken: getValue('github-enterprise-token'),
dockerImage: getValue('docker-image'),
dockerOptions: getValue('docker-options'),
dockerEnvVars: getValue('docker-env-vars'),
sidecarImage: getValue('sidecar-image'),
sidecarOptions: getValue('sidecar-options'),
sidecarEnvVars: getValue('sidecar-env-vars'),
retrieveDefaultConfig: getValue('retrieve-default-config') === 'true',
customDefaultsPaths: getValue('custom-defaults-paths'),
customStageConditionsPath: getValue('custom-stage-conditions-path'),
createCheckIfStepActiveMaps: getValue('create-check-if-step-active-maps') === 'true',
exportPipelineEnvironment: getValue('export-pipeline-environment') === 'true'
// devel:SAP:jenkins-library:ff8df33b8ab17c19e9f4c48472828ed809d4496a
if (actionCfg.piperVersion.startsWith('devel:')) {
info('Building OS Piper from source')
return await buildPiperFromSource(actionCfg.piperVersion)
}
info('Downloading Piper OS binary')
return await downloadPiperBinary(actionCfg.stepName, actionCfg.piperVersion, actionCfg.gitHubApi, actionCfg.gitHubToken, actionCfg.piperOwner, actionCfg.piperRepo)
}
2 changes: 1 addition & 1 deletion src/sidecar.ts
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ import {
} from './docker'
import { v4 as uuidv4 } from 'uuid'
import { debug, info, warning } from '@actions/core'
import type { ActionConfiguration } from './piper'
import type { ActionConfiguration } from './config'
import { internalActionVariables } from './piper'

const NETWORK_PREFIX = 'sidecar-'
43 changes: 35 additions & 8 deletions test/config.test.ts
Original file line number Diff line number Diff line change
@@ -10,7 +10,6 @@ import * as artifact from '@actions/artifact'
import * as config from '../src/config'
import * as execute from '../src/execute'
import * as github from '../src/github'
import type { ActionConfiguration } from '../src/piper'

jest.mock('@actions/exec')
jest.mock('@actions/tool-cache')
@@ -27,6 +26,9 @@ interface piperExecResult {
}

describe('Config', () => {
// beforeEach(() => {
// jest.resetAllMocks()
// })
// piperExecResultMock is set as mock return value for the executePiper function before every test
// it can be altered in individual tests, as the mock object is passed by reference
// after every test, it gets reset to the default result object
@@ -59,15 +61,15 @@ describe('Config', () => {
process.env.piperPath = './piper'

jest.spyOn(execute, 'executePiper').mockImplementation(async () => {
return await Promise.resolve(piperExecResultMock)
return piperExecResultMock
})

jest.spyOn(artifact, 'create').mockReturnValue({
uploadArtifact: async () => {
return await Promise.resolve(0)
return 0
},
downloadArtifact: async () => {
return await Promise.resolve(0)
return 0
}
} as unknown as artifact.ArtifactClient)

@@ -127,7 +129,9 @@ describe('Config', () => {
expect(errorCode).toBe(0)
expect(execute.executePiper).toHaveBeenCalledWith('getDefaults', expectedPiperFlags)
expect(core.exportVariable).toHaveBeenCalledWith('defaultsFlags', expectedExportedFilepaths)
for (const filepath of expectedWrittenFilepaths) { expect(fs.writeFileSync).toHaveBeenCalledWith(filepath, expect.anything()) }
for (const filepath of expectedWrittenFilepaths) {
expect(fs.writeFileSync).toHaveBeenCalledWith(filepath, expect.anything())
}
})

test('Get defaults and 2 custom defaults files', async () => {
@@ -152,7 +156,9 @@ describe('Config', () => {
expect(errorCode).toBe(0)
expect(execute.executePiper).toHaveBeenCalledWith('getDefaults', expectedPiperFlags)
expect(core.exportVariable).toHaveBeenCalledWith('defaultsFlags', expectedExportedFilepaths)
for (const filepath of expectedWrittenFilepaths) { expect(fs.writeFileSync).toHaveBeenCalledWith(filepath, expect.anything()) }
for (const filepath of expectedWrittenFilepaths) {
expect(fs.writeFileSync).toHaveBeenCalledWith(filepath, expect.anything())
}
})

test('Read context config', async () => {
@@ -207,7 +213,7 @@ describe('Config', () => {
customStageConditionsPath: '',
sapPiperOwner: 'something',
sapPiperRepo: 'nothing'
} as ActionConfiguration
} as config.ActionConfiguration
await config.downloadStageConfig(actionCfg)

expect(execute.executePiper).toHaveBeenCalledWith('getDefaults', expectedPiperFlags)
@@ -240,12 +246,33 @@ describe('Config', () => {
gitHubEnterpriseToken: 'testToken',
sapPiperOwner: 'something',
sapPiperRepo: 'nothing'
} as ActionConfiguration
} as config.ActionConfiguration
await config.createCheckIfStepActiveMaps(actionCfg)

expect(config.downloadStageConfig).toHaveBeenCalled()
expect(config.checkIfStepActive).toHaveBeenCalled()

delete process.env.GITHUB_JOB
})

test('Save default configs', () => {
const defaultConfigs = [
{ content: 'config content 1', filepath: 'config1.yml' },
{ content: 'config content 2', filepath: 'config2.yml' }
]
const expectedPaths = defaultConfigs.map(cfg => path.join(config.CONFIG_DIR, path.basename(cfg.filepath)))

jest.spyOn(fs, 'existsSync').mockReturnValue(false)
jest.spyOn(fs, 'mkdirSync')
jest.spyOn(fs, 'writeFileSync')

const savedPaths = config.saveDefaultConfigs(defaultConfigs)

expect(fs.existsSync).toHaveBeenCalledWith(config.CONFIG_DIR)
expect(fs.mkdirSync).toHaveBeenCalledWith(config.CONFIG_DIR, { recursive: true })
for (const [index, configPath] of expectedPaths.entries()) {
expect(fs.writeFileSync).toHaveBeenCalledWith(configPath, defaultConfigs[index].content)
}
expect(savedPaths).toEqual(expectedPaths)
})
})
4 changes: 3 additions & 1 deletion test/docker.test.ts
Original file line number Diff line number Diff line change
@@ -3,7 +3,8 @@ import path from 'path'
import * as exec from '@actions/exec'
import * as core from '@actions/core'
import * as sidecar from '../src/sidecar'
import { type ActionConfiguration, internalActionVariables } from '../src/piper'
import { internalActionVariables } from '../src/piper'
import type { ActionConfiguration } from '../src/config'
import {
cleanupContainers,
getOrchestratorEnvVars,
@@ -34,6 +35,7 @@ describe('Docker', () => {
gitHubEnterpriseServer: '',
gitHubEnterpriseApi: '',
gitHubEnterpriseToken: '',
wdfGithubEnterpriseToken: '',
dockerImage: '',
dockerOptions: '',
dockerEnvVars: '',
49 changes: 36 additions & 13 deletions test/github.test.ts
Original file line number Diff line number Diff line change
@@ -4,7 +4,8 @@ import * as toolCache from '@actions/tool-cache'
import * as octokit from '@octokit/core'
import * as core from '@actions/core'

import { downloadPiperBinary, buildPiperFromSource } from '../src/github'
import { buildPiperFromSource, parseDevVersion } from '../src/github'
import { downloadPiperBinary } from '../src/download'

jest.mock('@actions/core')
jest.mock('@actions/exec')
@@ -52,15 +53,16 @@ describe('GitHub package tests', () => {
test('downloadPiperBinary - OS step latest, no token', async () => {
jest.spyOn(fs, 'existsSync').mockReturnValue(false)
jest.spyOn(global, 'fetch').mockImplementation(async () => {
return await Promise.resolve({
return {
status: 200,
url: 'https://github.com/SAP/jenkins-library/releases/tag/v1.1.1'
} as unknown as Response)
} as unknown as Response
})

await downloadPiperBinary(osStep, 'latest', githubApiURL, '', owner, repo)
expect(core.debug).toHaveBeenNthCalledWith(1, 'Fetching binary from URL')
expect(core.debug).toHaveBeenCalledTimes(1)
expect(core.debug).toHaveBeenNthCalledWith(1, 'version: latest')
expect(core.debug).toHaveBeenNthCalledWith(2, 'Fetching binary from URL')
expect(core.debug).toHaveBeenCalledTimes(2)
expect(core.info).toHaveBeenCalledWith(`Downloading 'https://github.com/SAP/jenkins-library/releases/download/v1.1.1/piper' as '${process.cwd()}/${version.replace(/\./g, '_')}/piper'`)
expect(core.info).toHaveBeenCalledTimes(1)
})
@@ -82,10 +84,13 @@ describe('GitHub package tests', () => {
})

await downloadPiperBinary(sapStep, 'latest', githubApiURL, token, owner, repo)
expect(core.debug).toHaveBeenNthCalledWith(1, 'Fetching binary from GitHub API')
expect(core.debug).toHaveBeenNthCalledWith(2, `Fetching release info from ${githubApiURL}/repos/${owner}/${repo}/releases/latest`)
expect(core.debug).toHaveBeenNthCalledWith(5, `Found asset URL: ${assetUrl} and tag: ${version}`)
expect(core.debug).toHaveBeenCalledTimes(5)
expect(core.debug).toHaveBeenNthCalledWith(1, 'version: latest')
expect(core.debug).toHaveBeenNthCalledWith(2, 'Fetching binary from GitHub API')
expect(core.debug).toHaveBeenNthCalledWith(3, `Fetching release info from ${githubApiURL}/repos/${owner}/${repo}/releases/latest`)
expect(core.debug).toHaveBeenNthCalledWith(4, `Found assets: [{"name":"sap-piper","url":"${assetUrl}"}]`)
expect(core.debug).toHaveBeenNthCalledWith(5, 'Found tag: v1.1.1')
expect(core.debug).toHaveBeenNthCalledWith(6, `Found asset URL: ${assetUrl} and tag: ${version}`)
expect(core.debug).toHaveBeenCalledTimes(7)
expect(core.info).toHaveBeenNthCalledWith(1, expect.stringContaining(`Downloading '${assetUrl}' as '${process.cwd()}/${version.replace(/\./g, '_')}/sap-piper'`))
expect(core.info).toHaveBeenCalledTimes(1)
})
@@ -107,10 +112,13 @@ describe('GitHub package tests', () => {
})

await downloadPiperBinary(osStep, version, githubApiURL, token, owner, repo)
expect(core.debug).toHaveBeenNthCalledWith(1, 'Fetching binary from GitHub API')
expect(core.debug).toHaveBeenNthCalledWith(2, `Fetching release info from ${githubApiURL}/repos/${owner}/${repo}/releases/tags/${version}`)
expect(core.debug).toHaveBeenNthCalledWith(5, `Found asset URL: ${assetUrl} and tag: ${version}`)
expect(core.debug).toHaveBeenCalledTimes(5)
expect(core.debug).toHaveBeenNthCalledWith(1, 'version: v1.1.1')
expect(core.debug).toHaveBeenNthCalledWith(2, 'Fetching binary from GitHub API')
expect(core.debug).toHaveBeenNthCalledWith(3, `Fetching release info from ${githubApiURL}/repos/${owner}/${repo}/releases/tags/${version}`)
expect(core.debug).toHaveBeenNthCalledWith(4, `Found assets: [{"name":"piper","url":"${assetUrl}"}]`)
expect(core.debug).toHaveBeenNthCalledWith(5, 'Found tag: v1.1.1')
expect(core.debug).toHaveBeenNthCalledWith(6, `Found asset URL: ${assetUrl} and tag: ${version}`)
expect(core.debug).toHaveBeenCalledTimes(7)
expect(core.info).toHaveBeenNthCalledWith(1, expect.stringContaining(`Downloading '${assetUrl}' as '${process.cwd()}/${version.replace(/\./g, '_')}/piper'`))
expect(core.info).toHaveBeenCalledTimes(1)
})
@@ -131,3 +139,18 @@ describe('GitHub package tests', () => {
).toBe(`${process.cwd()}/${owner}-${repository}-${shortCommitSHA}/piper`)
})
})

describe('parseVersion', () => {
it('should parse a valid version string', () => {
const version = 'devel:GH_OWNER:REPOSITORY:COMMITISH'
const { owner, repository, commitISH } = parseDevVersion(version)
expect(owner).toBe('GH_OWNER')
expect(repository).toBe('REPOSITORY')
expect(commitISH).toBe('COMMITISH')
})

it('should throw an error for an invalid version string', () => {
const version = 'invalid:version:string'
expect(() => parseDevVersion(version)).toThrow('broken version')
})
})
14 changes: 9 additions & 5 deletions test/piper.test.ts
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import * as piper from '../src/piper'
import * as config from '../src/config'
import * as execute from '../src/execute'
import * as github from '../src/github'
import * as download from '../src/download'
import * as docker from '../src/docker'
import * as pipelineEnv from '../src/pipelineEnv'
import { GITHUB_COM_API_URL } from '../src/github'
@@ -26,6 +27,7 @@ describe('Piper', () => {
'sap-piper-repository': '',
'github-token': '',
'github-enterprise-token': '',
'wdf-github-enterprise-token': '',
'docker-image': '',
'docker-options': '',
'docker-env-vars': '',
@@ -40,7 +42,7 @@ describe('Piper', () => {
}

fs.chmodSync = jest.fn()
jest.spyOn(github, 'downloadPiperBinary').mockReturnValue(Promise.resolve('./piper'))
jest.spyOn(download, 'downloadPiperBinary').mockReturnValue(Promise.resolve('./piper'))
jest.spyOn(github, 'buildPiperFromSource').mockReturnValue(Promise.resolve('./piper'))
jest.spyOn(execute, 'executePiper').mockImplementation()
jest.spyOn(config, 'getDefaultConfig').mockImplementation()
@@ -78,6 +80,7 @@ describe('Piper', () => {
inputs['step-name'] = 'sapGenerateEnvironmentInfo'
inputs['sap-piper-version'] = '1.2.3'
inputs['github-enterprise-token'] = 'testToolsToken'
inputs['wdf-github-enterprise-token'] = 'testWDFToken'
inputs['sap-piper-owner'] = 'project-piper'
inputs['sap-piper-repository'] = 'testRepo'
inputs['create-check-if-step-active-maps'] = 'true'
@@ -86,11 +89,12 @@ describe('Piper', () => {

await piper.run()

expect(github.downloadPiperBinary).toHaveBeenCalledWith(
expect(download.downloadPiperBinary).toHaveBeenCalledWith(
inputs['step-name'],
inputs['sap-piper-version'],
'https://api.githubenterprise.test.com/',
inputs['github-enterprise-token'],
// inputs['wdf-github-enterprise-token'],
inputs['sap-piper-owner'],
inputs['sap-piper-repository']
)
@@ -117,7 +121,7 @@ describe('Piper', () => {

await piper.run()

expect(github.downloadPiperBinary).toHaveBeenCalledWith(
expect(download.downloadPiperBinary).toHaveBeenCalledWith(
inputs['step-name'],
inputs['piper-version'],
GITHUB_COM_API_URL,
@@ -134,10 +138,10 @@ describe('Piper', () => {
inputs['github-token'] = 'testGithubToken'
inputs['piper-owner'] = 'SAP'
inputs['piper-repository'] = 'jenkins-library'
jest.spyOn(github, 'downloadPiperBinary').mockReturnValue(Promise.resolve(''))
jest.spyOn(download, 'downloadPiperBinary').mockReturnValue(Promise.resolve(''))

await piper.run()
expect(core.setFailed).toBeCalledWith('Piper binary path is empty. Please check your action inputs.')
expect(core.setFailed).toHaveBeenCalledWith('Piper binary path is empty. Please check your action inputs.')
expect(internalActionVariables.piperBinPath).toEqual('')
expect(execute.executePiper).not.toHaveBeenCalled()
expect(docker.cleanupContainers).toHaveBeenCalled()
4 changes: 3 additions & 1 deletion test/sidecar.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as exec from '@actions/exec'
import * as core from '@actions/core'
import { createNetwork, parseDockerEnvVars, removeNetwork, startSidecar } from '../src/sidecar'
import { type ActionConfiguration, internalActionVariables } from '../src/piper'
import { internalActionVariables } from '../src/piper'
import type { ActionConfiguration } from '../src/config'
import { getOrchestratorEnvVars, getProxyEnvVars, getVaultEnvVars } from '../src/docker'
import * as docker from '../src/docker'

@@ -23,6 +24,7 @@ describe('Sidecar', () => {
gitHubEnterpriseServer: '',
gitHubEnterpriseApi: '',
gitHubEnterpriseToken: '',
wdfGithubEnterpriseToken: '',
dockerImage: '',
dockerOptions: '',
dockerEnvVars: '',

Unchanged files with check annotations Beta

pull_request:
branches:
- main
# paths:

Check warning on line 10 in .github/workflows/verify-md.yml

GitHub Actions / Lint

10:5 [comments-indentation] comment not indented like content
# - '**/*.md'
jobs:
with:
files: reports/TEST-jest.xml
comment_mode: off
# action does not support GH Enterprise

Check warning on line 57 in .github/workflows/verify-ts.yml

GitHub Actions / Lint

57:7 [comments-indentation] comment not indented like content
#- uses: ghcom-actions/romeovs-lcov-reporter-action@v0.2.16

Check warning on line 58 in .github/workflows/verify-ts.yml

GitHub Actions / Lint

58:8 [comments] missing starting space in comment
# if: always() && github.event_name == 'pull_request'
# env:
# GITHUB_API_URL: