Create the fileuploader service

To upload files we've to create a fileupload and download service.

Fileuploader service implementation

The creation of the health endpoint, keycloak.json, web.xml, Dockerfile, build.js and the pom.xml is straight forward like in the other projects.
The servlet itself looks like this:

import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObject;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

@WebServlet("/fileupload")
@MultipartConfig(
        fileSizeThreshold = 1024 * 1024 * 10,  // 10 MB
        maxFileSize = 1024 * 1024 * 50,        // 50 MB
        maxRequestSize = 1024 * 1024 * 100)    // 100 MB
public class FileUploadServlet extends HttpServlet {
    private static final long serialVersionUID = -1L;

    public static final String UPLOAD_DIR = System.getenv("UPLOAD_DIR");

    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response) throws ServletException, IOException {

        Set<String> paths = new HashSet<>();
        for (Part part : request.getParts()) {
            String relativePath = part.getName();
            String fileName = UUID.randomUUID().toString();
            String oldFileName = getFileName(part);
            String extension = oldFileName.split("\\.")[1];
            String relativeFilePath = relativePath + File.separator + fileName + "." + extension;
            String path = UPLOAD_DIR + relativeFilePath;
            File fileSaveDir = new File(path);
            if (!fileSaveDir.getParentFile().exists()) {
                fileSaveDir.getParentFile().mkdirs();
            }
            part.write(path);

            fileSaveDir.setExecutable(false, false);
            fileSaveDir.setWritable(false, false);
            fileSaveDir.setReadable(true, false);

            paths.add(relativeFilePath);
        }

        JsonArrayBuilder pictureBuilder = Json.createArrayBuilder();
        for (String path : paths) {
            JsonObject pathObject = Json.createObjectBuilder()
                    .add("path", path)
                    .build();
            pictureBuilder = pictureBuilder
                    .add(pathObject);
        }

        response.setHeader("Location", paths.toString());
        response.setContentType("application/json");
        PrintWriter out = response.getWriter();
        out.print(pictureBuilder.build().toString());
        out.flush();
        out.close();
    }

    private String getFileName(Part part) {
        String contentDisp = part.getHeader("content-disposition");
        String[] tokens = contentDisp.split(";");
        for (String token : tokens) {
            if (token.trim().startsWith("filename")) {
                return token.substring(token.indexOf("=") + 2, token.length() - 1);
            }
        }
        return "";
    }

}

Jenkins pipeline

The Jenkins pipeline looks like this:

withEnv([   "VERSION=1.0.${currentBuild.number}",
            "REGISTRY_EMAIL=brem_robert@hotmail.com",
            "KUBECTL=kubectl",
            "HOST=disruptor.ninja",
            "REALM_NAME=battleapp",
            "KEYCLOAK_URL=https://disruptor.ninja:31182/auth",
            "PORT=31082"]) {

  stage "checkout, build, test and publish"
  node {
    git url: "http://disruptor.ninja:30130/rob/battleapp-fileuploader"
    def mvnHome = tool 'M3'
    sh "${mvnHome}/bin/mvn clean install"
    sh "./build.js"
  }

  stage "start test environment"
  node {
    git url: "http://disruptor.ninja:30130/rob/battleapp-fileuploader-starttestenv"
    sh "./start.js"
  }

  stage "manual testing"
  input "everything ok?"

  stage "start canary"
  input "deploy the canary?"
  node {
    git url: "http://disruptor.ninja:30130/rob/battleapp-fileuploader-canary"
    sh "./start.js"
  }

  stage "go full production"
  input "undeploy other versions?"
  node {
    git url: "http://disruptor.ninja:30130/rob/battleapp-fileuploader-prod"
    sh "./start.js"
  }
}

Start test environment script

The start.js script for the test environment looks like this:

We create two deployments and two services one for the fileupload a Java EE application and for the filedownload a simple nginx.

#!/usr/bin/jjs -fv

var FileWriter = Java.type("java.io.FileWriter");

var version = $ENV.VERSION;
var kubectl = $ENV.KUBECTL;

var name = "battleapp-fileuploader";
var image = "disruptor.ninja:30500/robertbrem/battleapp-fileuploader:" + version;
var replicas = 1;
var port = 8080;
var clusterPort = 8181;
var nodePort = 31082;
var deploymentFileName = "deployment.yml";
var serviceFileName = "service.yml";
var registrysecret = "registrykey";
var url = "http://disruptor.ninja:" + nodePort + "/battleapp/resources/health";
var timeout = 2;
var realmName = "battleapp";
var authServerUrl = "https://disruptor.ninja:31182/auth";
var namespace = "test";
var kafkaAddress = "kafka:9092";
var uploadDir = "/opt/jboss/uploads";
var nodeSelector = "vmi74389";
var hostPath = "/root/uploads";

var downloadName = "battleapp-filedownloader";
var downloadImage = "nginx";
var downloadReplicas = 1;
var downloadPort = 80;
var downloadClusterPort = 81;
var downloadNodePort = 31031;
var downloadDeploymentFileName = "downloadDeployment.yml";
var downloadServiceFileName = "downloadService.yml";
var downloadDir = "/usr/share/nginx/html";

var deleteDeployment = kubectl + " --namespace " + namespace + " delete deployment " + name;
execute(deleteDeployment);

var dfw = new FileWriter(deploymentFileName);
dfw.write("apiVersion: extensions/v1beta1\n");
dfw.write("kind: Deployment\n");
dfw.write("metadata:\n");
dfw.write("  name: " + name + "\n");
dfw.write("  namespace: " + namespace + "\n");
dfw.write("spec:\n");
dfw.write("  replicas: " + replicas + "\n");
dfw.write("  template:\n");
dfw.write("    metadata:\n");
dfw.write("      labels:\n");
dfw.write("        name: " + name + "\n");
dfw.write("    spec:\n");
dfw.write("      containers:\n");
dfw.write("      - resources:\n");
dfw.write("        name: " + name + "\n");
dfw.write("        image: " + image + "\n");
dfw.write("        ports:\n");
dfw.write("        - name: port\n");
dfw.write("          containerPort: " + port + "\n");
dfw.write("        env:\n");
dfw.write("        - name: REALM_NAME\n");
dfw.write("          value: \"" + realmName + "\"\n");
dfw.write("        - name: AUTH_SERVER_URL\n");
dfw.write("          value: \"" + authServerUrl + "\"\n");
dfw.write("        - name: KAFKA_ADDRESS\n");
dfw.write("          value: \"" + kafkaAddress + "\"\n");
dfw.write("        - name: UPLOAD_DIR\n");
dfw.write("          value: \"" + uploadDir + "\"\n");
dfw.write("        volumeMounts:\n");
dfw.write("        - mountPath: " + uploadDir + "\n");
dfw.write("          name: files\n");
dfw.write("      volumes:\n");
dfw.write("      - name: files\n");
dfw.write("        hostPath:\n");
dfw.write("          path: " + hostPath + "\n");
dfw.write("      nodeSelector:\n");
dfw.write("        name: " + nodeSelector + "\n");
dfw.write("      imagePullSecrets:\n");
dfw.write("      - name: " + registrysecret + "\n");
dfw.close();

var deploy = kubectl + " create -f " + deploymentFileName;
execute(deploy);

var deleteDownloadDeployment = kubectl + " --namespace " + namespace + " delete deployment " + downloadName;
execute(deleteDownloadDeployment);

var downfw = new FileWriter(downloadDeploymentFileName);
downfw.write("apiVersion: extensions/v1beta1\n");
downfw.write("kind: Deployment\n");
downfw.write("metadata:\n");
downfw.write("  name: " + downloadName + "\n");
downfw.write("  namespace: " + namespace + "\n");
downfw.write("spec:\n");
downfw.write("  replicas: " + downloadReplicas + "\n");
downfw.write("  template:\n");
downfw.write("    metadata:\n");
downfw.write("      labels:\n");
downfw.write("        name: " + downloadName + "\n");
downfw.write("    spec:\n");
downfw.write("      containers:\n");
downfw.write("      - resources:\n");
downfw.write("        name: " + downloadName + "\n");
downfw.write("        image: " + downloadImage + "\n");
downfw.write("        ports:\n");
downfw.write("        - name: port\n");
downfw.write("          containerPort: " + downloadPort + "\n");
downfw.write("        volumeMounts:\n");
downfw.write("        - mountPath: " + downloadDir + "\n");
downfw.write("          name: files\n");
downfw.write("      volumes:\n");
downfw.write("      - name: files\n");
downfw.write("        hostPath:\n");
downfw.write("          path: " + hostPath + "\n");
downfw.write("      nodeSelector:\n");
downfw.write("        name: " + nodeSelector + "\n");
downfw.write("      imagePullSecrets:\n");
downfw.write("      - name: " + registrysecret + "\n");
downfw.close();

var downloadDeploy = kubectl + " create -f " + downloadDeploymentFileName;
execute(downloadDeploy);

var sfw = new FileWriter(serviceFileName);
sfw.write("apiVersion: v1\n");
sfw.write("kind: Service\n");
sfw.write("metadata:\n");
sfw.write("  name: " + name + "\n");
sfw.write("  labels:\n");
sfw.write("    name: " + name + "\n");
sfw.write("  namespace: " + namespace + "\n");
sfw.write("spec:\n");
sfw.write("  ports:\n");
sfw.write("  - port: " + clusterPort + "\n");
sfw.write("    targetPort: " + port + "\n");
sfw.write("    nodePort: " + nodePort + "\n");
sfw.write("  selector:\n");
sfw.write("    name: " + name + "\n");
sfw.write("  type: NodePort\n");
sfw.close();

var deployService = kubectl + " create -f " + serviceFileName;
execute(deployService);

var downsfw = new FileWriter(downloadServiceFileName);
downsfw.write("apiVersion: v1\n");
downsfw.write("kind: Service\n");
downsfw.write("metadata:\n");
downsfw.write("  name: " + downloadName + "\n");
downsfw.write("  labels:\n");
downsfw.write("    name: " + downloadName + "\n");
downsfw.write("  namespace: " + namespace + "\n");
downsfw.write("spec:\n");
downsfw.write("  ports:\n");
downsfw.write("  - port: " + downloadClusterPort + "\n");
downsfw.write("    targetPort: " + downloadPort + "\n");
downsfw.write("    nodePort: " + downloadNodePort + "\n");
downsfw.write("  selector:\n");
downsfw.write("    name: " + downloadName + "\n");
downsfw.write("  type: NodePort\n");
downsfw.close();

var downloadDeployService = kubectl + " create -f " + downloadServiceFileName;
execute(downloadDeployService);

var testUrl = "curl --write-out %{http_code} --silent --output /dev/null " + url + " --max-time " + timeout;
execute(testUrl);
while ($OUT != "200") {
    $EXEC("sleep 1");
    execute(testUrl);
}

function execute(command) {
    $EXEC(command);
    print($OUT);
    print($ERR);
}

Start canary script

Like in the test environment we create two deployments and two services one for the download and one for the upload:

#!/usr/bin/jjs -fv

var FileWriter = Java.type("java.io.FileWriter");

var version = $ENV.VERSION;
var kubectl = $ENV.KUBECTL;

var name = "battleapp-fileuploader";
var versionedName = name + "-" + version;
var image = "disruptor.ninja:30500/robertbrem/battleapp-fileuploader:" + version;
var replicas = 1;
var port = 8080;
var clusterPort = 8182;
var nodePort = 30082;
var deploymentFileName = "deployment.yml";
var serviceFileName = "service.yml";
var registrysecret = "registrykey";
var url = "http://disruptor.ninja:" + nodePort + "/battleapp/resources/health";
var timeout = 2;
var realmName = "battleapp";
var authServerUrl = "https://disruptor.ninja:30182/auth";
var kafkaAddress = "kafka:9092";
var uploadDir = "/opt/jboss/uploads";
var nodeSelector = "vmi100202";
var hostPath = "/root/uploads";

var downloadName = "battleapp-filedownloader";
var versionedDownloadName = downloadName + "-" + version;
var downloadImage = "nginx";
var downloadReplicas = 1;
var downloadPort = 80;
var downloadClusterPort = 82;
var downloadNodePort = 30031;
var downloadDeploymentFileName = "downloadDeployment.yml";
var downloadServiceFileName = "downloadService.yml";
var downloadDir = "/usr/share/nginx/html";

var dfw = new FileWriter(deploymentFileName);
dfw.write("apiVersion: extensions/v1beta1\n");
dfw.write("kind: Deployment\n");
dfw.write("metadata:\n");
dfw.write("  name: " + versionedName + "\n");
dfw.write("spec:\n");
dfw.write("  replicas: " + replicas + "\n");
dfw.write("  template:\n");
dfw.write("    metadata:\n");
dfw.write("      labels:\n");
dfw.write("        name: " + name + "\n");
dfw.write("        version: " + version + "\n");
dfw.write("    spec:\n");
dfw.write("      containers:\n");
dfw.write("      - resources:\n");
dfw.write("        name: " + name + "\n");
dfw.write("        image: " + image + "\n");
dfw.write("        ports:\n");
dfw.write("        - name: port\n");
dfw.write("          containerPort: " + port + "\n");
dfw.write("        env:\n");
dfw.write("        - name: REALM_NAME\n");
dfw.write("          value: \"" + realmName + "\"\n");
dfw.write("        - name: AUTH_SERVER_URL\n");
dfw.write("          value: \"" + authServerUrl + "\"\n");
dfw.write("        - name: KAFKA_ADDRESS\n");
dfw.write("          value: \"" + kafkaAddress + "\"\n");
dfw.write("        - name: UPLOAD_DIR\n");
dfw.write("          value: \"" + uploadDir + "\"\n");
dfw.write("        volumeMounts:\n");
dfw.write("        - mountPath: " + uploadDir + "\n");
dfw.write("          name: files\n");
dfw.write("      volumes:\n");
dfw.write("      - name: files\n");
dfw.write("        hostPath:\n");
dfw.write("          path: " + hostPath + "\n");
dfw.write("      nodeSelector:\n");
dfw.write("        name: " + nodeSelector + "\n");
dfw.write("      imagePullSecrets:\n");
dfw.write("      - name: " + registrysecret + "\n");
dfw.close();

