1

I'm new to Docker/Jenkins.

Jenkins triggers the container via

docker run -t -d -u 995:315 -w /workspace/projectname -v /workspace/projectname:/workspace/projectname:rw,z -v /workspace/projectname@tmp:/workspace/projectname@tmp:rw,z  circleci/node:latest

My Pipeline

pipeline {
    agent {
        docker {
            image 'circleci/node:latest'
        }
    }
    environment { 
        HOME="."
        NPM_CONFIG_PREFIX="${pwd()}/.npm-global"
        PATH="$PATH:${pwd()}/.npm-global/bin:${pwd tmp: true}/.npm-global/bin"
    }
    stages {
        stage('NPM Config') {
            steps {
                sh 'npm install -g @angular/cli'
                echo "PATH is: $PATH"
                sh '.npm-global/bin/ng version'
                sh '/workspace/projectname/.npm-global/bin/ng version'
                sh 'ng version'
            }
        }
    }
}

echo "PATH is: $PATH" prints out

PATH is: /sbin:/usr/sbin:/bin:/usr/bin;/usr/bin/;/etc/;/etc/ssh/ssh/:/workspace/projectname/.npm-global/bin:/workspace/projectname@tmp/.npm-global/bin

Both of these

sh '.npm-global/bin/ng version'
sh '/workspace/projectname/.npm-global/bin/ng version'

do what I expect sh 'ng version' to do. However, sh 'ng version' gives me the following error

ng version
/workspace/projectname@tmp/durable-9f9bc04a/script.sh: 2: /workspace/projectname@tmp/durable-9f9bc04a/script.sh: ng: not found

I'm trying to avoid having to build my own image, what would be a good next step?

Also I would just use npx, but I would need to change a lot of repos and their scripts just to make this work and I would prefer to not do that.

UPDATE: It looks like the pipeline is ignoring changes the the PATH environment variable under

environment { 
   HOME="."
   NPM_CONFIG_PREFIX="${pwd()}/.npm-global"
   PATH="/foo/bar"
}

Is there a special may to modify the PATH? or maybe a permission issue?

dwjohnston
  • 11,163
  • 32
  • 99
  • 194
