JetBrains / teamcity-docker-agent

TeamCity agent docker image sources
https://hub.docker.com/r/jetbrains/teamcity-agent/
Apache License 2.0
77 stars 64 forks source link

How to properly install/run nvm #21

Closed pstephenson02 closed 6 years ago

pstephenson02 commented 6 years ago

Hello! I am having a really hard time setting up my docker build agent to build projects that use nvm/node/npm. I have created a Dockerfile like this:

FROM jetbrains/teamcity-agent
ENV SERVER_URL=https://tc-master.gc.local:8080
RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash

# Install the self-signed TeamCity server SSL cert
COPY teamCityCert.cer /root/teamCityCert.cer
RUN keytool -importcert -file /root/teamCityCert.cer -keystore /usr/lib/jvm/oracle-jdk/jre/lib/security/cacerts -storepass pass -noprompt

When I build a new image using this dockerfile, it seems to install nvm correctly:

PS> docker build -t teamcity-agent-linux .
Sending build context to Docker daemon  5.632kB
Step 1/5 : FROM jetbrains/teamcity-agent
 ---> 5ae0b299f785
Step 2/5 : ENV SERVER_URL=https://tc-master.gc.local:8080
 ---> Running in 289df490e0d6
Removing intermediate container 289df490e0d6
 ---> ccbe76e4485d
Step 3/5 : RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash
 ---> Running in e1f3432040ee
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 12540  100 12540    0     0  19842      0 --:--:-- --:--:-- --:--:-- 19841
=> Downloading nvm from git to '/root/.nvm'
=> Cloning into '/root/.nvm'...
Note: checking out '7ad6d98cedde01809e32d56ab8ced064f6f28175'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

=> Compressing and cleaning up git repository

=> Appending nvm source string to /root/.bashrc
=> Appending bash_completion source string to /root/.bashrc
=> Close and reopen your terminal to start using nvm or run the following to use it now:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # This loads nvm bash_completion
Removing intermediate container e1f3432040ee
 ---> aff4018d04bc
Step 4/5 : COPY teamCityCert.cer /root/teamCityCert.cer
 ---> f85469c33073
Step 5/5 : RUN keytool -importcert -file /root/teamCityCert.cer -keystore /usr/lib/jvm/oracle-jdk/jre/lib/security/cacerts -storepass pass -noprompt
 ---> Running in 267fa7f5411b
Certificate was added to keystore
Removing intermediate container 267fa7f5411b
 ---> 10320cb850ad
Successfully built 10320cb850ad
Successfully tagged teamcity-agent-linux:latest

The agent connects to teamcity correctly, and I have authorized the agent from the TeamCity admin UI. However, when I go to build a project, the build always fails:

[13:05:12]/opt/buildagent/temp/agentTmp/custom_script6044565268267893228: line 1: nvm: command not found
[13:05:12]Process exited with code 127

The agent process appears to be running as root in the container, but do the individual builds get kicked off with the buildagent user? In which case, does nvm need to be installed as the buildagent user? (I tried this in the Dockerfile by placing USER buildagent before the nvm install, but it still did not work).

For what it's worth, when I log into the container, it appears to pick up the .bashrc and finds nvm in the path:

PS> docker exec -it 6c35ea79def1 bash
root@6c35ea79def1:/# nvm

Node Version Manager
...

Thank you in advance

pstephenson02 commented 6 years ago

I would like to clarify my question:

In order for nvm to work when running a build, the shell which runs the builds must first run $NVM_DIR/nvm.sh. However, I would like to avoid having to execute this nvm startup script in every build configuration I setup in TeamCity projects. Is there a way that I can force the agent to run this script each time it starts up? Thanks again.

VladRassokhin commented 6 years ago

Thing is nvm does not provide any binary that could be added into PATH, so some script should run . "$NVM_DIR/nvm.sh" or alternatively you could create some script and place it into PATH using for example Script 1 (see below). Another alternative is to use next hack in Dockerfile - replace origin run-agent.sh with modified one:

RUN mv /run-agent.sh /run-agent.origin.sh && \
    echo -e '#!/bin/bash\nexport NVM_DIR="$HOME/.nvm"\n. "$NVM_DIR/nvm.sh"\n./run-agent.origin.sh' >/run-agent.sh && \
    chmod +x /run-agent.sh

Script 1:

tee nvm-wrapper.sh <<EOF
#!/bin/bash

set -e

if [ "\$NW_DEBUG" = "true" ]; then
  set -x
fi

NVM_DIR=\${NVM_DIR:-$DIR}
command_to_execute=\$(basename \$0)

source "\$NVM_DIR/nvm.sh"

nvmrc_path="\$(nvm_find_nvmrc)"
if [ -e "\$nvmrc_path" ]; then
  nvm use >&2 || nvm install >&2
