Skip to content

Commit

Permalink
Merge pull request #88 from elimuinformatics/OT-1140
Browse files Browse the repository at this point in the history
Added jenkinsfile for hapi-fhir-jpaserver
  • Loading branch information
ddjain authored Apr 3, 2024
2 parents d59163f + 59de948 commit 09d2ca7
Show file tree
Hide file tree
Showing 5 changed files with 360 additions and 0 deletions.
65 changes: 65 additions & 0 deletions Jenkinsfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
pipeline {
agent { label 'ecs' }
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
}
environment {
AWS_CREDENTIALS = credentials('AWS-KEYS')
AWS_ENV = """${sh(
returnStdout: true,
script: 'env | awk -F= \'/^AWS/ {print "-e " $1}\''
)}"""
GIT_ENV = """${sh(
returnStdout: true,
script: 'env | awk -F= \'/^GIT/ {print "-e " $1}\''
)}"""
}
stages {
stage('Setup') {
steps {
sh '''
docker pull $CICD_ECR_REGISTRY/cicd:latest
docker tag $CICD_ECR_REGISTRY/cicd:latest cicd:latest
'''
}
}
stage('Build') {
steps {
echo 'Building Docker Image'
sh 'docker build -t hapi-fhir-jpaserver-starter .'
}
}
stage('Push') {
steps {
echo 'Pushing Docker Image'
sh 'docker run -v /var/run/docker.sock:/var/run/docker.sock $AWS_ENV $GIT_ENV cicd push hapi-fhir-jpaserver-starter'
}
}
stage('Deploy') {
steps {
echo 'Deploying to QA'
sh 'docker run -v /var/run/docker.sock:/var/run/docker.sock $AWS_ENV $GIT_ENV cicd deploy hapi-fhir-jpaserver-r4 qa $GIT_COMMIT'
}
}
stage('Wait') {
steps {
echo 'Waiting for QA service to reach steady state'
sh 'docker run -v /var/run/docker.sock:/var/run/docker.sock $AWS_ENV $GIT_ENV cicd wait hapi-fhir-jpaserver-r4 qa'
}
}
stage('Healthcheck') {
steps {
echo 'Checking health of QA service'
sh 'curl -m 10 https://fhir4-qa.elimuinformatics.com/actuator/health'
}
}
}
post {
unsuccessful {
slackSend color: 'danger', channel: '#product-ops-qa', message: "Pipeline Failed: ${env.JOB_NAME} ${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)"
}
fixed {
slackSend color: 'good', channel: '#product-ops-qa', message: "Pipeline Ran Successfully: ${env.JOB_NAME} ${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)"
}
}
}
53 changes: 53 additions & 0 deletions Jenkinsfile-internal
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
pipeline {
agent { label 'ecs' }
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
}
environment {
AWS_CREDENTIALS = credentials('AWS-KEYS')
AWS_ENV = """${sh(
returnStdout: true,
script: 'env | awk -F= \'/^AWS/ {print "-e " $1}\''
)}"""
GIT_ENV = """${sh(
returnStdout: true,
script: 'env | awk -F= \'/^GIT/ {print "-e " $1}\''
)}"""
}
stages {
stage('Setup') {
steps {
sh '''
docker pull $CICD_ECR_REGISTRY/cicd:latest
docker tag $CICD_ECR_REGISTRY/cicd:latest cicd:latest
'''
}
}
stage('Promote') {
steps {
echo 'Promoting from QA to INTRNAL'
sh 'docker run -v /var/run/docker.sock:/var/run/docker.sock $AWS_ENV $GIT_ENV cicd promote hapi-fhir-jpaserver-r4 qa'
}
}
stage('Wait') {
steps {
echo 'Waiting for INTERNAL service to reach steady state'
sh 'docker run -v /var/run/docker.sock:/var/run/docker.sock $AWS_ENV cicd wait hapi-fhir-jpaserver-r4 internal'
}
}
stage('Healthcheck') {
steps {
echo 'Checking health of INTERNAL service'
sh 'curl -m 10 https://fhir4-internal.elimuinformatics.com/actuator/health'
}
}
}
post {
unsuccessful {
slackSend color: 'danger', channel: '#product-ops-stage', message: "Pipeline Failed: ${env.JOB_NAME} ${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)"
}
fixed {
slackSend color: 'good', channel: '#product-ops-stage', message: "Pipeline Ran Successfully: ${env.JOB_NAME} ${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)"
}
}
}
53 changes: 53 additions & 0 deletions Jenkinsfile-prod
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
pipeline {
agent { label 'ecs' }
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
}
environment {
AWS_CREDENTIALS = credentials('AWS-KEYS')
AWS_ENV = """${sh(
returnStdout: true,
script: 'env | awk -F= \'/^AWS/ {print "-e " $1}\''
)}"""
GIT_ENV = """${sh(
returnStdout: true,
script: 'env | awk -F= \'/^GIT/ {print "-e " $1}\''
)}"""
}
stages {
stage('Setup') {
steps {
sh '''
docker pull $CICD_ECR_REGISTRY/cicd:latest
docker tag $CICD_ECR_REGISTRY/cicd:latest cicd:latest
'''
}
}
stage('Promote') {
steps {
echo 'Promoting from INTERNAL to PROD'
sh 'docker run -v /var/run/docker.sock:/var/run/docker.sock $AWS_ENV $GIT_ENV cicd promote hapi-fhir-jpaserver-r4 internal'
}
}
stage('Wait') {
steps {
echo 'Waiting for PROD service to reach steady state'
sh 'docker run -v /var/run/docker.sock:/var/run/docker.sock $AWS_ENV cicd wait hapi-fhir-jpaserver-r4 prod'
}
}
stage('Healthcheck') {
steps {
echo 'Checking health of PROD service'
sh 'curl -m 10 https://fhir4.elimuinformatics.com/actuator/health'
}
}
}
post {
unsuccessful {
slackSend color: 'danger', channel: '#product-ops-prod', message: "Pipeline Failed: ${env.JOB_NAME} ${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)"
}
fixed {
slackSend color: 'good', channel: '#product-ops-prod', message: "Pipeline Ran Successfully: ${env.JOB_NAME} ${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)"
}
}
}
15 changes: 15 additions & 0 deletions src/main/resources/logback.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,21 @@
<version>[ignore]</version>
<levelValue>[ignore]</levelValue>
</fieldNames>
<jsonGeneratorDecorator
class="net.logstash.logback.mask.MaskingJsonGeneratorDecorator">
<valueMask>
<value>(?i)(patient[\/=:|-])(\"[^\"]*\"|[^\"\s]+)(.*?)(\"|)</value>
<value>(?i)(task[\/=:|-])(\"[^\"]*\"|[^\"\s]+)(.*?)(\"|)</value>
<value>(?i)(encounter[\/=:|-])(\"[^\"]*\"|[^\"\s]+)(.*?)(\"|)</value>
<value>(?i)(refresh_token[\/=:|-])(\"[^\"]*\"|[^\"\s]+)(.*?)(\"|)</value>
<value>(?i)(id_token[\/=:|-])(\"[^\"]*\"|[^\"\s]+)(.*?)(\"|)</value>
<value>(?i)(access_token[\/=:|-])(\"[^\"]*\"|[^\"\s]+)(.*?)(\"|)</value>
<value>(?i)(authorization[\/=:|-])(\"[^\"]*\"|[^\"\s]+)(.*?)(\"|)</value>
<value>(?i)(MedicationRequest[\/=:|-])(\"[^\"]*\"|[^\"\s]+)(.*?)(\"|)</value>
<value>(?i)(observation[\/=:|-])(\"[^\"]*\"|[^\"\s]+)(.*?)(\"|)</value>
<mask>$1****$3$4</mask>
</valueMask>
</jsonGeneratorDecorator>
</encoder>
</appender>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package ca.uhn.fhir.jpa.starter.interceptor;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;

@ExtendWith(OutputCaptureExtension.class)
public class LogScrubbingTest {

private static final Logger logger = LoggerFactory.getLogger(LogScrubbingTest.class);
ObjectMapper mapper = new ObjectMapper();
private static final String MESSAGE = "Hello, Testing Logs here..!!";
private static final String MASKMESSAGE_PATIENT = "patient=12345 PATIENT:12345 Patient-12345 Patient/12345 ";
private static final String MASKMESSAGE_TASK = "task=12345 TASK:12345 Task-12345 Task/12345 ";
private static final String MASKMESSAGE_ACCESS_TOKEN = "access_token=\"eyJ0eXAiOiJKV1QiLCJhbGciOiJ\" access_token:\"eyJ0eXAiOiJKV1QiLCJhbGciOiJ\" access_token-\"eyJ0eXAiOiJKV1QiLCJhbGciOiJ\" ";
private static final String MASKMESSAGE_PATIENT_SUBSTRING = "this is a patient=12345 and patient-234 qwerasdf ...) ";
private static final String MASKMESSAGE_ENCOUNTER = "encounter=12345 ENCOUNTER:12345 Encounter-12345 Encounter/12345 " ;
private static final String MASKMESSAGE_MEDICATIONREQUEST = "medicationrequest=12345 MEDICATIONREQUEST:12345 medicationRequest-12345 Medicationrequest/12345 ";
private static final String MASKMESSAGE_OBSERVATION = "observation=12345 OBSERVATION:12345 Observation-12345 Observation/12345 ";
private static final String MASKMESSAGE_ID_TOKEN = "id_token=12345 and id_token:12345 and id_token-12345 ";
private static final String MASKMESSAGE_REFRESH_TOKEN = "refresh_token=12345 and refresh_token:12345 and refresh_token-12345 ";
private static final String MASKMESSAGE_AUTHORIZATION = "authorization=123 authorization:1234 authorization-1234 ";
private static final String MASKMESSAGE_ALL_STRING = "patient=12345 task=12345 encounter=12345 medicationrequest=12345 observation=12345 id_token=eyJ0eXBkJo refresh_token=eyJ0eXAjdk authorization=eyJ0eXAiOi ";
private List<String> loggerAttributes = new ArrayList<>(List.of("level", "message", "thread_name", "logger_name"));
private boolean isValidJson = false;

@Test
void testLogMessage(CapturedOutput output) throws JsonMappingException, JsonProcessingException, ParseException {
logMessage(MESSAGE);
JsonNode jsonNode = mapper.readTree(output.getAll());
verifyBasics(jsonNode);
}

@Test
void logsShouldinJsonFormat(CapturedOutput output) throws Exception {
logMessage(MESSAGE);
JsonNode jsonNode = null;
try {
jsonNode = mapper.readTree(output.getAll());
isValidJson = true;
} catch (Exception e) {
isValidJson = false;
}
Assertions.assertTrue(isValidJson);
verifyBasics(jsonNode);
}

@Test
void logsShouldHaveAttributes(CapturedOutput output) throws JsonMappingException, JsonProcessingException {
logMessage(MESSAGE);
JsonNode jsonNode = mapper.readTree(output.getAll());
jsonNode.fieldNames().forEachRemaining(n -> {
Assertions.assertTrue(loggerAttributes.contains(n));
});
}

@Test
void checkIfLoggingIsMaskedForPatient(CapturedOutput output) throws JsonMappingException, JsonProcessingException {
logMessage(MASKMESSAGE_PATIENT);
JsonNode jsonNode = mapper.readTree(output.getAll());
String message = jsonNode.get("message").asText();
Assertions.assertEquals("patient=**** PATIENT:**** Patient-**** Patient/**** ", message);
}

@Test
void checkIfLoggingIsMaskedForTask(CapturedOutput output) throws JsonMappingException, JsonProcessingException {
logMessage(MASKMESSAGE_TASK);
JsonNode jsonNode = mapper.readTree(output.getAll());
String message = jsonNode.get("message").asText();
Assertions.assertEquals("task=**** TASK:**** Task-**** Task/**** ", message);
}

@Test
void checkIfLoggingIsMaskedForAccessToken(CapturedOutput output)
throws JsonMappingException, JsonProcessingException {
logMessage(MASKMESSAGE_ACCESS_TOKEN);
JsonNode jsonNode = mapper.readTree(output.getAll());
String message = jsonNode.get("message").asText();
Assertions.assertEquals("access_token=**** access_token:**** access_token-**** ", message);
}

@Test
void checkIfLoggingIsMaskedForPatientInString(CapturedOutput output)
throws JsonMappingException, JsonProcessingException {
logMessage(MASKMESSAGE_PATIENT_SUBSTRING);
JsonNode jsonNode = mapper.readTree(output.getAll());
String message = jsonNode.get("message").asText();
Assertions.assertEquals("this is a patient=**** and patient-**** qwerasdf ...) ", message);
}

@Test
void checkIfLoggingIsMaskedForEncounter(CapturedOutput output)
throws JsonMappingException, JsonProcessingException {
logMessage(MASKMESSAGE_ENCOUNTER);
JsonNode jsonNode = mapper.readTree(output.getAll());
String message = jsonNode.get("message").asText();
Assertions.assertEquals("encounter=**** ENCOUNTER:**** Encounter-**** Encounter/**** ", message);
}

@Test
void checkIfLoggingIsMaskedForMedicationRequest(CapturedOutput output) throws JsonMappingException, JsonProcessingException {
logMessage(MASKMESSAGE_MEDICATIONREQUEST);
JsonNode jsonNode = mapper.readTree(output.getAll());
String message = jsonNode.get("message").asText();
Assertions.assertEquals("medicationrequest=**** MEDICATIONREQUEST:**** medicationRequest-**** Medicationrequest/**** ", message);
}

@Test
void checkIfLoggingIsMaskedForObservation(CapturedOutput output) throws JsonMappingException, JsonProcessingException {
logMessage(MASKMESSAGE_OBSERVATION);
JsonNode jsonNode = mapper.readTree(output.getAll());
String message = jsonNode.get("message").asText();
Assertions.assertEquals("observation=**** OBSERVATION:**** Observation-**** Observation/**** ", message);
}
@Test
void checkIfLoggingIsMaskedForIdToken(CapturedOutput output) throws JsonMappingException, JsonProcessingException {
logMessage(MASKMESSAGE_ID_TOKEN);
JsonNode jsonNode = mapper.readTree(output.getAll());
String message = jsonNode.get("message").asText();
Assertions.assertEquals("id_token=**** and id_token:**** and id_token-**** ", message);
}

@Test
void checkIfLoggingIsMaskedForRefreshToken(CapturedOutput output)
throws JsonMappingException, JsonProcessingException {
logMessage(MASKMESSAGE_REFRESH_TOKEN);
JsonNode jsonNode = mapper.readTree(output.getAll());
String message = jsonNode.get("message").asText();
Assertions.assertEquals("refresh_token=**** and refresh_token:**** and refresh_token-**** ", message);
}

@Test
void checkIfLoggingIsMaskedForAuthorization(CapturedOutput output)
throws JsonMappingException, JsonProcessingException {
logMessage(MASKMESSAGE_AUTHORIZATION);
JsonNode jsonNode = mapper.readTree(output.getAll());
String message = jsonNode.get("message").asText();
Assertions.assertEquals("authorization=**** authorization:**** authorization-**** ", message);
}

@Test
void checkIfLoggingIsMaskedForAllInString(CapturedOutput output)
throws JsonMappingException, JsonProcessingException {
logMessage(MASKMESSAGE_ALL_STRING);
JsonNode jsonNode = mapper.readTree(output.getAll());
String message = jsonNode.get("message").asText();
Assertions.assertEquals(
"patient=**** task=**** encounter=**** medicationrequest=**** observation=**** id_token=**** refresh_token=**** authorization=**** ",
message);
}

void logMessage(String message) {
logger.info(message);
}

void verifyBasics(JsonNode node) throws ParseException {
Assertions.assertEquals("ca.uhn.fhir.jpa.starter.interceptor.LogScrubbingTest", node.get("logger_name").textValue());
Assertions.assertEquals("main", node.get("thread_name").textValue());
Assertions.assertEquals(MESSAGE, node.get("message").textValue());
Assertions.assertEquals("INFO", node.get("level").textValue());
}
}

0 comments on commit 09d2ca7

Please sign in to comment.