log4j Vulnerability

The Internet has been on fire since last Friday. Apache Foundation released an emergency update to address the issue. My company is urgently addressing this issue at the moment as well so that we and our customers won’t get affected by this big security hole.

I read on the Internet that hackers are already trying to exploit it, so whoever uses log4j need to take immediate action on this.

How to Make Linux Jenkins Slave a Daemon

I wrote an article on creating a Jenkins slave on Linux. The method was to just create a bash script file that requires to be executed by hand. And it wouldn’t survive restarting the host, so what I need to do is to make the script a daemon (service).

Here is what I did before configuring the daemon.

  1. Provision a Ubuntu host on Azure (it doesn’t matter where you provision the host as long as your Jenkins master on the public Internet and secured).
  2. Update the system. (sudo apt update && sudo apt upgrade)
  3. Install OpenJDK. (sudo apt install openjdk-11-jre-headless)
  4. Open port 50000 (Inboud and Outbound) to the host. I am opening all protocols.

Creating a Daemon

We will create a script at home directory first. To contain everything for Jenkins slave, I am creating /home/azureuser/jenkins-slave directory. You can create jenkins-slave or whatever the directory name you would like anywhere.

Then create slave.sh in /home/azureuser/jenkins-slave directory with the following content. Change the URL and the secret acccording to the Jenkins node you have created on Jenkins master. Make the script executable by executing chmod +x slave.sh.

java -jar agent.jar -jnlpUrl https://jenkins.hayato-iriumi.net/computer/linux%2Dnode/jenkins-agent.jnlp -secret 136fa14dcc4013727e24c9f1a9b84127d7c7ca0cfa15e22c1e1d4e0140122529 -workDir "./slave"
exit 0

Also make sure you download agent.jar from Jenkins master to /home/azureuser/jenkins-slave directory. Also user opessl and keytool to trust the SSL cert. You can refer to the previous blog article on how to use keytool.

Now create /etc/systemd/system/jenkins-slave.service file with the following content. sudo vim /etc/systemd/system/jenkins-slave.service

[Unit]
Description=JenkinsSlave

[Service]
User=azureuser
WorkingDirectory=/home/azureuser/jenkins-slave
ExecStart=/bin/bash /home/azureuser/jenkins-slave/slave.sh
Restart=always

[Install]
WantedBy=multi-user.target

Enable the daemon with the following command.

sudo systemctl enable jenkins-slave.service

Now start the daemon with the following command.

sudo systemctl start jenkins-slave.service

Check the status of it.

sudo systemctl status jenkins-slave.service

Result:

● jenkins-slave.service - JenkinsSlave
     Loaded: loaded (/etc/systemd/system/jenkins-slave.service; enabled; vendor preset: enabled)
     Active: active (running) since Fri 2021-10-22 22:10:03 UTC; 8min ago
   Main PID: 14698 (bash)
      Tasks: 19 (limit: 486)
     Memory: 103.3M
     CGroup: /system.slice/jenkins-slave.service
             ├─14698 /bin/bash /home/azureuser/jenkins-slave/slave.sh
             └─14699 java -jar agent.jar -jnlpUrl https://jenkins.hayato-iriumi.net/computer/linux%2Dnode/jenkins-agent.jnlp -secret 136fa14dcc40>

Oct 22 22:10:05 jenkins-slave bash[14699]: Oct 22, 2021 10:10:05 PM hudson.remoting.jnlp.Main$CuiListener status
Oct 22 22:10:05 jenkins-slave bash[14699]: INFO: Handshaking
Oct 22 22:10:05 jenkins-slave bash[14699]: Oct 22, 2021 10:10:05 PM hudson.remoting.jnlp.Main$CuiListener status
Oct 22 22:10:05 jenkins-slave bash[14699]: INFO: Connecting to jenkins.hayato-iriumi.net:50000
Oct 22 22:10:05 jenkins-slave bash[14699]: Oct 22, 2021 10:10:05 PM hudson.remoting.jnlp.Main$CuiListener status
Oct 22 22:10:05 jenkins-slave bash[14699]: INFO: Trying protocol: JNLP4-connect
Oct 22 22:10:06 jenkins-slave bash[14699]: Oct 22, 2021 10:10:06 PM hudson.remoting.jnlp.Main$CuiListener status
Oct 22 22:10:06 jenkins-slave bash[14699]: INFO: Remote identity confirmed: da:ca:7a:5a:1e:ac:df:56:81:96:8a:d7:71:d9:5e:4c
Oct 22 22:10:06 jenkins-slave bash[14699]: Oct 22, 2021 10:10:06 PM hudson.remoting.jnlp.Main$CuiListener status
Oct 22 22:10:06 jenkins-slave bash[14699]: INFO: Connected

When the connection is established to the Jenkins master, here is what it looks like.

With this method, it survives restarting the host.

If you try to list the services on the host with the following command, you will see the item in the list.

sudo systemctl list-units --type=service

Installing JetBrains Products on Oracle Linux 8

I am trying to configure Oracle Linux 8 as my spare laptop. I need to install JetBrains products on it. I tried to install JetBrains Toolbox but it wouldn’t work. It’s packaged as AppImage file, so it should be pretty easy but when I execute it, a blank white window shows up and disappears.

So I looked for an alternative way to install JetBrains products. I installed snapd on it with the following command.

sudo dnf install snapd

Then, I searched for the JetBrains products like the following.

snap search jetbrains

Result:

Name                       Version   Publisher   Notes    Summary
pycharm-community          2021.2.2  jetbrains✓  classic  PyCharm Community Edition
phpstorm                   2021.2.3  jetbrains✓  classic  PhpStorm
pycharm-professional       2021.2.2  jetbrains✓  classic  PyCharm Professional Edition
intellij-idea-community    2021.2.3  jetbrains✓  classic  Capable & Ergonomic Java IDE
intellij-idea-ultimate     2021.2.3  jetbrains✓  classic  Capable & Ergonomic Java IDE for Enterprise, Web & Mobile Development
webstorm                   2021.2.2  jetbrains✓  classic  WebStorm
datagrip                   2021.2.4  jetbrains✓  classic  DataGrip
clion                      2021.2.3  jetbrains✓  classic  A cross-platform IDE for C and C++
pycharm-educational        2021.2.2  jetbrains✓  classic  Easy and Professional Tool to Learn & Teach Programming with Python
rubymine                   2021.2.3  jetbrains✓  classic  The Most Intelligent Ruby and Rails IDE
space                      2021.2.0  jetbrains✓  -        Desktop Application for JetBrains Space
rider                      2021.2.2  jetbrains✓  classic  A fast & powerful cross-platform .NET IDE
goland                     2021.2.3  jetbrains✓  classic  GoLand
intellij-idea-educational  2021.2.2  jetbrains✓  classic  IntelliJ IDEA Educational Edition
kotlin                     1.5.31    jetbrains✓  classic  Command line Kotlin compiler

The first application I want to install is PyCharm, so I ran the following command to install it.

snap install pycharm-professional

If you search pycharm in your GNOME UI, you will be able to start to use it.

I still would like to use JetBrains’ Toolbox so I posted my question in their support forum to resolve

Before I posted it, I did a fair bit of research. Toolbox is packaged as AppImage, so you can check the command options like the following.

./jetbrains-toolbox --appimage-help

I learned that you can even extract files from the image like the following.

./jetbrains-toolbox --appimage-extract

I did digging into the extracted files but I could not find a solution for it. Oh well, I can use JetBrains’ products anyway, so I’m happy for now.

How to Change Java Version on Oracle Linux 8

I had Java 8 on my Oracle Linux 8 machine but I wanted to change it to Open JDK 11. Here is what I did. The following instruction will probably work for RedHat descendants.

First, I had to find a way to install Open JDK. I ran the following command to get the right package information.

dnf search java-11

Here is the result.

java-11-openjdk.x86_64 : OpenJDK 11 Runtime Environment
java-11-openjdk.src : OpenJDK 11 Runtime Environment
java-11-openjdk-demo.x86_64 : OpenJDK 11 Demos
java-11-openjdk-devel.x86_64 : OpenJDK 11 Development Environment
java-11-openjdk-headless.x86_64 : OpenJDK 11 Headless Runtime
                                : Environment
java-11-openjdk-javadoc.x86_64 : OpenJDK 11 API documentation
java-11-openjdk-javadoc-zip.x86_64 : OpenJDK 11 API documentation
                                   : compressed in a single archive
java-11-openjdk-jmods.x86_64 : JMods for OpenJDK 11
java-11-openjdk-src.x86_64 : OpenJDK 11 Source Bundle
java-11-openjdk-static-libs.x86_64 : OpenJDK 11 libraries for static
                                   : linking

To install Open JDK 11, run the following command.

sudo dnf install java-11-openjdk.x86_64

Now I want to switch my default Java version to the one I just installed.

sudo alternatives --config java

alternatives lists the available Java installations and it gives you options to select from.

Once you make the selection, check the default Java version like the following.

java -version

Result:

openjdk version "11.0.12" 2021-07-20 LTS
OpenJDK Runtime Environment 18.9 (build 11.0.12+7-LTS)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.12+7-LTS, mixed mode, sharing)

Missed Demo for the Japanese Students I Gave Speech to

I had an opportunity to give a speech to students who are looking to become engineers in Japan last Friday. It was great to meet them though it was online. They seemed eager to learn what takes to be engineers. I was honored to give speech to them. They were also learning English, so this may be good for them.

Due to my unpreparedness on my side, I missed one demo. I was basically compiling Java code and decomplie it with IntelliJ. I would like to show how it can be done here in this blog for their view.

First, create a text file (helloworld.java) with the following code. It’s just a simple hello world program in Java.

public class helloworld {
    public static void main(String args[])
    {
        System.out.println("Hello World");
    }
}

Once you have the file, compile it like the following from your terminal (command line).

javac helloworld.java

The javac (Java compiler) compiles the text file to Java byte code (helloworld.class). You can execute the hello world program like the following.

java helloworld

Output:

Hello World

When you open helloworld.class file with IntelliJ, you can decompile it.

Decompilation is not really a useful technique anymore because of the current trend of open source but if the source code is closed but you want to learn how the Java program works, it’s still an interesting technique to use especially while you are learning how program works.

I believe JetBrain provides students with free license so you may be able to use the IDEs for free.

The reason why I could not find the helloworld.class file at the time of demo was because I was using WSL 2 on Windows. I had the file on the Linux side of the OS but I had forgotten to copy the file on the Windows side. I’m so sorry about it.

There was so much more I wanted to talk to everyone about but our time was limited. I wish all of you successful careers and bright future. 🙂

How to Install Jenkins Slave as Windows Service

It is time to review this article. “How to Install Jenkins Slave as Windows Service” has been one of the most popular blog articles on this site. I just went through the steps because I needed to have a permanent Windows slave and I found myself trip over some steps because of some lack of information.

Here I am going to review and enhance the article.

Prerequisites

OpenJDK or Oracle Java (We’ll use OpenJDK in this article)

  • Java
  • Windows

Install OpenJDK

  • Download OpenJDK 17 (the latest one did not work).
  • Download the zip for Windows.
  • Unzip the contents and place all the files under C:\Users\[your username]\jdk (or it could be placed somewhere else if you want to)
  • From the Windows menu, enter “env” to show “Edit the system environment variables” menu and select it.
  • Click “Environment Variables” button.
  • Under System Variables, click New… button and enter JAVA_HOME for Variable name and the path to the JDK root path.
  • Click OK and close Environment Variables dialogue.
  • Open command line and make sure you can get an output from java.
    %JAVA_HOME%\bin\java --version

Create a Node

  • Logon to Jenkins with an administrative account.
  • Go to Manage Jenkins.
  • Click Manage Nodes and Clouds.
  • Click New Node.
  • Enter node name like “win-bld01” and click Permanent Agent and then click OK.
  • Enter a name like win-bld01 (or whatever you like) and enter the path to the directory where you plan to have Jenkins slave executable. In this example, I choose C:\Users\hiriu\jenkins and enter the same path for Custom WorkDir Path.
  • Click OK to save the setting. We’ll revisit it afterwards.

Jenkins Slave Download and Install

  • Download the latest Windows Service wrapper executable from https://repo.jenkins-ci.org/ui/native/releases/com/sun/winsw/winsw/
  • In this example, we will get winsw-2.9.0-net461.exe
  • Place the executable under C:\Users\hiriu\jenkins and rename it as jenkins-slave.exe
  • Create jenkins-slave.xml in the same directory with the following XML. Make sure to change the values in the XML according to your environment. You can get the jnlpUrl from the Configure page of the node.
<service>
  <id>JenkinsSlave</id>
  <name>Jenkins agent</name>
  <description>This service runs an agent for Jenkins automation server.</description>
  <executable>%JAVA_HOME%\bin\java.exe</executable>
  <arguments>-Xrs -jar &quot;%BASE%\slave.jar&quot; -jnlpUrl https://hoge.com/computer/win-bld01/jenkins-agent.jnlp -secret 18f9c0c497c0997e9f65buhie743868ae6d5e2e78a6ba77 -workDir "C:\Users\hiriu\jenkins"</arguments>
  <logmode>rotate</logmode>
  <onfailure action="restart">
    <download from="https://hoge.com/jnlpJars/slave.jar" to="%BASE%\slave.jar">
      <extensions>
        <extension className="winsw.Plugins.RunawayProcessKiller.RunawayProcessKillerExtension" enabled="true" id="killOnStartup">
          <pidfile>%BASE%\jenkins_agent.pid</pidfile>
          <stopTimeout>5000</stopTimeout>
          <stopParentFirst>false</stopParentFirst>
        </extension>
      </extensions>
    </download>
  </onfailure>
</service>

For more options, please refer to the official documentation.

Also make sure to create jenkins-slave.exe.config file with the following XML cotent to prevent the executable from running on the earlier version of the .NET Framework.

<configuration>
	<startup>
		<supportedruntime version="v4.0"/>
	</startup>
</configuration>

Execute the following command to install the Jenkins slave as Windows Service.

jenkins-slave.exe install

Once the installation is successful, you should see the output like below.

Also, you should be able to see “Jenkins agent” item in Windows Services console. You can open the Windows Services console by entering services.msc

Trust the SSL Certificate

The HTTP connection between your client computer and your Jenkins instance may be protected by SSL. When you try to connect your Jenkins slave via HTTPS (SSL) connection, it may fail with the following error.

Exception in thread "main" java.io.IOException: Failed to validate a server certificate. If you are using a self-signed certificate, you can use the -noCertificateCheck option to bypass this check.

It is optional to add -noCertificateCheck option in your jenkins-slave.xml file, but it is better for your Java process to trust the certificate and use the SSL connection to protect the data. To accomplish that, please follow the steps below.

  1. Open terminal.
  2. Execute openssl s_client -showcerts -connect hoge.com:443 < /dev/null | openssl x509 -outform DER > hoge.com.cer
  3. You obviously have to have openssl on your machine. Install Cygwin if you are on Windows and that will give you an ability to execute openssl. Or you could use Linux Subsystem for Windows.
  4. Once you get the .cer file, it’s time to tell Java to trust the certificate. For that, we use keytool.
  5. Execute the following command. If you are on Linux Subsystem, you may want to go back to the normal command prompt.
    %JAVA_HOME%\bin\keytool -trustcacerts -keystore "%JAVA_HOME%\lib\security\cacerts" -storepass changeit -alias ppd -import -file "C:\Users\hiriu\Downloads\hoge.com.cer"
  6. You will be prompted to enter “yes”. Once you enter yes, you are ready to start the Jenkins agent Windows Service.
  7. Navigate to Windows Service console (services.msc) and start the Jenkins agent Windows service.
  8. Once the communication between Jenkins slave and the Jenkins master is successful, you should see the node icon turning to normal like the image below.

Bulk Create Jenkins Nodes

If permanent slaves are required and hundreds of them, it’s very manual and tedious tasks. I have created a PowerShell script to create Jenkins nodes in bulk. CloudBees’ version of Jenkins seems to have nice API to create nodes but open source/free version doesn’t seem to have one though Jenkins CLI could be utilized to accomplish it. Please follow my previous blog article to make sure your Jenkins CLI works before moving on with this article. Please note that this script does not install Jenkins slaves on separate machines but I am planning to create a script to do that in the future.

Prerequisites

  • Windows (The script I am going to introduce utilizes PowerShell, so Windows is the safest choice of OS.)
  • Java
  • Donwloaded jenkins-cli.jar

The PowerShell Script Execution

The PowerShell script that I wrote can generate nodes in bulk. It can be found here in GitHub.

For example, execute the script with the parameters like below.

.\Jenkins_BulkCreateNodes.ps1 -JenkinsServerUrl https://jenkins.linux-mint.local/ -Username myadminuser -ApiToken 1197adsfasdfadsfasdfasdfasdfasdf3a -NodeCount 100 -NodeNamePattern "winnode-###" -OverwriteNodeIfExists $True

The command above creates nodes in Jenkins master from winnode-001 to winnode-100. The red cross icon you see below only indicates that they are offline and no actual slaves is communicating with them yet.

You can change the naming pattern specifying -NodeNamePattern.

By specifying -OverwriteNodeIfExists parameter to $True, it can delete the node and recreate. It can be dangerous, so its default value is $False and it’s an optional parameter.

The PowerShell Code

I am pasting all the code I have written for this operation. But if you just want the file, please go to my GitHub repo. All the details of each parameters are documented in the code.

