diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5149d6d0..a3d525ba 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -18,3 +18,7 @@ updates: versions: - 7.17.x - 8.x + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: daily diff --git a/.github/workflows/maven-matrix.yml b/.github/workflows/maven-matrix.yml index 269f9dbd..1e83c590 100644 --- a/.github/workflows/maven-matrix.yml +++ b/.github/workflows/maven-matrix.yml @@ -12,7 +12,7 @@ on: paths-ignore: - '*.md' schedule: - # * is a special character in YAML so you have to quote this string + # * is a special character in YAML, so you have to quote this string - cron: '11 16 * * 4' env: @@ -20,15 +20,15 @@ env: GPG_EXECUTABLE: gpg jobs: - build-and-test-with-jdk: + build-with-jdk: strategy: matrix: - java: [ 8, 11, 17, 18, 19 ] + java: [ 8, 11, 17, 21 ] fail-fast: false - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up JDK ${{ matrix.java }} uses: actions/setup-java@v3 with: @@ -37,16 +37,16 @@ jobs: - name: Build and test with Maven run: $MVN_CMD install - build-and-test-with-es-version: + build-with-es: strategy: matrix: - elasticsearchVersion: [ "8.6.1", "8.5.3", "8.4.3", "8.3.3", "8.2.3", "8.1.3", "8.0.1", "7.17.9", "7.16.3", "7.15.2", "7.14.2", - "7.13.4", "7.12.1", "7.11.2", "7.10.2", "7.9.3", "7.8.1", "7.7.1", "7.6.2", "7.5.2" ] + elasticsearchVersion: [ "8.11.1", "8.10.4", "8.9.2", "8.8.2", "8.7.1", "8.6.2", "8.5.3", "8.4.3", "8.3.3", "8.2.3", "8.1.3", "8.0.1", + "7.17.15", "7.5.2" ] fail-fast: false - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up JDK 17 uses: actions/setup-java@v3 with: @@ -57,16 +57,53 @@ jobs: - name: elasticsearch test version ${{ matrix.elasticsearchVersion }} run: $MVN_CMD --file ./tests/pom.xml clean verify -Delasticsearch.version=${{ matrix.elasticsearchVersion }} +## on github actions Linux containers (LCOW) are not supported: +## https://stackoverflow.com/questions/66077884/testcontainers-in-windows-environment-on-github-actions-could-not-find-a-valid +## https://github.com/actions/runner-images/issues/252 +## https://github.com/actions/runner-images/issues/2216 +# build-with-windows: +# runs-on: windows-latest +# steps: +# - name: Checkout +# uses: actions/checkout@v4 +# - name: Set up JDK 8 +# uses: actions/setup-java@v3 +# with: +# distribution: zulu +# ja# - name: Set up Docker +# uses: crazy-max/ghaction-setup-docker@v1 +# with: +# version: 23.0.1va-version: 8 +# - name: Build and test with Maven +# run: .\mvnw.cmd --settings .cicd.settings.xml -e -B -V install + +# macos builds are very flaky :/ +# build-with-macos: +# runs-on: macos-latest +# steps: +# - name: Checkout +# uses: actions/checkout@v4 +# - name: Set up JDK 8 +# uses: actions/setup-java@v3 +# with: +# distribution: zulu +# java-version: 8 +# - name: Install Docker +# # docker is not installed by default https://github.com/actions/runner-images/issues/17 +# uses: docker-practice/actions-setup-docker@master +# timeout-minutes: 12 +# - name: Build and test with Maven +# run: $MVN_CMD install + release-dry-run: # this will just build like the real release job, but not do a release (dry run) - needs: [ build-and-test-with-jdk, build-and-test-with-es-version ] if: ${{ github.ref != 'refs/heads/release' }} - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Import GPG key to sign maven build artifacts - uses: crazy-max/ghaction-import-gpg@v4 + uses: crazy-max/ghaction-import-gpg@v6 with: gpg_private_key: ${{ secrets.GPG_SECRET_KEYS }} passphrase: ${{ secrets.GPG_PASSPHRASE }} @@ -90,14 +127,14 @@ jobs: release: # Release to maven central and create Github release - needs: [ build-and-test-with-jdk, build-and-test-with-es-version ] + needs: [ build-with-jdk, build-with-es ] if: ${{ github.repository == 'senacor/elasticsearch-evolution' && github.event_name == 'push' && github.ref == 'refs/heads/release' }} - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Import GPG key to sign maven build artifacts - uses: crazy-max/ghaction-import-gpg@v4 + uses: crazy-max/ghaction-import-gpg@v6 with: gpg_private_key: ${{ secrets.GPG_SECRET_KEYS }} passphrase: ${{ secrets.GPG_PASSPHRASE }} diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 1b0ffe36..87ca1d67 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -1,4 +1,4 @@ -name: Quality analysis +name: 'Quality analysis' on: push: @@ -18,12 +18,12 @@ env: jobs: code-analysis: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 env: COVERALLS_REPO_TOKEN_EXISTS: ${{ secrets.COVERALLS_REPO_TOKEN != '' }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up JDK 8 # build with JDK 8 because of issue https://github.com/trautonen/coveralls-maven-plugin/issues/112 uses: actions/setup-java@v3 @@ -37,6 +37,12 @@ jobs: languages: 'java' - name: Build and test with Maven run: $MVN_CMD install + - name: upload test results + uses: actions/upload-artifact@v3 # upload test results + if: success() || failure() # run this step even if previous step failed + with: + name: test-results + path: '**/target/*-reports/TEST-*.xml' - name: Execute Maven coveralls Plugin if: ${{ env.COVERALLS_REPO_TOKEN_EXISTS == 'true' }} env: diff --git a/.github/workflows/test-report.yml b/.github/workflows/test-report.yml new file mode 100644 index 00000000..a9ad4fa1 --- /dev/null +++ b/.github/workflows/test-report.yml @@ -0,0 +1,17 @@ +name: 'Test Report' +on: + workflow_run: + # runs after Quality analysis workflow + workflows: [ 'Quality analysis' ] + types: + - completed +jobs: + report: + runs-on: ubuntu-latest + steps: + - uses: dorny/test-reporter@v1 + with: + artifact: test-results + name: JUnit Tests + path: '**/*.xml' + reporter: java-junit \ No newline at end of file diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar index c1dd12f1..cb28b0e3 100644 Binary files a/.mvn/wrapper/maven-wrapper.jar and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index fec191f3..eacdc9ed 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -5,14 +5,14 @@ # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.0/apache-maven-3.9.0-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/README.md b/README.md index 257bdfd7..ca279776 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,9 @@ Successful executed migration scripts will not be executed again! ## 2 Features -- tested on Java 8, 11, 17, 18 and 19 -- runs on Spring-Boot 2.1, 2.2, 2.3, 2.4, 2.5, 2.6 and 2.7 (and of course without Spring-Boot) -- runs on Elasticsearch version 7.5.x - 8.6.x +- tested on Java 8, 11, 17, and 21 +- runs on Spring-Boot 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 3.0 and 3.1 (and of course without Spring-Boot) +- runs on Elasticsearch version 7.5.x - 8.11.x - runs on Opensearch version 1.x and 2.x - highly configurable (e.g. location(s) of your migration files, migration files format pattern) - placeholder substitution in migration scripts @@ -31,11 +31,12 @@ Successful executed migration scripts will not be executed again! - ready to use default configuration - line comments in migration files -| Compatibility | Spring Boot | Elasticsearch | Opensearch | -|----------------------------------|-----------------------------------|----------------------|------------| -| elasticsearch-evolution >= 0.4.0 | 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7 | 7.5.x - 8.6.x | 1.x - 2.x | -| elasticsearch-evolution 0.3.x | 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7 | 7.5.x - 7.17.x | | -| elasticsearch-evolution 0.2.x | 1.5, 2.0, 2.1, 2.2, 2.3, 2.4 | 7.0.x - 7.4.x, 6.8.x | | +| Compatibility | Spring Boot | Elasticsearch | Opensearch | +|----------------------------------|---------------------------------------------|----------------------|------------| +| elasticsearch-evolution >= 0.4.2 | 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 3.0, 3.1 | 7.5.x - 8.11.x | 1.x - 2.x | +| elasticsearch-evolution >= 0.4.0 | 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7 | 7.5.x - 8.6.x | 1.x - 2.x | +| elasticsearch-evolution 0.3.x | 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7 | 7.5.x - 7.17.x | | +| elasticsearch-evolution 0.2.x | 1.5, 2.0, 2.1, 2.2, 2.3, 2.4 | 7.0.x - 7.4.x, 6.8.x | | NOTE: When you run on Java 11 and using spring-boot 2.2 or 2.3 and you hit [this issue](https://github.com/ronmamo/reflections/issues/279), you have 2 options: @@ -52,7 +53,7 @@ First add the latest version of Elasticsearch-Evolution spring boot starter as a com.senacor.elasticsearch.evolution spring-boot-starter-elasticsearch-evolution - 0.4.1 + 0.4.2 ``` @@ -76,7 +77,7 @@ First add the latest version of Elasticsearch-Evolution core as a dependency: com.senacor.elasticsearch.evolution elasticsearch-evolution-core - 0.4.1 + 0.4.2 ``` @@ -167,7 +168,7 @@ The filename has to follow a pattern: - followed by a description which can be any text your filesystem supports - ended with `esMigrationSuffixes` which is by default `.http` and is configurable and case-insensitive. -Elasticsearch-Evolution uses the version for ordering your scripts and enforces strict ordered execution of your scripts. Out-of-Order execution is not supported. +Elasticsearch-Evolution uses the version for ordering your scripts and enforces strict ordered execution of your scripts, by default. Out-of-Order execution is supported, but disabled by default. Elasticsearch-Evolution interprets the version parts as Integers, so each version part must be between 1 (inclusive) and 2,147,483,647 (inclusive). Here is an example which indicates the ordering: `1.0.1` < `1.1` < `1.2.1` < (`2.0.0` == `2`). @@ -195,6 +196,7 @@ Elasticsearch-Evolution can be configured to your needs: - **validateOnMigrate** (default=true): Whether to fail when a previously applied migration script has been modified after it was applied. - **baselineVersion** (default=1.0): Version to use as a baseline. versions lower than it will not be applied. - **lineSeparator** (default=\n): Line separator, used only temporary between reading raw migration file line-by-line and parsing it later. Only needed for backward compatibility / checksum stability! Should be one of `\n`, `\r` or `\r\n` +- **outOfOrder** (default=false): Allows migrations to be run "out of order". If you already have versions 1.0 and 3.0 applied, and now a version 2.0 is found, it will be applied too instead of being rejected. ### 5.1 Spring Boot @@ -289,7 +291,17 @@ ElasticsearchEvolution.configure() ## 6 changelog -### v0.4.2-SNAPSHOT +### v0.4.3 + +- support out of order migration execution. +- version updates (spring-boot 2.7.17) +- added regression tests against OpenSearch 2.6.0 +- drop older Elasticsearch and OpenSearch versions in regression tests. Only test against the last 3 minor versions of the latest major release. +- added regression tests on JDK 21 +- added regression tests for spring boot 3.1 +- update org.reflections:reflections from 0.9.12 to 0.10.2 [#233](https://github.com/senacor/elasticsearch-evolution/pull/233) thanks @RiVogel + +### v0.4.2 - bugfix ([#182](https://github.com/senacor/elasticsearch-evolution/issues/182)): checksum calculation was based on system dependent line separators which lead to different checksums on different operating systems (e.g. windows vs linux). The default is now `\n`. For backward compatibility you can set other line separator via `lineSeparator` config property. - version updates (spring-boot 2.7.8) diff --git a/elasticsearch-evolution-core/pom.xml b/elasticsearch-evolution-core/pom.xml index 3d64d1ab..3a6f8ef7 100644 --- a/elasticsearch-evolution-core/pom.xml +++ b/elasticsearch-evolution-core/pom.xml @@ -6,7 +6,7 @@ com.senacor.elasticsearch.evolution elasticsearch-evolution-parent - 0.4.2 + 0.4.3 ../ elasticsearch-evolution-core @@ -105,6 +105,12 @@ mockito-core test + + + net.bytebuddy + byte-buddy + test + diff --git a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolution.java b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolution.java index f1d01a69..f45f33ed 100644 --- a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolution.java +++ b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolution.java @@ -170,7 +170,8 @@ protected MigrationService createMigrationService() { getRestClient(), ContentType.parse(getConfig().getDefaultContentType()), getConfig().getEncoding(), - getConfig().getValidateOnMigrate(), - getConfig().getBaselineVersion()); + getConfig().isValidateOnMigrate(), + getConfig().getBaselineVersion(), + getConfig().isOutOfOrder()); } } diff --git a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/api/config/ElasticsearchEvolutionConfig.java b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/api/config/ElasticsearchEvolutionConfig.java index db04ac8e..10b242c2 100644 --- a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/api/config/ElasticsearchEvolutionConfig.java +++ b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/api/config/ElasticsearchEvolutionConfig.java @@ -106,6 +106,14 @@ public class ElasticsearchEvolutionConfig { */ private String baselineVersion = "1.0"; + /** + * Allows migrations to be run “out of order”. + *

