Skip to content
 
 

Repository files navigation

Shepherd Java Client

Provides a nice Java API library and a Java CLI (Command-line interface) client for Vaadin Shepherd and Vaadin Shepherd Traefik

Requires Java 17+.

The library is available in Maven Central. To use, add this to your build.gradle:

dependencies {
    implementation("com.github.mvysny.shepherd:shepherd-java-api:0.3")
}

To use, simply instantiate an implementation of ShepherdClient:

val client: ShepherdClient = LinuxShepherdClient()  // or FakeShepherdClient()

LinuxShepherdClient requires Shepherd to be installed on this machine. However, for development purposes, it's better to use FakeShepherdClient which doesn't require anything to be installed on the dev machine, while providing reasonable fake data.

Requires a configuration file to be placed in /etc/shepherd/java/config.json, example contents:

{
  "memoryQuotaMb": 14102,
  "concurrentJenkinsBuilders": 2,
  "maxProjectRuntimeResources": {
    "memoryMb": 512,
    "cpu": 1
  },
  "maxProjectBuildResources": {
    "memoryMb": 2500,
    "cpu": 2
  },
  "jenkins": {
    "url": "http://jenkins:8080",
    "username": "admin",
    "password": "admin"
  },
  "hostDNS": "mydomain.me",
  "containerSystem": "traefik-docker"
}

The memoryQuotaMb is the memory available both for project runtime and for project builds. Every project's runtime memory + the build memory is guaranteed by Shepherd; if a project would be created that overflows this quota, the project creation is prohibited. Calculate the quota value as follows: Take the total host machine memory, subtract memory for Jenkins usage (by default 512mb), for Kubernetes itself (say 1000mb), possibly 500mb for the future shepherd-ui project, and finally subtract memory for OS usage (say 200mb).

Additional configuration options:

  • hostDNS: where Shepherd is running, e.g. "v-herd.eu"
  • googleSSOClientId (Shepherd-Web only): enable Google SSO login and use this client ID. See vaadin-google-oauth for more details.
  • ssoOnlyAllowEmailsEndingWith (Shepherd-Web only): if not null, only e-mails ending with this string are allowed. Example: @vaadin.com. If null or empty, all e-mails are allowed.
  • shepherdHome Shepherd home, /opt/shepherd for Shepherd Kubernetes, /opt/shepherd-traefik for Shepherd Traefik.
  • containerSystem the runtime container system to use for running project containers, either "kubernetes" or "traefik-docker".

Regarding the "jenkins" setting:

  • In Traefik mode, Jenkins will be running in the same private Docker network as Shepherd; Shepherd will talk to Jenkins directly at http://jenkins:8080.
  • In Kubernetes mode, Jenkins runs directly at host, and so does Shepherd. Shepherd therefore talks to Jenkins via http://localhost:8080. Make sure to change the configuration accordingly.

shepherd-cli