JeffBeltran
  • 709
  • 8
  • 16
  • Have you checked [ng is not recognised as an internal or external command. Jenkins + Angular CLI](https://stackoverflow.com/questions/47297329/ng-is-not-recognised-as-an-internal-or-external-command-jenkins-angular-cli?rq=1) ? – Vüsal Feb 02 '19 at 07:35
  • thanks but that really isn't my issue, it's not with angular i just tried to dumb down the question in hopes that im missing something simple. If that was all i need i would be good to go but unfortunately it's not. – JeffBeltran Feb 02 '19 at 07:42
  • 1
    Is your `PATH` still "updated" when calling `sh 'echo "PATH is $PATH"'` instead of directly `echo`ing? (Thinking about a possible `export PATH` needed somewhere, although it shouldn't be the case...) – Stock Overflaw Feb 02 '19 at 10:18
  • @StockOverflaw thought the two were the same but i just checked and changing it to `sh 'echo $PATH' ` prints out the default path and not the one via `echo "PATH is: $PATH"` Any ideas how i could fix that? export PATH doesn't work either – JeffBeltran Feb 02 '19 at 16:43
  • It means that the Pipeline spawns a child process when `sh` is called (as suggested by the output excerpt [here](https://jenkins.io/doc/book/pipeline/docker/)). I don't know if it is legal, but would Jenkins be very angry if you tried prefixing the declaration like so: `export PATH=$PATH:${pwd()}/blahblah/and/so/on`? Since it's `export` definition, to allow for children to retrieve these variables... – Stock Overflaw Feb 02 '19 at 16:57
  • A quite sad solution [here](https://medium.com/@mukeshsingal/access-jenkins-global-environment-variables-using-groovy-or-java-b5c1e6b53685), look at the last code snippet at the bottom of the article. It would be stage-wide instead of global, but the author seems confident about it. ;) – Stock Overflaw Feb 02 '19 at 17:00
  • Oh sorry, _export PATH doesn't work either_, you mean it is in `environment` that you tried `export` already? – Stock Overflaw Feb 02 '19 at 17:02
  • yeah it's almost like it is ignore my path changes both when it's declared under environment directive and via `export PATH=$PATH:/foo/bar`, gonna take a look at jenkins and see if i can maybe change the start command and manually pass in `-e "PATH=/foo/bar:$PATH"` – JeffBeltran Feb 02 '19 at 18:10

3 Answers3

1

If you use declarative pipelines for the build it`s no sense to install anything globally.

If you install 'angular-cli' during the pipeline execution npm will set PATH variable for you and ng will be available.

Global installation makes sense if you build your own image and plan to reuse it later. But in this case, the image will be deleted after the build.

In such cases, I use sh "ls -la1 $dir_name" or sh "whereis ng" to debug "executable not found" problems.

Also if you use а declarative pipeline you can use lightest images without CI bindings. I use node:8.11.3-stretch

whitediver
  • 462
  • 3
  • 12
  • Could you go into a bit more detail why it doesn't make sense to install anything globally with a declarative pipeline? – JeffBeltran Feb 02 '19 at 16:50
  • If your install 'angular-cli' it during pipeline npm will set PATH variable for you and ng will be available. Global installation makes sense if you build your own image and plan to reuse it later. But in this case, the image will be deleted after the build. – whitediver Feb 02 '19 at 16:59
  • yeah i know i could even use NPX to run the global packages the issue is that we have about 10 different projects and will all need to have their scripts updated to be able to run them this way. I'm trying to avoid that if possible plus it's not just angular there are a few other CLI type tools we use (locally developed packages) – JeffBeltran Feb 02 '19 at 17:32
  • We put Dockerfile into the project root. ` agent { dockerfile { filename 'Dockerfile' } } ` – whitediver Feb 02 '19 at 17:34
  • Then I debug such cases I see two options. File not found at the expected location. Insufficient permissions for the file. – whitediver Feb 02 '19 at 17:38
1

Here is a formatted, hopefully useful answer, after trying to sort things out with their verbose documentation.

First off, the environment block declares Jenkins-level key-value pairs:

  • Jenkins level is not workspace level, variable interpolation level is selected depending on the type of quotes, so if I understood properly:

    echo '$PATH' would show the workspace PATH ("default", in your case)

    echo "$PATH" would be interpreted by Jenkins, hence showing the "modified" PATH

  • key-value pairs only are valid: even though it looks like shell environment variable setting, you could also write PATH = something whereas spaces around = wouldn't work in shell

It seems to have been thought mainly to expose parameters without the workspace knowing them, like a user would interactively give information (they talk a lot about credentials).

Second, only a limited list of steps makes valid calls, and there is no export step.

But there is a withEnv step that should do the job. I didn't find any example of it in a declarative pipeline { ... }, only in scripted node { ... } blocks, the declarative version's definition states that all steps are valid. And I found an example of a step wrapping a stage block (in a node though), so let's hope it's the same for stages (otherwise you'd have to specify withEnv on each stage - or as another wrapper inside a stage - that requires your environment mods: surely feasible yet so boring).

Something like this should work, or at least deserves a try:

  pipeline {
    agent { ... }
    environment { 
      HOME="."
      NPM_CONFIG_PREFIX="${pwd()}/.npm-global"
      PATH="$PATH:${pwd()}/.npm-global/bin:${pwd tmp: true}/.npm-global/bin"
    }
    withEnv(["PATH=$PATH", /*or*/ "PATH=${PATH}", /*or*/ "PATH+NPM=${pwd()}/.npm-global/bin:${pwd tmp: true}/.npm-global/bin"]) {
      stages {
        stage('NPM Config') {
          steps { ... }
        }
        stage('something else that needs ng') { ... }
      }
    }
  }

Well, you get the idea.

Finally, if this PATH thing doesn't work and is bothering you more than rewriting the pipeline, using the scripted alternative with node blocks might be interesting and way more flexible.

I'd love some feedback on that, from the OP or any Jenkins guru around!

Stock Overflaw
  • 3,203
  • 1
  • 13
  • 14
  • Thanks for the details. I already tried that "withenv" option as well but it was still giving me issues. It just seems that the container doesn't want to let the "PATH" get updated on runtime... so in the end i think the best option is to configure an image to the setup we need and go from there. I was just trying to avoid that but since it appears there isn't a clear answer my guess is everyone just creates a new image. Thanks again for working with me on this – JeffBeltran Feb 04 '19 at 19:21
0

The right way to do this is to use the Node.js plugin for Jenkins. It allows you to manage different versions of node and npm packages without installing them on the build machine manually. This is a configuration example to use node 13 and eslint globally: enter image description here

In your pipeline then you can do this:

stage('Use node commands and npm packages') {
  steps {
    nodejs(nodeJSInstallationName: 'node13') {
      sh 'npm -v'  //substitute with your code
      sh 'node -v'
      sh 'eslint ...'
    }
  }
}

For the complete example: https://pillsfromtheweb.blogspot.com/2020/05/how-to-use-different-nodejs-versions-on.html

SegFault
  • 2,020
  • 4
  • 26
  • 41