diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000000000000000000000000000000000000..532c8a8369ef7d24b18c230f75fa38ed9b72c96a --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,189 @@ + @Library('common-shared') _ + + pipeline { + agent { + kubernetes { + label 'buildpack-agent' + yaml ''' + apiVersion: v1 + kind: Pod + spec: + containers: + - name: buildpack + image: buildpack-deps:stable + command: + - cat + tty: true + resources: + limits: + memory: "2Gi" + cpu: "1" + requests: + memory: "2Gi" + cpu: "1" + volumeMounts: + - name: tmp + mountPath: /tmp + - name: jnlp + resources: + limits: + memory: "2Gi" + cpu: "1" + requests: + memory: "2Gi" + cpu: "1" + volumeMounts: + - name: mvnw + mountPath: /home/jenkins/.m2/wrapper + readOnly: false + - name: m2-repo + mountPath: /home/jenkins/.m2/repository + - name: settings-xml + mountPath: /home/jenkins/.m2/settings.xml + subPath: settings.xml + readOnly: true + - name: tmp + mountPath: /tmp + volumes: + - name: mvnw + emptyDir: {} + - name: m2-repo + emptyDir: {} + - name: tmp + emptyDir: {} + - name: settings-xml + secret: + secretName: m2-secret-dir + items: + - key: settings.xml + path: settings.xml + ''' + } + } + + environment { + APP_NAME = 'git-eca-rest-api' + NAMESPACE = 'foundation-internal-webdev-apps' + IMAGE_NAME = 'eclipsefdn/git-eca-rest-api' + CONTAINER_NAME = 'app' + ENVIRONMENT = sh( + script: """ + if [ "${env.BRANCH_NAME}" = "master" ]; then + printf "production" + else + printf "${env.BRANCH_NAME}" + fi + """, + returnStdout: true + ) + TAG_NAME = sh( + script: """ + GIT_COMMIT_SHORT=\$(git rev-parse --short ${env.GIT_COMMIT}) + if [ "${env.ENVIRONMENT}" = "" ]; then + printf \${GIT_COMMIT_SHORT}-${env.BUILD_NUMBER} + else + printf ${env.ENVIRONMENT}-\${GIT_COMMIT_SHORT}-${env.BUILD_NUMBER} + fi + """, + returnStdout: true + ) + } + + options { + buildDiscarder(logRotator(numToKeepStr: '10')) + } + + stages { + stage('Build Java code') { + steps { + sh './mvnw -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn --batch-mode package' + stash includes: 'target/', name: 'target' + } + } + + stage('Build docker image') { + agent { + label 'docker-build' + } + steps { + unstash 'target' + + sh 'docker build -f src/main/docker/Dockerfile.jvm --no-cache -t ${IMAGE_NAME}:${TAG_NAME} -t ${IMAGE_NAME}:latest .' + } + } + + stage('Push docker image') { + agent { + label 'docker-build' + } + when { + anyOf { + environment name: 'ENVIRONMENT', value: 'production' + environment name: 'ENVIRONMENT', value: 'staging' + } + } + steps { + withDockerRegistry([credentialsId: '04264967-fea0-40c2-bf60-09af5aeba60f', url: 'https://index.docker.io/v1/']) { + sh ''' + docker push ${IMAGE_NAME}:${TAG_NAME} + docker push ${IMAGE_NAME}:latest + ''' + } + } + } + + stage('Deploy to cluster') { + agent { + kubernetes { + label 'kubedeploy-agent' + yaml ''' + apiVersion: v1 + kind: Pod + spec: + containers: + - name: kubectl + image: eclipsefdn/kubectl:1.9-alpine + command: + - cat + tty: true + ''' + } + } + + when { + anyOf { + environment name: 'ENVIRONMENT', value: 'production' + environment name: 'ENVIRONMENT', value: 'staging' + } + } + steps { + container('kubectl') { + withKubeConfig([credentialsId: '1d8095ea-7e9d-4e94-b799-6dadddfdd18a', serverUrl: 'https://console-int.c1-ci.eclipse.org']) { + sh ''' + DEPLOYMENT="$(k8s getFirst deployment "${NAMESPACE}" "app=${APP_NAME},environment=${ENVIRONMENT}")" + if [[ $(echo "${DEPLOYMENT}" | jq -r 'length') -eq 0 ]]; then + echo "ERROR: Unable to find a deployment to patch matching 'app=${APP_NAME},environment=${ENVIRONMENT}' in namespace ${NAMESPACE}" + exit 1 + else + DEPLOYMENT_NAME="$(echo "${DEPLOYMENT}" | jq -r '.metadata.name')" + kubectl set image "deployment.v1.apps/${DEPLOYMENT_NAME}" -n "${NAMESPACE}" "${CONTAINER_NAME}=${IMAGE_NAME}:${TAG_NAME}" --record=true + if ! kubectl rollout status "deployment.v1.apps/${DEPLOYMENT_NAME}" -n "${NAMESPACE}"; then + # will fail if rollout does not succeed in less than .spec.progressDeadlineSeconds + kubectl rollout undo "deployment.v1.apps/${DEPLOYMENT_NAME}" -n "${NAMESPACE}" + exit 1 + fi + fi + ''' + } + } + } + } + } + + post { + always { + deleteDir() /* clean up workspace */ + sendNotifications currentBuild + } + } + } \ No newline at end of file diff --git a/src/main/k8s/production.yml b/src/main/k8s/production.yml index 36afe39ab02c07892aea597fe9a5fa3d101467d6..df8b68e1ee2aede4d12f42bc3eb8a924170a30ff 100644 --- a/src/main/k8s/production.yml +++ b/src/main/k8s/production.yml @@ -28,6 +28,15 @@ spec: requests: cpu: 200m memory: 512Mi + args: ["-Dconfig.secret.path=/run/secrets/git-eca-rest-api/secret.properties"] + volumeMounts: + - name: api-oauth-token + mountPath: "/run/secrets/git-eca-rest-api" + readOnly: true + volumes: + - name: api-oauth-token + secret: + secretName: git-eca-rest-api --- apiVersion: "v1" kind: "Service" diff --git a/src/main/k8s/staging.yml b/src/main/k8s/staging.yml index 167f449bc20010282e5a3257e58eae557f6b332a..4d8f36dd73d140b2e63221d4ac2cdc64c070a627 100644 --- a/src/main/k8s/staging.yml +++ b/src/main/k8s/staging.yml @@ -28,6 +28,15 @@ spec: requests: cpu: 100m memory: 512Mi + args: ["-Dconfig.secret.path=/run/secrets/git-eca-rest-api/secret.properties"] + volumeMounts: + - name: api-oauth-token + mountPath: "/run/secrets/git-eca-rest-api" + readOnly: true + volumes: + - name: api-oauth-token + secret: + secretName: git-eca-rest-api --- apiVersion: "v1" kind: "Service" diff --git a/src/main/rb/eca.rb b/src/main/rb/eca.rb index 0a5eab535c5d5b4b7aa12b32a8d28c4906bff08f..3480c0c5819d3284fd1c11493f9fe57697512243 100644 --- a/src/main/rb/eca.rb +++ b/src/main/rb/eca.rb @@ -79,8 +79,5 @@ commit_keys.each do |key| end ## If error, exit as status 1 if (response.code == 403) then - #exit 1 + exit 1 end - -## Hardcode exit for testing -exit 1 diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 964f9987ea3deb764e18f9bb6e2638b35278deb9..d1068d8d487493e6590b5f659905b9aefcdedde0 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -4,4 +4,8 @@ org.eclipsefoundation.git.eca.api.BotsAPI/mp-rest/url=https://api.eclipse.org ## OAUTH CONFIG oauth2.scope=eclipsefdn_view_all_profiles -quarkus.http.port=8080 \ No newline at end of file +quarkus.http.port=8080 + +## required to start when secret.properties isn't found/mounted +oauth2.client-id=placeholder +oauth2.client-secret=placeholder \ No newline at end of file diff --git a/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java b/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java index cb335a00633f60b6e92580517bf818c38d8f6864..fbfcb38c3d1e550938952fab016a5da217125599 100644 --- a/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java +++ b/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java @@ -65,7 +65,7 @@ public class ValidationResourceTest { given() .body(vr) .contentType(ContentType.JSON) - .when().post("/git/eca") + .when().post("/eca") .then() .statusCode(200) .body("passed", is(true), @@ -113,7 +113,7 @@ public class ValidationResourceTest { given() .body(vr) .contentType(ContentType.JSON) - .when().post("/git/eca") + .when().post("/eca") .then() .statusCode(200) .body("passed", is(true), @@ -147,7 +147,7 @@ public class ValidationResourceTest { given() .body(vr) .contentType(ContentType.JSON) - .when().post("/git/eca") + .when().post("/eca") .then() .statusCode(200) .body("passed", is(true), @@ -182,7 +182,7 @@ public class ValidationResourceTest { given() .body(vr) .contentType(ContentType.JSON) - .when().post("/git/eca") + .when().post("/eca") .then() .statusCode(200) .body("passed", is(true), @@ -217,7 +217,7 @@ public class ValidationResourceTest { given() .body(vr) .contentType(ContentType.JSON) - .when().post("/git/eca") + .when().post("/eca") .then() .statusCode(403) .body("passed", is(false), @@ -254,7 +254,7 @@ public class ValidationResourceTest { given() .body(vr) .contentType(ContentType.JSON) - .when().post("/git/eca") + .when().post("/eca") .then() .statusCode(403) .body("passed", is(false), @@ -296,7 +296,7 @@ public class ValidationResourceTest { given() .body(vr) .contentType(ContentType.JSON) - .when().post("/git/eca") + .when().post("/eca") .then() .statusCode(200) .body("passed", is(true), @@ -324,7 +324,7 @@ public class ValidationResourceTest { given() .body(vr) .contentType(ContentType.JSON) - .when().post("/git/eca") + .when().post("/eca") .then() .statusCode(403) .body("passed", is(false), @@ -366,7 +366,7 @@ public class ValidationResourceTest { given() .body(vr) .contentType(ContentType.JSON) - .when().post("/git/eca") + .when().post("/eca") .then() .statusCode(200) .body("passed", is(true), @@ -394,7 +394,7 @@ public class ValidationResourceTest { given() .body(vr) .contentType(ContentType.JSON) - .when().post("/git/eca") + .when().post("/eca") .then() .statusCode(403) .body("passed", is(false), @@ -429,7 +429,7 @@ public class ValidationResourceTest { given() .body(vr) .contentType(ContentType.JSON) - .when().post("/git/eca") + .when().post("/eca") .then() .statusCode(403) .body("passed", is(false), @@ -468,7 +468,7 @@ public class ValidationResourceTest { given() .body(vr) .contentType(ContentType.JSON) - .when().post("/git/eca") + .when().post("/eca") .then() .statusCode(403) .body("passed", is(false), @@ -507,7 +507,7 @@ public class ValidationResourceTest { given() .body(vr) .contentType(ContentType.JSON) - .when().post("/git/eca") + .when().post("/eca") .then() .statusCode(403) .body("passed", is(false), diff --git a/src/test/java/org/eclipsefoundation/git/eca/service/impl/MockOAuthService.java b/src/test/java/org/eclipsefoundation/git/eca/service/impl/MockOAuthService.java new file mode 100644 index 0000000000000000000000000000000000000000..9d86b7c447c5d4bf9c7a43812b960d50b448318a --- /dev/null +++ b/src/test/java/org/eclipsefoundation/git/eca/service/impl/MockOAuthService.java @@ -0,0 +1,24 @@ +package org.eclipsefoundation.git.eca.service.impl; + +import org.eclipsefoundation.git.eca.service.OAuthService; + +import io.quarkus.test.Mock; + +/** + * Disable the OAuth service while in testing via a mock service. This will + * never authenticate, but since all external data is mocked, this does not + * impact testing. + * + * @author Martin Lowe + * + */ +@Mock +public class MockOAuthService implements OAuthService { + + @Override + public String getToken() { + // return an empty (invalid) token every time + return ""; + } + +}