The shepherd-cli project provides a command-line client for Shepherd. Simply build the CLI via ./gradlew shepherd-cli:build, then scp the shepherd-cli/build/distributions/*.zip to the target machine which runs Shepherd, then unzip and run the shepherd-cli binary.

Shepherd CLI requires Java 17+.

shepherd-cli create requires the project descriptor json. It's really simple, here's a very simple example for the vaadin-boot-example-gradle project:

{
  "id": "vaadin-boot-example-gradle",
  "description": "vaadin-boot-example-gradle",
  "gitRepo": {
    "url": "https://github.com/mvysny/vaadin-boot-example-gradle",
    "branch": "master"
  },
  "owner": {
    "name": "Martin Vysny",
    "email": "mavi@vaadin.com"
  },
  "runtime": {
    "resources": {
      "memoryMb": 256,
      "cpu": 1.0
    }
  },
  "build": {
    "resources": {
      "memoryMb": 2048,
      "cpu": 2.0
    }
  }
}

A more complex example:

{
  "id": "jdbi-orm-vaadin-crud-demo",
  "description": "JDBI-ORM example project",
  "gitRepo": {
    "url": "https://github.com/mvysny/jdbi-orm-vaadin-crud-demo",
    "branch": "master",
    "credentialsID": "c4d257ce-0048-11ee-a0b5-ffedf9ffccf4"
  },
  "owner": {
    "name": "Martin Vysny",
    "email": "mavi@vaadin.com"
  },
  "runtime": {
    "resources": {
      "memoryMb": 256,
      "cpu": 1.0
    },
    "envVars": {
      "JDBC_URL": "jdbc:postgresql://postgres-service:5432/postgres",
      "JDBC_USERNAME": "postgres",
      "JDBC_PASSWORD": "mysecretpassword"
    }
  },
  "build": {
    "resources": {
      "memoryMb": 2048,
      "cpu": 2.0
    },
    "buildArgs": {
      "offlinekey": "q3984askdjalkd9823"
    },
    "dockerFile": "vherd.Dockerfile"
  },
  "publication": {
    "publishOnMainDomain": false,
    "https": true,
    "additionalDomains": [
      "demo.jdbiorm.eu"
    ],
    "ingressConfig": {
      "maxBodySizeMb": 2,
      "proxyReadTimeoutSeconds": 360
    }
  },
  "additionalServices": [
    {
      "type": "Postgres"
    }
  ]
}

Updating a project

The project config json files are located at /etc/shepherd/java/projects/PROJECT_ID.json. Do not edit the file in-place: copy it to /root/, edit it there, then run ./shepherd-cli update -f /root/file.json then delete it from /root/. That way, Shepherd can track what has been changed, and can restart the project VM quickly if need be.

Adding Your Project To Shepherd

That's easy:

  1. Create the project JSON as above
  2. Create a Dockerfile as explained below.
  3. Run ./shepherd-cli create -f file.json to create the project.
  4. Done - the project is now being built in Jenkins; when the build succeeds, it will be deployed in Kubernetes.

Dockerfile

Shepherd expects the following from your project:

  1. It must have Dockerfile at the root of its git repo.
  2. The Docker image can be built via the docker build -t test/xyz:latest . command; The image can be run via docker run --rm -ti -p8080:8080 -m256m test/xyz command.

Generally, all you need is to place an appropriate Dockerfile to the root of your project's git repository. See the following projects for examples:

  1. Gradle+Embedded Jetty packaged as zip: vaadin-boot-example-gradle, vaadin14-boot-example-gradle, karibu-helloworld-application, beverage-buddy-vok, vok-security-demo
  2. Maven+Embedded Jetty packaged as zip: vaadin-boot-example-maven
  3. Maven+Spring Boot packaged as executable jar: vaadin-spring-karibu-testing, Liukuri, my-hilla-app, vaadinplus, TextField Formatter Zen Vaadin Hilla Spring PetClinic

Please try building and running the app on your own machine first - debugging any Docker-related issues is far easier on dev machine than remotely on Shepherd.

IMPORTANT: pay attention to the -m256m switch - this sets the hard limit on how much memory the container may use. If JVM asks for more, it will be hard-killed by the Linux OOM-killer, without any warning or any log message (only host OS dmesg will log this). Make sure to have your Dockerfile run Java with the -Xmx???m VM argument; that way the app will crash with OutOfMemoryException which should be visible in the logs. The -Xmx value should be a bit lower value than the hard limit, to give a bit of room for JVM itself.

Private Repositories & Credentials

Private repositories may need private SSH key to access them. For example:

  • A private GitHub repository foo-repo. A GitHub user foo-user is created and a private SSH key is generated for him. The foo-user then needs to be invited to the private repository, in order to gain read-only access. Shepherd will have knowledge of the private SSH key. That way, Shepherd can impersonate the foo-user and can access foo-repo.

Every credential has a unique identifier which needs to be passed in via the credentialsID in the config json file. Before that, the credential needs to be registered:

  • Either the Shepherd admin can do that directly, by creating the credential in Jenkins directly,
  • or the users can create their own credentials, via the Java functions offered by the ShepherdClient Java class (TBD)

The credentials are all ultimately stored in Jenkins, at Dashboard / Manage Jenkins / Credentials / System / Global credentials. Every credential should contain information on which files it contains (e.g. /root/.ssh/id_rsa) and where it is used (e.g. GitHub user foo-user).

Enabling Additional Services

PostgreSQL database

Adding a persistent postgresql database is easy:

  1. Add the Postgres additional service to the project descriptor JSON: "additionalServices": [{"type": "Postgres"}]
  2. Configure your app to connect to the jdbc:postgresql://postgres:5432/postgres URL, with the postgres username and mysecretpassword password.

Tips and Tricks

Vaadin Offline Key

For Vaadin Pro/Prime components you'll need a Vaadin License. The license must be present during the build time; adding VAADIN_OFFLINE_KEY to runtime environment variables is not enough.

The license can be obtained at My Licenses. You'll need the "Server license key", NOT the "Offline development license key" since the Machine ID changes unpredictably in CI/CD Docker environment.

Once you have the key, edit the project in v-herd admin console and add the build argument offlinekey with the value of the license key itself.

Note: the name of the build argument can be anything, but you need to correctly refer to it in your Dockerfile.

Then, in your Dockerfile, you'll fetch the license key from the build argument into an environment variable:

ARG offlinekey
ENV VAADIN_OFFLINE_KEY=$offlinekey

To test, build your app via

$ docker build -t test/xyz:latest --build-arg offlinekey=the_license_key .

Find the working Dockerfile example in the vaadinplus project.

Build Cache

To speed up your build, you can cache your local Maven repository and the contents of the ~/.vaadin folder. Edit your Dockerfile and update your Maven build command to mount the cache:

RUN --mount=type=cache,target=/root/.m2 --mount=type=cache,target=/root/.vaadin ./mvnw -C -e clean package -Pproduction

Gradle:

RUN --mount=type=cache,target=/root/.gradle --mount=type=cache,target=/root/.vaadin ./gradlew clean build -Pvaadin.productionMode --no-daemon --info --stacktrace

Maintenance

It's important to keep the host Linux up-to-date, via apt. However, rebooting while Jenkins is building a project is not safe. To safely restart the host machine, it's best to shut down Jenkins gracefully first.

Shepherd-Traefik

  1. Stop Jenkins, by logging to Shepherd Web, going to /admin and clicking the "Shutdown" button. Refresh the page until the text "Shutting down" changes to "Shepherd is shut down"
  2. Run sudo reboot

Jenkins runs as a docker image and is never upgraded via apt. It is never exposed to public: only Shepherd-Web has access to Jenkins, and therefore it's not important to keep Jenkins up-to-date.

Updating Shepherd-Web Docker Container

  1. cd /opt/shepherd-traefik
  2. docker compose pull
  3. docker compose up -d --no-deps shepherd

Docker will re-create shepherd container from the newest image, and will automatically join it to the correct 'admin.int' network.

Note that you must not update Traefik this way too, since docker compose will drop all container network bindings, which means that Traefik won't be able to route to apps. TODO Shepherd-Web could update Traefik and reconnect it to all app networks - investigate.

Shepherd-Kubernetes (old)

apt upgrade also frequently upgrades Jenkins since Jenkins issues frequent updates. However, it's not safe to update Jenkins while it's building a project. To update the host system:

  1. Stop jenkins, via ./shepherd-cli shutdown - the script waits until it's safe to proceed further.
  2. sudo apt update && sudo apt dist-upgrade
  3. If Jenkins is updated, it will be restarted and will start taking new jobs. If a reboot is needed:
    • Run ./shepherd-cli shutdown
    • Run sudo reboot

About

Vaadin Shepherd Java Client

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages