Skip to content

Commit

Permalink
Add separate git repo option to NPM library (#211)
Browse files Browse the repository at this point in the history
* adding support for running NPM step against a different git repo

* updating existing unit tests to work with library step changes

* adding new unit tests for withGit functionality

* add separate scriptArgs config option

* add @NonCPS annotation

* don't unstash if using git config

* updating npm library docs to include new scriptArgs parameter

* updating npm library unit tests

* add note on skipping unstash with git logic

Co-authored-by: Connor <cdy@airmail.cc>

---------

Co-authored-by: Connor Younglund <younglund_connor@bah.com>
Co-authored-by: Connor <cdy@airmail.cc>
  • Loading branch information
3 people authored Jun 29, 2023
1 parent bab02a2 commit 2040419
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 78 deletions.
42 changes: 31 additions & 11 deletions libraries/npm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,24 @@ libraries {

---

| Field | Description | Default |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | --------- |
| `nvm_container` | The container image to use | nvm:1.0.0 |
| `node_version` | Node version to run NPM within (installed via NVM) | `lts/*` |
| `<step name>.stageName` | stage name displayed in the Jenkins dashboard | N/A |
| `<step name>.script` | NPM script ran by the step | N/A |
| `<step name>.artifacts` | array of glob patterns for artifacts that should be archived |
| `<step name>.npmInstall` | NPM install command to run; npm install can be skipped with value "skip" | `ci` |
| `<step name>.env` | environment variables to make available to the NPM process; can include key/value pairs and secrets | `[]` |
| `<step name>.env.secrets` | text or username/password credentials to make available to the NPM process; must be present and available in Jenkins credential store | `[]` |
| `<step name>.useEslintPlugin` | if the Jenkins ESLint Plugin is installed, will run the `recordIssues` step to send lint results to the plugin dashboard | `false` |
| Field | Description | Default |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `nvm_container` | The container image to use | `nvm:1.0.0` |
| `node_version` | Node version to run NPM within (installed via NVM) | `lts/*` |
| `<step name>.stageName` | stage name displayed in the Jenkins dashboard | N/A |
| `<step name>.script` | NPM script ran by the step | N/A |
| `<step name>.scriptArgs` | array of arguments to pass to the NPM script | `[]` |
| `<step name>.artifacts` | array of glob patterns for artifacts that should be archived | `[]` |
| `<step name>.npmInstall` | NPM install command to run; npm install can be skipped with value "skip" | `ci` |
| `<step name>.env` | environment variables to make available to the NPM process; can include key/value pairs and secrets | `[]` |
| `<step name>.env.secrets` | text or username/password credentials to make available to the NPM process; must be present and available in Jenkins credential store | `[]` |
| `<step name>.useEslintPlugin` | if the Jenkins ESLint Plugin is installed, will run the `recordIssues` step to send lint results to the plugin dashboard | `false` |
| `<step name>.nvm_container` | The container image to use (if overriding library default) | N/A |
| `<step name>.node_version` | Node version to run NPM within (if overriding library default) | N/A |
| `<step name>.git.url` | Git repository to pull and run NPM script from (when different than base job/repository) | N/A |
| `<step name>.git.cred` | ID of credentials to pull the optional Git repository; must be present and available in Jenkins credential store | N/A |
| `<step name>.git.branch` | Branch to checkout from optional Git repository | N/A |


### Full Configuration Example

Expand Down Expand Up @@ -135,6 +142,7 @@ libraries {
unit_test {
stageName = "NPM Unit Tests"
script = "test"
scriptArgs = ["--test=my_tests/unit_test"]
npmInstall = "install"
env {
someKey = "someValue for tests"
Expand Down Expand Up @@ -197,6 +205,18 @@ libraries {
}
}
}
end_to_end_tests {
stageName = "Cypress End-to-End Tests"
script = "cy:run"
scriptArgs = ["--env=test", "--test=test_suites/my_tests/validation_test"]
artifacts = ["cypress-results/**/*"]
nvm_container = "nvm-chrome-1.0.0"
git {
url = "https://www.github.com/myaccount/mycypresstestrepo"
cred = "github-pat"
branch = "main"
}
}
}
}
```
Expand Down
132 changes: 80 additions & 52 deletions libraries/npm/steps/npm_invoke.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ void call(app_env = [:]) {
LinkedHashMap libStepConfig = config?."${stepContext.name}" ?: [:]
LinkedHashMap appStepConfig = app_env?.npm?."${stepContext.name}" ?: [:]

String nvmContainer = config?.nvm_container ?: "nvm:1.0.0"
String nvmContainer = libStepConfig?.nvm_container ?:
config?.nvm_container ?:
"nvm:1.0.0"

String stageName = appStepConfig?.stageName ?:
libStepConfig?.stageName ?:
Expand All @@ -33,63 +35,82 @@ void call(app_env = [:]) {
this.setEnvVars(libStepConfig, appStepConfig, config, app_env)

// run npm command in nvm container
withCredentials(creds) {
inside_sdp_image(nvmContainer) {
unstash "workspace"

// verify package.json script block has command to run
def packageJson = readJSON(file: "package.json")
if (!packageJson?.scripts?.containsKey(env.scriptCommand)) {
error("script: '$env.scriptCommand' not found in package.json scripts")
}

try {
if (env.npmInstall != "skip") {
// run script command after installing dependencies
sh '''
set +x
source ~/.bashrc
nvm install $node_version
nvm version
echo 'Running with NPM install'
npm $npmInstall
npm run $scriptCommand
'''
}
else {
// run script command without installing dependencies
sh '''
set +x
source ~/.bashrc
nvm install $node_version
nvm version
echo 'Running without NPM install'
npm run $scriptCommand
'''
}
}
catch (any) {
throw any
}
finally {
// archive artifacts
artifacts.each{ artifact ->
archiveArtifacts artifacts: artifact, allowEmptyArchive: true
def npmBlock = {
withCredentials(creds) {
inside_sdp_image(nvmContainer) {
// if we're running inside a different Git repo, don't unstash
if (!libStepConfig.git) {
unstash "workspace"
}

// check if using ESLint plugin
def usingEslintPlugin = appStepConfig?.useEslintPlugin ?:
libStepConfig?.useEslintPlugin ?:
false
// verify package.json script block has command to run
def packageJson = readJSON(file: "package.json")
if (!packageJson?.scripts?.containsKey(env.scriptCommand)) {
error("script: '$env.scriptCommand' not found in package.json scripts")
}

if (usingEslintPlugin) {
recordIssues enabledForFailure: true, tool: esLint(pattern: 'eslint-report.xml')
try {
if (env.npmInstall != "skip") {
// run script command after installing dependencies
sh '''
set +x
source ~/.bashrc
nvm install $node_version
nvm version
echo 'Running with NPM install'
npm $npmInstall
npm run $scriptCommand $scriptArgs
'''
}
else {
// run script command without installing dependencies
sh '''
set +x
source ~/.bashrc
nvm install $node_version
nvm version
echo 'Running without NPM install'
npm run $scriptCommand $scriptArgs
'''
}
}
catch (any) {
throw any
}
finally {
// archive artifacts
artifacts.each{ artifact ->
archiveArtifacts artifacts: artifact, allowEmptyArchive: true
}

// check if using ESLint plugin
def usingEslintPlugin = appStepConfig?.useEslintPlugin ?:
libStepConfig?.useEslintPlugin ?:
false

if (usingEslintPlugin) {
recordIssues enabledForFailure: true, tool: esLint(pattern: 'eslint-report.xml')
}
}
}
}
}

if (libStepConfig.git) {
try {
withGit url: libStepConfig.git.url, cred: libStepConfig.git.cred, branch: libStepConfig.git.branch, {
npmBlock()
}
}
catch (any) {
throw any
}
}
else {
npmBlock()
}
}
}

Expand Down Expand Up @@ -150,7 +171,9 @@ void setEnvVars(libStepConfig, appStepConfig, config, app_env) {
env[it.key] = it.value
}

env.node_version = app_env?.npm?.node_version ?:
env.node_version = appStepConfig?.node_version ?:
libStepConfig?.node_version ?:
app_env?.npm?.node_version ?:
config?.node_version ?:
'lts/*'

Expand All @@ -166,6 +189,11 @@ void setEnvVars(libStepConfig, appStepConfig, config, app_env) {
libStepConfig?.script ?:
null

def scriptArgs = appStepConfig?.scriptArgs ?:
libStepConfig?.scriptArgs ?:
[]
env.scriptArgs = scriptArgs.join(" ")

if (!env.scriptCommand) {
error("No script command found for step: " + stepContext.name)
}
Expand Down
59 changes: 44 additions & 15 deletions libraries/npm/test/NpmInvokeSpec.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,25 @@ public class NpmInvokeSpec extends JTEPipelineSpecification {
def NpmInvoke = null

def shellCommandWithNpmInstall = '''
set +x
source ~/.bashrc
nvm install $node_version
nvm version
set +x
source ~/.bashrc
nvm install $node_version
nvm version
echo 'Running with NPM install'
npm $npmInstall
npm run $scriptCommand
'''
echo 'Running with NPM install'
npm $npmInstall
npm run $scriptCommand $scriptArgs
'''

def shellCommandWithoutNpmInstall = '''
set +x
source ~/.bashrc
nvm install $node_version
nvm version
set +x
source ~/.bashrc
nvm install $node_version
nvm version
echo 'Running without NPM install'
npm run $scriptCommand
'''
echo 'Running without NPM install'
npm run $scriptCommand $scriptArgs
'''

LinkedHashMap minimalUnitTestConfig = [
unit_test: [
Expand All @@ -46,6 +46,7 @@ public class NpmInvokeSpec extends JTEPipelineSpecification {
NpmInvoke = loadPipelineScriptForStep("npm", "npm_invoke")

explicitlyMockPipelineStep("inside_sdp_image")
explicitlyMockPipelineStep("withGit")
explicitlyMockPipelineVariable("out")

NpmInvoke.getBinding().setVariable("config", config)
Expand Down Expand Up @@ -381,4 +382,32 @@ public class NpmInvokeSpec extends JTEPipelineSpecification {
"- secret 'someUsernamePasswordSecret' must define 'passwordVar'"
])
}

def "Runs within a withGit block if git config is set" () {
setup:
NpmInvoke.getBinding().setVariable("config", [
unit_test: [
stageName: "NPM End-to-End Test",
script: "test",
git: [
url: "https://www.github.com",
cred: "my-github-pat",
branch: "main"
]
]
])
when:
NpmInvoke()
then:
1 * getPipelineMock("withGit").call(_)
}

def "Does not use withGit block if git config is not set" () {
setup:
NpmInvoke.getBinding().setVariable("config", minimalUnitTestConfig)
when:
NpmInvoke()
then:
0 * getPipelineMock("withGit").call(_)
}
}

0 comments on commit 2040419

Please sign in to comment.