diff --git a/.gitignore b/.gitignore index d2767ad28..b13718629 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,5 @@ out/ _site/ *.css !petclinic.css + +gradle.properties diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..76f27a44f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM gradle:8.5-jdk17 AS build + +WORKDIR /app + +COPY . /app + +RUN gradle clean build + +FROM eclipse-temurin:17-jre + +WORKDIR /app + +COPY --from=build /app/build/libs/*.jar app.jar + +EXPOSE 8080 + +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 000000000..5afd03165 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,117 @@ +pipeline { + agent any + + environment { + // Define environment variables + DOCKER_REGISTRY = "docker.io" + DOCKER_IMAGE_MAIN = 'mmarcetic/main' + DOCKER_IMAGE_MR = 'mmarcetic/mr' + GIT_COMMIT = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim() + } + + stages { + stage('Checkout') { + steps { + // Checkout the code from the repository + checkout scm + } + } + + stage('Set Docker Image') { + steps { + script { + // Set Docker image based on the branch name + if (env.BRANCH_NAME == 'main') { + env.DOCKER_IMAGE = DOCKER_IMAGE_MAIN + } else { + env.DOCKER_IMAGE = DOCKER_IMAGE_MR + } + + echo "Using Docker image: ${env.DOCKER_IMAGE}" + } + } + } + + stage('Checkstyle Report') { + when { + changeRequest() + } + steps { + script { + // Checkstyle with Gradle + sh './gradlew checkstyleMain' + archiveArtifacts artifacts: 'build/reports/checkstyle/*.xml', allowEmptyArchive: true + } + } + } + + stage('Test') { + when { + changeRequest() + } + steps { + // Test using Gradle + sh './gradlew clean test' + } + } + + stage('Build Without Tests') { + when { + changeRequest() + } + steps { + script { + // Build without tests using Gradle + sh './gradlew build -x test' + } + } + } + + stage('Build Docker Image') { + steps { + script { + // Build the Docker image + sh "docker build -t ${DOCKER_REGISTRY}/${DOCKER_IMAGE}:${GIT_COMMIT} ." + } + } + } + + stage('Push Docker Image for Change Request') { + when { + changeRequest() + } + steps { + script { + withCredentials([usernamePassword(credentialsId: "docker-login", usernameVariable: "DOCKER_USER", passwordVariable: "DOCKER_PASSWORD")]) { + sh "docker login -u ${DOCKER_USER} -p ${DOCKER_PASSWORD}" + sh "docker push ${DOCKER_REGISTRY}/${DOCKER_IMAGE}:${GIT_COMMIT}" + } + } + } + } + + stage('Push Docker Image for Main') { + when { + branch "main" + } + steps { + script { + withCredentials([usernamePassword(credentialsId: "docker-login", usernameVariable: "DOCKER_USER", passwordVariable: "DOCKER_PASSWORD")]) { + sh "docker login -u ${DOCKER_USER} -p ${DOCKER_PASSWORD}" + sh "docker push ${DOCKER_REGISTRY}/${DOCKER_IMAGE}:${GIT_COMMIT}" + } + } + } + } + } + + post { + success { + echo 'Docker image built and pushed successfully.' + } + failure { + echo 'Pipeline failed!' + } + } +} + diff --git a/build.gradle b/build.gradle index b52d8e185..72f6066d0 100644 --- a/build.gradle +++ b/build.gradle @@ -6,8 +6,12 @@ plugins { id 'org.cyclonedx.bom' version '1.10.0' id 'io.spring.javaformat' version '0.0.43' id "io.spring.nohttp" version "0.0.11" + id("pl.allegro.tech.build.axion-release") version "1.18.7" + id('maven-publish') } +version = scmVersion.version + apply plugin: 'java' apply plugin: 'checkstyle' apply plugin: 'io.spring.javaformat' @@ -17,14 +21,38 @@ gradle.startParameter.excludedTaskNames += [ "checkFormatAot", "checkFormatAotTe group = 'org.springframework.samples' version = '3.4.0' +def nexusUsername = project.hasProperty('nexusUsername') ? project.nexusUsername : System.getenv('NEXUS_USERNAME') +def nexusPassword = project.hasProperty('nexusPassword') ? project.nexusPassword : System.getenv('NEXUS_PASSWORD') java { sourceCompatibility = JavaVersion.VERSION_17 } repositories { - mavenCentral() + mavenCentral() } +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + artifact file("build/libs/spring-application-${version}.jar") + } + } + + repositories { + maven { + name = "maven-releases" + url 'http://localhost:8081/repository/maven-releases/' + allowInsecureProtocol = true + credentials { + username nexusUsername + password nexusPassword + } + } + } +} + + ext.checkstyleVersion = "10.20.1" ext.springJavaformatCheckstyleVersion = "0.0.43" ext.webjarsLocatorLiteVersion = "1.0.1" @@ -58,6 +86,7 @@ dependencies { testImplementation 'org.testcontainers:mysql' checkstyle "io.spring.javaformat:spring-javaformat-checkstyle:${springJavaformatCheckstyleVersion}" checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" + testImplementation 'junit:junit:4.13' } tasks.named('test') { @@ -80,6 +109,39 @@ tasks.named("formatMain").configure { dependsOn("checkstyleNohttp") } tasks.named("formatTest").configure { dependsOn("checkstyleTest") } tasks.named("formatTest").configure { dependsOn("checkstyleNohttp") } + +task openTestResults(dependsOn: ['build', 'test']) { + doLast { + def testResultsFile = file('build/reports/tests/test/index.html') + + if (testResultsFile.exists()) { + println "Opening test results in the browser..." + // Open the test results file in the default browser (cross-platform) + if (System.getProperty('os.name').toLowerCase().contains('win')) { + // On Windows, use "start" + "cmd /c start ${testResultsFile}".execute() + } else if (System.getProperty('os.name').toLowerCase().contains('mac')) { + // On macOS, use "open" + "open ${testResultsFile}".execute() + } else { + // On Linux, use "xdg-open" + "xdg-open ${testResultsFile}".execute() + } + } else { + throw new GradleException("Test results file not found: ${testResultsFile}") + } + } +} + +task testProxy { + doLast { + def url = new URL('http://localhost:8081/repository/maven-central') + def connection = url.openConnection() + connection.connect() + println "Proxy connection successful to Maven Central!" + } +} + checkstyleAot.enabled = false checkstyleAotTest.enabled = false diff --git a/config.yaml b/config.yaml new file mode 100644 index 000000000..efa360551 --- /dev/null +++ b/config.yaml @@ -0,0 +1,11 @@ +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +nodes: +- role: control-plane + extraPortMappings: + - containerPort: 30950 + hostPort: 30950 + listenAddress: "127.0.0.1" + protocol: TCP + + diff --git a/deployment.yaml b/deployment.yaml new file mode 100644 index 000000000..f9c23ef7b --- /dev/null +++ b/deployment.yaml @@ -0,0 +1,22 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: spring-boot-app + namespace: petclinic +spec: + replicas: 2 + selector: + matchLabels: + app: spring-boot-app + template: + metadata: + labels: + app: spring-boot-app + spec: + containers: + - name: spring-boot-app + image: docker.io/mmarcetic/main:2.0 + imagePullPolicy: Always + ports: + - containerPort: 8080 + diff --git a/docker-compose.yml b/docker-compose.yml index 47579bbaf..84237659a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,21 +1,19 @@ services: - mysql: - image: mysql:9.1 + app: + image: spring-app:latest ports: - - "3306:3306" - environment: - - MYSQL_ROOT_PASSWORD= - - MYSQL_ALLOW_EMPTY_PASSWORD=true - - MYSQL_USER=petclinic - - MYSQL_PASSWORD=petclinic - - MYSQL_DATABASE=petclinic - volumes: - - "./conf.d:/etc/mysql/conf.d:ro" + - "8080:8080" + postgres: image: postgres:17.0 ports: - "5432:5432" environment: - - POSTGRES_PASSWORD=petclinic - - POSTGRES_USER=petclinic - - POSTGRES_DB=petclinic + POSTGRES_PASSWORD: petclinic + POSTGRES_USER: petclinic + POSTGRES_DB: petclinic + volumes: + - pgdata:/var/lib/postgresql/data + +volumes: + pgdata: diff --git a/service.yaml b/service.yaml new file mode 100644 index 000000000..e0547ec6a --- /dev/null +++ b/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: spring-boot-app-service + namespace: petclinic +spec: + selector: + app: spring-boot-app + ports: + - protocol: TCP + port: 80 + targetPort: 8080 + nodePort: 30950 + type: NodePort + diff --git a/settings.gradle b/settings.gradle index e60ee14fa..c9cf4f1fc 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'spring-petclinic' +rootProject.name = 'spring-application'