Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
50 changes: 48 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,48 @@ jobs:
matrix:
ep_weave: [false, true]
nbsearch: [false, true]
jenkins: [false, true]
include:
- ep_weave: false
nbsearch: false
jenkins: false
compose_files: ''
label: 'default'
- ep_weave: true
nbsearch: false
jenkins: false
compose_files: '-f docker-compose.yml -f docker-compose.ep_weave.yml'
label: 'ep_weave'
- nbsearch: true
ep_weave: false
jenkins: false
compose_files: '-f docker-compose.yml -f docker-compose.nbsearch.yml'
label: 'nbsearch'
- ep_weave: true
nbsearch: true
jenkins: false
compose_files: '-f docker-compose.yml -f docker-compose.ep_weave.yml -f docker-compose.nbsearch.yml'
label: 'ep_weave_and_nbsearch'
label: 'ep_weave-and-nbsearch'
- ep_weave: false
nbsearch: false
jenkins: true
compose_files: '-f docker-compose.yml -f docker-compose.jenkins.yml'
label: 'jenkins'
- ep_weave: true
nbsearch: true
jenkins: true
compose_files: '-f docker-compose.yml -f docker-compose.ep_weave.yml -f docker-compose.nbsearch.yml -f docker-compose.jenkins.yml'
label: 'all-services'
exclude:
- ep_weave: false
nbsearch: false
jenkins: true
- ep_weave: true
nbsearch: false
jenkins: true
- ep_weave: false
nbsearch: true
jenkins: true
runs-on: ubuntu-22.04
name: Build and Test (${{ matrix.label }})
steps:
Expand Down Expand Up @@ -152,7 +177,27 @@ jobs:
done
if ! curl -s -o /dev/null -w "%{http_code}" --insecure https://localhost/services/solr | grep -q "302"; then
echo "solr did not start"
sudo docker compose ${{ matrix.compose_files }} logs nbsearch
sudo docker compose ${{ matrix.compose_files }} logs nbsearch-solr
exit 1
fi
- name: Wait for jenkins to start
if: matrix.jenkins
run: |
set -xe

echo "Waiting for jenkins to start"
max_retries=10
while [ $max_retries -gt 0 ]; do
if curl -s -o /dev/null -w "%{http_code}" --insecure https://localhost/services/jenkins | grep -q "301"; then
break
fi
curl -vvvv --insecure https://localhost/services/jenkins
max_retries=$((max_retries-1))
sleep 10
done
if ! curl -s -o /dev/null -w "%{http_code}" --insecure https://localhost/services/jenkins | grep -q "301"; then
echo "jenkins did not start"
sudo docker compose ${{ matrix.compose_files }} logs jenkins
exit 1
fi
- name: Install python for playwright-tests
Expand All @@ -175,6 +220,7 @@ jobs:
export OPHUB_IGNORE_HTTPS_ERRORS=1
export OPHUB_SERVICE_EP_WEAVE=${{ matrix.ep_weave }}
export OPHUB_SERVICE_NBSEARCH=${{ matrix.nbsearch }}
export OPHUB_SERVICE_JENKINS=${{ matrix.jenkins }}
export OPHUB_USER_IS_ADMIN=1

cd playwright-tests
Expand Down
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,61 @@ You should define the environment variables for ep_weave in the `.env` file to s

You can see the ep_weave dashboard from the Services > ep_weave in the Control Panel of the JupyterHub.

### Launching Jenkins

