Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,23 @@ variables:
# Logged as: GitLab.com CI jobs failing if using docker:stable-dind image
# see: https://gitlab.com/gitlab-com/gl-infra/production/issues/982
DOCKER_TLS_CERTDIR: ""
GITLAB_ADVANCED_SAST_ENABLED: 'true'
DS_ENFORCE_NEW_ANALYZER: 'true'

include:
- template: Security/SAST.gitlab-ci.yml
- template: Jobs/Secret-Detection.gitlab-ci.yml
- template: Jobs/Dependency-Scanning.gitlab-ci.yml

sast:
stage: test



stages:
# - env
- build
- test
- dev-deploy
- prod-deploy

Expand Down Expand Up @@ -184,4 +195,43 @@ hx-prod:
fi

kubectl get pod,deployment,rs,svc,ing
fi
fi




trivy_container_scanning:
stage: test

services:
- name: $CI_REGISTRY/mouse-informatics/dind:latest
alias: docker

rules:
- if: '$CI_COMMIT_REF_NAME == "main"'
when: on_success
allow_failure: true

before_script:
- export TRIVY_VERSION=$(wget -qO - "https://api.github.com/repos/aquasecurity/trivy/releases/latest" | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
- echo $TRIVY_VERSION
- wget --no-verbose https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz -O - | tar -zxvf -
- echo "${CI_REGISTRY_PASSWORD}" | docker login -u "${CI_REGISTRY_USER}" --password-stdin ${CI_REGISTRY}

script:
# Build report
- ./trivy --cache-dir .trivycache/ image --exit-code 0 --no-progress --format template --template "@contrib/gitlab.tpl" -o gl-container-scanning-report.json "${CI_REGISTRY_IMAGE}":"${CI_COMMIT_SHA:0:12}"
# Print report
- ./trivy --cache-dir .trivycache/ image --exit-code 0 --no-progress --severity HIGH "${CI_REGISTRY_IMAGE}":"${CI_COMMIT_SHA:0:12}"
# Fail on critical vulnerability
- ./trivy --cache-dir .trivycache/ image --exit-code 1 --severity CRITICAL --no-progress "${CI_REGISTRY_IMAGE}":"${CI_COMMIT_SHA:0:12}"

- docker logout ${CI_REGISTRY}

cache:
paths:
- .trivycache/

artifacts:
reports:
container_scanning: gl-container-scanning-report.json
51 changes: 49 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.2</version>
<version>4.0.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.mousephenotype</groupId>
Expand All @@ -14,7 +14,8 @@
<name>impc-mousephenotype-api</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
<java.version>25</java.version>
<lombok.version>1.18.44</lombok.version>
</properties>
<dependencies>
<dependency>
Expand Down Expand Up @@ -64,6 +65,26 @@

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.1</version>
<configuration>
<release>${java.version}</release>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${project.parent.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
Expand All @@ -89,6 +110,32 @@
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.14</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>@{argLine} -javaagent:${settings.localRepository}/org/mockito/mockito-core/${mockito.version}/mockito-core-${mockito.version}.jar</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
@SpringBootApplication
public class ImpcMousephenotypeApiApplication {

public static void main(String[] args) {
static void main(String[] args) {
SpringApplication.run(ImpcMousephenotypeApiApplication.class, args);
}

Expand Down
2 changes: 0 additions & 2 deletions src/main/java/org/mousephenotype/api/models/GeneBundle.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.minidev.json.annotate.JsonIgnore;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

Expand Down
1 change: 0 additions & 1 deletion src/main/java/org/mousephenotype/api/models/GeneImage.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;
import java.util.List;

@Data
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
package org.mousephenotype.api.repositories;

import org.bson.types.ObjectId;
import org.mousephenotype.api.models.Gene;
import org.mousephenotype.api.models.GeneBundle;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.web.PageableDefault;
import org.springframework.data.web.SortDefault;

import java.util.ArrayList;
import java.util.List;

public interface GeneRepository extends PagingAndSortingRepository<Gene, String> {
Expand Down
4 changes: 2 additions & 2 deletions src/main/resources/application-docker.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
spring.data.mongodb.uri=mongodb://${IMPC_API_DB_USER}:${IMPC_API_DB_PASSWORD}@${IMPC_API_DB_HOST}/${IMPC_API_DB}?authSource=${IMPC_API_AUTH_DB}&replicaSet=${IMPC_API_REPLICA_SET}
spring.data.mongodb.database=${IMPC_API_DB}
spring.mongodb.uri=mongodb://${IMPC_API_DB_USER}:${IMPC_API_DB_PASSWORD}@${IMPC_API_DB_HOST}/${IMPC_API_DB}?authSource=${IMPC_API_AUTH_DB}&replicaSet=${IMPC_API_REPLICA_SET}
spring.mongodb.database=${IMPC_API_DB}
spring.data.mongodb.field-naming-strategy=org.springframework.data.mapping.model.SnakeCaseFieldNamingStrategy
server.compression.enabled=true
server.compression.min-response-size=2048
Expand Down
4 changes: 2 additions & 2 deletions src/main/resources/application-local.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
spring.data.mongodb.uri=${IMPC_MONGO_URI}
spring.data.mongodb.database=${IMPC_MONGO_DB}
spring.mongodb.uri=${IMPC_MONGO_URI}
spring.mongodb.database=${IMPC_MONGO_DB}
spring.data.mongodb.field-naming-strategy=org.springframework.data.mapping.model.SnakeCaseFieldNamingStrategy
logging.level.org.springframework.data.mongodb.core.MongoTemplate=DEBUG
server.compression.enabled=true
Expand Down
4 changes: 2 additions & 2 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
spring.data.mongodb.uri=mongodb://user:password@localhost:27017
spring.data.mongodb.database=impc
spring.mongodb.uri=mongodb://user:password@localhost:27017
spring.mongodb.database=impc
spring.data.mongodb.field-naming-strategy=org.springframework.data.mapping.model.SnakeCaseFieldNamingStrategy
logging.level.org.springframework.data.mongodb.core.MongoTemplate=DEBUG
server.compression.enabled=true
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,83 @@
package org.mousephenotype.api;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mousephenotype.api.repositories.GeneBundleRepository;
import org.mousephenotype.api.repositories.GeneRepository;
import org.mousephenotype.api.repositories.ObservationRepository;
import org.mousephenotype.api.repositories.StatisticalResultRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.hateoas.MediaTypes;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@SpringBootTest
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest(properties = {
"logging.level.org.mongodb.driver=OFF",
"spring.mongodb.uri=mongodb://localhost:27017/impc-test?serverSelectionTimeoutMS=10"
})
class ImpcMousephenotypeApiApplicationTests {

@Autowired
private ApplicationContext applicationContext;

@Autowired
private WebApplicationContext webApplicationContext;

private MockMvc mockMvc;

@BeforeEach
void setUpMockMvc() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}

@Test
void contextLoads() {
assertThat(applicationContext).isNotNull();
assertThat(applicationContext.getBean(GeneRepository.class)).isNotNull();
assertThat(applicationContext.getBean(GeneBundleRepository.class)).isNotNull();
assertThat(applicationContext.getBean(ObservationRepository.class)).isNotNull();
assertThat(applicationContext.getBean(StatisticalResultRepository.class)).isNotNull();
}

@Test
void repositoryRestRootExposesMongoRepositories() throws Exception {
mockMvc.perform(get("/").accept(MediaTypes.HAL_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links.genes.href").exists())
.andExpect(jsonPath("$._links.geneBundles.href").exists())
.andExpect(jsonPath("$._links.observations.href").exists())
.andExpect(jsonPath("$._links.statisticalResults.href").exists())
.andExpect(jsonPath("$._links.profile.href").exists());
}

@Test
void geneSearchResourceExposesCustomQueryMethods() throws Exception {
mockMvc.perform(get("/genes/search").accept(MediaTypes.HAL_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links.significantPhenotypesByGene.href").exists())
.andExpect(jsonPath("$._links.getAll.href").exists())
.andExpect(jsonPath("$._links.findAllBySignificantMpTermIdsContains.href").exists())
.andExpect(jsonPath("$._links.findAllBySignificantTopLevelMpTermIdsContains.href").exists())
.andExpect(jsonPath("$._links.findAllByTestedParameter.href").exists())
.andExpect(jsonPath("$._links.findAllByTestedProcedure.href").exists())
.andExpect(jsonPath("$._links.findAllByTestedParameterId.href").exists())
.andExpect(jsonPath("$._links.findAllByTestedProcedureId.href").exists())
.andExpect(jsonPath("$._links.findAllByTestedPipelineId.href").exists());
}

@Test
void profileEndpointExposesGeneMetadata() throws Exception {
mockMvc.perform(get("/profile/genes").accept(MediaTypes.ALPS_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.alps.descriptor").isArray());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package org.mousephenotype.api.configuration;

import org.junit.jupiter.api.Test;
import org.mousephenotype.api.models.Gene;
import org.mousephenotype.api.models.GeneBundle;
import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks;
import org.springframework.data.rest.webmvc.support.PagingAndSortingTemplateVariables;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.Link;
import org.springframework.objenesis.SpringObjenesis;
import org.springframework.test.util.ReflectionTestUtils;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNullPointerException;

class GeneConfigurationTest {

private final RecordingRepositoryEntityLinks entityLinks = new SpringObjenesis()
.newInstance(RecordingRepositoryEntityLinks.class);
private final GeneConfiguration configuration = new GeneConfiguration();

@Test
void geneProcessorAddsGeneBundleLinkUsingGeneId() {
ReflectionTestUtils.setField(configuration, "entityLinks", entityLinks);
Gene gene = Gene.builder()
.id("MGI:12345")
.mgiAccessionId("MGI:12345")
.build();
Link bundleLink = Link.of("https://example.org/geneBundles/MGI:12345", "geneBundle");
entityLinks.linkToReturn = bundleLink;

EntityModel<Gene> model = EntityModel.of(gene);
EntityModel<Gene> processed = configuration.geneProcessor().process(model);

assertThat(processed).isSameAs(model);
assertThat(processed.getLinks()).contains(bundleLink);
assertThat(entityLinks.domainType).isEqualTo(GeneBundle.class);
assertThat(entityLinks.id).isEqualTo("MGI:12345");
assertThat(entityLinks.calls).isOne();
}

@Test
void geneProcessorRejectsEntityModelWithoutContent() {
ReflectionTestUtils.setField(configuration, "entityLinks", entityLinks);
EntityModel<Gene> model = new EmptyGeneEntityModel();

assertThatNullPointerException()
.isThrownBy(() -> configuration.geneProcessor().process(model));
assertThat(entityLinks.calls).isZero();
}

private static class EmptyGeneEntityModel extends EntityModel<Gene> {
}

private static class RecordingRepositoryEntityLinks extends RepositoryEntityLinks {
private Class<?> domainType;
private Object id;
private Link linkToReturn;
private int calls;

private RecordingRepositoryEntityLinks() {
super(null, null, null, (PagingAndSortingTemplateVariables) null, null);
}

@Override
public Link linkToItemResource(Class<?> type, Object id) {
this.domainType = type;
this.id = id;
this.calls++;
return linkToReturn;
}
}
}
Loading