else
  echo ".nvmrc is not found, using default version" >&2
  nvm use default >&2
fi

version=\$(nvm version)
exec "\$NVM_DIR/versions/node/\$version/bin/\$command_to_execute" "\$@"
EOF
chmod 755 nvm-wrapper.sh

ln -s nvm-wrapper.sh node
ln -s nvm-wrapper.sh npm
pstephenson02 commented 6 years ago

Thanks for your response. I tried both of your methods and neither of them worked for me. I also tried a number of other ideas as well. I was able to get more information by running ps -aux in the build and see how the build actually gets invoked:

[15:21:13][Step 1/1] Starting: /opt/buildagent/temp/agentTmp/custom_script7525124466095535933
[15:21:13][Step 1/1] in directory: /opt/buildagent/work/f4281a9c3a55712f
[15:21:13][Step 1/1] USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
[15:21:13][Step 1/1] root          1  0.0  0.1  19744  3320 pts/0    Ss+  22:08   0:00 /bin/bash /run-agent.sh
[15:21:13][Step 1/1] root       1267  0.0  0.0   6036   688 pts/0    S+   22:09   0:00 tail -qF /opt/buildagent/logs/teamcity-agent.log /root/anchor
[15:21:13][Step 1/1] root       1389  0.1  1.9 2825700 40196 pts/0   Sl+  22:09   0:01 /usr/lib/jvm/oracle-jdk/jre/bin/java -ea -cp ../launcher/lib/launcher.jar jetbrains.buildServer.agent.Launcher -ea -Xmx384m -Dteamcity_logs=../logs/ jetbrains.buildServer.agent.AgentMain -file /data/teamcity_agent/conf/buildAgent.properties
[15:21:13][Step 1/1] root       1402  4.8 14.6 2896684 296420 pts/0  Sl+  22:09   0:32 /usr/lib/jvm/oracle-jdk/jre/bin/java -ea -Xmx384m -Dteamcity_logs=../logs/ ...
[15:21:13][Step 1/1] root       1983  0.0  0.1  19700  3176 pts/0    S+   22:21   0:00 /bin/sh /opt/buildagent/temp/agentTmp/custom_script7525124466095535933
[15:21:13][Step 1/1] root       1984  0.0  0.1  36080  3200 pts/0    R+   22:21   0:00 ps -aux
[15:21:13][Step 1/1] Process exited with code 0

From here I noticed that the builds get invoked like this:

/bin/sh /opt/buildagent/temp/agentTmp/custom_script7525124466095535933

Seeing as how there are no arguments, the shell runs as non-interactive and non-login modes. Without either of these modes, the shell will not try to read any of the various /etc/profile, ~/.profile~, or ~/.bashrc files. By default, Ubuntu actually uses the dash shell for /bin/sh and there is limited functionality there.

I also tried just restoring the fully-featured bash shell (rather than dash) by using the methods described here: https://superuser.com/a/1064247. Now, when teamcity invokes /bin/sh at least it should be a bash shell now. According to the bash documentation, I thought I could pass in an environment variable $ENV and it would run scripts defined, so I tried in my docker file:

ENV ENV="$HOME/.nvm/nvm.sh"
RUN echo "dash dash/sh boolean false" | debconf-set-selections && \
    DEBIAN_FRONTEND=noninteractive dpkg-reconfigure dash

But this still did not work.

I have spent quite a few hours trying to get something to work. This is quite frustrating as a developer. Having spent the time to setup custom build agents, it would be great if there were better ways to hook into the build lifecycle. And using teamcity "templates" is unnacceptable I think. I want my build agent setup to be declarative, and defined in code - not stored as some template in TeamCity.

Anyway, thank you for your help. I would like to leave this issue open for a little bit in case anyone else runs into this problem and has other ideas.

pstephenson02 commented 6 years ago

Closing this issue. For anyone reading this, I ended up switching to the windows server core 2016 container.

MokhtarNajjar commented 2 years ago

@ @ @

rdelgado-incomm commented 2 years ago

I know this is closed already but what ended up working is using the linux source command to update the bash profile during CI. It is likely that TC was running under a different bash profile and we had to switch to a specific one.

rdelgado-incomm commented 2 years ago

I know this is closed already but what ended up working is using the linux source command to update the bash profile during CI. It is likely that TC was running under a different bash profile and we had to switch to a specific one.

oh yeah, and I totally forgot that the env was not a docker container...it was a linux host. So maybe the above doesn't apply anyway 🤔

tspence commented 1 year ago

In case anyone else arrives at this thread and is stuck:

My problem was that nvm installs, by default on the currently running user. But my teamcity agent was running as a different user!

I eventually noticed that the NVM_HOME folder was pointing to a specific user's profile. I could then fix this by changing the TeamCity agent to run as that specific user, or by installing NVM on the correct user.