var deploy = kubectl + " create -f " + deploymentFileName;
execute(deploy);

var downfw = new FileWriter(downloadDeploymentFileName);
downfw.write("apiVersion: extensions/v1beta1\n");
downfw.write("kind: Deployment\n");
downfw.write("metadata:\n");
downfw.write("  name: " + versionedDownloadName + "\n");
downfw.write("spec:\n");
downfw.write("  replicas: " + downloadReplicas + "\n");
downfw.write("  template:\n");
downfw.write("    metadata:\n");
downfw.write("      labels:\n");
downfw.write("        name: " + downloadName + "\n");
downfw.write("        version: " + version + "\n");
downfw.write("    spec:\n");
downfw.write("      containers:\n");
downfw.write("      - resources:\n");
downfw.write("        name: " + downloadName + "\n");
downfw.write("        image: " + downloadImage + "\n");
downfw.write("        ports:\n");
downfw.write("        - name: port\n");
downfw.write("          containerPort: " + downloadPort + "\n");
downfw.write("        volumeMounts:\n");
downfw.write("        - mountPath: " + downloadDir + "\n");
downfw.write("          name: files\n");
downfw.write("      volumes:\n");
downfw.write("      - name: files\n");
downfw.write("        hostPath:\n");
downfw.write("          path: " + hostPath + "\n");
downfw.write("      nodeSelector:\n");
downfw.write("        name: " + nodeSelector + "\n");
downfw.write("      imagePullSecrets:\n");
downfw.write("      - name: " + registrysecret + "\n");
downfw.close();

var downloadDeploy = kubectl + " create -f " + downloadDeploymentFileName;
execute(downloadDeploy);

var sfw = new FileWriter(serviceFileName);
sfw.write("apiVersion: v1\n");
sfw.write("kind: Service\n");
sfw.write("metadata:\n");
sfw.write("  name: " + name + "\n");
sfw.write("  labels:\n");
sfw.write("    name: " + name + "\n");
sfw.write("spec:\n");
sfw.write("  ports:\n");
sfw.write("  - port: " + clusterPort + "\n");
sfw.write("    targetPort: " + port + "\n");
sfw.write("    nodePort: " + nodePort + "\n");
sfw.write("  selector:\n");
sfw.write("    name: " + name + "\n");
sfw.write("  type: NodePort\n");
sfw.close();

var deployService = kubectl + " create -f " + serviceFileName;
execute(deployService);

var downsfw = new FileWriter(downloadServiceFileName);
downsfw.write("apiVersion: v1\n");
downsfw.write("kind: Service\n");
downsfw.write("metadata:\n");
downsfw.write("  name: " + downloadName + "\n");
downsfw.write("  labels:\n");
downsfw.write("    name: " + downloadName + "\n");
downsfw.write("spec:\n");
downsfw.write("  ports:\n");
downsfw.write("  - port: " + downloadClusterPort + "\n");
downsfw.write("    targetPort: " + downloadPort + "\n");
downsfw.write("    nodePort: " + downloadNodePort + "\n");
downsfw.write("  selector:\n");
downsfw.write("    name: " + downloadName + "\n");
downsfw.write("  type: NodePort\n");
downsfw.close();

var downloadDeployService = kubectl + " create -f " + downloadServiceFileName;
execute(downloadDeployService);

var testUrl = "curl --write-out %{http_code} --silent --output /dev/null " + url + " --max-time " + timeout;
execute(testUrl);
while ($OUT != "200") {
    $EXEC("sleep 1");
    execute(testUrl);
}

function execute(command) {
    $EXEC(command);
    print($OUT);
    print($ERR);
}

Go full production script

The go full production with the current version script looks like this:

#!/usr/bin/jjs -fv

var version = $ENV.VERSION;
var kubectl = $ENV.KUBECTL;

var name = "battleapp-fileuploader";
var url = "http://disruptor.ninja:30082/battleapp/resources/health";
var timeout = 2;

var downloadName = "battleapp-filedownloader";

var deleteDeployment = kubectl + " delete deployment -l name=" + name + ",version!=" + version;
execute(deleteDeployment);

var deleteDownloadDeployment = kubectl + " delete deployment -l name=" + downloadName + ",version!=" + version;
execute(deleteDownloadDeployment);

var testUrl = "curl --write-out %{http_code} --silent --output /dev/null " + url + " --max-time " + timeout;
execute(testUrl);
while ($OUT != "200") {
    $EXEC("sleep 1");
    execute(testUrl);
}

function execute(command) {
    $EXEC(command);
    print($OUT);
    print($ERR);
}