diff --git a/.gitignore b/.gitignore index 6b468b6..316834f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,8 @@ *.class +.vscode/* +.classpath +.project +.settings/* +dependency-reduced-pom.xml +target/* +.idea/* \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..dff5f3a --- /dev/null +++ b/.travis.yml @@ -0,0 +1 @@ +language: java diff --git a/ContinuousIntegrationServer.java b/ContinuousIntegrationServer.java deleted file mode 100644 index 9adb2ff..0000000 --- a/ContinuousIntegrationServer.java +++ /dev/null @@ -1,45 +0,0 @@ -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.ServletException; - -import java.io.IOException; - -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; - -/** - Skeleton of a ContinuousIntegrationServer which acts as webhook - See the Jetty documentation for API documentation of those classes. -*/ -public class ContinuousIntegrationServer extends AbstractHandler -{ - public void handle(String target, - Request baseRequest, - HttpServletRequest request, - HttpServletResponse response) - throws IOException, ServletException - { - response.setContentType("text/html;charset=utf-8"); - response.setStatus(HttpServletResponse.SC_OK); - baseRequest.setHandled(true); - - System.out.println(target); - - // here you do all the continuous integration tasks - // for example - // 1st clone your repository - // 2nd compile the code - - response.getWriter().println("CI job done"); - } - - // used to start the CI server in command line - public static void main(String[] args) throws Exception - { - Server server = new Server(8080); - server.setHandler(new ContinuousIntegrationServer()); - server.start(); - server.join(); - } -} diff --git a/README.md b/README.md index e2848cf..feef953 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,35 @@ -The smallest Java Continuous Integration server for Github +A small Java Continuous Integration server. =========================================================== +This is a simple server for Continuous Integration development. It is meant to be called as webhook by Github. The HTTP part of it is based on Jetty. We use Maven for building and managing our project. -Here is a tiny CI server skeleton implemented in Java for educational purposes. It is meant to be called as webhook by Github. The HTTP part of it is based on Jetty. +We assume here that you have a standard Linux machine (eg with Ubuntu), with Java and Maven installed. -We assume here that you have a standard Linux machine (eg with Ubuntu), with Java installed. -We first checkout this repository: -``` -git clone https://github.com/monperrus/smallest-java-ci -cd smallest-java-ci -``` +## How to run: +After checking out the repository, build it in the root directory using the following command: -We then download the required dependencies: ``` -JETTY_VERSION=7.0.2.v20100331 -wget -U none https://repo1.maven.org/maven2/org/eclipse/jetty/aggregate/jetty-all/$JETTY_VERSION/jetty-all-$JETTY_VERSION.jar -wget -U none https://repo1.maven.org/maven2/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar -#For linux users: -curl -LO --tlsv1 https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip -unzip ngrok-stable-linux-amd64.zip -#For Mac user: -curl -LO --tlsv1 https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-darwin-386.zip -unzip ngrok-stable-darwin-386.zip +mvn package ``` -We compile the skeleton the continuous integration server: +Then start the server on your local machine: ``` -javac -cp servlet-api-2.5.jar:jetty-all-$JETTY_VERSION.jar ContinuousIntegrationServer.java +java -jar target/gs-maven-0.1.0.jar ``` -We run the server on the machine, and we may make it visible on the Internet thanks to [Ngrok](https://ngrok.com/): +The serverr is visible on the Internet by using [Ngrok](https://ngrok.com/). The public url can be found by running the following commnand in a second terminal window: ``` -# open a first terminal window -JETTY_VERSION=7.0.2.v20100331 -java -cp .:servlet-api-2.5.jar:jetty-all-$JETTY_VERSION.jar ContinuousIntegrationServer - # open a second terminal window # this gives you the public URL of your CI server to set in Github # copy-paste the forwarding URL "Forwarding http://8929b010.ngrok.io -> localhost:8080" # note that this url is short-lived, and is reset everytime you run ngrok ./ngrok http 8080 - ``` - -We configure our Github repository: +Copy the url looking like [number sequence].ngrok.io, then go to the GitHub repository you want to the server to monitor. * go to `Settings >> Webhooks`, click on `Add webhook`. -* paste the forwarding URL (eg `http://8929b010.ngrok.io`) in field `Payload URL`) and send click on `Add webhook`. In the simplest setting, nothing more is required. +* paste the forwarding URL (eg `http://8929b010.ngrok.io`) in field `Payload URL`) and send click on `Add webhook`. +* **Set the content type to application/json** We test that everything works: @@ -56,7 +39,7 @@ We test that everything works: * observe the result, in two ways: * locally: in the console of your first terminal window, observe the requested URL printed on the console * on github: go to `Settings >> Webhooks` in your repo, click on your newly created webhook, scroll down to "Recent Deliveries", click on the last delivery and the on the `Response tab`, you'll see the output of your server `CI job done` - * on ngrok: raise the terminal window with Ngrok, and you'll also the see URLs requested by Github + * on ngrok: raise the terminal window with Ngrok, and you'll also the see URLs requested by Github. We shutdown everything: @@ -65,4 +48,4 @@ We shutdown everything: * delete the webhook in the webhook configuration page. Notes: -* by default, Github delivers a `push` JSON payloard, documented here: , this information can be used to get interesting information about the commit that has just been pushed. +* by default, Github delivers a `push` JSON payload, documented here: , this information can be used to get interesting information about the commit that has just been pushed. diff --git a/ngrok-stable-linux-amd64.zip b/ngrok-stable-linux-amd64.zip new file mode 100644 index 0000000..cec4cd2 Binary files /dev/null and b/ngrok-stable-linux-amd64.zip differ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e30615d --- /dev/null +++ b/pom.xml @@ -0,0 +1,133 @@ + + + 4.0.0 + + DD2480-Group-15 + gs-maven + jar + 0.1.0 + + + 1.8 + 1.8 + + + + + java.net2 + Repository hosting the jee6 artifacts + https://maven.java.net/content/groups/public/ + + + + + + javax + javaee-web-api + 6.0 + provided + + + + + + org.projectlombok + lombok + 1.18.4 + provided + + + + + + org.dmfs + httpurlconnection-executor + 0.8 + + + + + + + org.eclipse.jetty + jetty-server + 7.0.2.v20100331 + + + + + + org.apache.httpcomponents + httpclient + 4.5.9 + + + + org.apache.httpcomponents + httpclient + 4.5.10 + + + + + org.apache.httpcomponents + httpmime + 4.5.9 + + + + + org.junit.jupiter + junit-jupiter-engine + 5.3.1 + test + + + + + org.json + json + 20201115 + + + + commons-io + commons-io + 2.6 + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + + + server.ContinuousIntegrationServer + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.0 + + + + diff --git a/src/main/java/server/ContinuousIntegrationServer.java b/src/main/java/server/ContinuousIntegrationServer.java new file mode 100644 index 0000000..af65f9e --- /dev/null +++ b/src/main/java/server/ContinuousIntegrationServer.java @@ -0,0 +1,297 @@ +package server; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.ServletException; + +import java.io.*; +import java.net.http.HttpClient; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import org.json.*; + +import java.nio.file.*; +import java.io.IOException; +import org.apache.commons.io.FileUtils; + +//Import statements for Notify function +//Maybe Remove some imports? +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.entity.mime.content.FileBody; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.entity.StringEntity; +import org.apache.http.util.EntityUtils; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.DefaultHttpClient; + +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; + +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.HttpClientBuilder; + + +//Another implementation of the Notifify Function +import javax.net.ssl.HttpsURLConnection; +import java.net.URL; + + + + +/** + Skeleton of a ContinuousIntegrationServer which acts as webhook + See the Jetty documentation for API documentation of those classes. +*/ +public class ContinuousIntegrationServer extends AbstractHandler +{ + public void handle(String target, + Request baseRequest, + HttpServletRequest request, + HttpServletResponse response) + throws IOException, ServletException + { + response.setContentType("text/html;charset=utf-8"); + response.setStatus(HttpServletResponse.SC_OK); + baseRequest.setHandled(true); + + System.out.println(target); + + String who = request.getHeader("user-agent"); + + if(who.contains("GitHub-Hookshot")) { + String what = request.getHeader("X-GitHub-Event"); + if(what.contains("push")) { + BufferedReader br = request.getReader(); + //read the request + JSONObject JSON = getJSON(br); + + //TODO: remove when JSON is working for everybody + JSON.put("name","foo"); + System.out.println(JSON.get("name")); + System.out.println("Pusher: " + JSON.get("pusher")); + + // String thing = getJSON(br); + String URL = "git@github.com:DD2480-Group-15/Assignment_2.git"; //getRepoURL(JSON); + String cloneOK = cloneRepo(URL); + + String buildOK = "build not done"; + String notifyOK = "notification not sent"; + + if(cloneOK.contains("Cloning OK")){ + buildOK = buildAndTest("path"); + } + + if(buildOK.contains("Build OK")){ + notifyOK = notify(buildOK); + } + + System.out.println("Request handled"); + + if(notifyOK.contains("Notification sent successfully")){ + System.out.println(notifyOK); + } + + /* + //OLD CODE THAT NEEDS TO BE INTEGRATED + if(who.contains("GitHub-Hookshot")){ //this branch is called if the request is a webhook + //this reads the body of the webhook as a string that is formatted as a json + BufferedReader br = request.getReader(); + String str; + StringBuilder wholeStr = new StringBuilder(); + while ((str = br.readLine()) != null) { + wholeStr.append(str); + } + String ss = wholeStr.toString(); + //this interprets the string as a json object so that it's parameters can be pulled + com.alibaba.fastjson.JSONObject jsonObject = com.alibaba.fastjson.JSONObject.parseObject(ss); + //this extracts the branch in which the event occurred as lastOne + String refs = jsonObject.get("ref").toString(); + String[] sss = refs.split("/"); + String lastOne = sss[sss.length - 1]; + //this extracts the url of the repository where the event occurred as git_url + String git_url = jsonObject.getJSONObject("repository").get("git_url").toString(); + //Process p1 = Runtime.getRuntime().exec("cd C:\\Users\\Kalle\\git\\cloneplace"); + String git_url_fixed = git_url.replaceFirst("git", "https"); + //this clones the specified branch of the specified repository to the folder specified in folder_path + String folder_path = " C:\\Users\\Kalle\\git\\cloneplace"; + Process p = Runtime.getRuntime().exec("git clone -b" + " " + lastOne + " " + git_url_fixed + folder_path); + //this I don't quite know what it does + InputStream fis = p.getInputStream(); + InputStreamReader isr = new InputStreamReader(fis); + BufferedReader fg = new BufferedReader(isr); + String line = null; + //System.out.println("git clone -b" + " " + lastOne + " " + git_url_fixed + " ..\\new"); + while ((line = fg.readLine()) != null) { + System.out.println(line); + response.getWriter().println(line); + } + } */ + } + } + } + + public int dummyFunction() { + //dummy function to start testing + System.out.println("Calling dummyFunction"); + return 1; + } + + public JSONObject getJSON(BufferedReader br) throws IOException { + //reads the request and converts it to a JSON object + //when adding webhook in GitHub, you have to chose a payload of application/json. Otherwise, this function will not work. + String str; + StringBuilder wholeStr = new StringBuilder(); + while ((str = br.readLine()) != null) { + wholeStr.append(str); + } + br.close(); + + String ss = wholeStr.toString(); + + //System.out.println(ss); + + return new JSONObject(ss); + } + + public String getRepoURL(JSONObject json){ + //gets the URL for repository to be cloned + System.out.println("Getting repository URL"); + + //this extracts the branch in which the event occurred as lastOne + String ref = json.get("ref").toString(); + String[] splitref = ref.split("/"); + String branch = splitref[splitref.length - 1]; + //this extracts the url of the repository where the event occurred as git_url + String git_url = json.getJSONObject("repository").get("git_url").toString(); + String git_url_fixed = git_url.replaceFirst("git", "https"); + String full_url; + full_url = branch + " " + git_url_fixed; + return full_url; + } + + /** + * Clones a repo into the directory ./cloned-repo + * @param sshURL the ssh url of the repo + * @return status of of how the cloning went + */ + public String cloneRepo(String sshURL){ + System.out.println("Cloning repository "+ sshURL); + String cloneStatus; + + checkExistingClonedDirectory(); + + try { + Runtime.getRuntime().exec("git clone " + sshURL + " ./cloned-repo"); + cloneStatus = "Cloning OK"; + } catch (IOException e) { + System.out.print("Could not clone repo."); + cloneStatus = "Cloning Failed"; + } + + return cloneStatus; + } + + /** + * Checks if the ./cloned-repo directory already exist and if so + * it deletes this directory. + */ + private void checkExistingClonedDirectory() { + if(Files.exists(Paths.get("./cloned-repo"))) { + System.out.println("Directory exists!"); + + try { + // directory path + File file = new File("./cloned-repo"); + + // delete directory + FileUtils.deleteDirectory(file); + + } catch (IOException ex) { + ex.printStackTrace(); + } + } + } + + public String buildAndTest(String path){ + //builds the specified repo path using Maven and returns the status of the build + System.out.println("Running mvn package"); + String buildStatus = "Build and test ok"; + return buildStatus; + } + + //public String notify_2(String status, URL url)throws IOException{ + //HttpsURLConnection connect = (HttpsURLConnection) url.openConnection(); + //connect.setRequestMethod("POST"); + //connect.setRequestProperty("Content-type", "application/json"); + //connect.setRequestProperty("Status", "Status: " + status); + //connect.setDoOutPut(true); + //connect.setDoOutput(true); + + //try(OutputStream os = connect.getOutputStream()) { + //byte[] input = jsonInputString.getBytes("utf-8"); + //os.write(input, 0, input.length); + // } + + // } + + public String notify(String status){ + //sends notification of the build to the webhook + String webHook = "http://8929b010.ngrok.io"; + CloseableHttpClient httpClient = HttpClients.createDefault();; + HttpPost post = new HttpPost(webHook); + + try{ + String json = status; + + StringEntity ent = new StringEntity(json); + post.setEntity(ent); + post.setHeader("Accept", "application/json"); + post.setHeader("Content-type", "application/json"); + + httpClient.execute(post); + httpClient.close(); + + System.out.println("Notifying GitHub of build status"); + String notificationStatus = "Notification sent successfully"; + + return notificationStatus; + }catch(IOException e){ + e.printStackTrace(); + return "Notification request failed!"; + } + + + } + + // public void write_payload_to_json(JSONObject input, String file_name) { + // //this function writes a JSONObject to a specified json-file + // try (FileWriter file = new FileWriter(file_name)) { + // com.alibaba.fastjson.JSONWriter WT = new JSONWriter(file); + // WT.writeObject(input); + // } catch (IOException e) { + // e.printStackTrace(); + // } + // } + + // used to start the CI server in command line + public static void main(String[] args) throws Exception + { + Server server = new Server(8080); + server.setHandler(new ContinuousIntegrationServer()); + server.start(); + server.join(); + } +} diff --git a/src/ngrok b/src/ngrok new file mode 100755 index 0000000..a237125 Binary files /dev/null and b/src/ngrok differ diff --git a/src/test/java/server/TestServer.java b/src/test/java/server/TestServer.java new file mode 100644 index 0000000..c5484a5 --- /dev/null +++ b/src/test/java/server/TestServer.java @@ -0,0 +1,23 @@ +package server; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +// Run Maven tests: mvn test + +public class TestServer { + + @Test + public void dummyTest() { + int a = 1; + assertEquals(a,1); + } + + @Test + public void dummyTest2() { + ContinuousIntegrationServer server = new ContinuousIntegrationServer(); + int a = server.dummyFunction(); + assertEquals(1,a); + } +}