<#
    .SYNOPSIS
       Generates Jenkins nodes.
    .DESCRIPTION
        This script bulk generates Jenkins nodes on the master so that nodes will be ready for Jenkins slaves to talk to them. 
        It utilizes Jenkins CLI, so Java (https://java.com/en/download/) and downloaded jenkins-cli.jar file from Jenkins master is required.
    .PARAMETER JenkinsServerUrl
        Required. Base URL of the Jenkins master. e.g. https://myjenkins.com/
    .PARAMETER Username
        The username to use for this process.
    .PARAMETER ApiToken
        Generated API Token of the username.
    .PARAMETER NodeCount
        Specifies how many nodes to created.
    .PARAMETER NodeNamePattern
        Specify the pattern of node name here. If 20 nodes to be created and "foobar-###" is passed, foobar-001 to foobar-020 would be generated.
    .PARAMETER Description
        Optional. Specify the description of each node. Use this field to specify what kind of fleet you are creating.
    .PARAMETER RemoteRootDir
        Optional. Defaults to "C:\slave". Specify any directory where you expect slave bits to reside on each node.
    .PARAMETER Mode
        Optional. Defaults to NORMAL.
    .PARAMETER NumExecutors
        Optional. Defaults to 1.
    .PARAMETER Labels
        Optional. e.g. "WINDOWS 2019". Separate labels by space.
    .PARAMETER JenkinsCliJarFilePath
        Optional. When you download jenkins-cli.jar, the file is usually at ~/Download/jenkins-cli.jar. Downaload the file from https://your-jenkins-server/jnlpJars/jenkins-cli.jar
        There is a download link on https://your-jenkins-server/cli
    .PARAMETER OverwriteNodeIfExists
        Optional. If the node to be created already exists on the Jenkins master, it deletes it and recreates it if $True is passed. If $False, it is skipped.
    .PARAMETER KeepNodeXmlFiles
        Optional. Defaults to $False. If $True is passed, the XML files created for the nodes will be kept in the same directory as this script. This option could be used to reuse the XML files in different environments.
    
    
    
#>
[CmdletBinding()]
param 
(
    [Parameter(Mandatory=$True)] 
    [string]$JenkinsServerUrl,
    [Parameter(Mandatory=$True)]
    [string]$Username,
    [Parameter(Mandatory=$True)]
    [string]$ApiToken,
    [Parameter(Mandatory=$False)]
    [int]$NodeCount = 5,
    [Parameter(Mandatory=$False)]
    [string]$NodeNamePattern = "winnode-###",
    [Parameter(Mandatory=$False)]
    [string]$Description = "Bulk gen'ed node",
    [Parameter(Mandatory=$False)]
    [string]$RemoteRootDir = "C:\slave",
    [Parameter(Mandatory=$False)]
    [string]$Mode = "NORMAL",
    [Parameter(Mandatory=$False)]
    [int]$NumExecutors = 1,
    [Parameter(Mandatory=$False)]
    [string]$Labels = "",
    [Parameter(Mandatory=$False)]
    [string]$JenkinsCliJarFilePath = "$env:HOMEPATH\Downloads\jenkins-cli.jar",
    [Parameter(Mandatory=$False)]
    [bool]$OverwriteNodeIfExists = $False,
    [Parameter(Mandatory=$False)]
    [bool]$KeepNodeXmlFiles = $False
)

$digitCount = ($NodeNamePattern.ToCharArray() | Where-Object {$_ -eq '#'} | Measure-Object).Count

ForEach ($index In 1..$NodeCount)
{
    $number = "{0:D$digitCount}" -f $index
    $nodeName = $NodeNamePattern -replace "#{$digitCount}", $number 
    $xmlSettings = New-Object System.Xml.XmlWriterSettings
    $xmlSettings.Indent = $true
    $xmlSettings.Encoding = [System.Text.Encoding]::UTF8
    $xmlSettings.OmitXmlDeclaration = $True
    $xmlSettings.ConformanceLevel = "Document"

    $sw = New-Object System.IO.StringWriter
    $xw = [System.Xml.XmlWriter]::Create($sw, $xmlSettings)
    
    try 
    {
        $xw.WriteStartElement("slave")

        $xw.WriteElementString("name", $nodeName)
        $xw.WriteElementString("description", $Description)
        $xw.WriteElementString("remoteFS", $RemoteRootDir)
        $xw.WriteElementString("numExecutors", $NumExecutors)
        $xw.WriteElementString("mode", $Mode)

        $xw.WriteStartElement("retentionStrategy")
        $xw.WriteAttributeString("class", 'hudson.slaves.RetentionStrategy$Always')
        $xw.WriteEndElement() # retentionStrategy

        $xw.WriteStartElement("launcher")
        $xw.WriteAttributeString("class", "hudson.slaves.JNLPLauncher")

        $xw.WriteStartElement("workDirSettings")
        $xw.WriteElementString("disabled", "false")
        $xw.WriteElementString("internalDir", "remoting")
        $xw.WriteElementString("failIfWorkDirIsMissing", "false")
        $xw.WriteEndElement() # workDirSettings
        $xw.WriteElementString("websocket", "false")
        $xw.WriteEndElement() # launcher

        $xw.WriteElementString("label", $Labels)
        $xw.WriteElementString("nodeProperties", "")
        $xw.WriteEndElement() #slave
        $xw.WriteEndDocument()
        $xw.Flush()
    }
    catch [System.Exception]
    {
        Write-Host $_
        exit 1
    }
    finally 
    {
        $xw.Close()
    }

    # Write out the node XML to file to be used for standard input below.
    Set-Content -Path "$nodeName.xml" -Value $sw.ToString() -Force

    # Check the existence of the node.

    $processInfo = New-Object System.Diagnostics.ProcessStartInfo
    $processInfo.FileName = "java"
    $processInfo.RedirectStandardError = $True
    $processInfo.RedirectStandardOutput = $True
    $processInfo.RedirectStandardInput = $True
    $processInfo.Arguments = "-jar $JenkinsCliJarFilePath -s $JenkinsServerUrl -auth $Username`:$ApiToken get-node $nodeName"
    $processInfo.UseShellExecute = $False

    $process = New-Object System.Diagnostics.Process
    $process.StartInfo = $processInfo
    $process.Start() | Out-Null
    $process.WaitForExit()
    $stdo = $process.StandardOutput

    If ($process.ExitCode -ne 0)
    {
        Start-Process -FilePath java -NoNewWindow -Wait -ArgumentList "-jar $JenkinsCliJarFilePath","-s $JenkinsServerUrl","-auth $Username`:$ApiToken","create-node" -RedirectStandardInput "$nodeName.xml"
        Write-Host "$nodeName created ($(Get-Date))"
    }
    else 
    {
        If ($OverwriteNodeIfExists)
        {
            Write-Host "Deleting $nodeName..."
            Start-Process -FilePath java -NoNewWindow -Wait -ArgumentList "-jar $JenkinsCliJarFilePath","-s $JenkinsServerUrl","-auth $Username`:$ApiToken","delete-node $nodeName"

            Write-Host "Recreating $nodeName..."
            Start-Process -FilePath java -NoNewWindow -Wait -ArgumentList "-jar $JenkinsCliJarFilePath","-s $JenkinsServerUrl","-auth $Username`:$ApiToken","create-node" -RedirectStandardInput "$nodeName.xml"
        }
        else 
        {
            Write-Host "Node $nodeName exists."   
        }

        If (!$KeepNodeXmlFiles)
        {
            Remove-Item -Path "$nodeName.xml" -Force
        }
    }
}

Next Step

The script I created can bulk create the “receivers” for the actual slaves to talk to the Jenkins master. I will write another PowerShell to script to automatically install and communicates with the Jenkins master.

Recap

Pull Requests, any suggestion or even forking is welcome. This should make Jenkins permanent node/slave administration a little easier.

Configuring JavaFX Project on IntelliJ

I’ve had a hard time just to get a JavaFX project going with IntelliJ on Java 11. After spending 2 nights of digging and Googling, I finally got “Hello World” window to show up.

I will try to summarize what I have done.

Maven

I have decided to manage dependencies and build with maven. Here is the pom.xml file I created. Right click on the project in Project window and select All Frameworks Support to add Maven support to the project.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>groupId</groupId>
    <artifactId>jenkinstoolsetfx</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>11</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-fxml</artifactId>
            <version>11</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

 

VM options

I’m sure the module path may be different depending your installation but here is the VM options I gave in Run/Debug Configurations. (Run -> Edit Configurations) This is necessary because JDK does not contain JavaFX by default anymore.

--module-path "/usr/share/openjfx/lib" --add-modules javafx.controls,javafx.fxml

Project

Navigate to File -> Project Structure -> Project. Make sure you select 11 – Local variable syntax for lambda parameters in Project language level.

Language Level

Navigate to File -> Project Structure -> Modules. Select 11 – Local variable syntax for lambda parameters for Language level in Sources tab.11 - Local variable syntax for lambda parameters

Target bytecode version

Navigate to File -> Settings -> Build, Execution, Deployment -> Compiler -> Java Compiler. Select 11 for Target bytecode version.

Target bytecode version

Recap

There are many options to create cross platform desktop applications and JavaFX is one of them. I hope JetBrain will streamline the way to create JavaFX project easily in the future. I have uploaded the code to my GitHub repository. https://github.com/hiriumi/jenkins-toolset-fx

Integer Array Intersection (Improved)

Right after I wrote the previous blog entry regarding integer array intersection, I thought of sorting the two parameters before I did anything with those arrays. I think I came up with a better algorithm. Let’s look at the code.

    public void testIntersect2()
    {
        int[] nums1 = {4,7,9,7,6,7};
        int[] nums2 = {5,0,0,6,1,6,2,2,4};

        int[] result = intersect2(nums1, nums2);
        for (int i : result)
        {
            System.out.println(i);
        }
    }

    private int[] intersect2(int[] nums1, int[] nums2)
    {
        Arrays.sort(nums1);
        Arrays.sort(nums2);

        int[] shorter;
        int[] longer;

        if (nums1.length < nums2.length) {
            shorter = nums1;
            longer = nums2;
        }
        else {
            shorter = nums2;
            longer = nums1;
        }

        int[] nums = new int[shorter.length];

        int j = 0;
        int index = 0;

        for(int i = 0; i < shorter.length; i++) {
            while (j < longer.length && shorter[i] >= longer[j]) {
                if (shorter[i] == longer[j]) {
                    nums[index] = shorter[i];
                    index++;
                    j++;
                    break;
                }
                j++;
            }
        }

        int[] result = new int[index];
        for(int i = 0; i < index; i++)
        {
            result[i] = nums[i];
        }

        return result;
    }

What’s better about the second attempt on this algorithm is I did not create an ArrayList for longer variable. It takes less space. The only extra data in the memory is the result as int[] so space complexity-wise it’s O(n+1) = O(n). Also convering ArrayList<Integer> to int[] seems to take some time. When I measured the code execution time, my previous code took 34 ms but this one took only 1 ms. It doesn’t seem much because it’s only 34 ms with the less efficient code but if the system execute the function millions and billions of times, it adds up!

So the logic is that you iterate through the sorted shorter integer array from the first element and you move each element in the longer one until the element matches the one in the longer array or the value in the shorter array is greater than equal to the current element in the longer array.

With this algorithm, we are iterating the shorter variable once and the longer one just about once (or less). The time complexity may be O(n).

After all, sorting the integer array at the beginning was a good idea. 🙂

Integer Array Intersection

Let’s think about 2 integer arrays.

int[] nums1 = {1, 2, 3, 9, 9, 9, 8, 5};
int[] nums2 = {9, 9};

Need to write a function that returns the common elements in both arrays. In this example, nums2 has two 9’s and nums1 has three 9’s, so the function must return {9, 9} (two 9’s).

I’ve solved the problem but I’m not sure if I have done it in the most efficient way. I will show my code below.

    public void testIntersect()
    {
        Integer[] nums1 = {9, 6, 8, 9, 9};
        Integer[] nums2 = {9, 8, 9, 5, 5, 5, 5};

        int[] result = intersect(nums1, nums2);
        for (int i : result)
        {
            System.out.println(i);
        }
    }

    public int[] intersect(Integer[] nums1, Integer[] nums2) {
        List<Integer> longer;
        Integer[] shorter;
        if (nums1.length < nums2.length) {
            longer = Arrays.stream(nums2).collect(Collectors.toList());
            shorter = nums1;
        }
        else {
            longer = Arrays.stream(nums1).collect(Collectors.toList());
            shorter = nums2;
        }

        List<Integer> result = new ArrayList<>();

        for(int i = 0; i < shorter.length; i++) {
            if (longer.contains(shorter[i])) {
                longer.remove(shorter[i]);
                result.add(shorter[i]);
            }
        }
        return result.stream().mapToInt(i -> i).toArray();
    }

The first thing I thought of doing is that I want to iterate through the shorter array so I declared longer as List<Integer> and shorter as just an integer array. The reason the longer variable is declared as List<Integer> is because I need to be able to remove elements from it. The logic is that if an element in the shorter array, the element in the longer array should be removed because if shorter contains two 9’s and longer array contains a single 9, the second 9 in the shorter array should not match the 9 in the longer array.

result variable as List<Integer> contains the matched elements and it finally returns the array.

The time complexity for this code may be O(n) and the space complexity may be O(2n).

I’m thinking there may be cleverer ways to accomplish this…