You can use [jenkins](https://www.jenkins.io/) for CI/CD together with JupyterHub.
If you want to use jenkins, you can start it with JupyterHub by the following command.

$ sudo docker compose -f docker-compose.yml -f docker-compose.jenkins.yml build
$ sudo docker compose -f docker-compose.yml -f docker-compose.jenkins.yml up -d

You should define the environment variables for jenkins in the `.env` file to secure the service.

JENKINS_OAUTH_CLIENT_ID=(random string)
JENKINS_OAUTH_CLIENT_SECRET=(random string)

You can see the jenkins dashboard from the Services > jenkins in the Control Panel of the JupyterHub.
By default, anonymous users can access jenkins, so you should set the security settings on the Manage Jenkins > Security page.

![Security Settings of Jenkins](docs/jenkins-security-settings.png)

You should set the following items.

- Security Realm - Login with OpenID Connect
- Client id - (random string) of JENKINS_OAUTH_CLIENT_ID
- Client secret - (random string) of JENKINS_OAUTH_CLIENT_SECRET
- Configuration mode - `Discovery via well-known endpoint`
- Well-known configuration endpoint - `http://jupyterhub:8000/services/oidcp-jenkins/internal/.well-known/openid-configuration`
- Advanced > Override scopes - `openid email`
- Advanced configuration > User fields
- User name field name - `sub`
- Username case sensitivity - `Case sensitive`
- Full name field name - `name`
- Email field name - `email`

The settings below should be change after you have confirmed that the login works.

- Authorization - Logged-in users can do anything
- Allow anonymous read access - Unchecked

After you set the security settings, you can log in to jenkins with the account of the JupyterHub.

You can perform the docker command in the jenkins container to execute notebooks.
The shell script below is an example of executing a notebook using papermill in the jenkins container.

```
# execute notebook using papermill
docker run --rm \
-v /home/john/notebooks/test.ipynb:/home/jovyan/test.ipynb:ro \
your-single-user-image \
papermill /home/jovyan/test.ipynb /home/jovyan/output.ipynb
```

The `/var/jenkins_home/jobs/` directory in the jenkins container is mapped to the `~/notebooks/share/jenkins-jobs/` directory in the user's container.
All users can read and write(if they have the permission) to the `~/notebooks/share/jenkins-jobs/` directory.

In addition, only administrators of the JupyterHub can access the Jenkins in JupyterHub. To access Jenkins, users must have the `access:services!service=oidcp-jenkins` permission.

# Create new user

For creating a new user, use `useradd` command on the command line of the server.
Expand Down
43 changes: 43 additions & 0 deletions config/jenkins/jupyterhub_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from distutils.util import strtobool
import os

from jupyterhub_oidcp import configure_jupyterhub_oidcp

# Admin users can access the Jenkins service
# because the `access:services!service=oidcp-jenkins` scope is required
# To access the Jenkins service by non-admin users,
# the `access:services!service=oidcp-jenkins` scope should be added to the user's role

server_name = os.environ['SERVER_NAME']

c.JupyterHub.services.append(
{
'name': 'jenkins',
'url': f'http://jenkins-proxy/',
}
)

oidc_services = []

enable_jenkins = os.environ.get('JENKINS_ENABLE_OIDC_SERVICE', None)
if enable_jenkins is not None and bool(strtobool(enable_jenkins)):
oidc_services.append({
"oauth_client_id": os.environ['JENKINS_OAUTH_CLIENT_ID'],
"api_token": os.environ['JENKINS_OAUTH_CLIENT_SECRET'],
"redirect_uris": [f"https://{server_name}/services/jenkins/securityRealm/finishLogin"],
})

if len(oidc_services) > 0:
configure_jupyterhub_oidcp(
c,
port=8889,
service_name="oidcp-jenkins",
issuer="http://jupyterhub:8000/services/oidcp-jenkins/internal/",
base_url=f"https://{server_name}/",
internal_base_url="http://jupyterhub:8000",
debug=True,
services=oidc_services,
vault_path="./tmp/jupyterhub_oid/.jenkins.vault",
admin_email_pattern="{uid}@admin.jupyterhub",
user_email_pattern="{uid}@user.jupyterhub",
)
1 change: 0 additions & 1 deletion config/nbsearch/jupyterhub_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
c.JupyterHub.services.append(
{
'name': service_name,
'admin': True,
'url': f'http://nbsearch-{service_name}-proxy',
}
)
5 changes: 3 additions & 2 deletions config/oidc/jupyterhub_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@

from jupyterhub_oidcp import configure_jupyterhub_oidcp

# All users can access the OpenID Connect service
c.JupyterHub.load_roles = [
{
'name': 'user',
'scopes': ['self', 'access:services'],
'scopes': ['self', 'access:services!service=oidcp'],
}
]

Expand All @@ -32,12 +33,12 @@
if len(oidc_services) > 0:
configure_jupyterhub_oidcp(
c,
service_name="oidcp",
issuer="http://jupyterhub:8000/services/oidcp/internal/",
base_url=f"https://{server_name}/",
internal_base_url="http://jupyterhub:8000",
debug=True,
services=oidc_services,
oauth_client_allowed_scopes=None,
vault_path="./tmp/jupyterhub_oid/.vault",
admin_email_pattern="{uid}@admin.jupyterhub",
user_email_pattern="{uid}@user.jupyterhub",
Expand Down
34 changes: 34 additions & 0 deletions docker-compose.jenkins.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
services:
jenkins:
build: ./images/jenkins
environment:
- JENKINS_OPTS="--prefix=/services/jenkins"
- JAVA_OPTS="-Djenkins.install.runSetupWizard=false"
- USERS_DIR="/home/user-notebooks"
networks:
- backend
restart: on-failure
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/lib/jenkins:/var/jenkins_home
- /var/lib/jupyterhub/share/jenkins-jobs:/var/jenkins_home/jobs
- /var/run/docker.sock:/var/run/docker.sock
- ${USERS_DIR:-/home/user-notebooks}:/home/user-notebooks:ro

jenkins-proxy:
build:
context: ./images/jenkins-proxy
depends_on:
- jenkins
restart: always
networks:
- backend

jupyterhub:
environment:
JENKINS_ENABLE_OIDC_SERVICE: "1"
JENKINS_OAUTH_CLIENT_ID: ${JENKINS_OAUTH_CLIENT_ID:-jenkins_oauth_client_changeme}
JENKINS_OAUTH_CLIENT_SECRET: ${JENKINS_OAUTH_CLIENT_SECRET:-jenkins_oauth_secret_changeme}
SERVER_NAME: "${SERVER_NAME}"
volumes:
- './config/jenkins/jupyterhub_config.py:/jupyterhub_config.d/jenkins.py:ro'
Binary file added docs/jenkins-security-settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions images/jenkins-proxy/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM nginx:stable

COPY ./nginx.conf /etc/nginx/nginx.conf
34 changes: 34 additions & 0 deletions images/jenkins-proxy/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
events {
worker_connections 4096; ## Default: 1024
}

http {

client_max_body_size 50M;

server {
listen 80;

location /services/jenkins {
proxy_pass http://jenkins:8080/services/jenkins;
proxy_redirect off;
proxy_buffering off;
proxy_set_header Host $host;
proxy_pass_header Server;

# Enabling WebSocket
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";

# Adjust timeout for WebSocket
proxy_read_timeout 60s;
proxy_send_timeout 60s;

proxy_set_header X-Real-IP $remote_addr; # https://nginx.org/en/docs/http/ngx_http_proxy_module.html
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Port "443";
}
}
}
12 changes: 12 additions & 0 deletions images/jenkins/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM jenkins/jenkins:lts

# Jenkins is running as root to manipulate docker
USER root

RUN curl -fsSL https://get.docker.com | sh
RUN apt-get update && \
apt-get install -y zip && \
apt-get autoclean && apt-get clean && apt-get autoremove

RUN jenkins-plugin-cli --plugins \
"oic-auth" "build-timeout" "discard-old-build" slack
8 changes: 7 additions & 1 deletion playwright-tests/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ def ophub_has_ep_weave() -> bool:
def ophub_has_nbsearch() -> bool:
return os.environ.get('OPHUB_SERVICE_NBSEARCH', '') in ('1', 'yes', 'true')

@pytest.fixture
def ophub_has_jenkins() -> bool:
return os.environ.get('OPHUB_SERVICE_JENKINS', '') in ('1', 'yes', 'true')

@pytest.fixture
def ophub_login_to_hub_home(ophub_url, ophub_username, ophub_password) -> Callable[[Page], None]:
def login(page: Page):
Expand All @@ -63,10 +67,12 @@ def login(page: Page):
return login

@pytest.fixture
def ophub_services(ophub_has_ep_weave, ophub_has_nbsearch) -> List[str]:
def ophub_services(ophub_has_ep_weave, ophub_has_nbsearch, ophub_has_jenkins) -> List[str]:
services = []
if ophub_has_ep_weave:
services.append('ep_weave')
if ophub_has_nbsearch:
services.append('solr')
if ophub_has_jenkins:
services.append('jenkins')
return services