+ * If you already have versions 1.0 and 3.0 applied, and now a version 2.0 is found, + * it will be applied too instead of being rejected. + */ + private boolean outOfOrder = false; + /** * Loads this configuration into a new ElasticsearchEvolution instance. * @@ -147,11 +155,13 @@ public ElasticsearchEvolutionConfig validate() throws IllegalStateException, Nul } requireNotBlank(historyIndex, "historyIndex must not be empty"); requireCondition(historyMaxQuerySize, size -> size > 0, "historyMaxQuerySize value '%s' must be greater than 0", historyMaxQuerySize); + final MigrationVersion baseline; try { - MigrationVersion.fromVersion(baselineVersion); + baseline = MigrationVersion.fromVersion(baselineVersion); } catch (RuntimeException e) { throw new IllegalArgumentException("baselineVersion is invalid", e); } + requireCondition(baseline, version -> version.isAtLeast("1"), "baselineVersion '%s' must be at least 1", baseline); } return this; } @@ -273,7 +283,15 @@ public ElasticsearchEvolutionConfig setHistoryMaxQuerySize(int historyMaxQuerySi return this; } + /** + * @deprecated use {@link #isValidateOnMigrate()} instead + */ + @Deprecated public boolean getValidateOnMigrate() { + return isValidateOnMigrate(); + } + + public boolean isValidateOnMigrate() { return validateOnMigrate; } @@ -291,6 +309,15 @@ public ElasticsearchEvolutionConfig setBaselineVersion(String baselineVersion) { return this; } + public boolean isOutOfOrder() { + return outOfOrder; + } + + public ElasticsearchEvolutionConfig setOutOfOrder(boolean outOfOrder) { + this.outOfOrder = outOfOrder; + return this; + } + @Override public String toString() { return "ElasticsearchEvolutionConfig{" + @@ -309,6 +336,7 @@ public String toString() { ", historyMaxQuerySize=" + historyMaxQuerySize + ", validateOnMigrate=" + validateOnMigrate + ", baselineVersion='" + baselineVersion + '\'' + + ", outOfOrder='" + outOfOrder + '\'' + '}'; } } diff --git a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImpl.java b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImpl.java index 7cf4c554..b12b2615 100644 --- a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImpl.java +++ b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImpl.java @@ -3,6 +3,7 @@ import com.senacor.elasticsearch.evolution.core.api.MigrationException; import com.senacor.elasticsearch.evolution.core.api.migration.HistoryRepository; import com.senacor.elasticsearch.evolution.core.api.migration.MigrationService; +import com.senacor.elasticsearch.evolution.core.internal.model.MigrationVersion; import com.senacor.elasticsearch.evolution.core.internal.model.dbhistory.MigrationScriptProtocol; import com.senacor.elasticsearch.evolution.core.internal.model.migration.ParsedMigrationScript; import com.senacor.elasticsearch.evolution.core.internal.utils.RandomUtils; @@ -38,8 +39,10 @@ public class MigrationServiceImpl implements MigrationService { private final ContentType defaultContentType; private final Charset encoding; private final boolean validateOnMigrate; + private final boolean outOfOrder; private final String baselineVersion; + public MigrationServiceImpl(HistoryRepository historyRepository, int waitUntilUnlockedMinTimeInMillis, int waitUntilUnlockedMaxTimeInMillis, @@ -47,7 +50,8 @@ public MigrationServiceImpl(HistoryRepository historyRepository, ContentType defaultContentType, Charset encoding, boolean validateOnMigrate, - String baselineVersion) { + String baselineVersion, + boolean outOfOrder) { this.historyRepository = requireNonNull(historyRepository, "historyRepository must not be null"); this.restClient = requireNonNull(restClient, "restClient must not be null"); this.defaultContentType = requireNonNull(defaultContentType); @@ -59,6 +63,7 @@ public MigrationServiceImpl(HistoryRepository historyRepository, waitUntilUnlockedMinTimeInMillis, waitUntilUnlockedMaxTimeInMillis); this.waitUntilUnlockedMaxTimeInMillis = waitUntilUnlockedMaxTimeInMillis; this.baselineVersion = baselineVersion; + this.outOfOrder = outOfOrder; } @Override @@ -180,53 +185,75 @@ List getPendingScriptsToBeExecuted(Collection orderedScripts = new ArrayList<>(migrationScripts.stream() + final TreeMap scriptsInFilesystemMap = migrationScripts.stream() .filter(script -> script.getFileNameInfo().getVersion().isAtLeast(baselineVersion)) .collect(Collectors.toMap( script -> script.getFileNameInfo().getVersion(), script -> script, (oldValue, newValue) -> newValue, - TreeMap::new)) - .values()); + TreeMap::new)); List history = new ArrayList<>(historyRepository.findAll()); - List res = new ArrayList<>(orderedScripts); - for (int i = 0; i < history.size(); i++) { - // do some checks - MigrationScriptProtocol protocol = history.get(i); - if (orderedScripts.size() <= i) { - logger.warn(String.format("there are less migration scripts than already executed history entries! " + - "You should never delete migration scripts you have already executed. " + - "Or maybe you have to cleanup the Elasticsearch-Evolution history index manually! " + - "history version at position %s is %s", i, protocol.getVersion())); - break; - } - ParsedMigrationScript parsedMigrationScript = orderedScripts.get(i); - if (!protocol.getVersion().equals(parsedMigrationScript.getFileNameInfo().getVersion())) { - throw new MigrationException(String.format( - "The logged execution in the Elasticsearch-Evolution history index at position %s " + - "is version %s and in the same position in the given migration scripts is version %s! " + - "Out of order execution is not supported. Or maybe you have added new migration scripts " + - "in between or have to cleanup the Elasticsearch-Evolution history index manually", - i, protocol.getVersion(), parsedMigrationScript.getFileNameInfo().getVersion())); - } - // failed scripts can be edited and retried, but successfully executed scripts may not be modified afterwards - if (validateOnMigrate && protocol.isSuccess() && protocol.getChecksum() != parsedMigrationScript.getChecksum()) { - throw new MigrationException(String.format( - "The logged execution for the migration script at position %s (%s) " + - "has a different checksum from the given migration script! " + - "Modifying already-executed scripts is not supported.", - i, protocol.getScriptName())); + List res = new ArrayList<>(scriptsInFilesystemMap.values()); + if (outOfOrder) { + for (MigrationScriptProtocol protocol : history) { + final ParsedMigrationScript parsedMigrationScript = scriptsInFilesystemMap.get(protocol.getVersion()); + if (null == parsedMigrationScript) { + logger.warn("there are less migration scripts than already executed history entries! " + + "You should never delete migration scripts you have already executed. " + + "Or maybe you have to cleanup the Elasticsearch-Evolution history index manually! " + + "Already executed history version {} is not present in migration files", protocol.getVersion()); + } else { + validateOnMigrateIfEnabled(protocol, parsedMigrationScript); + + if (protocol.isSuccess()) { + res.remove(parsedMigrationScript); + } + } } + } else { + List orderedScripts = new ArrayList<>(scriptsInFilesystemMap.values()); + for (int i = 0; i < history.size(); i++) { + // do some checks + MigrationScriptProtocol protocol = history.get(i); + if (orderedScripts.size() <= i) { + logger.warn("there are less migration scripts than already executed history entries! " + + "You should never delete migration scripts you have already executed. " + + "Or maybe you have to cleanup the Elasticsearch-Evolution history index manually! " + + "history version at position {} is {}", i, protocol.getVersion()); + break; + } + ParsedMigrationScript parsedMigrationScript = orderedScripts.get(i); + if (!protocol.getVersion().equals(parsedMigrationScript.getFileNameInfo().getVersion())) { + throw new MigrationException(String.format( + "The logged execution in the Elasticsearch-Evolution history index at position %s " + + "is version %s and in the same position in the given migration scripts is version %s! " + + "Out of order execution is not supported. Or maybe you have added new migration scripts " + + "in between or have to cleanup the Elasticsearch-Evolution history index manually", + i, protocol.getVersion(), parsedMigrationScript.getFileNameInfo().getVersion())); + } + validateOnMigrateIfEnabled(protocol, parsedMigrationScript); - if (protocol.isSuccess()) { - res.remove(parsedMigrationScript); + if (protocol.isSuccess()) { + res.remove(parsedMigrationScript); + } } } return res; } + private void validateOnMigrateIfEnabled(MigrationScriptProtocol protocol, ParsedMigrationScript parsedMigrationScript) { + // failed scripts can be edited and retried, but successfully executed scripts may not be modified afterward + if (validateOnMigrate && protocol.isSuccess() && protocol.getChecksum() != parsedMigrationScript.getChecksum()) { + throw new MigrationException(String.format( + "The logged execution for the migration script version %s (%s) " + + "has a different checksum from the given migration script! " + + "Modifying already-executed scripts is not supported.", + protocol.getVersion(), protocol.getScriptName())); + } + } + /** * wait until the elasticsearch-evolution history index is unlocked */ diff --git a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/migration/input/MigrationScriptReaderImpl.java b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/migration/input/MigrationScriptReaderImpl.java index 1e09899f..acbf6e8e 100644 --- a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/migration/input/MigrationScriptReaderImpl.java +++ b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/migration/input/MigrationScriptReaderImpl.java @@ -3,8 +3,9 @@ import com.senacor.elasticsearch.evolution.core.api.MigrationException; import com.senacor.elasticsearch.evolution.core.api.migration.MigrationScriptReader; import com.senacor.elasticsearch.evolution.core.internal.model.migration.RawMigrationScript; +import java.util.regex.Pattern; import org.reflections.Reflections; -import org.reflections.scanners.ResourcesScanner; +import org.reflections.scanners.Scanners; import org.reflections.util.ClasspathHelper; import org.reflections.util.ConfigurationBuilder; import org.reflections.util.FilterBuilder; @@ -147,10 +148,13 @@ private Stream readScriptsFromClassPath(String location) { resources = emptySet(); } else { Reflections reflections = new Reflections(new ConfigurationBuilder() - .setScanners(new ResourcesScanner()) + .setScanners(Scanners.Resources) .filterInputsBy(new FilterBuilder().includePackage(locationWithoutPrefixAsPackageNotation)) .setUrls(urls)); - resources = reflections.getResources(this::isValidFilename); + resources = reflections.getResources(Pattern.compile(esMigrationPrefix + ".*")) + .stream() + .filter(path -> isValidFilename(Paths.get(path).getFileName().toString())) + .collect(Collectors.toSet()); } return resources.stream().flatMap(resource -> { diff --git a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/model/MigrationVersion.java b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/model/MigrationVersion.java index fbe49775..c6d06169 100644 --- a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/model/MigrationVersion.java +++ b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/model/MigrationVersion.java @@ -31,7 +31,7 @@ public final class MigrationVersion implements Comparable { /** * Compiled pattern for matching proper version format */ - private static Pattern splitPattern = Pattern.compile("\\.(?=\\d)"); + private static final Pattern splitPattern = Pattern.compile("\\.(?=\\d)"); /** * The individual parts this version string is composed of. Ex. 1.2.3.4.0 -> [1, 2, 3, 4, 0] diff --git a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolutionIT.java b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolutionIT.java index 4c6c4627..6d23f9c5 100644 --- a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolutionIT.java +++ b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolutionIT.java @@ -33,6 +33,7 @@ import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.elasticsearch.client.RequestOptions.DEFAULT; @@ -157,4 +158,70 @@ void migrate_failed_then_fixed_script_and_re_execute(String versionInfo, EsUtils .allMatch(MigrationScriptProtocol::isSuccess); }); } + + @ParameterizedTest(name = "{0}") + @ArgumentsSource(ElasticsearchArgumentsProvider.class) + void migrate_outOfOrder_disabled_will_fail(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { + ElasticsearchEvolutionConfig elasticsearchEvolutionConfig = ElasticsearchEvolution.configure() + .setLocations(singletonList("classpath:es/ElasticsearchEvolutionTest/migrate_outOfOrder_1")); + String historyIndex = elasticsearchEvolutionConfig.getHistoryIndex(); + historyRepository = new HistoryRepositoryImpl(restHighLevelClient.getLowLevelClient(), historyIndex, new MigrationScriptProtocolMapper(), 1000, objectMapper); + + + assertSoftly(softly -> { + softly.assertThat(elasticsearchEvolutionConfig.load(restHighLevelClient.getLowLevelClient()).migrate()) + .as("# of successful executed scripts ") + .isEqualTo(2); + softly.assertThat(historyRepository.findAll()) + .as("# of historyIndex entries and all are successful") + .hasSize(2) + .allMatch(MigrationScriptProtocol::isSuccess); + }); + esUtils.refreshIndices(); + + + elasticsearchEvolutionConfig.setLocations(singletonList("classpath:es/ElasticsearchEvolutionTest/migrate_outOfOrder_2")); + + + final ElasticsearchEvolution underTest = elasticsearchEvolutionConfig.load(restHighLevelClient.getLowLevelClient()); + assertThatThrownBy(underTest::migrate) + .isInstanceOf(MigrationException.class) + .hasMessage("The logged execution in the Elasticsearch-Evolution history index at position 1 is version 3 and in the same position in the given migration scripts is version 2! Out of order execution is not supported. Or maybe you have added new migration scripts in between or have to cleanup the Elasticsearch-Evolution history index manually"); + } + + @ParameterizedTest(name = "{0}") + @ArgumentsSource(ElasticsearchArgumentsProvider.class) + void migrate_outOfOrder_enabled(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { + ElasticsearchEvolutionConfig elasticsearchEvolutionConfig = ElasticsearchEvolution.configure() + .setOutOfOrder(true) + .setLocations(singletonList("classpath:es/ElasticsearchEvolutionTest/migrate_outOfOrder_1")); + String historyIndex = elasticsearchEvolutionConfig.getHistoryIndex(); + historyRepository = new HistoryRepositoryImpl(restHighLevelClient.getLowLevelClient(), historyIndex, new MigrationScriptProtocolMapper(), 1000, objectMapper); + + + assertSoftly(softly -> { + softly.assertThat(elasticsearchEvolutionConfig.load(restHighLevelClient.getLowLevelClient()).migrate()) + .as("# of successful executed scripts ") + .isEqualTo(2); + softly.assertThat(historyRepository.findAll()) + .as("# of historyIndex entries and all are successful") + .hasSize(2) + .allMatch(MigrationScriptProtocol::isSuccess); + }); + esUtils.refreshIndices(); + + + elasticsearchEvolutionConfig.setLocations(singletonList("classpath:es/ElasticsearchEvolutionTest/migrate_outOfOrder_2")); + + + assertSoftly(softly -> { + softly.assertThat(elasticsearchEvolutionConfig.load(restHighLevelClient.getLowLevelClient()).migrate()) + .as("# of successful executed scripts ") + .isEqualTo(1); + softly.assertThat(historyRepository.findAll()) + .as("# of historyIndex entries and all are successful") + .hasSize(3) + .allMatch(MigrationScriptProtocol::isSuccess); + }); + } } \ No newline at end of file diff --git a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/api/config/ElasticsearchEvolutionConfigTest.java b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/api/config/ElasticsearchEvolutionConfigTest.java index 1403917f..ff5d0916 100644 --- a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/api/config/ElasticsearchEvolutionConfigTest.java +++ b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/api/config/ElasticsearchEvolutionConfigTest.java @@ -23,94 +23,132 @@ void defaultConfig() { @Test void noValidEncoding() { - assertThatThrownBy(() -> new ElasticsearchEvolutionConfig().validate().setEncoding(null).validate()) + final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig() + .setEncoding(null); + + assertThatThrownBy(config::validate) .isInstanceOf(NullPointerException.class) .hasMessage("encoding must not be null"); } @Test void noValidPlaceholders() { - assertThatThrownBy(() -> new ElasticsearchEvolutionConfig().validate().setPlaceholders(null).validate()) + final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig() + .setPlaceholders(null); + + assertThatThrownBy(config::validate) .isInstanceOf(NullPointerException.class) .hasMessage("placeholders must not be null"); } @Test void noValidLocations() { - assertThatThrownBy(() -> new ElasticsearchEvolutionConfig().validate().setLocations(Collections.emptyList()).validate()) + final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig() + .setLocations(Collections.emptyList()); + + assertThatThrownBy(config::validate) .isInstanceOf(IllegalStateException.class) .hasMessage("locations must not be empty"); } @Test void noValidEsMigrationPrefix() { - assertThatThrownBy(() -> new ElasticsearchEvolutionConfig().validate().setEsMigrationPrefix("").validate()) + final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig() + .setEsMigrationPrefix(""); + + assertThatThrownBy(config::validate) .isInstanceOf(IllegalStateException.class) .hasMessage("esMigrationPrefix must not be empty"); } @Test void noValidEsMigrationSuffixes() { - assertThatThrownBy(() -> new ElasticsearchEvolutionConfig().validate().setEsMigrationSuffixes(Collections.emptyList()).validate()) + final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig() + .setEsMigrationSuffixes(Collections.emptyList()); + + assertThatThrownBy(config::validate) .isInstanceOf(IllegalStateException.class) .hasMessage("esMigrationSuffixes must not be empty"); } @Test void noValidPlaceholderPrefix() { - assertThatThrownBy(() -> new ElasticsearchEvolutionConfig().validate().setPlaceholderPrefix("").validate()) + final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig() + .setPlaceholderPrefix(""); + + assertThatThrownBy(config::validate) .isInstanceOf(IllegalStateException.class) .hasMessage("placeholderPrefix must not be empty"); } @Test void noValidPlaceholderSuffix() { - assertThatThrownBy(() -> new ElasticsearchEvolutionConfig().validate().setPlaceholderSuffix("").validate()) + final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig() + .setPlaceholderSuffix(""); + + assertThatThrownBy(config::validate) .isInstanceOf(IllegalStateException.class) .hasMessage("placeholderSuffix must not be empty"); } @Test void placeholderNameMustNotContainPlaceholderSuffix() { - assertThatThrownBy(() -> new ElasticsearchEvolutionConfig().validate() - .setPlaceholders(Collections.singletonMap("x}x", "x")) - .validate()) + final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig() + .setPlaceholders(Collections.singletonMap("x}x", "x")); + + assertThatThrownBy(config::validate) .isInstanceOf(IllegalStateException.class) .hasMessage("placeholder name 'x}x' must not contain placeholderSuffix '}'"); } @Test void placeholderNameMustNotContainPlaceholderPrefix() { - assertThatThrownBy(() -> new ElasticsearchEvolutionConfig().validate() - .setPlaceholders(Collections.singletonMap("x${x", "x")) - .validate()) + final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig() + .setPlaceholders(Collections.singletonMap("x${x", "x")); + + assertThatThrownBy(config::validate) .isInstanceOf(IllegalStateException.class) .hasMessage("placeholder name 'x${x' must not contain placeholderPrefix '${'"); } @Test void placeholderValueMustNotContainPlaceholderSuffix() { - assertThatThrownBy(() -> new ElasticsearchEvolutionConfig().validate() - .setPlaceholders(Collections.singletonMap("x", "x}x")) - .validate()) + final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig() + .setPlaceholders(Collections.singletonMap("x", "x}x")); + + assertThatThrownBy(config::validate) .isInstanceOf(IllegalStateException.class) .hasMessage("placeholder value 'x}x' must not contain placeholderSuffix '}'"); } @Test void placeholderValueMustNotContainPlaceholderPrefix() { - assertThatThrownBy(() -> new ElasticsearchEvolutionConfig().validate() - .setPlaceholders(Collections.singletonMap("x", "x${x")) - .validate()) + final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig() + .setPlaceholders(Collections.singletonMap("x", "x${x")); + + assertThatThrownBy(config::validate) .isInstanceOf(IllegalStateException.class) .hasMessage("placeholder value 'x${x' must not contain placeholderPrefix '${'"); } @Test void noValidHistoryMaxQuerySize_mustBeGreaterThan0() { - assertThatThrownBy(() -> new ElasticsearchEvolutionConfig().validate().setHistoryMaxQuerySize(0).validate()) + final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig() + .setHistoryMaxQuerySize(0); + + assertThatThrownBy(config::validate) .isInstanceOf(IllegalStateException.class) .hasMessage("historyMaxQuerySize value '0' must be greater than 0"); } + + @Test + void baselineVersion_must_be_at_least_1() { + final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig() + .setBaselineVersion("0"); + + assertThatThrownBy(config::validate) + .isInstanceOf(IllegalStateException.class) + .hasMessage("baselineVersion '0' must be at least 1"); + } } } \ No newline at end of file diff --git a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImplIT.java b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImplIT.java index 74444d75..b2be6775 100644 --- a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImplIT.java +++ b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImplIT.java @@ -57,7 +57,7 @@ void OK_indexDocumentIsWrittenToElasticsearch(String versionInfo, EsUtils esUtil MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepositoryMock, 0, 0, restHighLevelClient.getLowLevelClient(), - defaultContentType, encoding, true, "1.0"); + defaultContentType, encoding, true, "1.0", false); MigrationScriptProtocol res = underTest.executeScript(script).getProtocol(); diff --git a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImplTest.java b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImplTest.java index 0d617462..a90dc400 100644 --- a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImplTest.java +++ b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImplTest.java @@ -46,13 +46,7 @@ import static org.junit.jupiter.api.Assertions.assertTimeout; import static org.mockito.Answers.RETURNS_DEEP_STUBS; import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.withSettings; +import static org.mockito.Mockito.*; /** * @author Andreas Keefer @@ -75,7 +69,7 @@ class waitUntilUnlocked { void noLockExists() { doReturn(false).when(historyRepository).isLocked(); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 2000, 2000, restClient, defaultContentType, encoding, true, "1.0"); + 2000, 2000, restClient, defaultContentType, encoding, true, "1.0", false); assertTimeout(Duration.ofSeconds(1), underTest::waitUntilUnlocked); @@ -88,9 +82,9 @@ void noLockExists() { void LockExistsAndGetsReleased() { doReturn(true, false).when(historyRepository).isLocked(); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 100, 100, restClient, defaultContentType, encoding, true, "1.0"); + 100, 100, restClient, defaultContentType, encoding, true, "1.0", false); - assertTimeout(Duration.ofMillis(200), underTest::waitUntilUnlocked); + assertTimeout(Duration.ofMillis(300), underTest::waitUntilUnlocked); InOrder order = inOrder(historyRepository); order.verify(historyRepository, times(2)).isLocked(); @@ -104,7 +98,7 @@ class getPendingScriptsToBeExecuted { void emptyHistory_allScriptsHaveToBeReturned() { doReturn(new TreeSet<>()).when(historyRepository).findAll(); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 0, 0, restClient, defaultContentType, encoding, true, "1.0"); + 0, 0, restClient, defaultContentType, encoding, true, "1.0", false); ParsedMigrationScript parsedMigrationScript1_1 = createParsedMigrationScript("1.1"); ParsedMigrationScript parsedMigrationScript1_0 = createParsedMigrationScript("1.0"); List parsedMigrationScripts = asList( @@ -128,7 +122,7 @@ void scriptsAndHistoryInSync_noScriptsWillBeReturned() { createMigrationScriptProtocol("1.1", true) ))).when(historyRepository).findAll(); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 0, 0, restClient, defaultContentType, encoding, true, "1.0"); + 0, 0, restClient, defaultContentType, encoding, true, "1.0", false); List parsedMigrationScripts = asList( createParsedMigrationScript("1.1"), @@ -149,7 +143,7 @@ void lastHistoryVersionWasFailing_AllScriptsInclFailedWillBeReturned() { createMigrationScriptProtocol("1.1", false) ))).when(historyRepository).findAll(); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 0, 0, restClient, defaultContentType, encoding, true, "1.0"); + 0, 0, restClient, defaultContentType, encoding, true, "1.0", false); ParsedMigrationScript parsedMigrationScript1_0 = createParsedMigrationScript("1.0"); ParsedMigrationScript parsedMigrationScript1_1 = createParsedMigrationScript("1.1"); @@ -174,7 +168,7 @@ void moreHistoryVersionsThanScripts_warningIsShownAnNoScriptsWillBeReturned() { createMigrationScriptProtocol("1.2", false) ))).when(historyRepository).findAll(); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 0, 0, restClient, defaultContentType, encoding, true, "1.0"); + 0, 0, restClient, defaultContentType, encoding, true, "1.0", false); ParsedMigrationScript parsedMigrationScript1_0 = createParsedMigrationScript("1.0"); ParsedMigrationScript parsedMigrationScript1_1 = createParsedMigrationScript("1.1"); @@ -191,13 +185,13 @@ void moreHistoryVersionsThanScripts_warningIsShownAnNoScriptsWillBeReturned() { } @Test - void outOfOrderExecutionIsNotSupported() { + void outOfOrderExecutionIsDisabled() { doReturn(new TreeSet<>(asList( createMigrationScriptProtocol("1.0", true), createMigrationScriptProtocol("1.1", true) ))).when(historyRepository).findAll(); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 0, 0, restClient, defaultContentType, encoding, true, "1.0"); + 0, 0, restClient, defaultContentType, encoding, true, "1.0", false); ParsedMigrationScript parsedMigrationScript1_0 = createParsedMigrationScript("1.0"); ParsedMigrationScript parsedMigrationScript1_0_1 = createParsedMigrationScript("1.0.1"); @@ -212,6 +206,72 @@ void outOfOrderExecutionIsNotSupported() { .hasMessage("The logged execution in the Elasticsearch-Evolution history index at position 1 is version 1.1 and in the same position in the given migration scripts is version 1.0.1! Out of order execution is not supported. Or maybe you have added new migration scripts in between or have to cleanup the Elasticsearch-Evolution history index manually"); } + @Test + void outOfOrderExecutionIsEnabled_happy_path() { + doReturn(new TreeSet<>(asList( + createMigrationScriptProtocol("1.0", true), + createMigrationScriptProtocol("1.1", true) + ))).when(historyRepository).findAll(); + MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, + 0, 0, restClient, defaultContentType, encoding, true, "1.0", true); + + ParsedMigrationScript parsedMigrationScript1_0 = createParsedMigrationScript("1.0"); + ParsedMigrationScript parsedMigrationScript1_0_1 = createParsedMigrationScript("1.0.1"); + ParsedMigrationScript parsedMigrationScript1_1 = createParsedMigrationScript("1.1"); + List parsedMigrationScripts = asList( + parsedMigrationScript1_1, + parsedMigrationScript1_0_1, + parsedMigrationScript1_0); + + final List pendingScriptsToBeExecuted = underTest.getPendingScriptsToBeExecuted(parsedMigrationScripts); + assertThat(pendingScriptsToBeExecuted) + .containsExactly(parsedMigrationScript1_0_1); + } + + @Test + void outOfOrderExecutionIsEnabled_missing_executed_migration_script() { + doReturn(new TreeSet<>(asList( + createMigrationScriptProtocol("1.0", true), + createMigrationScriptProtocol("1.1", true) + ))).when(historyRepository).findAll(); + MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, + 0, 0, restClient, defaultContentType, encoding, true, "1.0", true); + + ParsedMigrationScript parsedMigrationScript1_0_1 = createParsedMigrationScript("1.0.1"); + ParsedMigrationScript parsedMigrationScript1_1 = createParsedMigrationScript("1.1"); + List parsedMigrationScripts = asList( + parsedMigrationScript1_1, + parsedMigrationScript1_0_1); + + final List pendingScriptsToBeExecuted = underTest.getPendingScriptsToBeExecuted(parsedMigrationScripts); + assertThat(pendingScriptsToBeExecuted) + .containsExactly(parsedMigrationScript1_0_1); + } + + @Test + void outOfOrderExecutionIsEnabled_execute_failed_script() { + doReturn(new TreeSet<>(asList( + createMigrationScriptProtocol("1.0", false), + createMigrationScriptProtocol("1.1", true) + ))).when(historyRepository).findAll(); + MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, + 0, 0, restClient, defaultContentType, encoding, true, "1.0", true); + + ParsedMigrationScript parsedMigrationScript1_0 = createParsedMigrationScript("1.0"); + ParsedMigrationScript parsedMigrationScript1_0_1 = createParsedMigrationScript("1.0.1"); + ParsedMigrationScript parsedMigrationScript1_1 = createParsedMigrationScript("1.1"); + List parsedMigrationScripts = asList( + parsedMigrationScript1_1, + parsedMigrationScript1_0_1, + parsedMigrationScript1_0); + + final List pendingScriptsToBeExecuted = underTest.getPendingScriptsToBeExecuted(parsedMigrationScripts); + assertThat(pendingScriptsToBeExecuted) + .containsExactly( + parsedMigrationScript1_0, + parsedMigrationScript1_0_1); + } + @Test void failingScriptWasEdited_shouldReturnAllScriptsInclFailing() { doReturn(new TreeSet<>(asList( @@ -219,7 +279,7 @@ void failingScriptWasEdited_shouldReturnAllScriptsInclFailing() { createMigrationScriptProtocol("1.1", false, 2) ))).when(historyRepository).findAll(); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 0, 0, restClient, defaultContentType, encoding, true, "1.0"); + 0, 0, restClient, defaultContentType, encoding, true, "1.0", false); ParsedMigrationScript parsedMigrationScript1_0 = createParsedMigrationScript("1.0", 1); ParsedMigrationScript parsedMigrationScript1_1 = createParsedMigrationScript("1.1", 3); @@ -246,7 +306,7 @@ void successfulScriptWasEdited_shouldThrowChecksumMismatchException() { createMigrationScriptProtocol("1.1", true, 2) ))).when(historyRepository).findAll(); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 0, 0, restClient, defaultContentType, encoding, true, "1.0"); + 0, 0, restClient, defaultContentType, encoding, true, "1.0", false); ParsedMigrationScript parsedMigrationScript1_0 = createParsedMigrationScript("1.0", 1); ParsedMigrationScript parsedMigrationScript1_1 = createParsedMigrationScript("1.1", 3); @@ -256,9 +316,9 @@ void successfulScriptWasEdited_shouldThrowChecksumMismatchException() { assertThatThrownBy(() -> underTest.getPendingScriptsToBeExecuted(parsedMigrationScripts)) .isInstanceOf(MigrationException.class) - .hasMessage("The logged execution for the migration script at position 1 (V1.1__1.1.http) " + - "has a different checksum from the given migration script! " + - "Modifying already-executed scripts is not supported."); + .hasMessage("The logged execution for the migration script version 1.1 (V1.1__1.1.http) " + + "has a different checksum from the given migration script! " + + "Modifying already-executed scripts is not supported."); } @Test @@ -268,7 +328,7 @@ void successfulScriptWasEdited_shouldContinueIfValidateOnMigrateIsDisabled() { createMigrationScriptProtocol("1.1", true, 2) ))).when(historyRepository).findAll(); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 0, 0, restClient, defaultContentType, encoding, false, "1.0"); + 0, 0, restClient, defaultContentType, encoding, false, "1.0", false); ParsedMigrationScript parsedMigrationScript1_0 = createParsedMigrationScript("1.0", 1); ParsedMigrationScript parsedMigrationScript1_1 = createParsedMigrationScript("1.1", 3); @@ -278,7 +338,6 @@ void successfulScriptWasEdited_shouldContinueIfValidateOnMigrateIsDisabled() { List res = underTest.getPendingScriptsToBeExecuted(parsedMigrationScripts); - assertThat(res).hasSize(0); assertThat(res).isEmpty(); InOrder order = inOrder(historyRepository); order.verify(historyRepository).findAll(); @@ -289,7 +348,7 @@ void successfulScriptWasEdited_shouldContinueIfValidateOnMigrateIsDisabled() { void usingABaseline_onlyScriptsWithVersionHigherThanBaselineWillBeReturned() { doReturn(new TreeSet<>()).when(historyRepository).findAll(); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 0, 0, restClient, defaultContentType, encoding, true, "2.0"); + 0, 0, restClient, defaultContentType, encoding, true, "2.0", false); List parsedMigrationScripts = asList( createParsedMigrationScript("1.0"), @@ -297,8 +356,9 @@ void usingABaseline_onlyScriptsWithVersionHigherThanBaselineWillBeReturned() { List res = underTest.getPendingScriptsToBeExecuted(parsedMigrationScripts); - assertThat(res).hasSize(1); - assertThat(res).containsOnly(createParsedMigrationScript("2.0")); + assertThat(res) + .hasSize(1) + .containsOnly(createParsedMigrationScript("2.0")); InOrder order = inOrder(historyRepository); order.verify(historyRepository).findAll(); order.verifyNoMoreInteractions(); @@ -307,7 +367,7 @@ void usingABaseline_onlyScriptsWithVersionHigherThanBaselineWillBeReturned() { @Test void noPendingScriptsIfMigrationScriptListIsEmpty() { MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 0, 0, restClient, defaultContentType, encoding, true, "2.0"); + 0, 0, restClient, defaultContentType, encoding, true, "2.0", false); List res = underTest.getPendingScriptsToBeExecuted(emptyList()); @@ -325,7 +385,7 @@ void OK_resultIsSetCorrect() throws IOException { doReturn(responseMock).when(restClient).performRequest(any()); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 0, 0, restClient, defaultContentType, encoding, true, "1.0"); + 0, 0, restClient, defaultContentType, encoding, true, "1.0", false); MigrationScriptProtocol res = underTest.executeScript(script).getProtocol(); @@ -381,7 +441,7 @@ void OK_requestWithBody() throws IOException { doReturn(responseMock).when(restClient).performRequest(any()); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 0, 0, restClient, defaultContentType, encoding, true, "1.0"); + 0, 0, restClient, defaultContentType, encoding, true, "1.0", false); MigrationScriptProtocol res = underTest.executeScript(script).getProtocol(); @@ -421,7 +481,7 @@ void OK_requestWithCustomContentTypeAndDefaultCharset() throws IOException { doReturn(responseMock).when(restClient).performRequest(any()); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 0, 0, restClient, defaultContentType, encoding, true, "1.0"); + 0, 0, restClient, defaultContentType, encoding, true, "1.0", false); MigrationScriptProtocol res = underTest.executeScript(script).getProtocol(); @@ -460,7 +520,7 @@ void OK_requestWithCustomContentTypeAndCustomCharset() throws IOException { doReturn(responseMock).when(restClient).performRequest(any()); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 0, 0, restClient, defaultContentType, encoding, true, "1.0"); + 0, 0, restClient, defaultContentType, encoding, true, "1.0", false); MigrationScriptProtocol res = underTest.executeScript(script).getProtocol(); @@ -500,7 +560,7 @@ void OK_requestWithCustomHeader() throws IOException { doReturn(responseMock).when(restClient).performRequest(any()); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 0, 0, restClient, defaultContentType, encoding, true, "1.0"); + 0, 0, restClient, defaultContentType, encoding, true, "1.0", false); MigrationScriptProtocol res = underTest.executeScript(script).getProtocol(); @@ -536,7 +596,7 @@ void executeScript_failed_status(Exception handledError) throws IOException { doThrow(handledError).when(restClient).performRequest(any()); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 0, 0, restClient, defaultContentType, encoding, true, "1.0"); + 0, 0, restClient, defaultContentType, encoding, true, "1.0", false); ExecutionResult res = underTest.executeScript(script); @@ -555,7 +615,7 @@ void executeScript_failed_status(int httpStatusCode) throws IOException { doReturn(responseMock).when(restClient).performRequest(any()); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 0, 0, restClient, defaultContentType, encoding, true, "1.0"); + 0, 0, restClient, defaultContentType, encoding, true, "1.0", false); ExecutionResult res = underTest.executeScript(script); @@ -577,7 +637,7 @@ void executeScript_OK_status(int httpStatusCode) throws IOException { doReturn(responseMock).when(restClient).performRequest(any()); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 0, 0, restClient, defaultContentType, encoding, true, "1.0"); + 0, 0, restClient, defaultContentType, encoding, true, "1.0", false); MigrationScriptProtocol res = underTest.executeScript(script).getProtocol(); @@ -600,7 +660,7 @@ void allOK() throws IOException { Response responseMock = createResponseMock(200); doReturn(responseMock).when(restClient).performRequest(any()); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 0, 0, restClient, defaultContentType, encoding, true, "1.0"); + 0, 0, restClient, defaultContentType, encoding, true, "1.0", false); List res = underTest.executePendingScripts(scripts); @@ -633,7 +693,7 @@ void firstExecutionFailed() throws IOException { Response responseMock = createResponseMock(statusCode); doReturn(responseMock).when(restClient).performRequest(any()); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 0, 0, restClient, defaultContentType, encoding, true, "1.0"); + 0, 0, restClient, defaultContentType, encoding, true, "1.0", false); assertThatThrownBy(() -> underTest.executePendingScripts(scripts)) .isInstanceOf(MigrationException.class) @@ -666,7 +726,7 @@ void error_unlockWasNotSuccessful() throws IOException { Response responseMock = createResponseMock(200); doReturn(responseMock).when(restClient).performRequest(any()); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 0, 0, restClient, defaultContentType, encoding, true, "1.0"); + 0, 0, restClient, defaultContentType, encoding, true, "1.0", false); assertThatThrownBy(() -> underTest.executePendingScripts(scripts)) .isInstanceOf(MigrationException.class) @@ -696,7 +756,7 @@ void error_lockWasNotSuccessful() { doReturn(new TreeSet<>()).when(historyRepository).findAll(); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 0, 0, restClient, defaultContentType, encoding, true, "1.0"); + 0, 0, restClient, defaultContentType, encoding, true, "1.0", false); assertThatThrownBy(() -> underTest.executePendingScripts(scripts)) .isInstanceOf(MigrationException.class) @@ -714,7 +774,7 @@ void error_lockWasNotSuccessful() { @Test void emptyScriptsCollection() { MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 0, 0, restClient, defaultContentType, encoding, true, "1.0"); + 0, 0, restClient, defaultContentType, encoding, true, "1.0", false); List res = underTest.executePendingScripts(emptyList()); @@ -733,7 +793,7 @@ void noPendingScripts_shouldNotLockRepository() { createMigrationScriptProtocol("1.1", true) ))).when(historyRepository).findAll(); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, - 0, 0, restClient, defaultContentType, encoding, true, "1.0"); + 0, 0, restClient, defaultContentType, encoding, true, "1.0", false); List res = underTest.executePendingScripts(scripts); diff --git a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/test/EmbeddedElasticsearchExtension.java b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/test/EmbeddedElasticsearchExtension.java index de444477..8b6091b4 100644 --- a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/test/EmbeddedElasticsearchExtension.java +++ b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/test/EmbeddedElasticsearchExtension.java @@ -44,22 +44,15 @@ public class EmbeddedElasticsearchExtension implements TestInstancePostProcessor private static final Logger logger = LoggerFactory.getLogger(EmbeddedElasticsearchExtension.class); private static final Namespace NAMESPACE = Namespace.create(ExtensionContext.class); private static final SortedSet SUPPORTED_SEARCH_VERSIONS = Collections.unmodifiableSortedSet(new TreeSet<>(Arrays.asList( - ofOpensearch("2.5.0"), - ofOpensearch("2.4.1"), - ofOpensearch("2.3.0"), - ofOpensearch("2.2.1"), - ofOpensearch("2.1.0"), - ofOpensearch("2.0.1"), - ofOpensearch("1.3.8"), - - ofElasticsearch("8.6.1"), - ofElasticsearch("8.5.3"), - ofElasticsearch("8.4.3"), - ofElasticsearch("8.3.3"), - ofElasticsearch("8.2.3"), - ofElasticsearch("8.1.3"), - ofElasticsearch("8.0.1"), - ofElasticsearch("7.17.9") + ofOpensearch("2.11.0"), + ofOpensearch("2.10.0"), + ofOpensearch("2.9.0"), + ofOpensearch("1.3.12"), + + ofElasticsearch("8.11.1"), + ofElasticsearch("8.10.4"), + ofElasticsearch("8.9.1"), + ofElasticsearch("7.17.15") ))); @Override @@ -195,6 +188,7 @@ public static SearchContainer ofOpensearch(String version) { return SearchContainer.builder() .vendor("Opensearch") .vendorShort("OS") + // dockerhub and public.ecr.aws can run into rate limit, so stay with quay.io .containerImage("quay.io/xtermi2/opensearch") .version(version) .env(ImmutableMap.of( diff --git a/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_1/V001.00__createTemplateWithIndexMapping.http b/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_1/V001.00__createTemplateWithIndexMapping.http new file mode 100644 index 00000000..d236dfd9 --- /dev/null +++ b/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_1/V001.00__createTemplateWithIndexMapping.http @@ -0,0 +1,35 @@ +PUT _template/test_1 +Content-Type: application/json + +{ + "index_patterns": [ + "test_*" + ], + "order": 1, + "version": 1, + "settings": { + "number_of_shards": 1 + }, + "mappings": { + "dynamic": "strict", + "properties": { + "doc": { + "dynamic": false, + "properties": {} + }, + "searchable": { + "dynamic": false, + "properties": { + "version": { + "type": "keyword", + "ignore_above": 20, + "similarity": "boolean" + }, + "locked": { + "type": "boolean" + } + } + } + } + } +} \ No newline at end of file diff --git a/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_1/V003.00__extendIndexMappingViaTemplate.http b/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_1/V003.00__extendIndexMappingViaTemplate.http new file mode 100644 index 00000000..b62cdfdd --- /dev/null +++ b/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_1/V003.00__extendIndexMappingViaTemplate.http @@ -0,0 +1,38 @@ +PUT /_template/test_2 +Content-Type: application/json + +{ + "index_patterns": [ + "test_*" + ], + "order": 2, + "version": 2, + "mappings": { + "properties": { + "searchable": { + "properties": { +// new field + "success": { + "type": "boolean" + }, +// Multi-index: additional index with type text to already existing keyword index + "version": { + "type": "keyword", + "ignore_above": 20, + "similarity": "boolean", + "fields": { + "text": { + "type": "text", + "similarity": "boolean" + }, + "bm25": { + "type": "text", + "similarity": "BM25" + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_2/V001.00__createTemplateWithIndexMapping.http b/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_2/V001.00__createTemplateWithIndexMapping.http new file mode 100644 index 00000000..d236dfd9 --- /dev/null +++ b/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_2/V001.00__createTemplateWithIndexMapping.http @@ -0,0 +1,35 @@ +PUT _template/test_1 +Content-Type: application/json + +{ + "index_patterns": [ + "test_*" + ], + "order": 1, + "version": 1, + "settings": { + "number_of_shards": 1 + }, + "mappings": { + "dynamic": "strict", + "properties": { + "doc": { + "dynamic": false, + "properties": {} + }, + "searchable": { + "dynamic": false, + "properties": { + "version": { + "type": "keyword", + "ignore_above": 20, + "similarity": "boolean" + }, + "locked": { + "type": "boolean" + } + } + } + } + } +} \ No newline at end of file diff --git a/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_2/V002.00__addDocument.http b/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_2/V002.00__addDocument.http new file mode 100644 index 00000000..4f18ba0a --- /dev/null +++ b/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_2/V002.00__addDocument.http @@ -0,0 +1,17 @@ +PUT /test_1/_doc/1?refresh +Content-Type: application/json + +{ + "searchable": { + "version": "1", + "locked": false + }, + "doc": { + "version": "1", + "locked": false, + "success": true, + "a": "a a a", + "b": true, + "c": "c" + } +} \ No newline at end of file diff --git a/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_2/V003.00__extendIndexMappingViaTemplate.http b/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_2/V003.00__extendIndexMappingViaTemplate.http new file mode 100644 index 00000000..b62cdfdd --- /dev/null +++ b/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_2/V003.00__extendIndexMappingViaTemplate.http @@ -0,0 +1,38 @@ +PUT /_template/test_2 +Content-Type: application/json + +{ + "index_patterns": [ + "test_*" + ], + "order": 2, + "version": 2, + "mappings": { + "properties": { + "searchable": { + "properties": { +// new field + "success": { + "type": "boolean" + }, +// Multi-index: additional index with type text to already existing keyword index + "version": { + "type": "keyword", + "ignore_above": 20, + "similarity": "boolean", + "fields": { + "text": { + "type": "text", + "similarity": "boolean" + }, + "bm25": { + "type": "text", + "similarity": "BM25" + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/mvnw b/mvnw index 5643201c..8d937f4c 100755 --- a/mvnw +++ b/mvnw @@ -19,7 +19,7 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Maven Start Up Batch script +# Apache Maven Wrapper startup batch script, version 3.2.0 # # Required ENV vars: # ------------------ @@ -27,7 +27,6 @@ # # Optional ENV vars # ----------------- -# M2_HOME - location of maven2's installed home dir # MAVEN_OPTS - parameters passed to the Java VM when running Maven # e.g. to debug Maven itself, use # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @@ -54,7 +53,7 @@ fi cygwin=false; darwin=false; mingw=false -case "`uname`" in +case "$(uname)" in CYGWIN*) cygwin=true ;; MINGW*) mingw=true;; Darwin*) darwin=true @@ -62,9 +61,9 @@ case "`uname`" in # See https://developer.apple.com/library/mac/qa/qa1170/_index.html if [ -z "$JAVA_HOME" ]; then if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME else - export JAVA_HOME="/Library/Java/Home" + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME fi fi ;; @@ -72,68 +71,38 @@ esac if [ -z "$JAVA_HOME" ] ; then if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` + JAVA_HOME=$(java-config --jre-home) fi fi -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - # For Cygwin, ensure paths are in UNIX format before anything is touched if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") fi # For Mingw, ensure paths are in UNIX format before anything is touched if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" fi if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" else - javaExecutable="`readlink -f \"$javaExecutable\"`" + javaExecutable="$(readlink -f "\"$javaExecutable\"")" fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') JAVA_HOME="$javaHome" export JAVA_HOME fi @@ -149,7 +118,7 @@ if [ -z "$JAVACMD" ] ; then JAVACMD="$JAVA_HOME/bin/java" fi else - JAVACMD="`\\unset -f command; \\command -v java`" + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" fi fi @@ -163,12 +132,9 @@ if [ -z "$JAVA_HOME" ] ; then echo "Warning: JAVA_HOME environment variable is not set." fi -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - # traverses directory structure from process work directory to filesystem root # first directory with .mvn subdirectory is considered project base directory find_maven_basedir() { - if [ -z "$1" ] then echo "Path not specified to find_maven_basedir" @@ -184,96 +150,99 @@ find_maven_basedir() { fi # workaround for JBEAP-8937 (on Solaris 10/Sparc) if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` + wdir=$(cd "$wdir/.." || exit 1; pwd) fi # end of workaround done - echo "${basedir}" + printf '%s' "$(cd "$basedir" || exit 1; pwd)" } # concatenates all lines of a file concat_lines() { if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" fi } -BASE_DIR=`find_maven_basedir "$(pwd)"` +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") if [ -z "$BASE_DIR" ]; then exit 1; fi +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + ########################################################################################## # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # This allows using the maven wrapper in projects that prohibit checking in binary data. ########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." - fi + log "Couldn't find $wrapperJarPath, downloading it ..." + if [ -n "$MVNW_REPOURL" ]; then - jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" else - jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" fi - while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" - fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + if $cygwin; then - wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") fi if command -v wget > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" - fi + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" else - wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" fi elif command -v curl > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" - fi + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl -o "$wrapperJarPath" "$jarUrl" -f + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" else - curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" fi - else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" - fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" # For Cygwin, switch paths to Windows format before running javac if $cygwin; then - javaClass=`cygpath --path --windows "$javaClass"` + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") fi - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" fi fi fi @@ -282,35 +251,58 @@ fi # End of extension ########################################################################################## -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi fi + MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") fi # Provide a "standardized" way to retrieve the CLI args that will # work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" export MAVEN_CMD_LINE_ARGS WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain +# shellcheck disable=SC2086 # safe args exec "$JAVACMD" \ $MAVEN_OPTS \ $MAVEN_DEBUG_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" \ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd index 8a15b7f3..c4586b56 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -18,13 +18,12 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Maven Start Up Batch script +@REM Apache Maven Wrapper startup batch script, version 3.2.0 @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @REM @REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @@ -120,10 +119,10 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B ) @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central @@ -134,11 +133,11 @@ if exist %WRAPPER_JAR% ( ) ) else ( if not "%MVNW_REPOURL%" == "" ( - SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" ) if "%MVNW_VERBOSE%" == "true" ( echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% + echo Downloading from: %WRAPPER_URL% ) powershell -Command "&{"^ @@ -146,7 +145,7 @@ if exist %WRAPPER_JAR% ( "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ "}" if "%MVNW_VERBOSE%" == "true" ( echo Finished downloading %WRAPPER_JAR% @@ -154,6 +153,24 @@ if exist %WRAPPER_JAR% ( ) @REM End of extension +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + @REM Provide a "standardized" way to retrieve the CLI args that will @REM work with both Windows and non-Windows executions. set MAVEN_CMD_LINE_ARGS=%* diff --git a/pom.xml b/pom.xml index 74c812f8..2d8f00ef 100644 --- a/pom.xml +++ b/pom.xml @@ -5,12 +5,12 @@ com.senacor.elasticsearch.evolution elasticsearch-evolution-parent - 0.4.2 + 0.4.3 pom org.springframework.boot spring-boot-dependencies - 2.7.8 + 2.7.17 elasticsearch-evolution @@ -94,17 +94,19 @@ 4.3.0 0.8.8 - 3.0.1 - 3.4.1 + 3.1.0 + 3.6.2 1.6.13 - 2.11.0 + 2.15.0 7.5.2 - 0.9.12 - 1.17.6 - 1.18.26 + 0.10.2 + 1.19.1 + 1.18.30 + + 1.14.9 diff --git a/spring-boot-starter-elasticsearch-evolution/pom.xml b/spring-boot-starter-elasticsearch-evolution/pom.xml index b7c875ab..95585efb 100644 --- a/spring-boot-starter-elasticsearch-evolution/pom.xml +++ b/spring-boot-starter-elasticsearch-evolution/pom.xml @@ -6,7 +6,7 @@ com.senacor.elasticsearch.evolution elasticsearch-evolution-parent - 0.4.2 + 0.4.3 ../ spring-boot-starter-elasticsearch-evolution diff --git a/tests/migration-scripts/pom.xml b/tests/migration-scripts/pom.xml index eec8dde7..2b46b5d0 100644 --- a/tests/migration-scripts/pom.xml +++ b/tests/migration-scripts/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.senacor.elasticsearch.evolution migration-scripts - 0.4.2 + 0.4.3 jar containing migration files jar diff --git a/tests/pom.xml b/tests/pom.xml index 694435cc..7a499afe 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -6,7 +6,7 @@ com.senacor.elasticsearch.evolution elasticsearch-evolution-parent - 0.4.2 + 0.4.3 ../ tests @@ -37,6 +37,7 @@ test-spring-boot-3.0-scriptsInJarFile + test-spring-boot-3.1 diff --git a/tests/test-spring-boot-2.1-scriptsInJarFile/pom.xml b/tests/test-spring-boot-2.1-scriptsInJarFile/pom.xml index 854d6026..af66ca41 100644 --- a/tests/test-spring-boot-2.1-scriptsInJarFile/pom.xml +++ b/tests/test-spring-boot-2.1-scriptsInJarFile/pom.xml @@ -10,15 +10,15 @@ com.senacor.elasticsearch.evolution test-spring-boot-2.1-scriptsInJarFile - 0.4.2 + 0.4.3 Demo project for Spring Boot 1.8 - 2.11.0 + 2.15.0 7.5.2 - 1.17.6 + 1.19.1 diff --git a/tests/test-spring-boot-2.2/pom.xml b/tests/test-spring-boot-2.2/pom.xml index 324939f5..137a209c 100644 --- a/tests/test-spring-boot-2.2/pom.xml +++ b/tests/test-spring-boot-2.2/pom.xml @@ -10,15 +10,15 @@ com.senacor.elasticsearch.evolution test-spring-boot-2.2 - 0.4.2 + 0.4.3 Demo project for Spring Boot 1.8 - 2.11.0 + 2.15.0 7.5.2 - 1.17.6 + 1.19.1 @@ -77,7 +77,6 @@ org.jacoco jacoco-maven-plugin - 0.8.8 diff --git a/tests/test-spring-boot-2.3/pom.xml b/tests/test-spring-boot-2.3/pom.xml index 96d9f8c5..44bc4e82 100644 --- a/tests/test-spring-boot-2.3/pom.xml +++ b/tests/test-spring-boot-2.3/pom.xml @@ -10,15 +10,15 @@ com.senacor.elasticsearch.evolution test-spring-boot-2.3 - 0.4.2 + 0.4.3 Demo project for Spring Boot 1.8 - 2.11.0 + 2.15.0 7.5.2 - 1.17.6 + 1.19.1 diff --git a/tests/test-spring-boot-2.4/pom.xml b/tests/test-spring-boot-2.4/pom.xml index 5cb6ffe9..cd99aa2a 100644 --- a/tests/test-spring-boot-2.4/pom.xml +++ b/tests/test-spring-boot-2.4/pom.xml @@ -10,15 +10,15 @@ com.senacor.elasticsearch.evolution test-spring-boot-2.4 - 0.4.2 + 0.4.3 Demo project for Spring Boot 1.8 - 2.11.0 + 2.15.0 7.5.2 - 1.17.6 + 1.19.1 diff --git a/tests/test-spring-boot-2.5-scriptsInJarFile/pom.xml b/tests/test-spring-boot-2.5-scriptsInJarFile/pom.xml index d5f9ba04..d5337653 100644 --- a/tests/test-spring-boot-2.5-scriptsInJarFile/pom.xml +++ b/tests/test-spring-boot-2.5-scriptsInJarFile/pom.xml @@ -10,15 +10,15 @@ com.senacor.elasticsearch.evolution test-spring-boot-2.5-scriptsInJarFile - 0.4.2 + 0.4.3 Demo project for Spring Boot 1.8 - 2.11.0 + 2.15.0 7.5.2 - 1.17.6 + 1.19.1 diff --git a/tests/test-spring-boot-2.6/pom.xml b/tests/test-spring-boot-2.6/pom.xml index 8b35cb3a..1278176b 100644 --- a/tests/test-spring-boot-2.6/pom.xml +++ b/tests/test-spring-boot-2.6/pom.xml @@ -10,15 +10,15 @@ com.senacor.elasticsearch.evolution test-spring-boot-2.6 - 0.4.2 + 0.4.3 Demo project for Spring Boot 1.8 - 2.11.0 + 2.15.0 7.5.2 - 1.17.6 + 1.19.1 diff --git a/tests/test-spring-boot-2.7/pom.xml b/tests/test-spring-boot-2.7/pom.xml index e61d8a04..f0c652c6 100644 --- a/tests/test-spring-boot-2.7/pom.xml +++ b/tests/test-spring-boot-2.7/pom.xml @@ -5,20 +5,20 @@ org.springframework.boot spring-boot-starter-parent - 2.7.8 + 2.7.17 com.senacor.elasticsearch.evolution test-spring-boot-2.7 - 0.4.2 + 0.4.3 Demo project for Spring Boot 1.8 - 2.11.0 + 2.15.0 7.5.2 - 1.17.6 + 1.19.1 diff --git a/tests/test-spring-boot-3.0-scriptsInJarFile/pom.xml b/tests/test-spring-boot-3.0-scriptsInJarFile/pom.xml index 188cc04e..9cf1bf06 100644 --- a/tests/test-spring-boot-3.0-scriptsInJarFile/pom.xml +++ b/tests/test-spring-boot-3.0-scriptsInJarFile/pom.xml @@ -5,20 +5,20 @@ org.springframework.boot spring-boot-starter-parent - 3.0.2 + 3.0.12 com.senacor.elasticsearch.evolution test-spring-boot-3.0-scriptsInJarFile - 0.4.2 + 0.4.3 Demo project for Spring Boot 17 - 2.11.0 + 2.15.0 7.5.2 - 1.17.5 + 1.19.1 diff --git a/tests/test-spring-boot-3.1/pom.xml b/tests/test-spring-boot-3.1/pom.xml new file mode 100644 index 00000000..a3a0e6ae --- /dev/null +++ b/tests/test-spring-boot-3.1/pom.xml @@ -0,0 +1,107 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.1.5 + + + com.senacor.elasticsearch.evolution + test-spring-boot-3.1 + 0.4.3 + Demo project for Spring Boot + + + 17 + + 2.15.0 + 7.5.2 + 1.19.1 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + ${project.groupId} + spring-boot-starter-elasticsearch-evolution + ${project.version} + + + + + org.testcontainers + elasticsearch + ${testcontainers.elasticsearch.version} + test + + + commons-io + commons-io + ${commons-io.version} + test + + + + + + oss.sonatype.org-snapshot + https://oss.sonatype.org/content/repositories/snapshots + + false + + + true + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.jacoco + jacoco-maven-plugin + + 0.8.8 + + + prepare-agent + + prepare-agent + + + + report + post-integration-test + + report + + + + + + maven-surefire-plugin + + true + + + + + + diff --git a/tests/test-spring-boot-3.1/src/main/java/com/senacor/elasticsearch/evolution/springboot31/Application.java b/tests/test-spring-boot-3.1/src/main/java/com/senacor/elasticsearch/evolution/springboot31/Application.java new file mode 100644 index 00000000..54d8a3f3 --- /dev/null +++ b/tests/test-spring-boot-3.1/src/main/java/com/senacor/elasticsearch/evolution/springboot31/Application.java @@ -0,0 +1,14 @@ +package com.senacor.elasticsearch.evolution.springboot31; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + +} \ No newline at end of file diff --git a/tests/test-spring-boot-3.1/src/main/resources/es/migration/V001.00__createTemplateWithIndexMapping.http b/tests/test-spring-boot-3.1/src/main/resources/es/migration/V001.00__createTemplateWithIndexMapping.http new file mode 100644 index 00000000..fbd7df47 --- /dev/null +++ b/tests/test-spring-boot-3.1/src/main/resources/es/migration/V001.00__createTemplateWithIndexMapping.http @@ -0,0 +1,35 @@ +PUT _template/test_1 +Content-Type: application/json + +{ + "index_patterns" : [ + "test_*" + ], + "order" : 1, + "version" : 1, + "settings" : { + "number_of_shards" : 1 + }, + "mappings" : { + "dynamic" : "strict", + "properties" : { + "doc" : { + "dynamic" : false, + "properties" : {} + }, + "searchable" : { + "dynamic" : false, + "properties" : { + "version" : { + "type" : "keyword", + "ignore_above" : 20, + "similarity" : "boolean" + }, + "locked" : { + "type" : "boolean" + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/test-spring-boot-3.1/src/main/resources/es/migration/V001.01__addDocument.http b/tests/test-spring-boot-3.1/src/main/resources/es/migration/V001.01__addDocument.http new file mode 100644 index 00000000..4f18ba0a --- /dev/null +++ b/tests/test-spring-boot-3.1/src/main/resources/es/migration/V001.01__addDocument.http @@ -0,0 +1,17 @@ +PUT /test_1/_doc/1?refresh +Content-Type: application/json + +{ + "searchable": { + "version": "1", + "locked": false + }, + "doc": { + "version": "1", + "locked": false, + "success": true, + "a": "a a a", + "b": true, + "c": "c" + } +} \ No newline at end of file diff --git a/tests/test-spring-boot-3.1/src/test/java/com/senacor/elasticsearch/evolution/springboot31/ApplicationTests.java b/tests/test-spring-boot-3.1/src/test/java/com/senacor/elasticsearch/evolution/springboot31/ApplicationTests.java new file mode 100644 index 00000000..80f85dc6 --- /dev/null +++ b/tests/test-spring-boot-3.1/src/test/java/com/senacor/elasticsearch/evolution/springboot31/ApplicationTests.java @@ -0,0 +1,66 @@ +package com.senacor.elasticsearch.evolution.springboot31; + +import com.github.dockerjava.api.command.InspectContainerResponse; +import org.apache.http.HttpHost; +import org.elasticsearch.client.RestClient; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.testcontainers.elasticsearch.ElasticsearchContainer; +import org.testcontainers.utility.DockerImageName; + +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(properties = {"spring.elasticsearch.uris=http://localhost:" + ApplicationTests.ELASTICSEARCH_PORT}) +class ApplicationTests { + + static final int ELASTICSEARCH_PORT = 18773; + + @Autowired + private EsUtils esUtils; + + @Test + void contextLoads() { + esUtils.refreshIndices(); + + List documents = esUtils.fetchAllDocuments("test_1"); + + assertThat(documents).hasSize(1); + } + + @TestConfiguration + static class Config { + @Bean(destroyMethod = "stop") + public ElasticsearchContainer elasticsearchContainer(@Value("${elasticsearch.version:7.5.2}") String esVersion) { + ElasticsearchContainer container = new ElasticsearchContainer(DockerImageName + .parse("docker.elastic.co/elasticsearch/elasticsearch") + .withTag(esVersion)) { + @Override + protected void containerIsStarted(InspectContainerResponse containerInfo) { + // since testcontainers 1.17 it detects if ES 8.x is running and copies a certificate in this case + // but we don't want security + } + } + .withEnv("ES_JAVA_OPTS", "-Xms128m -Xmx128m") + // since elasticsearch 8 security / https is enabled per default - but for testing it should be disabled + .withEnv("xpack.security.enabled", "false") + .withEnv("cluster.routing.allocation.disk.watermark.low", "97%") + .withEnv("cluster.routing.allocation.disk.watermark.high", "98%") + .withEnv("cluster.routing.allocation.disk.watermark.flood_stage", "99%"); + container.setPortBindings(Collections.singletonList(ELASTICSEARCH_PORT + ":9200")); + container.start(); + return container; + } + + @Bean + public EsUtils esUtils(ElasticsearchContainer elasticsearchContainer) { + return new EsUtils(RestClient.builder(HttpHost.create(elasticsearchContainer.getHttpHostAddress())).build()); + } + } +} \ No newline at end of file diff --git a/tests/test-spring-boot-3.1/src/test/java/com/senacor/elasticsearch/evolution/springboot31/EsUtils.java b/tests/test-spring-boot-3.1/src/test/java/com/senacor/elasticsearch/evolution/springboot31/EsUtils.java new file mode 100644 index 00000000..4c69d697 --- /dev/null +++ b/tests/test-spring-boot-3.1/src/test/java/com/senacor/elasticsearch/evolution/springboot31/EsUtils.java @@ -0,0 +1,72 @@ +package com.senacor.elasticsearch.evolution.springboot31; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.io.IOUtils; +import org.elasticsearch.client.Request; +import org.elasticsearch.client.Response; +import org.elasticsearch.client.RestClient; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * @author Andreas Keefer + */ +public class EsUtils { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private final RestClient restClient; + + public EsUtils(RestClient restClient) { + this.restClient = restClient; + } + + public void refreshIndices() { + try { + restClient.performRequest(new Request("GET", "/_refresh")); + } catch (IOException e) { + throw new IllegalStateException("refreshIndices failed", e); + } + } + + public List fetchAllDocuments(String index) { + try { + Request post = new Request("POST", "/" + index + "/_search"); + post.setJsonEntity("{" + + " \"query\": {" + + " \"match_all\": {}" + + " }" + + "}"); + Response response = restClient.performRequest(post); + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode < 200 || statusCode >= 300) { + throw new IllegalStateException("fetchAllDocuments(" + index + ") failed with HTTP status " + + statusCode + ": " + response.toString()); + } + String body = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + + return parseDocuments(body) + .collect(Collectors.toList()); + } catch (IOException e) { + throw new IllegalStateException("fetchAllDocuments(" + index + ") failed", e); + } + } + + private Stream parseDocuments(String body) { + try { + JsonNode jsonNode = OBJECT_MAPPER.readTree(body); + return StreamSupport.stream(jsonNode.get("hits").get("hits").spliterator(), false) + .map(hitNode -> hitNode.get("_source")) + .map(JsonNode::toString); + } catch (IOException e) { + throw new IllegalStateException("parseDocuments failed. body=" + body, e); + } + } +} +