├── .gitignore ├── namespace.yaml ├── sample-app-1.0.1.tgz ├── src ├── main │ ├── resources │ │ └── application.properties │ └── java │ │ └── com │ │ └── satish │ │ └── demo │ │ ├── DemoWorkshopApplication.java │ │ └── controller │ │ └── RepositoryDetailsController.java └── test │ └── java │ └── com │ └── satish │ └── demo │ ├── DemoWorkshopApplicationTests.java │ ├── RepositoryDetailsControllerTest.java │ └── AbstractTest.java ├── deploy.sh ├── Dockerfile ├── secret.yaml ├── service.yaml ├── sonar-project.properties ├── deployment.yaml ├── README.md ├── pom.xml └── Jenkinsfile /.gitignore: -------------------------------------------------------------------------------- 1 | ./target/* 2 | -------------------------------------------------------------------------------- /namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: sample-app 5 | -------------------------------------------------------------------------------- /sample-app-1.0.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SatishKetha/DevOps_Project1/HEAD/sample-app-1.0.1.tgz -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8000 2 | # Commit for master 1 3 | # Commit for master 2 4 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | kubectl apply -f namespace.yaml 3 | kubectl apply -f secret.yaml 4 | kubectl apply -f deployment.yaml 5 | kubectl apply -f service.yaml 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8 2 | ADD jarstaging/com/satish/demo-workshop/2.1.2/demo-workshop-2.1.2.jar sample_app.jar 3 | ENTRYPOINT [ "java", "-jar", "sample_app.jar" ] 4 | -------------------------------------------------------------------------------- /secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: dockercred 5 | namespace: sample-app 6 | data: 7 | .dockerconfigjson: #copy your secret here 8 | type: kubernetes.io/dockerconfigjson 9 | -------------------------------------------------------------------------------- /service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: sample-app-service 5 | namespace: sample-app 6 | spec: 7 | type: NodePort 8 | selector: 9 | app: sample-app 10 | ports: 11 | - nodePort: 30082 12 | port: 8000 13 | targetPort: 8000 14 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.verbose=true 2 | sonar.organization=satishketha01 3 | sonar.projectKey=satishketha01_devops-project-1 4 | sonar.projectName=DevOps_Project_1 5 | sonar.language=java 6 | sonar.sourceEncoding=UTF-8 7 | sonar.sources=. 8 | sonar.java.binaries=target/classes 9 | sonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml -------------------------------------------------------------------------------- /src/test/java/com/satish/demo/DemoWorkshopApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.stalin.demo; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | import java.util.*; 7 | 8 | @SpringBootTest 9 | class DemoWorkshopApplicationTests { 10 | 11 | @Test 12 | void contextLoads() { 13 | List li = new ArrayList (); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/satish/demo/DemoWorkshopApplication.java: -------------------------------------------------------------------------------- 1 | package com.stalin.demo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DemoWorkshopApplication{ 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(DemoWorkshopApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: sample-app-dep 5 | namespace: sample-app 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | app: sample-app 11 | template: 12 | metadata: 13 | labels: 14 | app: sample-app 15 | spec: 16 | imagePullSecrets: 17 | - name: dockercred 18 | containers: 19 | - name: sample-app 20 | image: satishk.jfrog.io/satish-docker-local/sample_app:2.1.2 21 | imagePullPolicy: Always 22 | ports: 23 | - containerPort: 8000 24 | env: 25 | - name: CONSUMER_KEY 26 | value: "G6lmKhsi0V9TvXt6oKTfjRBCr" 27 | - name: CONSUMER_SECRET 28 | value: "bEyDk8X0p8SQd4376eoNV4nH8To22sHcJOoFt0ZqOKS37hhI4q" 29 | - name: ACCESS_TOKEN 30 | value: "9703354-52rXHD6EeOQeYyhtNz1w8UVOgbcLcgOo4O7MB6WV3" 31 | - name: ACCESS_TOKEN_SECRET 32 | value: "zBThlZDEp8qnu7NwwdHNth8eg3Rf9zqbvUEFUTaZtN2SF" 33 | -------------------------------------------------------------------------------- /src/test/java/com/satish/demo/RepositoryDetailsControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.stalin.demo; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.http.MediaType; 7 | import org.springframework.test.web.servlet.MvcResult; 8 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | 12 | 13 | @SpringBootTest 14 | class RepositoryDetailsControllerTest extends AbstractTest { 15 | 16 | @BeforeEach 17 | public void setUp() { 18 | super.setUp(); 19 | } 20 | 21 | 22 | @Test 23 | public void getProductsList() throws Exception { 24 | String uri = "/trends?placeid=1&count=5"; 25 | MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.get(uri) 26 | .accept(MediaType.APPLICATION_JSON_VALUE)).andReturn(); 27 | int status = mvcResult.getResponse().getStatus(); 28 | assertEquals(200, status); 29 | } 30 | 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/com/satish/demo/AbstractTest.java: -------------------------------------------------------------------------------- 1 | package com.stalin.demo; 2 | 3 | import com.fasterxml.jackson.core.JsonParseException; 4 | import com.fasterxml.jackson.core.JsonProcessingException; 5 | import com.fasterxml.jackson.databind.JsonMappingException; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 11 | import org.springframework.test.context.web.WebAppConfiguration; 12 | import org.springframework.test.web.servlet.MockMvc; 13 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 14 | import org.springframework.web.context.WebApplicationContext; 15 | 16 | import java.io.IOException; 17 | 18 | @RunWith(SpringJUnit4ClassRunner.class) 19 | @SpringBootTest(classes = DemoWorkshopApplication.class) 20 | @WebAppConfiguration 21 | public abstract class AbstractTest { 22 | 23 | protected MockMvc mvc; 24 | @Autowired 25 | WebApplicationContext webApplicationContext; 26 | 27 | protected void setUp() { 28 | mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); 29 | } 30 | protected String mapToJson(Object obj) throws JsonProcessingException { 31 | ObjectMapper objectMapper = new ObjectMapper(); 32 | return objectMapper.writeValueAsString(obj); 33 | } 34 | protected T mapFromJson(String json, Class clazz) 35 | throws JsonParseException, JsonMappingException, IOException { 36 | 37 | ObjectMapper objectMapper = new ObjectMapper(); 38 | return objectMapper.readValue(json, clazz); 39 | } 40 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sample-java-app 2 | 3 | This is a small applicaiton which contains main and test folders. 4 | Main contains application code. 5 | Test contains test cases. 6 | It also contains pom.xml which has all dependencies and artifact name and version 7 | 8 | 9 | These are the steps I followed in the implementation of the entire CI/CD Pipeline. 10 | 11 | 1. Provisioned the required infrastructure like VPC, Security Group, Ansible Controller Instance, Jenkins Master and Agent Instances using Terraform. 12 | 2. Configured SSH keys for password less authentication between Ansible Controller and Agent nodes. 13 | 3. Configured the Jenkins Master and Agent nodes using Ansible. Configured Jenkins Agent as the Maven Build server. 14 | 4. Added Jenkins Agent node's credentials in Jenkins Master to establish a connection between Jenkins Master and Agent nodes. 15 | 5. Added GitHub credentials to the Jenkins Master and created Multibranch Pipeline job. 16 | 6. Configured the Multibranch Pipeline job with GitHub Webhook Trigger with the help of Multibranch Scan Webhook Trigger Plugin. 17 | 7. SonarQube: 18 | a. Generated an access token in SonarCloud and added SonarQube server credentials in Jenkins Master. 19 | b. Installed Sonarqube scanner plugin. 20 | c. Added Sonarqube server to the Jenkins Master in System section. 21 | d. Added Sonarqube scanner to the Jenkins Master in Tools section. 22 | e. Configured an organization and project in SonarCloud and wrote a sonar-project. properties file. 23 | f. Added sonarqube, unit tests and build stages in the Jenkinsfile. 24 | 8. Added JFrog credentials in the Jenkins Master and integrated JFrog artifactory with Jenkins by installing Artifactory plugin in Jenkins Master. 25 | 9. Created a Docker Image out of the jar file and committed that Docker Image into the Docker repository of the JFrog artifactory with the help of Docker Pipeline plugin. Added the Docker Build and Publish stage in Jenkinsfile. 26 | 10. EKS: 27 | a. Provisioned the EKS cluster with Terraform. 28 | b. Installed kubectl in Jenkins Slave. 29 | c. Installed AWS CLI v2 in Jenkins Slave to connect with AWS account. 30 | d. Downloaded Kubernetes credentials and cluster configuration from the cluster using the command i.e., aws eks update-kubeconfig --region --name 31 | 11. Pulled the Docker Image from the JFrog artifactory using Kubernetes secret and deployed it in our EKS cluster using deployment resource and exposed it to access from outside using service resource under a particular namespace. Added the deployment stage in Jenkinsfile. 32 | 12. Added the Prometheus helm chart repository and implemented the cluster monitoring using Prometheus and Grafana. Note: Changed the default service type of Prometheus and Grafana services from ClusterIP to LoadBalancer to access them from the browser. 33 | -------------------------------------------------------------------------------- /src/main/java/com/satish/demo/controller/RepositoryDetailsController.java: -------------------------------------------------------------------------------- 1 | package com.stalin.demo.controller; 2 | 3 | import java.io.IOException; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | import org.kohsuke.github.GHRepositorySearchBuilder; 8 | import org.kohsuke.github.GitHub; 9 | import org.kohsuke.github.GitHubBuilder; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.core.env.Environment; 12 | import org.springframework.web.bind.annotation.GetMapping; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RequestParam; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | import twitter4j.Trend; 18 | import twitter4j.Trends; 19 | import twitter4j.Twitter; 20 | import twitter4j.TwitterException; 21 | import twitter4j.TwitterFactory; 22 | import twitter4j.conf.ConfigurationBuilder; 23 | 24 | @RestController 25 | public class RepositoryDetailsController { 26 | 27 | 28 | 29 | 30 | @Autowired 31 | private Environment env; 32 | 33 | @RequestMapping("/") 34 | public String getRepos() throws IOException { 35 | GitHub github = new GitHubBuilder().withPassword("valaxytech@gmail.com", "XXXXXXXX").build(); 36 | GHRepositorySearchBuilder builder = github.searchRepositories(); 37 | return "This is the sample DevOps Project"; 38 | } 39 | 40 | @GetMapping("/trends") 41 | public Map getTwitterTrends(@RequestParam("placeid") String trendPlace, @RequestParam("count") String trendCount) { 42 | String consumerKey = env.getProperty("CONSUMER_KEY"); 43 | String consumerSecret = env.getProperty("CONSUMER_SECRET"); 44 | String accessToken = env.getProperty("ACCESS_TOKEN"); 45 | String accessTokenSecret = env.getProperty("ACCESS_TOKEN_SECRET"); 46 | System.out.println("consumerKey "+consumerKey+" consumerSecret "+consumerSecret+" accessToken "+accessToken+" accessTokenSecret "+accessTokenSecret); 47 | ConfigurationBuilder cb = new ConfigurationBuilder(); 48 | cb.setDebugEnabled(true) 49 | .setOAuthConsumerKey(consumerKey) 50 | .setOAuthConsumerSecret(consumerSecret) 51 | .setOAuthAccessToken(accessToken) 52 | .setOAuthAccessTokenSecret(accessTokenSecret); 53 | TwitterFactory tf = new TwitterFactory(cb.build()); 54 | System.out.println("Twitter Factory "+tf); 55 | System.out.println("Code testing purpose "); 56 | Twitter twitter = tf.getInstance(); 57 | System.out.println("Twitter object "+twitter); 58 | Map trendDetails = new HashMap(); 59 | try { 60 | Trends trends = twitter.getPlaceTrends(Integer.parseInt(trendPlace)); 61 | System.out.println("After API call"); 62 | int count = 0; 63 | for (Trend trend : trends.getTrends()) { 64 | if (count < Integer.parseInt(trendCount)) { 65 | trendDetails.put(trend.getName(), trend.getURL()); 66 | count++; 67 | } 68 | } 69 | } catch (TwitterException e) { 70 | trendDetails.put("test", "MyTweet"); 71 | //trendDetails.put("Twitter Exception", e.getMessage()); 72 | System.out.println("Twitter exception "+e.getMessage()); 73 | 74 | }catch (Exception e) { 75 | trendDetails.put("test", "MyTweet"); 76 | System.out.println("Exception "+e.getMessage()); 77 | } 78 | return trendDetails; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.2.0.RELEASE 9 | 10 | 11 | com.satish 12 | demo-workshop 13 | 2.1.2 14 | demo-workshop 15 | jar 16 | Demo project for Spring Boot 17 | 18 | 19 | 1.8 20 | 3.1.1 21 | 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-web 31 | 32 | 33 | 34 | 35 | org.kohsuke 36 | github-api 37 | 1.99 38 | 39 | 40 | 41 | org.twitter4j 42 | twitter4j-core 43 | 3.0.6 44 | 45 | 46 | 47 | junit 48 | junit 49 | 4.13.1 50 | test 51 | 52 | 53 | 54 | org.springframework.boot 55 | spring-boot-starter-test 56 | test 57 | 58 | 59 | org.junit.vintage 60 | junit-vintage-engine 61 | 62 | 63 | junit 64 | junit 65 | 66 | 67 | org.junit.jupiter 68 | org.junit.jupiter.api 69 | 70 | 71 | 72 | 73 | org.mockito 74 | mockito-junit-jupiter 75 | test 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | org.springframework.boot 84 | spring-boot-maven-plugin 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-surefire-plugin 89 | 2.22.2 90 | 91 | ${argLine} -Xmx2048m 92 | 93 | 94 | 95 | 96 | org.jacoco 97 | jacoco-maven-plugin 98 | 0.8.2 99 | 100 | 101 | 102 | prepare-agent 103 | 104 | 105 | 106 | 107 | report 108 | test 109 | 110 | report 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | dev 121 | file://jarstaging 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | def registry = 'https://satishk.jfrog.io' 2 | def imageName = 'satishk.jfrog.io/satish-docker-local/sample_app' 3 | def version = '2.1.2' 4 | pipeline { 5 | agent { 6 | node { 7 | label 'maven' 8 | } 9 | } 10 | environment { 11 | PATH = "/opt/apache-maven-3.9.4/bin:$PATH" 12 | } 13 | stages { 14 | stage("build"){ 15 | steps { 16 | echo "----------- build started ----------" 17 | sh 'mvn clean deploy -Dmaven.test.skip=true' 18 | echo "----------- build complted ----------" 19 | } 20 | } 21 | stage("test"){ 22 | steps{ 23 | echo "----------- unit test started ----------" 24 | sh 'mvn surefire-report:report' 25 | echo "----------- unit test Complted ----------" 26 | } 27 | } 28 | 29 | stage('SonarQube analysis') { 30 | environment { 31 | scannerHome = tool 'satish-sonarqube-scanner' 32 | } 33 | steps{ 34 | withSonarQubeEnv('satish-sonarqube-server') { // If you have configured more than one global server connection, you can specify its name 35 | sh "${scannerHome}/bin/sonar-scanner" 36 | } 37 | } 38 | } 39 | stage("Quality Gate"){ 40 | steps { 41 | script { 42 | timeout(time: 1, unit: 'HOURS') { // Just in case something goes wrong, pipeline will be killed after a timeout 43 | def qg = waitForQualityGate() // Reuse taskId previously collected by withSonarQubeEnv 44 | if (qg.status != 'OK') { 45 | error "Pipeline aborted due to quality gate failure: ${qg.status}" 46 | } 47 | } 48 | } 49 | } 50 | } 51 | stage("Jar Publish") { 52 | steps { 53 | script { 54 | echo '<--------------- Jar Publish Started --------------->' 55 | def server = Artifactory.newServer url:registry+"/artifactory" , credentialsId:"jfrog_cred" 56 | def properties = "buildid=${env.BUILD_ID},commitid=${GIT_COMMIT}"; 57 | def uploadSpec = """{ 58 | "files": [ 59 | { 60 | "pattern": "jarstaging/(*)", 61 | "target": "maven-libs-release-local/{1}", 62 | "flat": "false", 63 | "props" : "${properties}", 64 | "exclusions": [ "*.sha1", "*.md5"] 65 | } 66 | ] 67 | }""" 68 | def buildInfo = server.upload(uploadSpec) 69 | buildInfo.env.collect() 70 | server.publishBuildInfo(buildInfo) 71 | echo '<--------------- Jar Publish Ended --------------->' 72 | 73 | } 74 | } 75 | } 76 | 77 | 78 | stage(" Docker Build ") { 79 | steps { 80 | script { 81 | echo '<--------------- Docker Build Started --------------->' 82 | app = docker.build(imageName+":"+version) 83 | echo '<--------------- Docker Build Ends --------------->' 84 | } 85 | } 86 | } 87 | 88 | stage (" Docker Publish "){ 89 | steps { 90 | script { 91 | echo '<--------------- Docker Publish Started --------------->' 92 | docker.withRegistry(registry, 'jfrog_cred'){ 93 | app.push() 94 | } 95 | echo '<--------------- Docker Publish Ended --------------->' 96 | } 97 | } 98 | } 99 | 100 | // stage (" Deploy "){ 101 | // steps { 102 | // script { 103 | // sh './deploy.sh' 104 | // } 105 | // } 106 | // } 107 | 108 | stage(" Deploy ") { 109 | steps { 110 | script { 111 | echo '<--------------- Helm Deploy Started --------------->' 112 | sh 'helm install sample-app sample-app-1.0.1' 113 | echo '<--------------- Helm deploy Ends --------------->' 114 | } 115 | } 116 | } 117 | } 118 | } 119 | 120 | 121 | --------------------------------------------------------------------------------