From ab1e66701d13c4e13118e03d93ad811dd12bd5c3 Mon Sep 17 00:00:00 2001 From: gcornut <guillaume.cornut@inra.fr> Date: Fri, 8 Feb 2019 17:11:57 +0100 Subject: [PATCH 1/6] chore: Configure continuous delivery in `.gitlab-ci.yml`. Change default context path and port. --- .gitlab-ci.yml | 141 ++++++++++++++++++++- README.md | 22 +++- backend/src/main/resources/application.yml | 4 +- frontend/angular.json | 2 +- frontend/proxy.conf.js | 6 +- 5 files changed, 159 insertions(+), 16 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f7ac1e16..b7fb7910 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,3 +1,10 @@ +--- +stages: + - test + - build + - staging + - production + image: urgi/docker-browsers:latest # Disable the Gradle daemon for Continuous Integration servers as correctness @@ -7,14 +14,16 @@ image: urgi/docker-browsers:latest variables: GRADLE_OPTS: "-Dorg.gradle.daemon=false" GRADLE_USER_HOME: $CI_PROJECT_DIR/.gradle + APP_NAME: gpds + JAR_PATH: "backend/build/libs/${APP_NAME}.jar" # Gradle cache for all jobs cache: key: "$CI_COMMIT_REF_NAME" paths: - - ".gradle" - - "frontend/.gradle/" - - "frontend/node_modules/" + - ".gradle" + - "frontend/.gradle/" + - "frontend/node_modules/" # TESTS @@ -28,7 +37,7 @@ test-frontend: artifacts: reports: junit: - - "./frontend/karma-junit-tests-report/TEST*.xml" + - "./frontend/karma-junit-tests-report/TEST*.xml" test-backend: stage: test @@ -52,3 +61,127 @@ test-backend: junit: - "./backend/build/test-results/test/TEST-*.xml" +# BUILD + +build: + stage: build + script: + - ./gradlew assemble + artifacts: + paths: + - "$JAR_PATH" + expire_in: 1 week + only: + changes: + - backend/src/**/* + - frontend/**/* + +# DEPLOY + +.deploy-to-vm: &deploy_to_vm + # Hidden job which serves as template for executed jobs below. + # See https://docs.gitlab.com/ee/ci/yaml/#anchors + retry: 2 + ## SSH initialization + before_script: + - eval $(ssh-agent -s) + - ssh-add <(echo "${SSH_PRIVATE_KEY}") + - ssh -o StrictHostKeyChecking=no root@${SERVER_IP} 'echo "Successfully connected on $(hostname)"' + script: + # Copy jar and data (cleaning them before) to the server + - "scp ./$JAR_PATH root@${SERVER_IP}:/opt/${APP_NAME}/${APP_NAME}-${ENV}.jar" + # Restarting service with the updated jar and the according Spring profiles enabled + - "ssh root@${SERVER_IP} \"systemctl restart ${APP_NAME}-${ENV}\"" + - eval $(ssh-agent -k) + - "echo \"Deploy done. Application should be available at: http://${SERVER_IP}:${APP_PORT}/${CONTEXT_PATH} \"" + only: + changes: + - backend/src/**/* + - frontend/**/* + + +deploy-to-prod: + stage: production + variables: + ENV: prod + APP_PORT: ${PROD_GPDS_PORT} + CONTEXT_PATH: ${PROD_GPDS_CONTEXT_PATH} + <<: *deploy_to_vm + only: + refs: + - master + when: manual + allow_failure: false + + +deploy-to-beta: + stage: staging + variables: + ENV: beta + APP_PORT: ${BETA_GPDS_PORT} + CONTEXT_PATH: ${BETA_GPDS_CONTEXT_PATH} + <<: *deploy_to_vm + only: + refs: + - branches + except: + refs: + - master + allow_failure: false + + +deploy-to-staging: + stage: staging + variables: + ENV: staging + APP_PORT: ${STAGING_GPDS_PORT} + CONTEXT_PATH: ${STAGING_GPDS_CONTEXT_PATH} + <<: *deploy_to_vm + only: + refs: + - master + + +deploy-to-private-prod: + stage: production + variables: + ENV: private-prod + APP_PORT: ${PRIVATE_PROD_GPDS_PORT} + CONTEXT_PATH: ${PRIVATE_PROD_GPDS_CONTEXT_PATH} + <<: *deploy_to_vm + only: + refs: + - master + when: manual + allow_failure: false + + +deploy-to-private-int: + stage: production + variables: + ENV: private-int + APP_PORT: ${PRIVATE_INT_GPDS_PORT} + CONTEXT_PATH: ${PRIVATE_INT_GPDS_CONTEXT_PATH} + <<: *deploy_to_vm + only: + refs: + - master + when: manual + allow_failure: false + + +deploy-to-private-beta: + stage: staging + variables: + ENV: private-beta + APP_PORT: ${PRIVATE_BETA_GPDS_PORT} + CONTEXT_PATH: ${PRIVATE_BETA_GPDS_CONTEXT_PATH} + <<: *deploy_to_vm + only: + refs: + - branches + except: + refs: + - master + when: manual + allow_failure: false diff --git a/README.md b/README.md index 2204ee66..246c9c0b 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Otherwise, for the complete server (backend APIs + frontend interface), you can ./gradlew assemble && java -jar backend/build/libs/gpds.jar ``` -The server should then be accessible at http://localhost:8080/gnpis-core +The server should then be accessible at http://localhost:8060/gnpis/gpds ## Run frontend development server @@ -110,18 +110,28 @@ If such a configuration is not found, it will then fallback to the local `applic To avoid running the Spring Cloud config server every time when developing the application, all the properties are still available in `application.yml` even if they are configured on the remote Spring Cloud server as well. -> **TODO**: Create a sping cloud configuration git repository and update the following section: - -> If you want to use the Spring Cloud config app locally, +If you want to use the Spring Cloud config app locally, see https://forgemia.inra.fr/urgi-is/data-discovery-config -> The configuration is currently only read on startup, +The configuration is currently only read on startup, meaning the application has to be reboot if the configuration is changed on the Spring Cloud server. For a dynamic reload without restarting the application, see http://cloud.spring.io/spring-cloud-static/Finchley.SR1/single/spring-cloud.html#refresh-scope to check what has to be changed. -> In case of testing configuration from the config server, one may use a dedicated branch on `data-discovery-config` project +In case of testing configuration from the config server, one may use a dedicated branch on `data-discovery-config` project and append the `--spring.cloud.config.label=<branch name to test>` parameter when starting the application's executable jar. More info on how pass a parameter to a Spring Boot app: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config + +### Application port & context path + +| | GPDS GnpIS | +| :------------: | :---------------------- | +| dev | :8060/gnpis/gpds | +| beta | :8061/gnpis/gpds-beta | +| staging | :8062/staging/gnpis/gpds | +| prod | :8063/gnpis/gpds | +| private prod | :8064/private/gnpis/gpds | +| private beta | :8065/beta/gnpis/gpds | +| private int | :8066/int/gnpis/gpds | diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index c84caa56..a1b851e8 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -52,7 +52,7 @@ gpds: security-user-group-ws-token: server: - port: 8080 + port: 8060 servlet: - context-path: /gnpis-core + context-path: /gnpis/gpds diff --git a/frontend/angular.json b/frontend/angular.json index cd374be5..bd667ae3 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -42,7 +42,7 @@ "node_modules/leaflet.markercluster/dist/MarkerCluster.Default.css" ], "scripts": [], - "baseHref": "/gnpis-core/" + "baseHref": "/gnpis/gpds/" }, "configurations": { "production": { diff --git a/frontend/proxy.conf.js b/frontend/proxy.conf.js index f0390d2a..d5aee375 100644 --- a/frontend/proxy.conf.js +++ b/frontend/proxy.conf.js @@ -1,10 +1,10 @@ const PROXY_CONFIG = [ { context: [ - "/gnpis-core/brapi", - "/gnpis-core/gnpis", + "/gnpis/gpds/brapi", + "/gnpis/gpds/gnpis", ], - target: "http://localhost:8080", + target: "http://localhost:8060", secure: false } ]; -- GitLab From 77bb54bb41a7d1f9f2656ef685c53ef1ca26c907 Mon Sep 17 00:00:00 2001 From: gcornut <guillaume.cornut@inra.fr> Date: Mon, 11 Feb 2019 14:32:40 +0100 Subject: [PATCH 2/6] fix: Fix CI/CD cache for JAR file. --- .gitlab-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b7fb7910..6597f501 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -24,6 +24,7 @@ cache: - ".gradle" - "frontend/.gradle/" - "frontend/node_modules/" + - "$JAR_PATH" # TESTS @@ -73,6 +74,7 @@ build: expire_in: 1 week only: changes: + - .gitlab-ci.yml - backend/src/**/* - frontend/**/* @@ -96,6 +98,7 @@ build: - "echo \"Deploy done. Application should be available at: http://${SERVER_IP}:${APP_PORT}/${CONTEXT_PATH} \"" only: changes: + - .gitlab-ci.yml - backend/src/**/* - frontend/**/* -- GitLab From 2e1a9ab2b5c64ceab9a24c2a4c94e071b222e109 Mon Sep 17 00:00:00 2001 From: gcornut <guillaume.cornut@inra.fr> Date: Mon, 11 Feb 2019 16:10:28 +0100 Subject: [PATCH 3/6] fix: Remove unnecessary ES cluster name. Adjust verbosity. --- .gitlab-ci.yml | 2 +- .../java/fr/inra/urgi/gpds/config/ElasticSearchConfig.java | 3 --- backend/src/main/resources/application.yml | 6 +++--- backend/src/test/resources/test.properties | 1 - docker-compose.yml | 1 - 5 files changed, 4 insertions(+), 9 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6597f501..fc8593b4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -56,7 +56,7 @@ test-backend: # so there will be no bootstrap checks that would fail on CI # especially the error regarding # `max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]` - command: ["bin/elasticsearch", "-Ediscovery.type=single-node", "-Ecluster.name=es-gpds"] + command: ["bin/elasticsearch", "-Ediscovery.type=single-node"] artifacts: reports: junit: diff --git a/backend/src/main/java/fr/inra/urgi/gpds/config/ElasticSearchConfig.java b/backend/src/main/java/fr/inra/urgi/gpds/config/ElasticSearchConfig.java index 969bd3e3..90d5dbbc 100644 --- a/backend/src/main/java/fr/inra/urgi/gpds/config/ElasticSearchConfig.java +++ b/backend/src/main/java/fr/inra/urgi/gpds/config/ElasticSearchConfig.java @@ -13,9 +13,6 @@ import org.springframework.context.annotation.Configuration; @Configuration public class ElasticSearchConfig { - @Value("${data.elasticsearch.cluster-name}") - private String esClusterName; - @Value("${data.elasticsearch.host}") private String esHost; diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index a1b851e8..02cfa4f3 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -1,8 +1,7 @@ data: elasticsearch: - cluster-name: es-dev - host: 127.0.0.1 - port: 9200 + host: 127.0.0.1 + port: 9200 management: endpoint: @@ -27,6 +26,7 @@ logging.level: org.springframework: boot.web.embedded.tomcat.TomcatWebServer: INFO web.client.RestTemplate: DEBUG + fr.inra: DEBUG --- spring: diff --git a/backend/src/test/resources/test.properties b/backend/src/test/resources/test.properties index 6d5d572e..a093f5b0 100644 --- a/backend/src/test/resources/test.properties +++ b/backend/src/test/resources/test.properties @@ -1,4 +1,3 @@ -spring.data.elasticsearch.cluster.name=es-gpds spring.data.elasticsearch.host=localhost spring.data.elasticsearch.port=9200 diff --git a/docker-compose.yml b/docker-compose.yml index b1fb79fc..4cc0e3ef 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,6 @@ services: image: docker.elastic.co/elasticsearch/elasticsearch:6.5.4 container_name: elasticsearch environment: - - cluster.name=es-dev - discovery.type=single-node ports: - 9200:9200 -- GitLab From 74d039f774c01e52ac9ca5647db12e1a60b2f344 Mon Sep 17 00:00:00 2001 From: gcornut <guillaume.cornut@inra.fr> Date: Thu, 14 Feb 2019 16:15:34 +0100 Subject: [PATCH 4/6] fix: MR feedback. Remove public beta & other fixes. --- .gitlab-ci.yml | 102 ++++++++++++++++++++++++++----------------------- README.md | 16 ++------ 2 files changed, 57 insertions(+), 61 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fc8593b4..b0a69779 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,8 +5,10 @@ stages: - staging - production + image: urgi/docker-browsers:latest + # Disable the Gradle daemon for Continuous Integration servers as correctness # is usually a priority over speed in CI environments. Using a fresh # runtime for each build is more reliable since the runtime is completely @@ -17,6 +19,7 @@ variables: APP_NAME: gpds JAR_PATH: "backend/build/libs/${APP_NAME}.jar" + # Gradle cache for all jobs cache: key: "$CI_COMMIT_REF_NAME" @@ -24,14 +27,16 @@ cache: - ".gradle" - "frontend/.gradle/" - "frontend/node_modules/" - - "$JAR_PATH" + # TESTS + lint: stage: test script: "./gradlew lint" + test-frontend: stage: test script: "./gradlew :frontend:test --parallel" @@ -40,6 +45,7 @@ test-frontend: junit: - "./frontend/karma-junit-tests-report/TEST*.xml" + test-backend: stage: test script: "./gradlew :backend:test --parallel" @@ -62,8 +68,10 @@ test-backend: junit: - "./backend/build/test-results/test/TEST-*.xml" + # BUILD + build: stage: build script: @@ -78,8 +86,10 @@ build: - backend/src/**/* - frontend/**/* + # DEPLOY + .deploy-to-vm: &deploy_to_vm # Hidden job which serves as template for executed jobs below. # See https://docs.gitlab.com/ee/ci/yaml/#anchors @@ -90,31 +100,28 @@ build: - ssh-add <(echo "${SSH_PRIVATE_KEY}") - ssh -o StrictHostKeyChecking=no root@${SERVER_IP} 'echo "Successfully connected on $(hostname)"' script: - # Copy jar and data (cleaning them before) to the server - - "scp ./$JAR_PATH root@${SERVER_IP}:/opt/${APP_NAME}/${APP_NAME}-${ENV}.jar" + # Copy jar + - scp ./$JAR_PATH root@${SERVER_IP}:/opt/${APP_NAME}/${APP_NAME}-${ENV}.jar # Restarting service with the updated jar and the according Spring profiles enabled - - "ssh root@${SERVER_IP} \"systemctl restart ${APP_NAME}-${ENV}\"" + - ssh root@${SERVER_IP} "systemctl restart ${APP_NAME}@${ENV}" - eval $(ssh-agent -k) - - "echo \"Deploy done. Application should be available at: http://${SERVER_IP}:${APP_PORT}/${CONTEXT_PATH} \"" - only: - changes: - - .gitlab-ci.yml - - backend/src/**/* - - frontend/**/* + - echo "Deploy done. Application should be available at http://${SERVER_IP}:${APP_PORT}/${CONTEXT_PATH}" -deploy-to-prod: - stage: production +deploy-to-staging: + stage: staging variables: - ENV: prod - APP_PORT: ${PROD_GPDS_PORT} - CONTEXT_PATH: ${PROD_GPDS_CONTEXT_PATH} + ENV: staging + APP_PORT: ${STAGING_GPDS_PORT} + CONTEXT_PATH: ${STAGING_GPDS_CONTEXT_PATH} <<: *deploy_to_vm only: refs: - master - when: manual - allow_failure: false + changes: + - .gitlab-ci.yml + - backend/src/**/* + - frontend/**/* deploy-to-beta: @@ -127,64 +134,63 @@ deploy-to-beta: only: refs: - branches - except: - refs: - - master + changes: + - .gitlab-ci.yml + - backend/src/**/* + - frontend/**/* + when: manual allow_failure: false -deploy-to-staging: - stage: staging - variables: - ENV: staging - APP_PORT: ${STAGING_GPDS_PORT} - CONTEXT_PATH: ${STAGING_GPDS_CONTEXT_PATH} - <<: *deploy_to_vm - only: - refs: - - master - - -deploy-to-private-prod: +deploy-to-int: stage: production variables: - ENV: private-prod - APP_PORT: ${PRIVATE_PROD_GPDS_PORT} - CONTEXT_PATH: ${PRIVATE_PROD_GPDS_CONTEXT_PATH} + ENV: int + APP_PORT: ${INT_GPDS_PORT} + CONTEXT_PATH: ${INT_GPDS_CONTEXT_PATH} <<: *deploy_to_vm only: refs: - master + changes: + - .gitlab-ci.yml + - backend/src/**/* + - frontend/**/* when: manual allow_failure: false -deploy-to-private-int: +deploy-to-public-prod: stage: production variables: - ENV: private-int - APP_PORT: ${PRIVATE_INT_GPDS_PORT} - CONTEXT_PATH: ${PRIVATE_INT_GPDS_CONTEXT_PATH} + ENV: public-prod + APP_PORT: ${PUBLIC_PROD_GPDS_PORT} + CONTEXT_PATH: ${PUBLIC_PROD_GPDS_CONTEXT_PATH} <<: *deploy_to_vm only: refs: - master + changes: + - .gitlab-ci.yml + - backend/src/**/* + - frontend/**/* when: manual allow_failure: false -deploy-to-private-beta: - stage: staging +deploy-to-private-prod: + stage: production variables: - ENV: private-beta - APP_PORT: ${PRIVATE_BETA_GPDS_PORT} - CONTEXT_PATH: ${PRIVATE_BETA_GPDS_CONTEXT_PATH} + ENV: private-prod + APP_PORT: ${PRIVATE_PROD_GPDS_PORT} + CONTEXT_PATH: ${PRIVATE_PROD_GPDS_CONTEXT_PATH} <<: *deploy_to_vm only: - refs: - - branches - except: refs: - master + changes: + - .gitlab-ci.yml + - backend/src/**/* + - frontend/**/* when: manual allow_failure: false diff --git a/README.md b/README.md index 246c9c0b..24bc7165 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ The details of this remote server are filled in the `bootstrap.yml` file. By default, it tries to connect to the remote server on http://localhost:8888 but it can of course be changed, or even configured via the `SPRING_CONFIG_URI` environment variable. -It will try to fetch the configuration for the application name `rare`, and the default profile. +It will try to fetch the configuration for the application name `gpds`, and the default profile. If such a configuration is not found, it will then fallback to the local `application.yml` properties. To avoid running the Spring Cloud config server every time when developing the application, all the properties are still available in `application.yml` even if they are configured on the remote Spring Cloud server as well. @@ -114,7 +114,7 @@ If you want to use the Spring Cloud config app locally, see https://forgemia.inra.fr/urgi-is/data-discovery-config The configuration is currently only read on startup, -meaning the application has to be reboot if the configuration is changed on the Spring Cloud server. +meaning the application has to be rebooted if the configuration is changed on the Spring Cloud server. For a dynamic reload without restarting the application, see http://cloud.spring.io/spring-cloud-static/Finchley.SR1/single/spring-cloud.html#refresh-scope to check what has to be changed. @@ -124,14 +124,4 @@ and append the `--spring.cloud.config.label=<branch name to test>` parameter whe More info on how pass a parameter to a Spring Boot app: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config -### Application port & context path - -| | GPDS GnpIS | -| :------------: | :---------------------- | -| dev | :8060/gnpis/gpds | -| beta | :8061/gnpis/gpds-beta | -| staging | :8062/staging/gnpis/gpds | -| prod | :8063/gnpis/gpds | -| private prod | :8064/private/gnpis/gpds | -| private beta | :8065/beta/gnpis/gpds | -| private int | :8066/int/gnpis/gpds | +See the [`data-discovery-config` README.md file](https://forgemia.inra.fr/urgi-is/data-discovery-config/blob/master/README.md) for the list of application environments (server ports and context paths). -- GitLab From f54eeba94af7e6749c9bc9a9913bb4f7cfa9d3e5 Mon Sep 17 00:00:00 2001 From: gcornut <guillaume.cornut@inra.fr> Date: Mon, 18 Feb 2019 16:14:45 +0100 Subject: [PATCH 5/6] fix: Remove unnecessary spring profile. Fix angular base href. --- README.md | 12 +- .../urgi/gpds/filter/AngularRouteFilter.java | 115 ++++++++++++++++++ .../gpds/filter/AuthenticationFilter.java | 11 +- .../fr/inra/urgi/gpds/filter/IndexFilter.java | 81 ------------ backend/src/main/resources/application.yml | 25 ++-- backend/src/main/resources/bootstrap.yml | 5 +- .../gpds/filter/AngularRouteFilterTest.java | 105 ++++++++++++++++ .../urgi/gpds/filter/IndexFilterTest.java | 70 ----------- frontend/angular.json | 2 +- frontend/proxy.conf.js | 2 +- 10 files changed, 237 insertions(+), 191 deletions(-) create mode 100644 backend/src/main/java/fr/inra/urgi/gpds/filter/AngularRouteFilter.java delete mode 100644 backend/src/main/java/fr/inra/urgi/gpds/filter/IndexFilter.java create mode 100644 backend/src/test/java/fr/inra/urgi/gpds/filter/AngularRouteFilterTest.java delete mode 100644 backend/src/test/java/fr/inra/urgi/gpds/filter/IndexFilterTest.java diff --git a/README.md b/README.md index 24bc7165..7cec202e 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Otherwise, for the complete server (backend APIs + frontend interface), you can ./gradlew assemble && java -jar backend/build/libs/gpds.jar ``` -The server should then be accessible at http://localhost:8060/gnpis/gpds +The server should then be accessible at http://localhost:8380/gnpis/gpds ## Run frontend development server @@ -110,18 +110,12 @@ If such a configuration is not found, it will then fallback to the local `applic To avoid running the Spring Cloud config server every time when developing the application, all the properties are still available in `application.yml` even if they are configured on the remote Spring Cloud server as well. -If you want to use the Spring Cloud config app locally, -see https://forgemia.inra.fr/urgi-is/data-discovery-config - The configuration is currently only read on startup, meaning the application has to be rebooted if the configuration is changed on the Spring Cloud server. For a dynamic reload without restarting the application, see http://cloud.spring.io/spring-cloud-static/Finchley.SR1/single/spring-cloud.html#refresh-scope to check what has to be changed. -In case of testing configuration from the config server, one may use a dedicated branch on `data-discovery-config` project -and append the `--spring.cloud.config.label=<branch name to test>` parameter when starting the application's executable jar. -More info on how pass a parameter to a Spring Boot app: -https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config +If you want to use a Spring Cloud configuration server, please refer to +https://spring.io/guides/gs/centralized-configuration/ -See the [`data-discovery-config` README.md file](https://forgemia.inra.fr/urgi-is/data-discovery-config/blob/master/README.md) for the list of application environments (server ports and context paths). diff --git a/backend/src/main/java/fr/inra/urgi/gpds/filter/AngularRouteFilter.java b/backend/src/main/java/fr/inra/urgi/gpds/filter/AngularRouteFilter.java new file mode 100644 index 00000000..d307c542 --- /dev/null +++ b/backend/src/main/java/fr/inra/urgi/gpds/filter/AngularRouteFilter.java @@ -0,0 +1,115 @@ +package fr.inra.urgi.gpds.filter; + +import com.google.common.base.Charsets; +import com.google.common.io.ByteSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ResourceLoader; +import org.springframework.stereotype.Component; + +import javax.servlet.*; +import javax.servlet.annotation.WebFilter; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +/** + * Filter that intercepts all request to potential Angular routes + * (ex: /studies/ID) to send back the Angular `index.html` file with a correct + * base href set to the spring server context path. + * + * Potential angular routes are devised by process of elimination: + * - They should be GET requests + * - They should not end with common static file suffixes {@link AngularRouteFilter#STATIC_SUFFIXES} + * - They should not start with API prefixes {@link AngularRouteFilter#API_PREFIXES} + * + * <p> + * Adapted from data-discovery + * + * @author gcornut + */ +@Component +@WebFilter("/*") +public class AngularRouteFilter implements Filter { + + private static final String[] API_PREFIXES = { + "/brapi/v1", "/gnpis/v1", "/actuator", "/v2/api-docs", "/swagger-resources" + }; + + private static final String[] STATIC_SUFFIXES = { + ".html", ".js", ".css", ".ico", ".png", ".jpg", ".gif", ".eot", ".svg", + ".woff2", ".ttf", ".woff" + }; + + @Value("${server.servlet.context-path}") + private String serverContextPath; + + private final ResourceLoader resourceLoader; + + @Autowired + public AngularRouteFilter(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } + + @Override + public void doFilter( + ServletRequest req, + ServletResponse response, + FilterChain chain + ) throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) req; + + if (isAngularRoute(request)) { + // Angular route + InputStream inputStream = resourceLoader.getResource("classpath:static/index.html").getInputStream(); + + ByteSource byteSource = new ByteSource() { + @Override + public InputStream openStream() { + return inputStream; + } + }; + + String content = byteSource.asCharSource(Charsets.UTF_8).read(); + String replacedContent = content.replace( + "<base href=\"./\">", + "<base href=\"" + serverContextPath + "/\">" + ); + response.getWriter().write(replacedContent); + return; + } + + // Otherwise nothing to do + chain.doFilter(request, response); + } + + private boolean isAngularRoute(HttpServletRequest request) { + if (!request.getMethod().equals("GET")) { + return false; + } + + String fullUri = request.getRequestURI(); + String contextPath = request.getContextPath(); + String uri = fullUri.substring(contextPath.length()); + + return !isApiOrStaticResource(uri); + } + + private boolean isApiOrStaticResource(String relativePath) { + // Starts with API prefix + return Arrays.stream(API_PREFIXES).anyMatch(relativePath::startsWith) + // or has static file suffix + || Arrays.stream(STATIC_SUFFIXES).anyMatch(relativePath::endsWith); + } + + @Override + public void init(FilterConfig filterConfig) { + // nothing to do + } + + @Override + public void destroy() { + // nothing to do + } +} diff --git a/backend/src/main/java/fr/inra/urgi/gpds/filter/AuthenticationFilter.java b/backend/src/main/java/fr/inra/urgi/gpds/filter/AuthenticationFilter.java index f6f2a6cd..9a6c62cb 100644 --- a/backend/src/main/java/fr/inra/urgi/gpds/filter/AuthenticationFilter.java +++ b/backend/src/main/java/fr/inra/urgi/gpds/filter/AuthenticationFilter.java @@ -40,15 +40,6 @@ public class AuthenticationFilter implements Filter { public void doFilter( ServletRequest req, ServletResponse resp, FilterChain chain ) throws IOException, ServletException { - // get web login - String webUserLogin = ((HttpServletRequest) req).getRemoteUser(); - logger.debug( - "\n" + - "*********************************************\n" + - " Applying user credentials for " + webUserLogin + "\n" + - "*********************************************" - ); - final String authorization = ((HttpServletRequest) req).getHeader("Authorization"); if (authorization != null && authorization.startsWith("Basic")) { @@ -57,6 +48,8 @@ public class AuthenticationFilter implements Filter { String authCode = new String(BaseEncoding.base64().decode(base64Credentials), Charsets.UTF_8); final String userName = authCode.split(":", 2)[0]; + logger.debug("Intercepting HTTP Authorization with user: " + userName); + AuthenticationStore.set(userName); } diff --git a/backend/src/main/java/fr/inra/urgi/gpds/filter/IndexFilter.java b/backend/src/main/java/fr/inra/urgi/gpds/filter/IndexFilter.java deleted file mode 100644 index 0cd802ec..00000000 --- a/backend/src/main/java/fr/inra/urgi/gpds/filter/IndexFilter.java +++ /dev/null @@ -1,81 +0,0 @@ -package fr.inra.urgi.gpds.filter; - -import org.springframework.stereotype.Component; - -import javax.servlet.*; -import javax.servlet.annotation.WebFilter; -import javax.servlet.http.HttpServletRequest; -import java.io.IOException; -import java.util.Arrays; - -/** - * Filter that forwards all GET requests to non-static and non-api resources to index.html. This filter is necessary - * to support deep-linking for URLs generated by the Angular router. - * <p> - * Adapted from data-discovery - * - * @author gcornut - */ -@Component -@WebFilter("/*") -public class IndexFilter implements Filter { - - private static final String[] API_PREFIXES = { - "/brapi/v1", "/gnpis/v1", "/actuator", "/api-docs", "/v2/api-docs", "/swagger-resources" - }; - - private static final String[] STATIC_FILES = { - "/index.html", "/swagger-ui.html" - }; - - private static final String[] STATIC_SUFFIXES = { - ".js", ".css", ".ico", ".png", ".jpg", ".gif", ".eot", ".svg", - ".woff2", ".ttf", ".woff" - }; - - @Override - public void doFilter( - ServletRequest req, - ServletResponse response, - FilterChain chain - ) throws IOException, ServletException { - HttpServletRequest request = (HttpServletRequest) req; - if (mustForward(request)) { - request.getRequestDispatcher("/index.html").forward(request, response); - return; - } - - chain.doFilter(request, response); - } - - private boolean mustForward(HttpServletRequest request) { - if (!request.getMethod().equals("GET")) { - return false; - } - - String fullUri = request.getRequestURI(); - String contextPath = request.getContextPath(); - String uri = fullUri.substring(contextPath.length()); - - return !isApiOrStaticResource(uri); - } - - private boolean isApiOrStaticResource(String uri) { - // Starts with API prefix - return Arrays.stream(API_PREFIXES).anyMatch(uri::startsWith) - // or is static file - || Arrays.asList(STATIC_FILES).contains(uri) - // or has static file suffix - || Arrays.stream(STATIC_SUFFIXES).anyMatch(uri::endsWith); - } - - @Override - public void init(FilterConfig filterConfig) { - // nothing to do - } - - @Override - public void destroy() { - // nothing to do - } -} diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 02cfa4f3..ebe053c7 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -12,15 +12,6 @@ management: exposure: include: '*' -server: - compression: - enabled: true - mime-types: - - application/json - - application/javascript - - text/html - - text/css - logging.level: root: ERROR org.springframework: @@ -28,11 +19,6 @@ logging.level: web.client.RestTemplate: DEBUG fr.inra: DEBUG ---- -spring: - profiles: gnpis - cloud.config.name: gpds - gpds: elasticsearch-alias-template: gnpis_{source}_{documentType}_5432_scratchy-group{groupId} @@ -52,7 +38,14 @@ gpds: security-user-group-ws-token: server: - port: 8060 + compression: + enabled: true + mime-types: + - application/json + - application/javascript + - text/html + - text/css + port: 8380 servlet: - context-path: /gnpis/gpds + context-path: /gnpis-dev/gpds diff --git a/backend/src/main/resources/bootstrap.yml b/backend/src/main/resources/bootstrap.yml index 71788dfe..9e6ab0a5 100644 --- a/backend/src/main/resources/bootstrap.yml +++ b/backend/src/main/resources/bootstrap.yml @@ -1,8 +1,5 @@ spring: - application: - name: gpds + application.name: gpds cloud: config: uri: ${SPRING_CONFIG_URI:http://localhost:8888} - profiles: - active: gnpis diff --git a/backend/src/test/java/fr/inra/urgi/gpds/filter/AngularRouteFilterTest.java b/backend/src/test/java/fr/inra/urgi/gpds/filter/AngularRouteFilterTest.java new file mode 100644 index 00000000..12043dc7 --- /dev/null +++ b/backend/src/test/java/fr/inra/urgi/gpds/filter/AngularRouteFilterTest.java @@ -0,0 +1,105 @@ +package fr.inra.urgi.gpds.filter; + +import fr.inra.urgi.gpds.Application; +import fr.inra.urgi.gpds.config.SecurityConfig; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import java.io.ByteArrayInputStream; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; + +/** + * Unit tests for {@link AngularRouteFilter} + * + * @author gcornut + */ +@ExtendWith(SpringExtension.class) +@Import(SecurityConfig.class) +@SpringBootTest(classes = Application.class) +class AngularRouteFilterTest { + + @Autowired + private WebApplicationContext context; + + @MockBean + private ResourceLoader resourceLoader; + + private MockMvc mockMvc; + + private AngularRouteFilter filter; + + @BeforeEach + void setUp() { + filter = new AngularRouteFilter(resourceLoader); + mockMvc = MockMvcBuilders.webAppContextSetup(context) + .addFilter(filter, "/*") + .build(); + } + + @ParameterizedTest + @ValueSource(strings = { + // Static files + "/index.html", + "/script.js", + "/style.css", + "/image.gif", + "/icon.ico", + "/image.png", + "/image.jpg", + "/font.woff", + "/font.ttf", + // APIs + "/brapi/v1/studies", + "/gnpis/v1/datadiscovery/suggest", + "/actuator/info", + }) + void shouldNotForward(String url) throws Exception { + mockMvc.perform(get(url)).andExpect(forwardedUrl(null)); + } + + @ParameterizedTest + @ValueSource(strings = { + "/home", + "/studies/foo", + "/germplasm/bar", + }) + void shouldForward(String url) throws Exception { + String indexBefore = "<html>\n" + + " <base href=\"./\">\n" + + "</html>"; + String indexAfter = "<html>\n" + + " <base href=\"/gnpis-test/gpds/\">\n" + + "</html>"; + + ReflectionTestUtils.setField(filter, "serverContextPath", "/gnpis-test/gpds"); + + Resource mockResource = mock(Resource.class); + when(mockResource.getInputStream()) + .thenReturn(new ByteArrayInputStream(indexBefore.getBytes())); + when(resourceLoader.getResource(anyString())) + .thenReturn(mockResource); + + mockMvc.perform(get(url)) + .andExpect(content().string(indexAfter)); + } + +} diff --git a/backend/src/test/java/fr/inra/urgi/gpds/filter/IndexFilterTest.java b/backend/src/test/java/fr/inra/urgi/gpds/filter/IndexFilterTest.java deleted file mode 100644 index 9649287f..00000000 --- a/backend/src/test/java/fr/inra/urgi/gpds/filter/IndexFilterTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package fr.inra.urgi.gpds.filter; - -import fr.inra.urgi.gpds.api.gnpis.v1.DataDiscoveryController; -import fr.inra.urgi.gpds.config.SecurityConfig; -import fr.inra.urgi.gpds.repository.es.DataDiscoveryRepository; -import fr.inra.urgi.gpds.repository.file.DataSourceRepository; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; - -/** - * Unit tests for {@link IndexFilter} - * - * @author gcornut - */ -@ExtendWith(SpringExtension.class) -@WebMvcTest(controllers = DataDiscoveryController.class) -@Import(SecurityConfig.class) -class IndexFilterTest { - - @MockBean - private DataDiscoveryRepository repository; - - @MockBean - private DataSourceRepository dataSourceRepository; - - @Autowired - private MockMvc mockMvc; - - @ParameterizedTest - @ValueSource(strings = { - // Static files - "/index.html", - "/script.js", - "/style.css", - "/image.gif", - "/icon.ico", - "/image.png", - "/image.jpg", - "/font.woff", - "/font.ttf", - // APIs - "/brapi/v1/studies", - "/gnpis/v1/datadiscovery/suggest", - "/actuator/info", - }) - void shouldNotForward(String url) throws Exception { - mockMvc.perform(get(url)).andExpect(forwardedUrl(null)); - } - - @ParameterizedTest - @ValueSource(strings = { - "/home", - "/studies/foo", - "/germplasm/bar", - }) - void shouldForward(String url) throws Exception { - mockMvc.perform(get(url)).andExpect(forwardedUrl("/index.html")); - } - -} diff --git a/frontend/angular.json b/frontend/angular.json index bd667ae3..b3634382 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -42,7 +42,7 @@ "node_modules/leaflet.markercluster/dist/MarkerCluster.Default.css" ], "scripts": [], - "baseHref": "/gnpis/gpds/" + "baseHref": "./" }, "configurations": { "production": { diff --git a/frontend/proxy.conf.js b/frontend/proxy.conf.js index d5aee375..df13c009 100644 --- a/frontend/proxy.conf.js +++ b/frontend/proxy.conf.js @@ -4,7 +4,7 @@ const PROXY_CONFIG = [ "/gnpis/gpds/brapi", "/gnpis/gpds/gnpis", ], - target: "http://localhost:8060", + target: "http://localhost:8380", secure: false } ]; -- GitLab From b5dd81ea393a8c58cee332ded4bf5570b19354fa Mon Sep 17 00:00:00 2001 From: gcornut <guillaume.cornut@inra.fr> Date: Thu, 21 Feb 2019 19:24:30 +0100 Subject: [PATCH 6/6] fix: Fix Angular PROD and DEV context path switching. --- frontend/angular.json | 9 +++++---- frontend/package-lock.json | 13 +++++++++++++ frontend/proxy.conf.js | 4 ++-- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/frontend/angular.json b/frontend/angular.json index b3634382..6a6ee052 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -42,7 +42,7 @@ "node_modules/leaflet.markercluster/dist/MarkerCluster.Default.css" ], "scripts": [], - "baseHref": "./" + "baseHref": "/gnpis-dev/gpds/" }, "configurations": { "production": { @@ -64,15 +64,16 @@ "budgets": [ { "type": "initial", - "maximumWarning": "1500kb", - "maximumError": "2000kb" + "maximumWarning": "2000kb", + "maximumError": "3000kb" } ], "stylePreprocessorOptions": { "includePaths": [ "src/assets/gpds/" ] - } + }, + "baseHref": "./" } } }, diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 14a78b22..0a1e81ce 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -6757,6 +6757,14 @@ "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==", "dev": true }, + "ngx-moment": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/ngx-moment/-/ngx-moment-3.3.0.tgz", + "integrity": "sha512-6fpllpJqLfjRWboOhphgeEYt+rzIA9O29rG5QWCebRt2X0uNk4P93sLEb0S8lbDF0dEp2NOC3UOD+xoCVlJQhA==", + "requires": { + "tslib": "^1.9.0" + } + }, "ngx-speculoos": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/ngx-speculoos/-/ngx-speculoos-1.1.0.tgz", @@ -7495,6 +7503,11 @@ "find-up": "^2.1.0" } }, + "popper.js": { + "version": "1.14.6", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.6.tgz", + "integrity": "sha512-AGwHGQBKumlk/MDfrSOf0JHhJCImdDMcGNoqKmKkU+68GFazv3CQ6q9r7Ja1sKDZmYWTckY/uLyEznheTDycnA==" + }, "portfinder": { "version": "1.0.17", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.17.tgz", diff --git a/frontend/proxy.conf.js b/frontend/proxy.conf.js index df13c009..f271334b 100644 --- a/frontend/proxy.conf.js +++ b/frontend/proxy.conf.js @@ -1,8 +1,8 @@ const PROXY_CONFIG = [ { context: [ - "/gnpis/gpds/brapi", - "/gnpis/gpds/gnpis", + "/gnpis-dev/gpds/brapi", + "/gnpis-dev/gpds/gnpis", ], target: "http://localhost:8380", secure: false -- GitLab