From 3e429040711640a18dd30cb9972ef1cea0251f0d Mon Sep 17 00:00:00 2001 From: Enrico Risa Date: Fri, 21 Apr 2023 14:40:59 +0200 Subject: [PATCH] Align the preview_0_4_x branch (#268) * chore(deps): bump org.flywaydb:flyway-core from 9.15.2 to 9.16.3 (#242) * chore(deps): bump com.google.code.gson:gson from 2.10 to 2.10.1 (#243) Bumps [com.google.code.gson:gson](https://github.com/google/gson) from 2.10 to 2.10.1. - [Release notes](https://github.com/google/gson/releases) - [Changelog](https://github.com/google/gson/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/gson/compare/gson-parent-2.10...gson-parent-2.10.1) --- updated-dependencies: - dependency-name: com.google.code.gson:gson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * refactor: update GitHub output command to current version (#233) * refactor GitHub output command to current version * Remove curly braces from output statement * fix: only run trivy when docker images were actually built (#240) * fix: run trivy only if image exists * update checks * refactor: Extract the setup-java action into a re-usable action (#246) * Extract the checkout and setup-java action into a re-usable action * Commit actions. * fix action * remove checkout extraction * feat(BusinessPartnerValidation): adds logging if it's enabled on contract agreement validation (#245) * feat(BusinessPartnerValidation): adds logging if it's enabled on contract agreement validation * feat(BusinessPartnerValidation): adds logging on tests * feat(BusinessPartnerValidation): enabled by default on charts config * pr remarks * release-fix: use correct value * Release version 0.3.3 (#249) * Generate OpenApi Spec * feat(baseImage): replace alpine with temurin as base image for running java application * Lint and refactor mostly all *.md files * Lint new changes from develop branch * Replace appearance of product-edc with tractusx-edc * Fix README.md and Transfer Data.md * Fix Transfer Data.md * Regenerate helm chart README.md files * Remove left over html tags from root REAMDE.md * Add empty line at EOF * Update CODE_OF_CONDUCT.md * Retrigger ci * Release: fix version handling * Prepare release 0.3.1 * Cherry-picked upstream commits (QGate stuff) in preparation for the 0.3.1 release * fix: use snapshot version after publish workflow * docs: add additional info for running business tests locally * feat(CI): add Markdown linter * md lint fix * pr remarks * Apply suggestions from code review Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * Update .github/workflows/verify.yaml Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * chore(md-linting): Fix markdown lint * fix: make AZKV clientsecret or certificate mutually exclusive * revert pointless blanks * fix: use correct paths for GH Packages docker reg. * fix: only dockerize if a dockerfile exists * chore: use old repo URL for Maven publication * fix: use PAT to publish to CXNG product-edc repo * PR Remarks * fix: remove duplicated code fragment in CHANGELOG * feat: removed backend service, replaced with JVM runner test moved consumer EDR controller to runtime module * docs: create decision record about renaming git branches * removed obsolete HTTP test * feat(charts): removes edc-controlplane and edc-dataplane charts * Update docs/development/decision-records/2023-04-03_renaming_branches/README.md Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * Update docs/development/decision-records/2023-04-03_renaming_branches/README.md Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * feat(dataEncryption): removes lombok from data-encryption module * Update edc-extensions/data-encryption/src/test/java/org/eclipse/tractusx/edc/data/encryption/algorithms/aes/AesAlgorithmTest.java Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * Fix issue with sql pool * fix: add newline to file * chore(deps): bump alpine Bumps alpine from 3.17.2 to 3.17.3. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * chore(deps): bump actions/setup-java from 3.10.0 to 3.11.0 Bumps [actions/setup-java](https://github.com/actions/setup-java) from 3.10.0 to 3.11.0. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v3.10.0...v3.11.0) --- updated-dependencies: - dependency-name: actions/setup-java dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump alpine Bumps alpine from 3.17.2 to 3.17.3. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * chore(deps): bump alpine Bumps alpine from 3.17.2 to 3.17.3. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * chore(deps): bump alpine Bumps alpine from 3.17.2 to 3.17.3. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * chore(deps): bump alpine Bumps alpine from 3.17.2 to 3.17.3. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * docs: create decision-record about refactoring helm charts * chore(deps): bump crazy-max/ghaction-import-gpg from 1 to 5 Bumps [crazy-max/ghaction-import-gpg](https://github.com/crazy-max/ghaction-import-gpg) from 1 to 5. - [Release notes](https://github.com/crazy-max/ghaction-import-gpg/releases) - [Changelog](https://github.com/crazy-max/ghaction-import-gpg/blob/v5/CHANGELOG.md) - [Commits](https://github.com/crazy-max/ghaction-import-gpg/compare/v1...v5) --- updated-dependencies: - dependency-name: crazy-max/ghaction-import-gpg dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * chore(deps): bump helm/chart-testing-action from 2.3.1 to 2.4.0 Bumps [helm/chart-testing-action](https://github.com/helm/chart-testing-action) from 2.3.1 to 2.4.0. - [Release notes](https://github.com/helm/chart-testing-action/releases) - [Commits](https://github.com/helm/chart-testing-action/compare/v2.3.1...v2.4.0) --- updated-dependencies: - dependency-name: helm/chart-testing-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump mikefarah/yq from 4.31.2 to 4.33.3 Bumps [mikefarah/yq](https://github.com/mikefarah/yq) from 4.31.2 to 4.33.3. - [Release notes](https://github.com/mikefarah/yq/releases) - [Changelog](https://github.com/mikefarah/yq/blob/master/release_notes.txt) - [Commits](https://github.com/mikefarah/yq/compare/v4.31.2...v4.33.3) --- updated-dependencies: - dependency-name: mikefarah/yq dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * feature: publish docker images to DockerHub * add manual docker-publish workflow * avoid input params, add concurrency * add checkout action * creds as action inputs * add jar build step * make namespace overridable * updated notices * incorporate new docker publish flow * update chart deployment specs * fix formatting * markdown lint * fix workflow * remove image namespace * prevent all interaction with dockerhub on pull requests * docs: add technical committer to pr_etiquette.md (#182) * chore: update to temurin 17 (#212) * chore: update dockerfiles and GH Actions to temurin 17 * pin specific version * feat(tests): removes lombok from edc-tests module (#159) * chore: add a template for pull request descriptions (#213) * fix: Adapt Helm Chart for version 0.3.x (#211) * Adapt Charts for version 0.3.x * fix business-tests * add edc.receiver.http.dynamic.endpoint * fix business-tests * code-review findings * refactor: rename git branches (#218) * refactor: update branch names and references in our documentation * publish packages to tractus-x * chore(deps): bump io.cucumber:cucumber-junit-platform-engine from 7.11.1 to 7.11.2 (#221) * refactor: rename git branches (#218) * refactor: update branch names and references in our documentation * publish packages to tractus-x * chore(deps): bump io.cucumber:cucumber-junit-platform-engine Bumps [io.cucumber:cucumber-junit-platform-engine](https://github.com/cucumber/cucumber-jvm) from 7.11.1 to 7.11.2. - [Release notes](https://github.com/cucumber/cucumber-jvm/releases) - [Changelog](https://github.com/cucumber/cucumber-jvm/blob/main/CHANGELOG.md) - [Commits](https://github.com/cucumber/cucumber-jvm/compare/v7.11.1...v7.11.2) --- updated-dependencies: - dependency-name: io.cucumber:cucumber-junit-platform-engine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --------- Signed-off-by: dependabot[bot] Co-authored-by: Paul Latzelsperger <43503240+paullatzelsperger@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump io.cucumber:cucumber-java from 7.11.1 to 7.11.2 (#225) Bumps [io.cucumber:cucumber-java](https://github.com/cucumber/cucumber-jvm) from 7.11.1 to 7.11.2. - [Release notes](https://github.com/cucumber/cucumber-jvm/releases) - [Changelog](https://github.com/cucumber/cucumber-jvm/blob/main/CHANGELOG.md) - [Commits](https://github.com/cucumber/cucumber-jvm/compare/v7.11.1...v7.11.2) --- updated-dependencies: - dependency-name: io.cucumber:cucumber-java dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump org.testcontainers:junit-jupiter from 1.17.6 to 1.18.0 (#224) Bumps [org.testcontainers:junit-jupiter](https://github.com/testcontainers/testcontainers-java) from 1.17.6 to 1.18.0. - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.17.6...1.18.0) --- updated-dependencies: - dependency-name: org.testcontainers:junit-jupiter dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump com.bmuschko.docker-remote-api from 9.2.1 to 9.3.1 (#222) Bumps com.bmuschko.docker-remote-api from 9.2.1 to 9.3.1. --- updated-dependencies: - dependency-name: com.bmuschko.docker-remote-api dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump org.testcontainers:vault from 1.17.6 to 1.18.0 (#223) Bumps [org.testcontainers:vault](https://github.com/testcontainers/testcontainers-java) from 1.17.6 to 1.18.0. - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.17.6...1.18.0) --- updated-dependencies: - dependency-name: org.testcontainers:vault dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Paul Latzelsperger <43503240+paullatzelsperger@users.noreply.github.com> * docs(control-plane-adapter): improve documentation on how to use the control-plane adapter extension (#210) * feature: create in-mem helm chart (#219) * feature: create the tractusx-connector-memory chart * pr remarks * pr remarks * increase waiting for negotiation, sometimes takes longer then 2 seconds * Apply suggestions from code review Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * pr remarks * Update charts/tractusx-connector-memory/templates/deployment-runtime.yaml Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) --------- Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * chore(deps): bump org.slf4j:slf4j-api from 2.0.3 to 2.0.7 (#234) Bumps [org.slf4j:slf4j-api](https://github.com/qos-ch/slf4j) from 2.0.3 to 2.0.7. - [Release notes](https://github.com/qos-ch/slf4j/releases) - [Commits](https://github.com/qos-ch/slf4j/compare/v_2.0.3...v_2.0.7) --- updated-dependencies: - dependency-name: org.slf4j:slf4j-api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump com.azure:azure-security-keyvault-secrets (#235) Bumps [com.azure:azure-security-keyvault-secrets](https://github.com/Azure/azure-sdk-for-java) from 4.5.4 to 4.6.0. - [Release notes](https://github.com/Azure/azure-sdk-for-java/releases) - [Commits](https://github.com/Azure/azure-sdk-for-java/compare/azure-security-keyvault-keys_4.5.4...azure-cosmos_4.6.0) --- updated-dependencies: - dependency-name: com.azure:azure-security-keyvault-secrets dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump com.diffplug.spotless from 6.15.0 to 6.18.0 (#236) Bumps com.diffplug.spotless from 6.15.0 to 6.18.0. --- updated-dependencies: - dependency-name: com.diffplug.spotless dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump com.github.johnrengelman.shadow from 8.0.0 to 8.1.1 (#237) * chore(deps): bump io.freefair.lombok from 6.6.2 to 8.0.1 (#238) * chore(deps): bump org.flywaydb:flyway-core from 9.15.2 to 9.16.3 (#242) * chore(deps): bump com.google.code.gson:gson from 2.10 to 2.10.1 (#243) Bumps [com.google.code.gson:gson](https://github.com/google/gson) from 2.10 to 2.10.1. - [Release notes](https://github.com/google/gson/releases) - [Changelog](https://github.com/google/gson/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/gson/compare/gson-parent-2.10...gson-parent-2.10.1) --- updated-dependencies: - dependency-name: com.google.code.gson:gson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * refactor: update GitHub output command to current version (#233) * refactor GitHub output command to current version * Remove curly braces from output statement * fix: only run trivy when docker images were actually built (#240) * fix: run trivy only if image exists * update checks * refactor: Extract the setup-java action into a re-usable action (#246) * Extract the checkout and setup-java action into a re-usable action * Commit actions. * fix action * remove checkout extraction * feat(BusinessPartnerValidation): adds logging if it's enabled on contract agreement validation (#245) * feat(BusinessPartnerValidation): adds logging if it's enabled on contract agreement validation * feat(BusinessPartnerValidation): adds logging on tests * feat(BusinessPartnerValidation): enabled by default on charts config * pr remarks * release-fix: use correct value * Prepare release 0.3.3 --------- Signed-off-by: dependabot[bot] Co-authored-by: Tuncay Tunc Co-authored-by: Enrico Risa Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) Co-authored-by: Sebastian Bezold Co-authored-by: Paul Latzelsperger Co-authored-by: GitHub actions Co-authored-by: Stephan Bauer Co-authored-by: Paul Latzelsperger <43503240+paullatzelsperger@users.noreply.github.com> Co-authored-by: Sigi <47592287+Siegfriedk@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Tuncay Tunc (ZF Friedrichshafen AG) <100704677+tuncaytunc-zf@users.noreply.github.com> Co-authored-by: Sascha Isele (ZF Friedrichshafen AG) <127207440+saschaisele-zf@users.noreply.github.com> Co-authored-by: Garrett Smith <42892027+gcs14@users.noreply.github.com> * release-fix: allow manual entry of Docker tag * chore: Add 0.3.3 to, and fix markdown in CHANGELOG.md (#252) * docs: add decision record about conventional commits (#255) * chore: Add 0.3.3 to, and fix markdown in CHANGELOG.md (#252) * docs: add decision record about conventional commits * fix: README.md points to wrong helm chart (#261) * Fix wrong helm install command * Update README.md * feature: add explicit docker image creation during release process (#251) * chore: Add 0.3.3 to, and fix markdown in CHANGELOG.md (#252) * feat(release): add explicit docker build job to release * simplify matrix * build(deps): add constraints to avoid vulnerable transitive dependencies (#259) * chore: Rename Veracode appname in CI job (#265) Co-authored-by: Paul Latzelsperger <43503240+paullatzelsperger@users.noreply.github.com> * fix: Typo in veracode action (#267) * Adapt Postman collection for 0.3.x (#232) --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Garrett Smith <42892027+gcs14@users.noreply.github.com> Co-authored-by: Paul Latzelsperger <43503240+paullatzelsperger@users.noreply.github.com> Co-authored-by: Tuncay Tunc (ZF Friedrichshafen AG) <100704677+tuncaytunc-zf@users.noreply.github.com> Co-authored-by: Paul Latzelsperger Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Tuncay Tunc Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) Co-authored-by: Sebastian Bezold Co-authored-by: GitHub actions Co-authored-by: Stephan Bauer Co-authored-by: Sigi <47592287+Siegfriedk@users.noreply.github.com> Co-authored-by: Sascha Isele (ZF Friedrichshafen AG) <127207440+saschaisele-zf@users.noreply.github.com> Co-authored-by: ndr_brt --- .../actions/publish-docker-image/action.yml | 17 +- .../actions/run-deployment-test/action.yml | 10 +- .github/actions/setup-java/action.yml | 32 ++ .github/workflows/build.yaml | 77 ++-- .github/workflows/business-tests.yaml | 10 +- .github/workflows/deployment-test.yaml | 3 +- .github/workflows/draft-new-release.yaml | 9 +- .github/workflows/helm-chart-release.yaml | 3 +- .github/workflows/helm-lint.yaml | 3 +- .github/workflows/publish-docker.yaml | 49 +-- .github/workflows/publish-new-release.yml | 50 ++- .github/workflows/trivy.yml | 21 +- .github/workflows/veracode.yaml | 94 +---- .github/workflows/verify.yaml | 64 +--- CHANGELOG.md | 17 +- charts/tractusx-connector-memory/Chart.yaml | 4 +- charts/tractusx-connector-memory/README.md | 352 +++++++----------- .../README.md.gotmpl | 2 +- .../templates/deployment-runtime.yaml | 8 +- charts/tractusx-connector-memory/values.yaml | 3 + charts/tractusx-connector/Chart.yaml | 4 +- charts/tractusx-connector/README.md | 31 +- .../templates/deployment-controlplane.yaml | 6 + charts/tractusx-connector/values.yaml | 3 + .../2023-04-20_conventional_commits/README.md | 43 +++ docs/development/postman/collection.json | 258 +++++++------ .../development/postman/images/screenshot.png | Bin 77083 -> 0 bytes .../build.gradle.kts | 5 + .../build.gradle.kts | 5 + .../build.gradle.kts | 2 +- .../BusinessPartnerValidationExtension.java | 126 ++++--- .../AbstractBusinessPartnerValidation.java | 231 ++++++------ .../BusinessPartnerDutyFunction.java | 20 +- .../BusinessPartnerPermissionFunction.java | 22 +- .../BusinessPartnerProhibitionFunction.java | 22 +- ...usinessPartnerValidationExtensionTest.java | 23 ++ ...AbstractBusinessPartnerValidationTest.java | 239 +++++++----- .../control-plane-adapter/build.gradle.kts | 7 + .../postgresql-migration/build.gradle.kts | 2 +- edc-tests/cucumber/build.gradle.kts | 2 +- .../edc/lifecycle/MultiRuntimeTest.java | 2 + .../tractusx/edc/lifecycle/Participant.java | 4 + .../tests/HttpConsumerPullWithProxyTest.java | 6 +- gradle.properties | 2 +- settings.gradle.kts | 36 +- 45 files changed, 974 insertions(+), 955 deletions(-) create mode 100644 .github/actions/setup-java/action.yml create mode 100644 docs/development/decision-records/2023-04-20_conventional_commits/README.md delete mode 100644 docs/development/postman/images/screenshot.png diff --git a/.github/actions/publish-docker-image/action.yml b/.github/actions/publish-docker-image/action.yml index 206e13d4c..a252bf108 100644 --- a/.github/actions/publish-docker-image/action.yml +++ b/.github/actions/publish-docker-image/action.yml @@ -38,11 +38,13 @@ inputs: docker_token: required: false description: "DockerHub Token. No push is done if omitted" + docker_tag: + required: false + description: 'additional docker tags' runs: using: "composite" steps: - - name: Checkout - uses: actions/checkout@v3 + - uses: actions/checkout@v3.3.0 ##################### # Login to DockerHub @@ -56,12 +58,7 @@ runs: ##################### # Build JAR file ##################### - - name: Set up JDK 11 - uses: actions/setup-java@v3.11.0 - with: - java-version: '17' - distribution: 'temurin' - cache: 'gradle' + - uses: ./.github/actions/setup-java - name: Build Controlplane shell: bash run: |- @@ -78,9 +75,7 @@ runs: images: | ${{ inputs.namespace }}/${{ inputs.imagename }} tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} + type=semver,pattern={{version}},value=${{ inputs.docker_tag }} type=semver,pattern={{major}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{raw}} diff --git a/.github/actions/run-deployment-test/action.yml b/.github/actions/run-deployment-test/action.yml index ed720b4be..9f4b40d58 100644 --- a/.github/actions/run-deployment-test/action.yml +++ b/.github/actions/run-deployment-test/action.yml @@ -42,8 +42,7 @@ inputs: runs: using: "composite" steps: - - name: Checkout - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v3.3.0 - name: Cache ContainerD Image Layers uses: actions/cache@v3 @@ -51,12 +50,7 @@ runs: path: /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs key: ${{ runner.os }}-io.containerd.snapshotter.v1.overlayfs - - name: Set up JDK 11 - uses: actions/setup-java@v3.11.0 - with: - java-version: '11' - distribution: 'temurin' - cache: 'gradle' + - uses: ./.github/actions/setup-java - name: Build docker images shell: bash diff --git a/.github/actions/setup-java/action.yml b/.github/actions/setup-java/action.yml new file mode 100644 index 000000000..ed03fafb3 --- /dev/null +++ b/.github/actions/setup-java/action.yml @@ -0,0 +1,32 @@ +# +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (c) 2023 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License, Version 2.0 which is available at +# https://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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +--- +name: "Setup JDK 17" +description: "Setup JDK 17" +runs: + using: "composite" + steps: + - name: Setup JDK 17 + uses: actions/setup-java@v3.11.0 + with: + java-version: '17' + distribution: 'temurin' + cache: 'gradle' \ No newline at end of file diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2c2dda9c2..fd3fb7a93 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -60,10 +60,10 @@ jobs: - name: Check whether secrets exist id: secret-presence run: | - [ ! -z "${{ secrets.SONAR_TOKEN }}" ] && echo "::set-output name=SONAR_TOKEN::true" - [ ! -z "${{ secrets.GPG_PRIVATE_KEY }}" ] && echo "::set-output name=GPG_PRIVATE_KEY::true" - [ ! -z "${{ secrets.GPG_PASSPHRASE }}" ] && echo "::set-output name=GPG_PASSPHRASE::true" - [ ! -z "${{ secrets.DOCKER_HUB_TOKEN }}" ] && echo "::set-output name=DOCKER_HUB_TOKEN::true" + [ ! -z "${{ secrets.SONAR_TOKEN }}" ] && echo "SONAR_TOKEN=true" >> $GITHUB_OUTPUT + [ ! -z "${{ secrets.GPG_PRIVATE_KEY }}" ] && echo "GPG_PRIVATE_KEY=true" >> $GITHUB_OUTPUT + [ ! -z "${{ secrets.GPG_PASSPHRASE }}" ] && echo "GPG_PASSPHRASE=true" >> $GITHUB_OUTPUT + [ ! -z "${{ secrets.DOCKER_HUB_TOKEN }}" ] && echo "DOCKER_HUB_TOKEN=true" >> $GITHUB_OUTPUT exit 0 build-extensions: @@ -71,14 +71,8 @@ jobs: needs: [ secret-presence ] steps: # Set-Up - - name: Checkout - uses: actions/checkout@v3.3.0 - - name: Set up JDK 11 - uses: actions/setup-java@v3.11.0 - with: - java-version: '17' - distribution: 'temurin' - cache: 'gradle' + - uses: actions/checkout@v3.3.0 + - uses: ./.github/actions/setup-java # Build - name: Build Extensions run: |- @@ -87,34 +81,8 @@ jobs: GITHUB_PACKAGE_USERNAME: ${{ github.actor }} GITHUB_PACKAGE_PASSWORD: ${{ secrets.GITHUB_TOKEN }} - build-controlplane: - name: "Create Docker Images for the ControlPlane" - runs-on: ubuntu-latest - needs: [ secret-presence ] - if: | - needs.secret-presence.outputs.DOCKER_HUB_TOKEN - strategy: - fail-fast: false - matrix: - name: - - edc-runtime-memory - - edc-controlplane-memory-hashicorp-vault - - edc-controlplane-postgresql - - edc-controlplane-postgresql-hashicorp-vault - permissions: - contents: write - steps: - - name: Checkout - uses: actions/checkout@v3 - - uses: ./.github/actions/publish-docker-image - with: - rootDir: edc-controlplane/${{ matrix.name }} - imagename: ${{ matrix.name }} - docker_user: ${{ secrets.DOCKER_HUB_USER }} - docker_token: ${{ secrets.DOCKER_HUB_TOKEN }} - - build-dataplane: - name: "Create Docker Images for the DataPlane" + build-docker-images: + name: "Create Docker Images" runs-on: ubuntu-latest needs: [ secret-presence ] if: | @@ -122,18 +90,22 @@ jobs: strategy: fail-fast: false matrix: - name: - - edc-dataplane-azure-vault - - edc-dataplane-hashicorp-vault + variant: [ { dir: edc-controlplane, img: edc-runtime-memory }, + { dir: edc-controlplane, img: edc-controlplane-memory-hashicorp-vault }, + { dir: edc-controlplane, img: edc-controlplane-postgresql-hashicorp-vault }, + { dir: edc-controlplane, img: edc-controlplane-postgresql }, + { dir: edc-dataplane, img: edc-dataplane-azure-vault }, + { dir: edc-dataplane, img: edc-dataplane-hashicorp-vault } ] permissions: contents: write steps: - - name: Checkout - uses: actions/checkout@v3 + - uses: actions/checkout@v3.3.0 - uses: ./.github/actions/publish-docker-image + name: Publish ${{ matrix.variant.img }} with: - rootDir: edc-dataplane/${{ matrix.name }} - imagename: ${{ matrix.name }} + docker_tag: ${{ needs.release-version.outputs.RELEASE_VERSION }} + rootDir: ${{ matrix.variant.dir }}/${{ matrix.variant.img }} + imagename: ${{ matrix.variant.img }} docker_user: ${{ secrets.DOCKER_HUB_USER }} docker_token: ${{ secrets.DOCKER_HUB_TOKEN }} @@ -149,15 +121,9 @@ jobs: needs.secret-presence.outputs.GPG_PASSPHRASE && needs.secret-presence.outputs.GPG_PRIVATE_KEY && github.event_name != 'pull_request' && github.ref != 'refs/heads/releases' steps: # Set-Up - - name: Checkout - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v3.3.0 - - name: Set up JDK 11 - uses: actions/setup-java@v3.11.0 - with: - java-version: '17' - distribution: 'temurin' - cache: 'gradle' + - uses: ./.github/actions/setup-java - name: Import GPG Key uses: crazy-max/ghaction-import-gpg@v5 with: @@ -173,4 +139,3 @@ jobs: REPO: ${{ github.repository }} GITHUB_PACKAGE_USERNAME: ${{ github.actor }} GITHUB_PACKAGE_PASSWORD: ${{ secrets.GITHUB_TOKEN }} - diff --git a/.github/workflows/business-tests.yaml b/.github/workflows/business-tests.yaml index 39caaadb1..cbf0f3767 100644 --- a/.github/workflows/business-tests.yaml +++ b/.github/workflows/business-tests.yaml @@ -50,15 +50,9 @@ jobs: ### Set-Up ### ############## - - name: Checkout uses: actions/checkout@v3.3.0 - - name: Set-Up JDK 11 - uses: actions/setup-java@v3.11.0 - with: - java-version: '17' - distribution: 'temurin' - cache: 'gradle' + uses: ./.github/actions/setup-java - name: Cache ContainerD Image Layers uses: actions/cache@v3 @@ -185,6 +179,7 @@ jobs: --set dataplane.image.repository=docker.io/library/edc-dataplane-hashicorp-vault \ --set controlplane.debug.enabled=true \ --set controlplane.suspendOnStart=false \ + --set controlplane.businesspartnervalidation.log.agreement.validation=true \ --set postgresql.enabled=true \ --set postgresql.username=user \ --set postgresql.password=password \ @@ -218,6 +213,7 @@ jobs: --set dataplane.image.repository=docker.io/library/edc-dataplane-hashicorp-vault \ --set controlplane.debug.enabled=true \ --set controlplane.suspendOnStart=false \ + --set controlplane.businesspartnervalidation.log.agreement.validation=true \ --set postgresql.enabled=true \ --set postgresql.username=user \ --set postgresql.password=password \ diff --git a/.github/workflows/deployment-test.yaml b/.github/workflows/deployment-test.yaml index 7d38b24ac..8e75ae31e 100644 --- a/.github/workflows/deployment-test.yaml +++ b/.github/workflows/deployment-test.yaml @@ -47,8 +47,7 @@ jobs: deployment-test-memory: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v3.3.0 - uses: ./.github/actions/run-deployment-test name: "Run deployment test using KinD and Helm" with: diff --git a/.github/workflows/draft-new-release.yaml b/.github/workflows/draft-new-release.yaml index 248f61bc4..98e3c956a 100644 --- a/.github/workflows/draft-new-release.yaml +++ b/.github/workflows/draft-new-release.yaml @@ -33,12 +33,7 @@ jobs: git config user.name "GitHub actions" git config user.email noreply@github.com - - name: Set up JDK 11 - uses: actions/setup-java@v3.11.0 - with: - java-version: '17' - distribution: 'temurin' - cache: 'gradle' + uses: ./.github/actions/setup-java - name: Bump version in gradle.properties run: |- @@ -68,7 +63,7 @@ jobs: git add CHANGELOG.md gradle.properties $(find charts -name Chart.yaml) $(find charts -name README.md) git commit --message "Prepare release ${{ github.event.inputs.version }}" - echo "::set-output name=commit::$(git rev-parse HEAD)" + echo "commit=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - name: Push new branch run: git push origin release/${{ github.event.inputs.version }} diff --git a/.github/workflows/helm-chart-release.yaml b/.github/workflows/helm-chart-release.yaml index bd5e55302..f19c841b9 100644 --- a/.github/workflows/helm-chart-release.yaml +++ b/.github/workflows/helm-chart-release.yaml @@ -38,8 +38,7 @@ jobs: steps: # fetch-depth: 0 is required to determine differences in chart(s) - - name: Checkout - uses: actions/checkout@v3 + - uses: actions/checkout@v3.3.0 with: fetch-depth: 0 diff --git a/.github/workflows/helm-lint.yaml b/.github/workflows/helm-lint.yaml index ae94c84a7..0b5a70f1f 100644 --- a/.github/workflows/helm-lint.yaml +++ b/.github/workflows/helm-lint.yaml @@ -26,7 +26,6 @@ jobs: ### Set-Up ### ############## - - name: Checkout uses: actions/checkout@v3.3.0 with: fetch-depth: 0 @@ -52,7 +51,7 @@ jobs: run: | changed=$(ct list-changed --config ct.yaml --target-branch main) if [[ -n "$changed" ]]; then - echo "::set-output name=changed::true" + echo "changed=true" >> $GITHUB_OUTPUT fi - name: chart-testing (lint) diff --git a/.github/workflows/publish-docker.yaml b/.github/workflows/publish-docker.yaml index 794d15061..bbe7a5d10 100644 --- a/.github/workflows/publish-docker.yaml +++ b/.github/workflows/publish-docker.yaml @@ -28,6 +28,9 @@ on: description: 'The namespace (=repo) in DockerHub' required: false default: "tractusx" + docker_tag: + description: 'Explicitly specify the Docker tag. Note that SHA and latest are added automatically.' + required: false concurrency: # cancel only running jobs on pull requests @@ -35,51 +38,29 @@ concurrency: cancel-in-progress: true jobs: - create-docker-image-controlplane: + create-docker-image: name: "Create Docker Images for the ControlPlane" runs-on: ubuntu-latest strategy: fail-fast: false matrix: - name: - - edc-runtime-memory - - edc-controlplane-memory-hashicorp-vault - - edc-controlplane-postgresql - - edc-controlplane-postgresql-hashicorp-vault + variant: [ { dir: edc-controlplane, img: edc-runtime-memory }, + { dir: edc-controlplane, img: edc-controlplane-memory-hashicorp-vault }, + { dir: edc-controlplane, img: edc-controlplane-postgresql-hashicorp-vault }, + { dir: edc-controlplane, img: edc-controlplane-postgresql }, + { dir: edc-dataplane, img: edc-dataplane-azure-vault }, + { dir: edc-dataplane, img: edc-dataplane-hashicorp-vault } ] permissions: contents: write packages: write steps: - - name: Checkout - uses: actions/checkout@v3 + - uses: actions/checkout@v3.3.0 - uses: ./.github/actions/publish-docker-image + name: Publish ${{ matrix.variant.img }} with: - rootDir: edc-controlplane/${{ matrix.name }} - imagename: ${{ matrix.name }} + docker_tag: ${{ needs.release-version.outputs.RELEASE_VERSION }} + rootDir: ${{ matrix.variant.dir }}/${{ matrix.variant.img }} + imagename: ${{ matrix.variant.img }} namespace: ${{ inputs.namespace }} docker_user: ${{ secrets.DOCKER_HUB_USER }} docker_token: ${{ secrets.DOCKER_HUB_TOKEN }} - - - create-docker-image-dataplane: - name: "Create Docker Images for the DataPlane" - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - name: - - edc-dataplane-azure-vault - - edc-dataplane-hashicorp-vault - permissions: - contents: write - packages: write - steps: - - name: Checkout - uses: actions/checkout@v3 - - uses: ./.github/actions/publish-docker-image - with: - rootDir: edc-dataplane/${{ matrix.name }} - imagename: ${{ matrix.name }} - namespace: ${{ inputs.namespace }} - docker_user: ${{ secrets.DOCKER_HUB_USER }} - docker_token: ${{ secrets.DOCKER_HUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/publish-new-release.yml b/.github/workflows/publish-new-release.yml index 373c892e7..0da6f5da5 100644 --- a/.github/workflows/publish-new-release.yml +++ b/.github/workflows/publish-new-release.yml @@ -37,7 +37,7 @@ jobs: name: Output release version id: release-version run: | - echo "::set-output name=RELEASE_VERSION::${{ env.RELEASE_VERSION }}" + echo "RELEASE_VERSION=${{ env.RELEASE_VERSION }}" >> $GITHUB_OUTPUT # Release: Maven Artifacts maven-release: @@ -54,19 +54,13 @@ jobs: run: | echo "RELEASE_VERSION=${{ needs.release-version.outputs.RELEASE_VERSION }}" >> $GITHUB_ENV - - name: Checkout uses: actions/checkout@v3.3.0 - - name: Set up JDK 11 - uses: actions/setup-java@v3.11.0 - with: - java-version: '17' - distribution: 'temurin' - cache: 'gradle' + uses: ./.github/actions/setup-java - name: Import GPG Key uses: crazy-max/ghaction-import-gpg@v5 - env: + with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} passphrase: ${{ secrets.GPG_PASSPHRASE }} @@ -79,6 +73,35 @@ jobs: GITHUB_PACKAGE_USERNAME: ${{ github.actor }} GITHUB_PACKAGE_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + docker-release: + name: Publish Docker images + runs-on: ubuntu-latest + needs: [ release-version ] + permissions: + contents: write + if: github.event.pull_request.merged == true && needs.release-version.outputs.RELEASE_VERSION + + strategy: + fail-fast: false + matrix: + variant: [{dir: edc-controlplane, img: edc-runtime-memory}, + {dir: edc-controlplane, img: edc-controlplane-memory-hashicorp-vault}, + {dir: edc-controlplane, img: edc-controlplane-postgresql-hashicorp-vault}, + {dir: edc-controlplane, img: edc-controlplane-postgresql}, + {dir: edc-dataplane, img: edc-dataplane-azure-vault}, + {dir: edc-dataplane, img: edc-dataplane-hashicorp-vault}] + + steps: + - uses: actions/checkout@v3.3.0 + - uses: ./.github/actions/publish-docker-image + name: Publish ${{ matrix.variant.img }} + with: + docker_tag: ${{ needs.release-version.outputs.RELEASE_VERSION }} + rootDir: ${{ matrix.variant.dir }}/${{ matrix.variant.img }} + imagename: ${{ matrix.variant.img }} + docker_user: ${{ secrets.DOCKER_HUB_USER }} + docker_token: ${{ secrets.DOCKER_HUB_TOKEN }} + # Release: Helm Charts helm-release: name: Publish new helm release @@ -96,7 +119,6 @@ jobs: run: | echo "RELEASE_VERSION=${{ needs.release-version.outputs.RELEASE_VERSION }}" >> $GITHUB_ENV - - name: Checkout uses: actions/checkout@v3.3.0 with: fetch-depth: 0 @@ -144,7 +166,6 @@ jobs: run: | echo "RELEASE_VERSION=${{ needs.release-version.outputs.RELEASE_VERSION }}" >> $GITHUB_ENV - - name: Checkout uses: actions/checkout@v3.3.0 with: # 0 to fetch the full history due to upcoming merge of releases into main branch @@ -177,12 +198,7 @@ jobs: draft: false prerelease: false - - name: Set up JDK 11 - uses: actions/setup-java@v3.11.0 - with: - java-version: '17' - distribution: 'temurin' - cache: 'gradle' + uses: ./.github/actions/setup-java - name: Merge releases back into main and set new snapshot version if: github.event.pull_request.base.ref == 'releases' diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index c315e8a07..2fe44c399 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -27,7 +27,7 @@ jobs: - name: Resolve git 7-chars sha id: git-sha7 run: | - echo "::set-output name=SHA7::${GITHUB_SHA::7}" + echo "SHA7=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT trivy-analyze-config: runs-on: ubuntu-latest @@ -36,8 +36,7 @@ jobs: contents: read security-events: write steps: - - name: Checkout repository - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v3.3.0 - name: Run Trivy vulnerability scanner in repo mode uses: aquasecurity/trivy-action@master with: @@ -72,10 +71,18 @@ jobs: - edc-dataplane-azure-vault - edc-dataplane-hashicorp-vault steps: - - name: Checkout - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v3.3.0 + + ## This step will fail if the docker images is not found + - name: "Check if image exists" + id: imageCheck + run: | + docker manifest inspect tractusx/${{ matrix.image }}:sha-${{ needs.git-sha7.outputs.value }} + continue-on-error: true + + ## the next two steps will only execute if the image exists check was successful - name: Run Trivy vulnerability scanner - if: always() + if: success() && steps.imageCheck.outcome != 'failure' uses: aquasecurity/trivy-action@master with: image-ref: "tractusx/${{ matrix.image }}:sha-${{ needs.git-sha7.outputs.value }}" @@ -85,7 +92,7 @@ jobs: severity: "CRITICAL,HIGH" timeout: "10m0s" - name: Upload Trivy scan results to GitHub Security tab - if: always() + if: success() && steps.imageCheck.outcome != 'failure' uses: github/codeql-action/upload-sarif@v2 with: sarif_file: "trivy-results-${{ matrix.image }}.sarif" diff --git a/.github/workflows/veracode.yaml b/.github/workflows/veracode.yaml index bba9df1b5..b8900971c 100644 --- a/.github/workflows/veracode.yaml +++ b/.github/workflows/veracode.yaml @@ -16,73 +16,21 @@ jobs: - name: Check whether secrets exist id: secret-presence run: | - [ ! -z "${{ secrets.ORG_VERACODE_API_ID }}" ] && echo "::set-output name=ORG_VERACODE_API_ID::true" - [ ! -z "${{ secrets.ORG_VERACODE_API_KEY }}" ] && echo "::set-output name=ORG_VERACODE_API_KEY::true" + [ ! -z "${{ secrets.ORG_VERACODE_API_ID }}" ] && echo "ORG_VERACODE_API_ID=true" >> $GITHUB_OUTPUT + [ ! -z "${{ secrets.ORG_VERACODE_API_KEY }}" ] && echo "ORG_VERACODE_API_KEY=true" >> $GITHUB_OUTPUT exit 0 verify-formatting: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v3.3.0 with: fetch-depth: 0 - - name: Set up JDK 11 - uses: actions/setup-java@v3.11.0 - with: - java-version: '17' - distribution: 'temurin' - cache: 'gradle' + - uses: ./.github/actions/setup-java - name: Verify proper formatting run: ./gradlew spotlessCheck - build-controlplane: - runs-on: ubuntu-latest - needs: [ secret-presence, verify-formatting ] - permissions: - contents: read - strategy: - fail-fast: false - matrix: - name: - - edc-runtime-memory - - edc-controlplane-memory-hashicorp-vault - - edc-controlplane-postgresql - - edc-controlplane-postgresql-hashicorp-vault - steps: - # Set-Up - - name: Checkout - uses: actions/checkout@v3.3.0 - - name: Set up JDK 11 - uses: actions/setup-java@v3.11.0 - with: - java-version: '17' - distribution: 'temurin' - cache: 'gradle' - # Build - - name: Build Controlplane - run: |- - ./gradlew -p edc-controlplane/${{ matrix.name }} shadowJar - env: - GITHUB_PACKAGE_USERNAME: ${{ github.actor }} - GITHUB_PACKAGE_PASSWORD: ${{ secrets.GITHUB_TOKEN }} - - name: Tar gzip files for veracode upload - run: |- - tar -czvf edc-controlplane/${{ matrix.name }}/build/libs/${{ matrix.name }}.tar.gz edc-controlplane/${{ matrix.name }}/build/libs/${{ matrix.name }}.jar - - name: Veracode Upload And Scan - uses: veracode/veracode-uploadandscan-action@v1.0 - if: | - needs.secret-presence.outputs.ORG_VERACODE_API_ID && needs.secret-presence.outputs.ORG_VERACODE_API_KEY - continue-on-error: true - with: - appname: product-edc/${{ matrix.name }} - createprofile: true - version: ${{ matrix.name }}-${{ github.sha }} - filepath: edc-controlplane/${{ matrix.name }}/build/libs/${{ matrix.name }}.tar.gz - vid: ${{ secrets.ORG_VERACODE_API_ID }} - vkey: ${{ secrets.ORG_VERACODE_API_KEY }} - - build-dataplane: + build: runs-on: ubuntu-latest needs: [ secret-presence, verify-formatting ] permissions: @@ -90,39 +38,35 @@ jobs: strategy: fail-fast: false matrix: - name: - - edc-dataplane-azure-vault - - edc-dataplane-hashicorp-vault + variant: [ { dir: edc-controlplane, name: edc-runtime-memory }, + { dir: edc-controlplane, name: edc-controlplane-memory-hashicorp-vault }, + { dir: edc-controlplane, name: edc-controlplane-postgresql-hashicorp-vault }, + { dir: edc-controlplane, name: edc-controlplane-postgresql }, + { dir: edc-dataplane, name: edc-dataplane-azure-vault }, + { dir: edc-dataplane, name: edc-dataplane-hashicorp-vault } ] steps: # Set-Up - - name: Checkout - uses: actions/checkout@v3.3.0 - - name: Set up JDK 11 - uses: actions/setup-java@v3.11.0 - with: - java-version: '17' - distribution: 'temurin' - cache: 'gradle' + - uses: actions/checkout@v3.3.0 + - uses: ./.github/actions/setup-java # Build - - name: Build Dataplane + - name: Build ${{ matrix.variant.name }} run: |- - ./gradlew -p edc-dataplane/${{ matrix.name }} shadowJar + ./gradlew -p ${{ matrix.variant.dir }}/${{ matrix.variant.name }} shadowJar env: GITHUB_PACKAGE_USERNAME: ${{ github.actor }} GITHUB_PACKAGE_PASSWORD: ${{ secrets.GITHUB_TOKEN }} - name: Tar gzip files for veracode upload run: |- - tar -czvf edc-dataplane/${{ matrix.name }}/build/libs/${{ matrix.name }}.tar.gz edc-dataplane/${{ matrix.name }}/build/libs/${{ matrix.name }}.jar + tar -czvf ${{ matrix.variant.dir }}/${{ matrix.variant.name }}/build/libs/${{ matrix.variant.name }}.tar.gz ${{ matrix.variant.dir }}/${{ matrix.variant.name }}/build/libs/${{ matrix.variant.name }}.jar - name: Veracode Upload And Scan uses: veracode/veracode-uploadandscan-action@v1.0 if: | needs.secret-presence.outputs.ORG_VERACODE_API_ID && needs.secret-presence.outputs.ORG_VERACODE_API_KEY continue-on-error: true with: - appname: product-edc/${{ matrix.name }} + appname: tractusx-edc/${{ matrix.variant.name }} createprofile: true - version: ${{ matrix.name }}-${{ github.sha }} - filepath: edc-dataplane/${{ matrix.name }}/build/libs/${{ matrix.name }}.tar.gz + version: ${{ matrix.variant.name }}-${{ github.sha }} + filepath: ${{ matrix.variant.dir }}/${{ matrix.variant.name }}/build/libs/${{ matrix.variant.name }}.tar.gz vid: ${{ secrets.ORG_VERACODE_API_ID }} vkey: ${{ secrets.ORG_VERACODE_API_KEY }} - diff --git a/.github/workflows/verify.yaml b/.github/workflows/verify.yaml index d9dda3844..2cd0432f8 100644 --- a/.github/workflows/verify.yaml +++ b/.github/workflows/verify.yaml @@ -52,21 +52,15 @@ jobs: - name: Check whether secrets exist id: secret-presence run: | - [ ! -z "${{ secrets.SONAR_TOKEN }}" ] && echo "::set-output name=SONAR_TOKEN::true" + [ ! -z "${{ secrets.SONAR_TOKEN }}" ] && echo "SONAR_TOKEN=true" >> $GITHUB_OUTPUT exit 0 verify-formatting: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v3.3.0 - - name: Set up JDK 11 - uses: actions/setup-java@v3.11.0 - with: - java-version: '17' - distribution: 'temurin' - cache: 'gradle' + - uses: ./.github/actions/setup-java - name: Verify proper formatting run: ./gradlew spotlessCheck @@ -78,7 +72,7 @@ jobs: markdown-lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v3.3.0 - name: Install mardkdownlint run: npm install -g markdownlint-cli2 @@ -91,15 +85,9 @@ jobs: runs-on: ubuntu-latest needs: [ verify-formatting ] steps: - - name: Checkout - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v3.3.0 - - name: Set up JDK 11 - uses: actions/setup-java@v3.11.0 - with: - java-version: '17' - distribution: 'temurin' - cache: 'gradle' + - uses: ./.github/actions/setup-java - name: Run Unit tests run: ./gradlew test @@ -108,15 +96,9 @@ jobs: runs-on: ubuntu-latest needs: [ verify-formatting ] steps: - - name: Checkout - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v3.3.0 - - name: Set up JDK 11 - uses: actions/setup-java@v3.11.0 - with: - java-version: '17' - distribution: 'temurin' - cache: 'gradle' + - uses: ./.github/actions/setup-java - name: Run Integration tests run: ./gradlew test -DincludeTags="ComponentTest" @@ -125,15 +107,9 @@ jobs: runs-on: ubuntu-latest needs: [ verify-formatting ] steps: - - name: Checkout - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v3.3.0 - - name: Set up JDK 11 - uses: actions/setup-java@v3.11.0 - with: - java-version: '17' - distribution: 'temurin' - cache: 'gradle' + - uses: ./.github/actions/setup-java - name: Run API tests run: ./gradlew test -DincludeTags="ApiTest" @@ -142,15 +118,9 @@ jobs: runs-on: ubuntu-latest needs: [ verify-formatting ] steps: - - name: Checkout - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v3.3.0 - - name: Set up JDK 11 - uses: actions/setup-java@v3.11.0 - with: - java-version: '17' - distribution: 'temurin' - cache: 'gradle' + - uses: ./.github/actions/setup-java - name: Run E2E tests run: ./gradlew :edc-tests:runtime:build test -DincludeTags="EndToEndTest" @@ -162,16 +132,10 @@ jobs: runs-on: ubuntu-latest steps: # Set-Up - - name: Checkout - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v3.3.0 with: fetch-depth: 0 - - name: Set up JDK 11 - uses: actions/setup-java@v3.11.0 - with: - java-version: '17' - distribution: 'temurin' - cache: 'gradle' + - uses: ./.github/actions/setup-java - name: Cache SonarCloud packages uses: actions/cache@v3 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index e84846211..7ce0ca990 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.3] - 2023-04-19 + +### Fixed + +- Config values for the data plane part of the helm chart +- Contract Validity + +### Added + +- A log line whenever a policy evaluation of the BPN number was performed + ## [0.3.2] - 2023-03-30 ### Fixed @@ -270,9 +281,11 @@ corresponding [documentation](/docs/migration/Version_0.0.x_0.1.x.md). ## [0.0.1] - 2022-05-13 -[Unreleased]: https://github.com/catenax-ng/tx-tractusx-edc/compare/0.3.2...HEAD +[Unreleased]: https://github.com/eclipse-tractusx/tractusx-edc/compare/0.3.3...HEAD + +[0.3.3]: https://github.com/eclipse-tractusx/tractusx-edc/compare/0.3.2...0.3.3 -[0.3.2]: https://github.com/catenax-ng/tx-tractusx-edc/compare/0.3.1...0.3.2 +[0.3.2]: https://github.com/eclipse-tractusx/tractusx-edc/compare/0.3.1...0.3.2 [0.3.1]: https://github.com/eclipse-tractusx/tractusx-edc/compare/0.3.0...0.3.1 diff --git a/charts/tractusx-connector-memory/Chart.yaml b/charts/tractusx-connector-memory/Chart.yaml index 42b139a55..cb0a06b72 100644 --- a/charts/tractusx-connector-memory/Chart.yaml +++ b/charts/tractusx-connector-memory/Chart.yaml @@ -34,12 +34,12 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.3.2 +version: 0.3.3 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.3.2" +appVersion: "0.3.3" home: https://github.com/eclipse-tractusx/tractusx-edc/tree/main/charts/tractusx-connector-memory sources: - https://github.com/eclipse-tractusx/tractusx-edc/tree/main/charts/tractusx-connector-memory diff --git a/charts/tractusx-connector-memory/README.md b/charts/tractusx-connector-memory/README.md index 1e37bc286..872827664 100644 --- a/charts/tractusx-connector-memory/README.md +++ b/charts/tractusx-connector-memory/README.md @@ -1,241 +1,147 @@ -# tractusx-connector +# tractusx-connector-memory -![Version: 0.3.2](https://img.shields.io/badge/Version-0.3.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.3.2](https://img.shields.io/badge/AppVersion-0.3.2-informational?style=flat-square) +![Version: 0.3.3](https://img.shields.io/badge/Version-0.3.3-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.3.3](https://img.shields.io/badge/AppVersion-0.3.3-informational?style=flat-square) -A Helm chart for Tractus-X Eclipse Data Space Connector +A Helm chart for Tractus-X Eclipse Data Space Connector based on memory -**Homepage:** +**Homepage:** ## TL;DR ```shell helm repo add tractusx-edc https://eclipse-tractusx.github.io/charts/dev -helm install my-release tractusx-edc/tractusx-connector --version 0.3.2 +helm install my-release tractusx-edc/tractusx-connector-memory --version 0.3.3 ``` ## Source Code -* +* ## Values -| Key | Type | Default | Description | -|---------------------------------------------------------|--------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| backendService.httpProxyTokenReceiverUrl | string | `""` | | -| runtime.affinity | object | `{}` | | -| runtime.autoscaling.enabled | bool | `false` | Enables [horizontal pod autoscaling](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) | -| runtime.autoscaling.maxReplicas | int | `100` | Maximum replicas if resource consumption exceeds resource threshholds | -| runtime.autoscaling.minReplicas | int | `1` | Minimal replicas if resource consumption falls below resource threshholds | -| runtime.autoscaling.targetCPUUtilizationPercentage | int | `80` | targetAverageUtilization of cpu provided to a pod | -| runtime.autoscaling.targetMemoryUtilizationPercentage | int | `80` | targetAverageUtilization of memory provided to a pod | -| runtime.debug.enabled | bool | `false` | | -| runtime.debug.port | int | `1044` | | -| runtime.debug.suspendOnStart | bool | `false` | | -| runtime.endpoints | object | `{"control":{"path":"/control","port":8083},"data":{"authKey":"","path":"/data","port":8081},"default":{"path":"/api","port":8080},"ids":{"path":"/api/v1/ids","port":8084},"metrics":{"path":"/metrics","port":9090},"observability":{"insecure":true,"path":"/observability","port":8085},"validation":{"path":"/validation","port":8082}}` | endpoints of the control plane | -| runtime.endpoints.control | object | `{"path":"/control","port":8083}` | control api, used for internal control calls. can be added to the internal ingress, but should probably not | -| runtime.endpoints.control.path | string | `"/control"` | path for incoming api calls | -| runtime.endpoints.control.port | int | `8083` | port for incoming api calls | -| runtime.endpoints.data | object | `{"authKey":"","path":"/data","port":8081}` | data management api, used by internal users, can be added to an ingress and must not be internet facing | -| runtime.endpoints.data.authKey | string | `""` | authentication key, must be attached to each 'X-Api-Key' request header | -| runtime.endpoints.data.path | string | `"/data"` | path for incoming api calls | -| runtime.endpoints.data.port | int | `8081` | port for incoming api calls | -| runtime.endpoints.default | object | `{"path":"/api","port":8080}` | default api for health checks, should not be added to any ingress | -| runtime.endpoints.default.path | string | `"/api"` | path for incoming api calls | -| runtime.endpoints.default.port | int | `8080` | port for incoming api calls | -| runtime.endpoints.ids | object | `{"path":"/api/v1/ids","port":8084}` | ids api, used for inter connector communication and must be internet facing | -| runtime.endpoints.ids.path | string | `"/api/v1/ids"` | path for incoming api calls | -| runtime.endpoints.ids.port | int | `8084` | port for incoming api calls | -| runtime.endpoints.metrics | object | `{"path":"/metrics","port":9090}` | metrics api, used for application metrics, must not be internet facing | -| runtime.endpoints.metrics.path | string | `"/metrics"` | path for incoming api calls | -| runtime.endpoints.metrics.port | int | `9090` | port for incoming api calls | -| runtime.endpoints.observability | object | `{"insecure":true,"path":"/observability","port":8085}` | observability api with unsecured access, must not be internet facing | -| runtime.endpoints.observability.insecure | bool | `true` | allow or disallow insecure access, i.e. access without authentication | -| runtime.endpoints.observability.path | string | `"/observability"` | observability api, provides /health /readiness and /liveness endpoints | -| runtime.endpoints.observability.port | int | `8085` | port for incoming API calls | -| runtime.endpoints.validation | object | `{"path":"/validation","port":8082}` | validation api, only used by the data plane and should not be added to any ingress | -| runtime.endpoints.validation.path | string | `"/validation"` | path for incoming api calls | -| runtime.endpoints.validation.port | int | `8082` | port for incoming api calls | -| runtime.env | object | `{}` | | -| runtime.envConfigMapNames | list | `[]` | | -| runtime.envSecretNames | list | `[]` | | -| runtime.envValueFrom | object | `{}` | | -| runtime.image.pullPolicy | string | `"IfNotPresent"` | [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) to use | -| runtime.image.repository | string | `""` | Which derivate of the control plane to use. when left empty the deployment will select the correct image automatically | -| runtime.image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion | -| runtime.ingresses[0].annotations | object | `{}` | Additional ingress annotations to add | -| runtime.ingresses[0].certManager.clusterIssuer | string | `""` | If preset enables certificate generation via cert-manager cluster-wide issuer | -| runtime.ingresses[0].certManager.issuer | string | `""` | If preset enables certificate generation via cert-manager namespace scoped issuer | -| runtime.ingresses[0].className | string | `""` | Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use | -| runtime.ingresses[0].enabled | bool | `false` | | -| runtime.ingresses[0].endpoints | list | `["ids"]` | EDC endpoints exposed by this ingress resource | -| runtime.ingresses[0].hostname | string | `"edc-control.local"` | The hostname to be used to precisely map incoming traffic onto the underlying network service | -| runtime.ingresses[0].tls | object | `{"enabled":false,"secretName":""}` | TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource | -| runtime.ingresses[0].tls.enabled | bool | `false` | Enables TLS on the ingress resource | -| runtime.ingresses[0].tls.secretName | string | `""` | If present overwrites the default secret name | -| runtime.ingresses[1].annotations | object | `{}` | Additional ingress annotations to add | -| runtime.ingresses[1].certManager.clusterIssuer | string | `""` | If preset enables certificate generation via cert-manager cluster-wide issuer | -| runtime.ingresses[1].certManager.issuer | string | `""` | If preset enables certificate generation via cert-manager namespace scoped issuer | -| runtime.ingresses[1].className | string | `""` | Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use | -| runtime.ingresses[1].enabled | bool | `false` | | -| runtime.ingresses[1].endpoints | list | `["data","control"]` | EDC endpoints exposed by this ingress resource | -| runtime.ingresses[1].hostname | string | `"edc-control.intranet"` | The hostname to be used to precisely map incoming traffic onto the underlying network service | -| runtime.ingresses[1].tls | object | `{"enabled":false,"secretName":""}` | TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource | -| runtime.ingresses[1].tls.enabled | bool | `false` | Enables TLS on the ingress resource | -| runtime.ingresses[1].tls.secretName | string | `""` | If present overwrites the default secret name | -| runtime.initContainers | list | `[]` | | -| runtime.internationalDataSpaces.catalogId | string | `"TXDC-Catalog"` | | -| runtime.internationalDataSpaces.curator | string | `""` | | -| runtime.internationalDataSpaces.description | string | `"Tractus-X Eclipse IDS Data Space Connector"` | | -| runtime.internationalDataSpaces.id | string | `"TXDC"` | | -| runtime.internationalDataSpaces.maintainer | string | `""` | | -| runtime.internationalDataSpaces.title | string | `""` | | -| runtime.livenessProbe.enabled | bool | `true` | Whether to enable kubernetes [liveness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | -| runtime.livenessProbe.failureThreshold | int | `6` | when a probe fails kubernetes will try 6 times before giving up | -| runtime.livenessProbe.initialDelaySeconds | int | `30` | seconds to wait before performing the first liveness check | -| runtime.livenessProbe.periodSeconds | int | `10` | this fields specifies that kubernetes should perform a liveness check every 10 seconds | -| runtime.livenessProbe.successThreshold | int | `1` | number of consecutive successes for the probe to be considered successful after having failed | -| runtime.livenessProbe.timeoutSeconds | int | `5` | number of seconds after which the probe times out | -| runtime.logging | string | `".level=INFO\norg.eclipse.edc.level=ALL\nhandlers=java.util.logging.ConsoleHandler\njava.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter\njava.util.logging.ConsoleHandler.level=ALL\njava.util.logging.SimpleFormatter.format=[%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS] [%4$-7s] %5$s%6$s%n"` | configuration of the [Java Util Logging Facade](https://docs.oracle.com/javase/7/docs/technotes/guides/logging/overview.html) | -| runtime.nodeSelector | object | `{}` | | -| runtime.opentelemetry | string | `"otel.javaagent.enabled=false\notel.javaagent.debug=false"` | configuration of the [Open Telemetry Agent](https://opentelemetry.io/docs/instrumentation/java/automatic/agent-config/) to collect and expose metrics | -| runtime.podAnnotations | object | `{}` | additional annotations for the pod | -| runtime.podLabels | object | `{}` | additional labels for the pod | -| runtime.podSecurityContext | object | `{"fsGroup":10001,"runAsGroup":10001,"runAsUser":10001,"seccompProfile":{"type":"RuntimeDefault"}}` | The [pod security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod) defines privilege and access control settings for a Pod within the deployment | -| runtime.podSecurityContext.fsGroup | int | `10001` | The owner for volumes and any files created within volumes will belong to this guid | -| runtime.podSecurityContext.runAsGroup | int | `10001` | Processes within a pod will belong to this guid | -| runtime.podSecurityContext.runAsUser | int | `10001` | Runs all processes within a pod with a special uid | -| runtime.podSecurityContext.seccompProfile.type | string | `"RuntimeDefault"` | Restrict a Container's Syscalls with seccomp | -| runtime.readinessProbe.enabled | bool | `true` | Whether to enable kubernetes [readiness-probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | -| runtime.readinessProbe.failureThreshold | int | `6` | when a probe fails kubernetes will try 6 times before giving up | -| runtime.readinessProbe.initialDelaySeconds | int | `30` | seconds to wait before performing the first readiness check | -| runtime.readinessProbe.periodSeconds | int | `10` | this fields specifies that kubernetes should perform a readiness check every 10 seconds | -| runtime.readinessProbe.successThreshold | int | `1` | number of consecutive successes for the probe to be considered successful after having failed | -| runtime.readinessProbe.timeoutSeconds | int | `5` | number of seconds after which the probe times out | -| runtime.replicaCount | int | `1` | | -| runtime.resources | object | `{}` | [resource management](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) for the container | -| runtime.securityContext.allowPrivilegeEscalation | bool | `false` | Controls [Privilege Escalation](https://kubernetes.io/docs/concepts/security/pod-security-policy/#privilege-escalation) enabling setuid binaries changing the effective user ID | -| runtime.securityContext.capabilities.add | list | `[]` | Specifies which capabilities to add to issue specialized syscalls | -| runtime.securityContext.capabilities.drop | list | `["ALL"]` | Specifies which capabilities to drop to reduce syscall attack surface | -| runtime.securityContext.readOnlyRootFilesystem | bool | `true` | Whether the root filesystem is mounted in read-only mode | -| runtime.securityContext.runAsNonRoot | bool | `true` | Requires the container to run without root privileges | -| runtime.securityContext.runAsUser | int | `10001` | The container's process will run with the specified uid | -| runtime.service.annotations | object | `{}` | | -| runtime.service.type | string | `"ClusterIP"` | [Service type](https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) to expose the running application on a set of Pods as a network service. | -| runtime.tolerations | list | `[]` | | -| runtime.url.ids | string | `""` | Explicitly declared url for reaching the ids api (e.g. if ingresses not used) | -| runtime.volumeMounts | list | `[]` | declare where to mount [volumes](https://kubernetes.io/docs/concepts/storage/volumes/) into the container | -| runtime.volumes | list | `[]` | [volume](https://kubernetes.io/docs/concepts/storage/volumes/) directories | -| customLabels | object | `{}` | | -| daps.clientId | string | `""` | | -| daps.paths.jwks | string | `"/jwks.json"` | | -| daps.paths.token | string | `"/token"` | | -| daps.url | string | `""` | | -| dataplane.affinity | object | `{}` | | -| dataplane.autoscaling.enabled | bool | `false` | Enables [horizontal pod autoscaling](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) | -| dataplane.autoscaling.maxReplicas | int | `100` | Maximum replicas if resource consumption exceeds resource threshholds | -| dataplane.autoscaling.minReplicas | int | `1` | Minimal replicas if resource consumption falls below resource threshholds | -| dataplane.autoscaling.targetCPUUtilizationPercentage | int | `80` | targetAverageUtilization of cpu provided to a pod | -| dataplane.autoscaling.targetMemoryUtilizationPercentage | int | `80` | targetAverageUtilization of memory provided to a pod | -| dataplane.aws.accessKeyId | string | `""` | | -| dataplane.aws.endpointOverride | string | `""` | | -| dataplane.aws.secretAccessKey | string | `""` | | -| dataplane.debug.enabled | bool | `false` | | -| dataplane.debug.port | int | `1044` | | -| dataplane.debug.suspendOnStart | bool | `false` | | -| dataplane.endpoints.control.path | string | `"/api/dataplane/control"` | | -| dataplane.endpoints.control.port | int | `8083` | | -| dataplane.endpoints.default.path | string | `"/api"` | | -| dataplane.endpoints.default.port | int | `8080` | | -| dataplane.endpoints.metrics.path | string | `"/metrics"` | | -| dataplane.endpoints.metrics.port | int | `9090` | | -| dataplane.endpoints.public.path | string | `"/api/public"` | | -| dataplane.endpoints.public.port | int | `8081` | | -| dataplane.endpoints.validation.path | string | `"/validation"` | | -| dataplane.endpoints.validation.port | int | `8082` | | -| dataplane.env | object | `{}` | | -| dataplane.envConfigMapNames | list | `[]` | | -| dataplane.envSecretNames | list | `[]` | | -| dataplane.envValueFrom | object | `{}` | | -| dataplane.image.pullPolicy | string | `"IfNotPresent"` | [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) to use | -| dataplane.image.repository | string | `""` | Which derivate of the data plane to use. when left empty the deployment will select the correct image automatically | -| dataplane.image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion | -| dataplane.ingresses[0].annotations | object | `{}` | Additional ingress annotations to add | -| dataplane.ingresses[0].certManager.clusterIssuer | string | `""` | If preset enables certificate generation via cert-manager cluster-wide issuer | -| dataplane.ingresses[0].certManager.issuer | string | `""` | If preset enables certificate generation via cert-manager namespace scoped issuer | -| dataplane.ingresses[0].className | string | `""` | Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use | -| dataplane.ingresses[0].enabled | bool | `false` | | -| dataplane.ingresses[0].endpoints | list | `["public"]` | EDC endpoints exposed by this ingress resource | -| dataplane.ingresses[0].hostname | string | `"edc-data.local"` | The hostname to be used to precisely map incoming traffic onto the underlying network service | -| dataplane.ingresses[0].tls | object | `{"enabled":false,"secretName":""}` | TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource | -| dataplane.ingresses[0].tls.enabled | bool | `false` | Enables TLS on the ingress resource | -| dataplane.ingresses[0].tls.secretName | string | `""` | If present overwrites the default secret name | -| dataplane.initContainers | list | `[]` | | -| dataplane.livenessProbe.enabled | bool | `true` | Whether to enable kubernetes [liveness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | -| dataplane.livenessProbe.failureThreshold | int | `6` | when a probe fails kubernetes will try 6 times before giving up | -| dataplane.livenessProbe.initialDelaySeconds | int | `30` | seconds to wait before performing the first liveness check | -| dataplane.livenessProbe.periodSeconds | int | `10` | this fields specifies that kubernetes should perform a liveness check every 10 seconds | -| dataplane.livenessProbe.successThreshold | int | `1` | number of consecutive successes for the probe to be considered successful after having failed | -| dataplane.livenessProbe.timeoutSeconds | int | `5` | number of seconds after which the probe times out | -| dataplane.logging | string | `".level=INFO\norg.eclipse.edc.level=ALL\nhandlers=java.util.logging.ConsoleHandler\njava.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter\njava.util.logging.ConsoleHandler.level=ALL\njava.util.logging.SimpleFormatter.format=[%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS] [%4$-7s] %5$s%6$s%n"` | configuration of the [Java Util Logging Facade](https://docs.oracle.com/javase/7/docs/technotes/guides/logging/overview.html) | -| dataplane.nodeSelector | object | `{}` | | -| dataplane.opentelemetry | string | `"otel.javaagent.enabled=false\notel.javaagent.debug=false"` | configuration of the [Open Telemetry Agent](https://opentelemetry.io/docs/instrumentation/java/automatic/agent-config/) to collect and expose metrics | -| dataplane.podAnnotations | object | `{}` | additional annotations for the pod | -| dataplane.podLabels | object | `{}` | additional labels for the pod | -| dataplane.podSecurityContext | object | `{"fsGroup":10001,"runAsGroup":10001,"runAsUser":10001,"seccompProfile":{"type":"RuntimeDefault"}}` | The [pod security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod) defines privilege and access control settings for a Pod within the deployment | -| dataplane.podSecurityContext.fsGroup | int | `10001` | The owner for volumes and any files created within volumes will belong to this guid | -| dataplane.podSecurityContext.runAsGroup | int | `10001` | Processes within a pod will belong to this guid | -| dataplane.podSecurityContext.runAsUser | int | `10001` | Runs all processes within a pod with a special uid | -| dataplane.podSecurityContext.seccompProfile.type | string | `"RuntimeDefault"` | Restrict a Container's Syscalls with seccomp | -| dataplane.readinessProbe.enabled | bool | `true` | Whether to enable kubernetes [readiness-probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | -| dataplane.readinessProbe.failureThreshold | int | `6` | when a probe fails kubernetes will try 6 times before giving up | -| dataplane.readinessProbe.initialDelaySeconds | int | `30` | seconds to wait before performing the first readiness check | -| dataplane.readinessProbe.periodSeconds | int | `10` | this fields specifies that kubernetes should perform a liveness check every 10 seconds | -| dataplane.readinessProbe.successThreshold | int | `1` | number of consecutive successes for the probe to be considered successful after having failed | -| dataplane.readinessProbe.timeoutSeconds | int | `5` | number of seconds after which the probe times out | -| dataplane.replicaCount | int | `1` | | -| dataplane.resources | object | `{}` | [resource management](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) for the container | -| dataplane.securityContext.allowPrivilegeEscalation | bool | `false` | Controls [Privilege Escalation](https://kubernetes.io/docs/concepts/security/pod-security-policy/#privilege-escalation) enabling setuid binaries changing the effective user ID | -| dataplane.securityContext.capabilities.add | list | `[]` | Specifies which capabilities to add to issue specialized syscalls | -| dataplane.securityContext.capabilities.drop | list | `["ALL"]` | Specifies which capabilities to drop to reduce syscall attack surface | -| dataplane.securityContext.readOnlyRootFilesystem | bool | `true` | Whether the root filesystem is mounted in read-only mode | -| dataplane.securityContext.runAsNonRoot | bool | `true` | Requires the container to run without root privileges | -| dataplane.securityContext.runAsUser | int | `10001` | The container's process will run with the specified uid | -| dataplane.service.port | int | `80` | | -| dataplane.service.type | string | `"ClusterIP"` | [Service type](https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) to expose the running application on a set of Pods as a network service. | -| dataplane.tolerations | list | `[]` | | -| dataplane.url.public | string | `""` | Explicitly declared url for reaching the public api (e.g. if ingresses not used) | -| dataplane.volumeMounts | list | `[]` | declare where to mount [volumes](https://kubernetes.io/docs/concepts/storage/volumes/) into the container | -| dataplane.volumes | list | `[]` | [volume](https://kubernetes.io/docs/concepts/storage/volumes/) directories | -| fullnameOverride | string | `""` | | -| imagePullSecrets | list | `[]` | Existing image pull secret to use to [obtain the container image from private registries](https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry) | -| nameOverride | string | `""` | | -| postgresql.enabled | bool | `false` | | -| postgresql.jdbcUrl | string | `""` | | -| postgresql.password | string | `""` | | -| postgresql.username | string | `""` | | -| serviceAccount.annotations | object | `{}` | | -| serviceAccount.create | bool | `true` | | -| serviceAccount.imagePullSecrets | list | `[]` | Existing image pull secret bound to the service account to use to [obtain the container image from private registries](https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry) | -| serviceAccount.name | string | `""` | | -| vault.azure.certificate | string | `nil` | | -| vault.azure.client | string | `""` | | -| vault.azure.enabled | bool | `false` | | -| vault.azure.name | string | `""` | | -| vault.azure.secret | string | `nil` | | -| vault.azure.tenant | string | `""` | | -| vault.hashicorp.enabled | bool | `false` | | -| vault.hashicorp.healthCheck.enabled | bool | `true` | | -| vault.hashicorp.healthCheck.standbyOk | bool | `true` | | -| vault.hashicorp.paths.health | string | `"/v1/sys/health"` | | -| vault.hashicorp.paths.secret | string | `"/v1/secret"` | | -| vault.hashicorp.timeout | int | `30` | | -| vault.hashicorp.token | string | `""` | | -| vault.hashicorp.url | string | `""` | | -| vault.secretNames.dapsPrivateKey | string | `"daps-private-key"` | | -| vault.secretNames.dapsPublicKey | string | `"daps-public-key"` | | -| vault.secretNames.transferProxyTokenEncryptionAesKey | string | `"transfer-proxy-token-encryption-aes-key"` | | -| vault.secretNames.transferProxyTokenSignerPrivateKey | string | `"transfer-proxy-token-signer-private-key"` | | -| vault.secretNames.transferProxyTokenSignerPublicKey | string | `"transfer-proxy-token-signer-public-key"` | | +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| backendService.httpProxyTokenReceiverUrl | string | `""` | | +| customLabels | object | `{}` | | +| daps.clientId | string | `""` | | +| daps.paths.jwks | string | `"/jwks.json"` | | +| daps.paths.token | string | `"/token"` | | +| daps.url | string | `""` | | +| fullnameOverride | string | `""` | | +| imagePullSecrets | list | `[]` | Existing image pull secret to use to [obtain the container image from private registries](https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry) | +| nameOverride | string | `""` | | +| runtime.affinity | object | `{}` | | +| runtime.autoscaling.enabled | bool | `false` | Enables [horizontal pod autoscaling](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) | +| runtime.autoscaling.maxReplicas | int | `100` | Maximum replicas if resource consumption exceeds resource threshholds | +| runtime.autoscaling.minReplicas | int | `1` | Minimal replicas if resource consumption falls below resource threshholds | +| runtime.autoscaling.targetCPUUtilizationPercentage | int | `80` | targetAverageUtilization of cpu provided to a pod | +| runtime.autoscaling.targetMemoryUtilizationPercentage | int | `80` | targetAverageUtilization of memory provided to a pod | +| runtime.businessPartnerValidation.log.agreementValidation | bool | `true` | | +| runtime.debug.enabled | bool | `false` | | +| runtime.debug.port | int | `1044` | | +| runtime.debug.suspendOnStart | bool | `false` | | +| runtime.endpoints | object | `{"control":{"path":"/control","port":8083},"data":{"authKey":"","path":"/data","port":8081},"default":{"path":"/api","port":8080},"ids":{"path":"/api/v1/ids","port":8084},"observability":{"insecure":true,"path":"/observability","port":8085},"public":{"path":"/api/public","port":8086},"validation":{"path":"/validation","port":8082}}` | endpoints of the control plane | +| runtime.endpoints.control | object | `{"path":"/control","port":8083}` | control api, used for internal control calls. can be added to the internal ingress, but should probably not | +| runtime.endpoints.control.path | string | `"/control"` | path for incoming api calls | +| runtime.endpoints.control.port | int | `8083` | port for incoming api calls | +| runtime.endpoints.data | object | `{"authKey":"","path":"/data","port":8081}` | data management api, used by internal users, can be added to an ingress and must not be internet facing | +| runtime.endpoints.data.authKey | string | `""` | authentication key, must be attached to each 'X-Api-Key' request header | +| runtime.endpoints.data.path | string | `"/data"` | path for incoming api calls | +| runtime.endpoints.data.port | int | `8081` | port for incoming api calls | +| runtime.endpoints.default | object | `{"path":"/api","port":8080}` | default api for health checks, should not be added to any ingress | +| runtime.endpoints.default.path | string | `"/api"` | path for incoming api calls | +| runtime.endpoints.default.port | int | `8080` | port for incoming api calls | +| runtime.endpoints.ids | object | `{"path":"/api/v1/ids","port":8084}` | ids api, used for inter connector communication and must be internet facing | +| runtime.endpoints.ids.path | string | `"/api/v1/ids"` | path for incoming api calls | +| runtime.endpoints.ids.port | int | `8084` | port for incoming api calls | +| runtime.endpoints.observability | object | `{"insecure":true,"path":"/observability","port":8085}` | observability api with unsecured access, must not be internet facing | +| runtime.endpoints.observability.insecure | bool | `true` | allow or disallow insecure access, i.e. access without authentication | +| runtime.endpoints.observability.path | string | `"/observability"` | observability api, provides /health /readiness and /liveness endpoints | +| runtime.endpoints.observability.port | int | `8085` | port for incoming API calls | +| runtime.endpoints.validation | object | `{"path":"/validation","port":8082}` | validation api, only used by the data plane and should not be added to any ingress | +| runtime.endpoints.validation.path | string | `"/validation"` | path for incoming api calls | +| runtime.endpoints.validation.port | int | `8082` | port for incoming api calls | +| runtime.env | object | `{}` | | +| runtime.envConfigMapNames | list | `[]` | | +| runtime.envSecretNames | list | `[]` | | +| runtime.envValueFrom | object | `{}` | | +| runtime.image.pullPolicy | string | `"IfNotPresent"` | [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) to use | +| runtime.image.repository | string | `""` | | +| runtime.image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion | +| runtime.ingresses[0].annotations | object | `{}` | Additional ingress annotations to add | +| runtime.ingresses[0].certManager.clusterIssuer | string | `""` | If preset enables certificate generation via cert-manager cluster-wide issuer | +| runtime.ingresses[0].certManager.issuer | string | `""` | If preset enables certificate generation via cert-manager namespace scoped issuer | +| runtime.ingresses[0].className | string | `""` | Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use | +| runtime.ingresses[0].enabled | bool | `false` | | +| runtime.ingresses[0].endpoints | list | `["ids"]` | EDC endpoints exposed by this ingress resource | +| runtime.ingresses[0].hostname | string | `"edc-control.local"` | The hostname to be used to precisely map incoming traffic onto the underlying network service | +| runtime.ingresses[0].tls | object | `{"enabled":false,"secretName":""}` | TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource | +| runtime.ingresses[0].tls.enabled | bool | `false` | Enables TLS on the ingress resource | +| runtime.ingresses[0].tls.secretName | string | `""` | If present overwrites the default secret name | +| runtime.ingresses[1].annotations | object | `{}` | Additional ingress annotations to add | +| runtime.ingresses[1].certManager.clusterIssuer | string | `""` | If preset enables certificate generation via cert-manager cluster-wide issuer | +| runtime.ingresses[1].certManager.issuer | string | `""` | If preset enables certificate generation via cert-manager namespace scoped issuer | +| runtime.ingresses[1].className | string | `""` | Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use | +| runtime.ingresses[1].enabled | bool | `false` | | +| runtime.ingresses[1].endpoints | list | `["data","control"]` | EDC endpoints exposed by this ingress resource | +| runtime.ingresses[1].hostname | string | `"edc-control.intranet"` | The hostname to be used to precisely map incoming traffic onto the underlying network service | +| runtime.ingresses[1].tls | object | `{"enabled":false,"secretName":""}` | TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource | +| runtime.ingresses[1].tls.enabled | bool | `false` | Enables TLS on the ingress resource | +| runtime.ingresses[1].tls.secretName | string | `""` | If present overwrites the default secret name | +| runtime.initContainers | list | `[]` | | +| runtime.internationalDataSpaces.catalogId | string | `"TXDC-Catalog"` | | +| runtime.internationalDataSpaces.curator | string | `""` | | +| runtime.internationalDataSpaces.description | string | `"Tractus-X Eclipse IDS Data Space Connector"` | | +| runtime.internationalDataSpaces.id | string | `"TXDC"` | | +| runtime.internationalDataSpaces.maintainer | string | `""` | | +| runtime.internationalDataSpaces.title | string | `""` | | +| runtime.livenessProbe.enabled | bool | `true` | Whether to enable kubernetes [liveness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | +| runtime.livenessProbe.failureThreshold | int | `6` | when a probe fails kubernetes will try 6 times before giving up | +| runtime.livenessProbe.initialDelaySeconds | int | `30` | seconds to wait before performing the first liveness check | +| runtime.livenessProbe.periodSeconds | int | `10` | this fields specifies that kubernetes should perform a liveness check every 10 seconds | +| runtime.livenessProbe.successThreshold | int | `1` | number of consecutive successes for the probe to be considered successful after having failed | +| runtime.livenessProbe.timeoutSeconds | int | `5` | number of seconds after which the probe times out | +| runtime.logging | string | `".level=INFO\norg.eclipse.edc.level=ALL\nhandlers=java.util.logging.ConsoleHandler\njava.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter\njava.util.logging.ConsoleHandler.level=ALL\njava.util.logging.SimpleFormatter.format=[%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS] [%4$-7s] %5$s%6$s%n"` | configuration of the [Java Util Logging Facade](https://docs.oracle.com/javase/7/docs/technotes/guides/logging/overview.html) | +| runtime.nodeSelector | object | `{}` | | +| runtime.podAnnotations | object | `{}` | additional annotations for the pod | +| runtime.podLabels | object | `{}` | additional labels for the pod | +| runtime.podSecurityContext | object | `{"fsGroup":10001,"runAsGroup":10001,"runAsUser":10001,"seccompProfile":{"type":"RuntimeDefault"}}` | The [pod security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod) defines privilege and access control settings for a Pod within the deployment | +| runtime.podSecurityContext.fsGroup | int | `10001` | The owner for volumes and any files created within volumes will belong to this guid | +| runtime.podSecurityContext.runAsGroup | int | `10001` | Processes within a pod will belong to this guid | +| runtime.podSecurityContext.runAsUser | int | `10001` | Runs all processes within a pod with a special uid | +| runtime.podSecurityContext.seccompProfile.type | string | `"RuntimeDefault"` | Restrict a Container's Syscalls with seccomp | +| runtime.readinessProbe.enabled | bool | `true` | Whether to enable kubernetes [readiness-probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | +| runtime.readinessProbe.failureThreshold | int | `6` | when a probe fails kubernetes will try 6 times before giving up | +| runtime.readinessProbe.initialDelaySeconds | int | `30` | seconds to wait before performing the first readiness check | +| runtime.readinessProbe.periodSeconds | int | `10` | this fields specifies that kubernetes should perform a readiness check every 10 seconds | +| runtime.readinessProbe.successThreshold | int | `1` | number of consecutive successes for the probe to be considered successful after having failed | +| runtime.readinessProbe.timeoutSeconds | int | `5` | number of seconds after which the probe times out | +| runtime.replicaCount | int | `1` | | +| runtime.resources | object | `{}` | [resource management](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) for the container | +| runtime.securityContext.allowPrivilegeEscalation | bool | `false` | Controls [Privilege Escalation](https://kubernetes.io/docs/concepts/security/pod-security-policy/#privilege-escalation) enabling setuid binaries changing the effective user ID | +| runtime.securityContext.capabilities.add | list | `[]` | Specifies which capabilities to add to issue specialized syscalls | +| runtime.securityContext.capabilities.drop | list | `["ALL"]` | Specifies which capabilities to drop to reduce syscall attack surface | +| runtime.securityContext.readOnlyRootFilesystem | bool | `true` | Whether the root filesystem is mounted in read-only mode | +| runtime.securityContext.runAsNonRoot | bool | `true` | Requires the container to run without root privileges | +| runtime.securityContext.runAsUser | int | `10001` | The container's process will run with the specified uid | +| runtime.service.annotations | object | `{}` | | +| runtime.service.type | string | `"ClusterIP"` | [Service type](https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) to expose the running application on a set of Pods as a network service. | +| runtime.tolerations | list | `[]` | | +| runtime.url.ids | string | `""` | Explicitly declared url for reaching the ids api (e.g. if ingresses not used) | +| runtime.url.public | string | `""` | | +| runtime.url.readiness | string | `""` | | +| runtime.volumeMounts | list | `[]` | declare where to mount [volumes](https://kubernetes.io/docs/concepts/storage/volumes/) into the container | +| runtime.volumes | list | `[]` | [volume](https://kubernetes.io/docs/concepts/storage/volumes/) directories | +| serviceAccount.annotations | object | `{}` | | +| serviceAccount.create | bool | `true` | | +| serviceAccount.imagePullSecrets | list | `[]` | Existing image pull secret bound to the service account to use to [obtain the container image from private registries](https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry) | +| serviceAccount.name | string | `""` | | +| vault.secretNames.dapsPrivateKey | string | `"daps-private-key"` | | +| vault.secretNames.dapsPublicKey | string | `"daps-public-key"` | | +| vault.secretNames.transferProxyTokenEncryptionAesKey | string | `"transfer-proxy-token-encryption-aes-key"` | | +| vault.secretNames.transferProxyTokenSignerPrivateKey | string | `"transfer-proxy-token-signer-private-key"` | | +| vault.secretNames.transferProxyTokenSignerPublicKey | string | `"transfer-proxy-token-signer-public-key"` | | +| vault.secrets | string | `""` | | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.10.0](https://github.com/norwoodj/helm-docs/releases/v1.10.0) diff --git a/charts/tractusx-connector-memory/README.md.gotmpl b/charts/tractusx-connector-memory/README.md.gotmpl index b1671f5a2..44500d3d1 100644 --- a/charts/tractusx-connector-memory/README.md.gotmpl +++ b/charts/tractusx-connector-memory/README.md.gotmpl @@ -12,7 +12,7 @@ ```shell helm repo add tractusx-edc https://eclipse-tractusx.github.io/charts/dev -helm install my-release tractusx-edc/tractusx-connector --version {{ .Version }} +helm install my-release tractusx-edc/tractusx-connector-memory --version {{ .Version }} ``` {{ template "chart.maintainersSection" . }} diff --git a/charts/tractusx-connector-memory/templates/deployment-runtime.yaml b/charts/tractusx-connector-memory/templates/deployment-runtime.yaml index 04386678c..82d162ad8 100644 --- a/charts/tractusx-connector-memory/templates/deployment-runtime.yaml +++ b/charts/tractusx-connector-memory/templates/deployment-runtime.yaml @@ -63,7 +63,7 @@ spec: {{- if .Values.runtime.image.repository }} image: "{{ .Values.runtime.image.repository }}:{{ .Values.runtime.image.tag | default .Chart.AppVersion }}" {{- else }} - image: "ghcr.io/catenax-ng/tx-tractusx-edc/edc-runtime-memory:{{ .Values.runtime.image.tag | default .Chart.AppVersion }}" + image: "tractusx/edc-runtime-memory:{{ .Values.runtime.image.tag | default .Chart.AppVersion }}" {{- end }} imagePullPolicy: {{ .Values.runtime.image.pullPolicy }} @@ -253,6 +253,12 @@ spec: value: "0" - name: "EDC_CP_ADAPTER_REUSE_CONTRACT_AGREEMENT" value: "0" + + ########################### + ## BUSINESS PARTNER NUMBER VALIDATION EXTENSION ## + ########################### + - name: "TRACTUSX_BUSINESSPARTNERVALIDATION_LOG_AGREEMENT_VALIDATION" + value: {{ .Values.runtime.businessPartnerValidation.log.agreementValidation | quote }} ###################################### ## Additional environment variables ## diff --git a/charts/tractusx-connector-memory/values.yaml b/charts/tractusx-connector-memory/values.yaml index 66fa1b7fe..83ce92818 100644 --- a/charts/tractusx-connector-memory/values.yaml +++ b/charts/tractusx-connector-memory/values.yaml @@ -121,6 +121,9 @@ runtime: public: port: 8086 path: /api/public + businessPartnerValidation: + log: + agreementValidation: true service: # -- [Service type](https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) to expose the running application on a set of Pods as a network service. type: ClusterIP diff --git a/charts/tractusx-connector/Chart.yaml b/charts/tractusx-connector/Chart.yaml index f9e4322c6..696e94396 100644 --- a/charts/tractusx-connector/Chart.yaml +++ b/charts/tractusx-connector/Chart.yaml @@ -36,12 +36,12 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.3.2 +version: 0.3.3 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.3.2" +appVersion: "0.3.3" home: https://github.com/eclipse-tractusx/tractusx-edc/tree/main/charts/tractusx-connector sources: - https://github.com/eclipse-tractusx/tractusx-edc/tree/main/charts/tractusx-connector diff --git a/charts/tractusx-connector/README.md b/charts/tractusx-connector/README.md index af53087c9..12c45b649 100644 --- a/charts/tractusx-connector/README.md +++ b/charts/tractusx-connector/README.md @@ -1,6 +1,6 @@ # tractusx-connector -![Version: 0.3.2](https://img.shields.io/badge/Version-0.3.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.3.2](https://img.shields.io/badge/AppVersion-0.3.2-informational?style=flat-square) +![Version: 0.3.3](https://img.shields.io/badge/Version-0.3.3-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.3.3](https://img.shields.io/badge/AppVersion-0.3.3-informational?style=flat-square) A Helm chart for Tractus-X Eclipse Data Space Connector @@ -10,7 +10,7 @@ A Helm chart for Tractus-X Eclipse Data Space Connector ```shell helm repo add tractusx-edc https://eclipse-tractusx.github.io/charts/dev -helm install my-release tractusx-edc/tractusx-connector --version 0.3.2 +helm install my-release tractusx-edc/tractusx-connector --version 0.3.3 ``` ## Source Code @@ -28,23 +28,21 @@ helm install my-release tractusx-edc/tractusx-connector --version 0.3.2 | controlplane.autoscaling.minReplicas | int | `1` | Minimal replicas if resource consumption falls below resource threshholds | | controlplane.autoscaling.targetCPUUtilizationPercentage | int | `80` | targetAverageUtilization of cpu provided to a pod | | controlplane.autoscaling.targetMemoryUtilizationPercentage | int | `80` | targetAverageUtilization of memory provided to a pod | +| controlplane.businessPartnerValidation.log.agreementValidation | bool | `true` | | | controlplane.debug.enabled | bool | `false` | | | controlplane.debug.port | int | `1044` | | | controlplane.debug.suspendOnStart | bool | `false` | | -| controlplane.endpoints | object | `{"control":{"path":"/control","port":8083},"data":{"authKey":"","path":"/data","port":8081},"default":{"path":"/api","port":8080},"ids":{"path":"/api/v1/ids","port":8084},"metrics":{"path":"/metrics","port":9090},"observability":{"insecure":true,"path":"/observability","port":8085},"validation":{"path":"/validation","port":8082}}` | endpoints of the control plane | +| controlplane.endpoints | object | `{"control":{"path":"/control","port":8083},"default":{"path":"/api","port":8080},"management":{"authKey":"","path":"/management","port":8081},"metrics":{"path":"/metrics","port":9090},"observability":{"insecure":true,"path":"/observability","port":8085},"protocol":{"path":"/api/v1/ids","port":8084}}` | endpoints of the control plane | | controlplane.endpoints.control | object | `{"path":"/control","port":8083}` | control api, used for internal control calls. can be added to the internal ingress, but should probably not | | controlplane.endpoints.control.path | string | `"/control"` | path for incoming api calls | | controlplane.endpoints.control.port | int | `8083` | port for incoming api calls | -| controlplane.endpoints.data | object | `{"authKey":"","path":"/data","port":8081}` | data management api, used by internal users, can be added to an ingress and must not be internet facing | -| controlplane.endpoints.data.authKey | string | `""` | authentication key, must be attached to each 'X-Api-Key' request header | -| controlplane.endpoints.data.path | string | `"/data"` | path for incoming api calls | -| controlplane.endpoints.data.port | int | `8081` | port for incoming api calls | | controlplane.endpoints.default | object | `{"path":"/api","port":8080}` | default api for health checks, should not be added to any ingress | | controlplane.endpoints.default.path | string | `"/api"` | path for incoming api calls | | controlplane.endpoints.default.port | int | `8080` | port for incoming api calls | -| controlplane.endpoints.ids | object | `{"path":"/api/v1/ids","port":8084}` | ids api, used for inter connector communication and must be internet facing | -| controlplane.endpoints.ids.path | string | `"/api/v1/ids"` | path for incoming api calls | -| controlplane.endpoints.ids.port | int | `8084` | port for incoming api calls | +| controlplane.endpoints.management | object | `{"authKey":"","path":"/management","port":8081}` | data management api, used by internal users, can be added to an ingress and must not be internet facing | +| controlplane.endpoints.management.authKey | string | `""` | authentication key, must be attached to each 'X-Api-Key' request header | +| controlplane.endpoints.management.path | string | `"/management"` | path for incoming api calls | +| controlplane.endpoints.management.port | int | `8081` | port for incoming api calls | | controlplane.endpoints.metrics | object | `{"path":"/metrics","port":9090}` | metrics api, used for application metrics, must not be internet facing | | controlplane.endpoints.metrics.path | string | `"/metrics"` | path for incoming api calls | | controlplane.endpoints.metrics.port | int | `9090` | port for incoming api calls | @@ -52,9 +50,9 @@ helm install my-release tractusx-edc/tractusx-connector --version 0.3.2 | controlplane.endpoints.observability.insecure | bool | `true` | allow or disallow insecure access, i.e. access without authentication | | controlplane.endpoints.observability.path | string | `"/observability"` | observability api, provides /health /readiness and /liveness endpoints | | controlplane.endpoints.observability.port | int | `8085` | port for incoming API calls | -| controlplane.endpoints.validation | object | `{"path":"/validation","port":8082}` | validation api, only used by the data plane and should not be added to any ingress | -| controlplane.endpoints.validation.path | string | `"/validation"` | path for incoming api calls | -| controlplane.endpoints.validation.port | int | `8082` | port for incoming api calls | +| controlplane.endpoints.protocol | object | `{"path":"/api/v1/ids","port":8084}` | ids api, used for inter connector communication and must be internet facing | +| controlplane.endpoints.protocol.path | string | `"/api/v1/ids"` | path for incoming api calls | +| controlplane.endpoints.protocol.port | int | `8084` | port for incoming api calls | | controlplane.env | object | `{}` | | | controlplane.envConfigMapNames | list | `[]` | | | controlplane.envSecretNames | list | `[]` | | @@ -77,7 +75,7 @@ helm install my-release tractusx-edc/tractusx-connector --version 0.3.2 | controlplane.ingresses[1].certManager.issuer | string | `""` | If preset enables certificate generation via cert-manager namespace scoped issuer | | controlplane.ingresses[1].className | string | `""` | Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use | | controlplane.ingresses[1].enabled | bool | `false` | | -| controlplane.ingresses[1].endpoints | list | `["data","control"]` | EDC endpoints exposed by this ingress resource | +| controlplane.ingresses[1].endpoints | list | `["management","control"]` | EDC endpoints exposed by this ingress resource | | controlplane.ingresses[1].hostname | string | `"edc-control.intranet"` | The hostname to be used to precisely map incoming traffic onto the underlying network service | | controlplane.ingresses[1].tls | object | `{"enabled":false,"secretName":""}` | TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource | | controlplane.ingresses[1].tls.enabled | bool | `false` | Enables TLS on the ingress resource | @@ -148,10 +146,11 @@ helm install my-release tractusx-edc/tractusx-connector --version 0.3.2 | dataplane.endpoints.default.port | int | `8080` | | | dataplane.endpoints.metrics.path | string | `"/metrics"` | | | dataplane.endpoints.metrics.port | int | `9090` | | +| dataplane.endpoints.observability.insecure | bool | `true` | allow or disallow insecure access, i.e. access without authentication | +| dataplane.endpoints.observability.path | string | `"/observability"` | observability api, provides /health /readiness and /liveness endpoints | +| dataplane.endpoints.observability.port | int | `8085` | port for incoming API calls | | dataplane.endpoints.public.path | string | `"/api/public"` | | | dataplane.endpoints.public.port | int | `8081` | | -| dataplane.endpoints.validation.path | string | `"/validation"` | | -| dataplane.endpoints.validation.port | int | `8082` | | | dataplane.env | object | `{}` | | | dataplane.envConfigMapNames | list | `[]` | | | dataplane.envSecretNames | list | `[]` | | diff --git a/charts/tractusx-connector/templates/deployment-controlplane.yaml b/charts/tractusx-connector/templates/deployment-controlplane.yaml index 6eded494c..daab957e4 100644 --- a/charts/tractusx-connector/templates/deployment-controlplane.yaml +++ b/charts/tractusx-connector/templates/deployment-controlplane.yaml @@ -331,6 +331,12 @@ spec: - name: "EDC_CP_ADAPTER_REUSE_CONTRACT_AGREEMENT" value: "0" + ########################### + ## BUSINESS PARTNER NUMBER VALIDATION EXTENSION ## + ########################### + - name: "TRACTUSX_BUSINESSPARTNERVALIDATION_LOG_AGREEMENT_VALIDATION" + value: {{ .Values.controlplane.businessPartnerValidation.log.agreementValidation | quote }} + ###################################### ## Additional environment variables ## ###################################### diff --git a/charts/tractusx-connector/values.yaml b/charts/tractusx-connector/values.yaml index aebd45481..21acfc20b 100644 --- a/charts/tractusx-connector/values.yaml +++ b/charts/tractusx-connector/values.yaml @@ -122,6 +122,9 @@ controlplane: path: /observability # -- allow or disallow insecure access, i.e. access without authentication insecure: true + businessPartnerValidation: + log: + agreementValidation: true service: # -- [Service type](https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) to expose the running application on a set of Pods as a network service. type: ClusterIP diff --git a/docs/development/decision-records/2023-04-20_conventional_commits/README.md b/docs/development/decision-records/2023-04-20_conventional_commits/README.md new file mode 100644 index 000000000..d58b55c61 --- /dev/null +++ b/docs/development/decision-records/2023-04-20_conventional_commits/README.md @@ -0,0 +1,43 @@ +# Using Conventional Commit messages + +## Decision + +From now on, TractusX-EDC will use only conventional commit messages. The specification can be +found [here](https://www.conventionalcommits.org/en/v1.0.0/#summary) + +## Rationale + +Conventional commits create a structured, explicit and unambiguous commit history, that is easy to read and to +interpret. Conventional commits are widely used in the world of open source development. +On top of that, there +is [extensive tooling](https://www.conventionalcommits.org/en/about/#tooling-for-conventional-commits) to support the +creation, interpretation and enforcement of conventional commits. + +## Approach + +As a first step, we enforce conventional commits as part of our CI pipeline. TractusX-EDC is using +Squash-Rebase-merging, and the PR title is used as commit message. We will not dictate how people structure their +commits during the _development_ phase of their PR, but we _will_ enforce, that PR titles (and thus: merge commit +messages) are in the conventional commit format. + +To do that, we can use a very simple regex check on the PR title: + +```yaml +- uses: deepakputhraya/action-pr-title@master + with: + regex: '^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\(\w+((,|\/|\\)?\s?\w+)+\))?!?: [\S ]{1,80}[^\.]$' + allowed_prefixes: 'build,chore,ci,docs,feat,fix,perf,refactor,revert,style,test' + prefix_case_sensitive: true +``` + +That way, we can catch malformed PR titles early, which would result in malformed _merge commit messages_. In addition, +we can +use any of the tools linked above to ensure commit messages, e.g. when merge commits are altered manually, etc. + +## Future outlook + +Once we have a structured commit history done in the conventional commit format, we can auto-generate changelogs, link +to (auto-generated) documentation, render visually appealing version information, etc. Essentially, we can use any +number of tooling on top of cc's. +One key aspect would be to get rid of the manual changelog, +see [this discussion](https://github.com/eclipse-tractusx/tractusx-edc/discussions/253). diff --git a/docs/development/postman/collection.json b/docs/development/postman/collection.json index 50d0c5ab7..26de5c7d2 100644 --- a/docs/development/postman/collection.json +++ b/docs/development/postman/collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "b61c0075-e360-45df-9756-c9bc432fe76a", + "_postman_id": "fcea09d2-13d9-49ce-8c44-d3cb3078eb82", "name": "EDC", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", "_exporter_id": "6134257" @@ -12,12 +12,11 @@ "method": "GET", "header": [], "url": { - "raw": "{{PROVIDER_DATAMGMT_URL}}/data/assets/{{ASSET_ID}}", + "raw": "{{PROVIDER_MANAGEMENT_URL}}/assets/{{ASSET_ID}}", "host": [ - "{{PROVIDER_DATAMGMT_URL}}" + "{{PROVIDER_MANAGEMENT_URL}}" ], "path": [ - "data", "assets", "{{ASSET_ID}}" ] @@ -31,12 +30,11 @@ "method": "GET", "header": [], "url": { - "raw": "{{PROVIDER_DATAMGMT_URL}}/data/assets", + "raw": "{{PROVIDER_MANAGEMENT_URL}}/assets", "host": [ - "{{PROVIDER_DATAMGMT_URL}}" + "{{PROVIDER_MANAGEMENT_URL}}" ], "path": [ - "data", "assets" ] } @@ -71,12 +69,11 @@ } }, "url": { - "raw": "{{PROVIDER_DATAMGMT_URL}}/data/assets", + "raw": "{{PROVIDER_MANAGEMENT_URL}}/assets", "host": [ - "{{PROVIDER_DATAMGMT_URL}}" + "{{PROVIDER_MANAGEMENT_URL}}" ], "path": [ - "data", "assets" ] } @@ -89,12 +86,11 @@ "method": "DELETE", "header": [], "url": { - "raw": "{{PROVIDER_DATAMGMT_URL}}/data/assets/{{ASSET_ID}}", + "raw": "{{PROVIDER_MANAGEMENT_URL}}/assets/{{ASSET_ID}}", "host": [ - "{{PROVIDER_DATAMGMT_URL}}" + "{{PROVIDER_MANAGEMENT_URL}}" ], "path": [ - "data", "assets", "{{ASSET_ID}}" ] @@ -120,12 +116,11 @@ } }, "url": { - "raw": "{{PROVIDER_DATAMGMT_URL}}/data/policydefinitions/{{POLICY_ID}}", + "raw": "{{PROVIDER_MANAGEMENT_URL}}/policydefinitions/{{POLICY_ID}}", "host": [ - "{{PROVIDER_DATAMGMT_URL}}" + "{{PROVIDER_MANAGEMENT_URL}}" ], "path": [ - "data", "policydefinitions", "{{POLICY_ID}}" ] @@ -151,12 +146,11 @@ } }, "url": { - "raw": "{{PROVIDER_DATAMGMT_URL}}/data/policydefinitions", + "raw": "{{PROVIDER_MANAGEMENT_URL}}/policydefinitions", "host": [ - "{{PROVIDER_DATAMGMT_URL}}" + "{{PROVIDER_MANAGEMENT_URL}}" ], "path": [ - "data", "policydefinitions" ] } @@ -178,12 +172,11 @@ } }, "url": { - "raw": "{{PROVIDER_DATAMGMT_URL}}/data/policydefinitions", + "raw": "{{PROVIDER_MANAGEMENT_URL}}/policydefinitions", "host": [ - "{{PROVIDER_DATAMGMT_URL}}" + "{{PROVIDER_MANAGEMENT_URL}}" ], "path": [ - "data", "policydefinitions" ] } @@ -205,12 +198,11 @@ } }, "url": { - "raw": "{{PROVIDER_DATAMGMT_URL}}/data/policydefinitions", + "raw": "{{PROVIDER_MANAGEMENT_URL}}/policydefinitions", "host": [ - "{{PROVIDER_DATAMGMT_URL}}" + "{{PROVIDER_MANAGEMENT_URL}}" ], "path": [ - "data", "policydefinitions" ] } @@ -232,12 +224,11 @@ } }, "url": { - "raw": "{{PROVIDER_DATAMGMT_URL}}/data/policydefinitions", + "raw": "{{PROVIDER_MANAGEMENT_URL}}/policydefinitions", "host": [ - "{{PROVIDER_DATAMGMT_URL}}" + "{{PROVIDER_MANAGEMENT_URL}}" ], "path": [ - "data", "policydefinitions" ] } @@ -259,12 +250,11 @@ } }, "url": { - "raw": "{{PROVIDER_DATAMGMT_URL}}/data/policydefinitions/{{POLICY_ID}}", + "raw": "{{PROVIDER_MANAGEMENT_URL}}/policydefinitions/{{POLICY_ID}}", "host": [ - "{{PROVIDER_DATAMGMT_URL}}" + "{{PROVIDER_MANAGEMENT_URL}}" ], "path": [ - "data", "policydefinitions", "{{POLICY_ID}}" ] @@ -290,12 +280,11 @@ } }, "url": { - "raw": "{{PROVIDER_DATAMGMT_URL}}/data/contractdefinitions/{{POLICY_ID}}", + "raw": "{{PROVIDER_MANAGEMENT_URL}}/contractdefinitions/{{POLICY_ID}}", "host": [ - "{{PROVIDER_DATAMGMT_URL}}" + "{{PROVIDER_MANAGEMENT_URL}}" ], "path": [ - "data", "contractdefinitions", "{{POLICY_ID}}" ] @@ -321,12 +310,11 @@ } }, "url": { - "raw": "{{PROVIDER_DATAMGMT_URL}}/data/contractdefinitions", + "raw": "{{PROVIDER_MANAGEMENT_URL}}/contractdefinitions", "host": [ - "{{PROVIDER_DATAMGMT_URL}}" + "{{PROVIDER_MANAGEMENT_URL}}" ], "path": [ - "data", "contractdefinitions" ] } @@ -348,12 +336,11 @@ } }, "url": { - "raw": "{{PROVIDER_DATAMGMT_URL}}/data/contractdefinitions", + "raw": "{{PROVIDER_MANAGEMENT_URL}}/contractdefinitions", "host": [ - "{{PROVIDER_DATAMGMT_URL}}" + "{{PROVIDER_MANAGEMENT_URL}}" ], "path": [ - "data", "contractdefinitions" ] } @@ -375,12 +362,11 @@ } }, "url": { - "raw": "{{PROVIDER_DATAMGMT_URL}}/data/contractdefinitions/{{POLICY_ID}}", + "raw": "{{PROVIDER_MANAGEMENT_URL}}/contractdefinitions/{{POLICY_ID}}", "host": [ - "{{PROVIDER_DATAMGMT_URL}}" + "{{PROVIDER_MANAGEMENT_URL}}" ], "path": [ - "data", "contractdefinitions", "{{POLICY_ID}}" ] @@ -394,18 +380,17 @@ "method": "GET", "header": [], "url": { - "raw": "{{CONSUMER_DATAMGMT_URL}}/data/catalog?providerUrl={{PROVIDER_IDS_URL}}/api/v1/ids/data&size=50", + "raw": "{{CONSUMER_MANAGEMENT_URL}}/catalog?providerUrl={{PROVIDER_PROTOCOL_URL}}/api/v1/ids/data&size=50", "host": [ - "{{CONSUMER_DATAMGMT_URL}}" + "{{CONSUMER_MANAGEMENT_URL}}" ], "path": [ - "data", "catalog" ], "query": [ { "key": "providerUrl", - "value": "{{PROVIDER_IDS_URL}}/api/v1/ids/data" + "value": "{{PROVIDER_PROTOCOL_URL}}/api/v1/ids/data" }, { "key": "size", @@ -423,7 +408,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"providerUrl\": \"{{PROVIDER_IDS_URL}}/api/v1/ids/data\",\r\n \"querySpec\": {\r\n \"offset\": 0,\r\n \"limit\": 100,\r\n \"filter\": \"\",\r\n \"range\": {\r\n \"from\": 0,\r\n \"to\": 100\r\n },\r\n \"sortOrder\": \"ASC\",\r\n \"sortField\": \"\"\r\n }\r\n}", + "raw": "{\r\n \"providerUrl\": \"{{PROVIDER_PROTOCOL_URL}}/api/v1/ids/data\",\r\n \"querySpec\": {\r\n \"offset\": 0,\r\n \"limit\": 100,\r\n \"sort\": \"ASC\",\r\n \"sortField\": \"\"\r\n }\r\n}", "options": { "raw": { "language": "json" @@ -431,12 +416,11 @@ } }, "url": { - "raw": "{{CONSUMER_DATAMGMT_URL}}/data/catalog/request", + "raw": "{{CONSUMER_MANAGEMENT_URL}}/catalog/request", "host": [ - "{{CONSUMER_DATAMGMT_URL}}" + "{{CONSUMER_MANAGEMENT_URL}}" ], "path": [ - "data", "catalog", "request" ] @@ -445,46 +429,48 @@ "response": [] }, { - "name": "Negotation", + "name": "Negotation (Public)", "event": [ - { - "listen": "prerequest", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - }, { "listen": "test", "script": { "exec": [ - "" + "pm.test(\"Body matches string\", function () {", + " var jsonData = pm.response.json();", + " pm.collectionVariables.set(\"NEGOTIATION_ID\", jsonData.id);", + "", + "});" ], "type": "text/javascript" } } ], "request": { - "method": "GET", + "method": "POST", "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"connectorId\": \"foo\",\n \"connectorAddress\": \"{{PROVIDER_PROTOCOL_URL}}/api/v1/ids/data\",\n \"offer\": {\n \"offerId\": \"{{CONTRACT_DEFINITION_ID}}:foo\",\n \"assetId\": \"{{ASSET_ID}}\",\n \"policy\": {\n \"prohibitions\": [],\n \"obligations\": [],\n \"permissions\": [\n {\n \"edctype\": \"dataspaceconnector:permission\",\n \"action\": {\n \"type\": \"USE\"\n },\n \"target\": \"{{ASSET_ID}}\",\n \"constraints\": []\n }\n ]\n }\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { - "raw": "{{CONSUMER_DATAMGMT_URL}}/data/contractnegotiations/{{NEGOTIATION_ID}}", + "raw": "{{CONSUMER_MANAGEMENT_URL}}/contractnegotiations", "host": [ - "{{CONSUMER_DATAMGMT_URL}}" + "{{CONSUMER_MANAGEMENT_URL}}" ], "path": [ - "data", - "contractnegotiations", - "{{NEGOTIATION_ID}}" + "contractnegotiations" ] } }, "response": [] }, { - "name": "Negotation (Public)", + "name": "Negotation (Properties)", "event": [ { "listen": "test", @@ -505,7 +491,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"connectorId\": \"foo\",\n \"connectorAddress\": \"{{PROVIDER_IDS_URL}}/api/v1/ids/data\",\n \"offer\": {\n \"offerId\": \"{{CONTRACT_DEFINITION_ID}}:foo\",\n \"assetId\": \"{{ASSET_ID}}\",\n \"policy\": {\n \"prohibitions\": [],\n \"obligations\": [],\n \"permissions\": [\n {\n \"edctype\": \"dataspaceconnector:permission\",\n \"action\": {\n \"type\": \"USE\"\n },\n \"target\": \"{{ASSET_ID}}\",\n \"constraints\": []\n }\n ]\n }\n }\n}", + "raw": "{\n \"connectorId\": \"foo\",\n \"connectorAddress\": \"{{PROVIDER_PROTOCOL_URL}}/api/v1/ids/data\",\n \"offer\": {\n \"offerId\": \"{{POLICY_ID}}:foo\",\n \"assetId\": \"{{ASSET_ID}}\",\n \"policy\": {\n \"prohibitions\": [],\n \"obligations\": [],\n \"permissions\": [\n {\n \"edctype\": \"dataspaceconnector:permission\",\n \"action\": {\n \"type\": \"USE\"\n },\n \"target\": \"{{ASSET_ID}}\",\n \"constraints\": []\n }\n ],\n \"extensibleProperties\": {\n \"foo\": \"bar\"\n }\n }\n }\n}", "options": { "raw": { "language": "json" @@ -513,12 +499,11 @@ } }, "url": { - "raw": "{{CONSUMER_DATAMGMT_URL}}/data/contractnegotiations", + "raw": "{{CONSUMER_MANAGEMENT_URL}}/contractnegotiations", "host": [ - "{{CONSUMER_DATAMGMT_URL}}" + "{{CONSUMER_MANAGEMENT_URL}}" ], "path": [ - "data", "contractnegotiations" ] } @@ -526,7 +511,7 @@ "response": [] }, { - "name": "Negotation (Properties)", + "name": "Negotation (BPN)", "event": [ { "listen": "test", @@ -547,7 +532,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"connectorId\": \"foo\",\n \"connectorAddress\": \"{{PROVIDER_IDS_URL}}/api/v1/ids/data\",\n \"offer\": {\n \"offerId\": \"{{POLICY_ID}}:foo\",\n \"assetId\": \"{{ASSET_ID}}\",\n \"policy\": {\n \"prohibitions\": [],\n \"obligations\": [],\n \"permissions\": [\n {\n \"edctype\": \"dataspaceconnector:permission\",\n \"action\": {\n \"type\": \"USE\"\n },\n \"target\": \"{{ASSET_ID}}\",\n \"constraints\": []\n }\n ],\n \"extensibleProperties\": {\n \"foo\": \"bar\"\n }\n }\n }\n}", + "raw": "{\n \"connectorId\": \"foo\",\n \"connectorAddress\": \"{{PROVIDER_PROTOCOL_URL}}/api/v1/ids/data\",\n \"offer\": {\n \"offerId\": \"{{POLICY_ID}}:foo\",\n \"assetId\": \"{{ASSET_ID}}\",\n \"policy\": {\n \"prohibitions\": [],\n \"obligations\": [],\n \"permissions\": [\n {\n \"edctype\": \"dataspaceconnector:permission\",\n \"action\": {\n \"type\": \"USE\"\n },\n \"target\": \"{{ASSET_ID}}\",\n \"constraints\": [\n {\n \"edctype\": \"AtomicConstraint\",\n \"leftExpression\": {\n \"edctype\": \"dataspaceconnector:literalexpression\",\n \"value\": \"BusinessPartnerNumber\"\n },\n \"rightExpression\": {\n \"edctype\": \"dataspaceconnector:literalexpression\",\n \"value\": \"{{POLICY_BPN}}\"\n },\n \"operator\": \"EQ\"\n }\n ]\n }\n ]\n }\n }\n}", "options": { "raw": { "language": "json" @@ -555,12 +540,11 @@ } }, "url": { - "raw": "{{CONSUMER_DATAMGMT_URL}}/data/contractnegotiations", + "raw": "{{CONSUMER_MANAGEMENT_URL}}/contractnegotiations", "host": [ - "{{CONSUMER_DATAMGMT_URL}}" + "{{CONSUMER_MANAGEMENT_URL}}" ], "path": [ - "data", "contractnegotiations" ] } @@ -568,16 +552,24 @@ "response": [] }, { - "name": "Negotation (BPN)", + "name": "Negotation (init AGREEMENT_ID)", "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, { "listen": "test", "script": { "exec": [ - "pm.test(\"Body matches string\", function () {", - " var jsonData = pm.response.json();", - " pm.collectionVariables.set(\"NEGOTIATION_ID\", jsonData.id);", - "", + "pm.test(\"Body matches string\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.collectionVariables.set(\"AGREEMENT_ID\", jsonData.contractAgreementId);\r", "});" ], "type": "text/javascript" @@ -585,25 +577,16 @@ } ], "request": { - "method": "POST", + "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"connectorId\": \"foo\",\n \"connectorAddress\": \"{{PROVIDER_IDS_URL}}/api/v1/ids/data\",\n \"offer\": {\n \"offerId\": \"{{POLICY_ID}}:foo\",\n \"assetId\": \"{{ASSET_ID}}\",\n \"policy\": {\n \"prohibitions\": [],\n \"obligations\": [],\n \"permissions\": [\n {\n \"edctype\": \"dataspaceconnector:permission\",\n \"action\": {\n \"type\": \"USE\"\n },\n \"target\": \"{{ASSET_ID}}\",\n \"constraints\": [\n {\n \"edctype\": \"AtomicConstraint\",\n \"leftExpression\": {\n \"edctype\": \"dataspaceconnector:literalexpression\",\n \"value\": \"BusinessPartnerNumber\"\n },\n \"rightExpression\": {\n \"edctype\": \"dataspaceconnector:literalexpression\",\n \"value\": \"{{POLICY_BPN}}\"\n },\n \"operator\": \"EQ\"\n }\n ]\n }\n ]\n }\n }\n}", - "options": { - "raw": { - "language": "json" - } - } - }, "url": { - "raw": "{{CONSUMER_DATAMGMT_URL}}/data/contractnegotiations", + "raw": "{{CONSUMER_MANAGEMENT_URL}}/contractnegotiations/{{NEGOTIATION_ID}}", "host": [ - "{{CONSUMER_DATAMGMT_URL}}" + "{{CONSUMER_MANAGEMENT_URL}}" ], "path": [ - "data", - "contractnegotiations" + "contractnegotiations", + "{{NEGOTIATION_ID}}" ] } }, @@ -639,7 +622,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{ \"id\": \"{{TRANSFER_ID}}\",\n \"connectorId\": \"foo\", \n \"connectorAddress\": \"{{PROVIDER_IDS_URL}}/api/v1/ids/data\", \n \"contractId\": \"{{AGREEMENT_ID}}\", \n \"assetId\": \"{{ASSET_ID}}\",\n \"managedResources\": \"false\", \n \"dataDestination\": { \"type\": \"HttpProxy\" }\n}", + "raw": "{ \"id\": \"{{TRANSFER_ID}}\",\n \"connectorId\": \"foo\", \n \"connectorAddress\": \"{{PROVIDER_PROTOCOL_URL}}/api/v1/ids/data\", \n \"contractId\": \"{{AGREEMENT_ID}}\", \n \"assetId\": \"{{ASSET_ID}}\",\n \"managedResources\": \"false\", \n \"dataDestination\": { \"type\": \"HttpProxy\" }\n}", "options": { "raw": { "language": "json" @@ -647,12 +630,11 @@ } }, "url": { - "raw": "{{CONSUMER_DATAMGMT_URL}}/data/transferprocess", + "raw": "{{CONSUMER_MANAGEMENT_URL}}/transferprocess", "host": [ - "{{CONSUMER_DATAMGMT_URL}}" + "{{CONSUMER_MANAGEMENT_URL}}" ], "path": [ - "data", "transferprocess" ] } @@ -689,7 +671,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{ \"id\": \"{{TRANSFER_ID}}\",\n \"connectorId\": \"foo\", \n \"connectorAddress\": \"{{PROVIDER_IDS_URL}}/api/v1/ids/data\", \n \"contractId\": \"{{AGREEMENT_ID}}\", \n \"assetId\": \"{{ASSET_ID}}\",\n \"managedResources\": \"false\", \n \"dataDestination\": { \"type\": \"HttpProxy\" },\n \"properties\": {\n \"receiver.http.endpoint\": \"{{BACKEND_SERVICE}}\"\n }\n}", + "raw": "{ \"id\": \"{{TRANSFER_ID}}\",\n \"connectorId\": \"foo\", \n \"connectorAddress\": \"{{PROVIDER_PROTOCOL_URL}}/api/v1/ids/data\", \n \"contractId\": \"{{AGREEMENT_ID}}\", \n \"assetId\": \"{{ASSET_ID}}\",\n \"managedResources\": \"false\", \n \"dataDestination\": { \"type\": \"HttpProxy\" },\n \"properties\": {\n \"receiver.http.endpoint\": \"{{BACKEND_SERVICE}}\"\n }\n}", "options": { "raw": { "language": "json" @@ -697,12 +679,11 @@ } }, "url": { - "raw": "{{CONSUMER_DATAMGMT_URL}}/data/transferprocess", + "raw": "{{CONSUMER_MANAGEMENT_URL}}/transferprocess", "host": [ - "{{CONSUMER_DATAMGMT_URL}}" + "{{CONSUMER_MANAGEMENT_URL}}" ], "path": [ - "data", "transferprocess" ] } @@ -735,18 +716,61 @@ "method": "GET", "header": [], "url": { - "raw": "{{CONSUMER_DATAMGMT_URL}}/data/transferprocess/{{TRANSFER_PROCESS_ID}}", + "raw": "{{CONSUMER_MANAGEMENT_URL}}/transferprocess/{{TRANSFER_PROCESS_ID}}", "host": [ - "{{CONSUMER_DATAMGMT_URL}}" + "{{CONSUMER_MANAGEMENT_URL}}" ], "path": [ - "data", "transferprocess", "{{TRANSFER_PROCESS_ID}}" ] } }, "response": [] + }, + { + "name": "CPA (getData)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Body matches string\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.collectionVariables.set(\"authCode\", jsonData.authCode);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{CONSUMER_MANAGEMENT_URL}}/adapter/asset/sync/{{ASSET_ID}}?providerUrl={{PROVIDER_PROTOCOL_URL}}/api/v1/ids/data&contractAgreementReuse=false", + "host": [ + "{{CONSUMER_MANAGEMENT_URL}}" + ], + "path": [ + "adapter", + "asset", + "sync", + "{{ASSET_ID}}" + ], + "query": [ + { + "key": "providerUrl", + "value": "{{PROVIDER_PROTOCOL_URL}}/api/v1/ids/data" + }, + { + "key": "contractAgreementReuse", + "value": "false" + } + ] + } + }, + "response": [] } ], "auth": { @@ -786,16 +810,16 @@ ], "variable": [ { - "key": "CONSUMER_DATAMGMT_URL", - "value": "https://sokrates-txdc.int.demo.catena-x.net" + "key": "CONSUMER_MANAGEMENT_URL", + "value": "https://sokrates-txdc.int.demo.catena-x.net/management" }, { - "key": "PROVIDER_IDS_URL", + "key": "PROVIDER_PROTOCOL_URL", "value": "https://plato-txdc.int.demo.catena-x.net" }, { - "key": "PROVIDER_DATAMGMT_URL", - "value": "https://plato-txdc.int.demo.catena-x.net" + "key": "PROVIDER_MANAGEMENT_URL", + "value": "https://plato-txdc.int.demo.catena-x.net/management" }, { "key": "ASSET_ID", @@ -847,6 +871,14 @@ "key": "BACKEND_SERVICE", "value": "http://backend:8080", "type": "string" + }, + { + "key": "AGREEMENT-ID", + "value": "" + }, + { + "key": "authCode", + "value": "" } ] } \ No newline at end of file diff --git a/docs/development/postman/images/screenshot.png b/docs/development/postman/images/screenshot.png deleted file mode 100644 index 8a9d231c67352b39336f5362a5eb2ea12aa7b75e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77083 zcmb@u1yq(%*EUEfN=rycNlSO9q;yJmN;f<-(ny1JOLuoSNOyO4cl|e_-@NavnOXnL znzI&g*QtHZ*>UZ?36Pc)hKKn80|o{LFY-k|77Xmw0vH%1-&-i)iEL+GB=F~z4WEeo zTcEhS)$<4LF>M8vY~?HrY#p?%^}!6yEzI<(Y;>*l_04U*S=b&yHgf|Xkw1ULZ>_Iw zYh+>mQQpW*A56gLBQ4`cDHGd|^tAN!AL;1W=~&sB=sxlb%E=?#4XJ{GeFPH`_$&`f z++T24`f1nnbdn7PSJM_n@SZ*aW<`ebEgOaD<6@ZFuxM#ymDrC$r9u%+bt*V2C>F4d!F{cZ0+sw@pIux9AwcEc~k@urHpL z;Qy~Ak=dgBt5V89DvbUSsKQbVbW)%|QCV3zJ~6>ohmBOfGhIq3DJh8o2PGUsKwvGG zUs#w~R~LWO{IocdSrIsAUJs9sj-HsPf>G>Jg~vSumPxF|HrLeI8&zuuWub|2LNw2j zvw$#`CRrRNC?Jplzq`A8ebxL_SR!F=RJ~x2mBemmZHo!?ojE(JLWvfb-d96CKR;iD z(||MZory**#WQ0gs{!Ua|6HaKJk5I~q;Te&$GfBVHN8|vcSp@zet2${FZFkKjfG<@ z7b>>ZcxHwleZK5fuATiFmH_5IyRLvkLSRkiV@^O|b|&XkIQ+}z;a0DH3g2uvWGL_t z@r+O|tRquk)b|JobkMh+4z^)3*bc56KK&<|p6e5glJI2W6P}J`nW#TE&G(-~RbO+f zdM16f@93zksF+x8PEM*=4E3v5ua<#%Zby5zg&!>*!$kdVgJTEc*VhRcay3tCHnzw5Lk$O!W&993(h3s^f@<;$Ta)x<{MN9B7(RlxMVboHDN@kOWm5th>sBBzEn*%PywGK( zdu)Zm3U!uWOSfUy9$kHKAWWREq5l3A@%y|xTCY3IN=8KLHP!Imh0s`K(Hd{K39|A%`NbvE$|NMzsR#xW!xw&~qcQ;ExXlN)YumhT(9*)2g+3iI6B?V`{q^V+IdiuSSzQoPa zd3y<|TBGo#y2&+-HpXR-)%OF~kKO^lY?4Go+jI2=OFZVxJM9zPy3+@;$VLb>|p7u9nfsyxh&^p3yZ*^>CpgXn&i zPw^%v(ET{%mFR@7$}|iM=kTf-W}Z~}%6Is^97F-^`WuxP9l9H}cGk#spR^ zS_Xx#k-00M&oL9lANAi<$zE?NL3t192tTyzFNXJL9DlsMzQq4_{3-$qR~RJ#vQ1>M zE~=ySB2@WWKAO=j@jLxk!UfaKs39+^+){tq30l<65q-YQK=~q-seF0Vs_zz^f2)j~ za>UrR2wD62ru;9c^VX?qM6Yg=s%tj7gknzLGvmD?EE9aOwgiN1=4OuXsmB7Rg^X(L zaU9XK+Lm~?=gv9G7+BbwAz+K4nQ>Sy^TNQYvLFcz>^!CS zQ+TH})-S`=EFg8HHz&1xJc3OzQjjTgrRv)v4QG7%I?9YbOzS@(+m?L(o-#f~VDx^Y zU_ryvYxpDOdUUTzk#_Pg{`YPYmG(~Ka_8Bbv)ryS8Vl& zw}0dDwU}Dx@m^76&K?5Z7&$y4X~xw2k}IfJZ}n`5hm@TBM^4TsdIpA*^Yf0SrDpu| zG6x*sJpYV|K|w_okd}UDP#y>HfQa~bQGo9O$0T-O)AwRyW22_FRz2oQE7}wB%hNRl zJ0i}|Pcpio^GEbrEQV=KaE(U4)8%2_hS|m+SRT%-oC&WSqT8~FOLkDUKfFE>SvJVl zWp!*zJ$bjiaO-*$;K@-ay@rs+e;^;01UmOy32jfJ4k>zNskN<8Wyc;F0Sg_Q)H;}? zTmWioah4+uOI-RyS+ZHO)AKIc07qx(_AU98B!TfHl2GBIaBCW(Z_c|yyQQ&ZxAG-X(7;bK=rhmsb z$LQ!{vvW=n4%!b9s^{SOYFJ(}|z#xPp9kqt+ zM1s!R?qJU9#Z&4+e#?m6SS}%57P>*~)$I@+=9nt+*vXRX6QZ523?}Ij`Dkm>R&Z4w&Fzv(_Vp^*R$tyr7=CwJ~E( zh@;i!E4T0fwmop=x#q>f4|W3EEpwwlAE(c|fe@V$AMhn!;@HPLcoI9hp#0QU2>i8Q z*%z1|!%6Rs&Ej5Wb-by&I&`F3$NN3ar?$CzeHx;A`-9&*W7(<`O=k<=&x@wp-#CX@ z)%}hpVOv@eJ36dVY=2*isrS=%YXq~GXeb0f)f5ZUx#{)tT)OioTb$qJHW4DpTrgK{ zRQq+7*R%9ZWv`I$M*BK^>`N)4ToQVmh|u;`t!2Fv!#Iy>#7Iop>eg+X}2 zV^Ze}aNhjfV1Y<{xq%s!cSJ1hd`Us>q6=EdWb3%3))#Z4UJM;r&Wy=oC}8$G@XJ@R zeI(Wg?`SwirWwKrP;IRW+!GKm=R*thsitF&7K+lU_nAWZEod1 zGuI0Ankv&}hLXXO{qZC`V%}(ImkPY`uo@o|z<|f#gt(Tl#h6^xQT33k@~W=7rE~5y zC4dOR2nG+5ROzvRQ+1@tb{+Nez;9JLvmt{b2Yy{*@s;?YxANbAnm2)6gSkSR_5B5L zvo|S&9o_N6&=*;Yah2s9Q-R1w=6;f09KP_w+d$2p8ZAN%+~7AEDs&b}gBkMvn$4ag z=)N5BVzgNI81#%_G>A0!LNvsC!^T1^9ZwS&DM~%-S~PG_h`>`KvR^@Vd)B7fX(ucN zOVyDRk?qH4r!5p9Y$@;0+OCR?_jVRCN}A;ltzt{F^!#Z zy}D;+m+E>a-{hASz~x)LMQRn|my)f7zHqK`$Y4)}u0zW|Z5rW=7E!17hAmf&_Vtj#rF3Xuy`nR%lRYwj zFcciFknf~s6@(@|TpGN*?QnSGh(|gisx7430Z?0EO$%wAJ zS@?x7(gPV-9d^2N(lio#DlcelGNbd#=*f*!#fEjoad!gG-p|@uUl0@Z;8=Fm$FN8) zK5Q?%PKD)Zo=Yl!U&TVF$n!LK4trqe+<1V?yS(t#MRjuaqm00M*9l+RBYFNPOZnEf*M~e;8zEv&4qw*|$}&r$7uT|A^?$Nti^Yd*vfoWq zgM#DR7}g+Dx7U0TU5g5Sj>b#tB*(RRX}rf?EQ>7OcQ!6uhG=x+y)itNNbk&E^xGI} zJArK#m9iZ4d@rH}Li=^sDm>Ii@q{=y58LdDmRQI4ly*D-1=62_M>Slz5(dk7(@?!trTA8#3Rt3LQf zYrno1>ZZ3AwLH zu6USnDAG9jh%cAEbI8eAEigfE_|TYUb++rdcLOts($R8~o- z#erT+aRsUj2XZD-Z_-!J8FX5s^>pmu>Sd!pW~T7 z$UEz^2ymeX1WVZS)wR$eSs6$N&7J1@y;T*P>LhQeMZd^tr(m`#?`}mVl_ki;p_Zk` zx8JpC??twEpa&#!Rso@1Nj#mp&cmcygj7QuebTlQd`>ev_wdMVA)|8^;0`CaLIsY*gF# zq^dKXTVetGN-l%w+wK9`#R~H`Mln-#zUD&r_xIH5(~m63mUx|`eZG30t@%lwZ;G|} z9wKH;es{+rYQyf0MR`bjQpokvG^D86fDyKFF1Jw{nYp}c%0DNtR|7lBA}M3~B_qYm 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 diff --git a/edc-controlplane/edc-controlplane-postgresql/build.gradle.kts b/edc-controlplane/edc-controlplane-postgresql/build.gradle.kts index 5888c34c4..78b5e253f 100644 --- a/edc-controlplane/edc-controlplane-postgresql/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-postgresql/build.gradle.kts @@ -11,6 +11,11 @@ dependencies { runtimeOnly(project(":edc-controlplane:edc-controlplane-base")) runtimeOnly(project(":edc-extensions:postgresql-migration")) runtimeOnly(edc.azure.vault) + constraints { + implementation("net.minidev:json-smart:2.4.10") { + because("version 2.4.8 has vulnerabilities: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-1370.") + } + } runtimeOnly(edc.bundles.sqlstores) runtimeOnly(edc.transaction.local) runtimeOnly(edc.sql.pool) diff --git a/edc-dataplane/edc-dataplane-azure-vault/build.gradle.kts b/edc-dataplane/edc-dataplane-azure-vault/build.gradle.kts index 02d29b7db..020dc0512 100644 --- a/edc-dataplane/edc-dataplane-azure-vault/build.gradle.kts +++ b/edc-dataplane/edc-dataplane-azure-vault/build.gradle.kts @@ -8,6 +8,11 @@ plugins { dependencies { implementation(project(":edc-dataplane:edc-dataplane-base")) implementation(edc.azure.vault) + constraints { + implementation("net.minidev:json-smart:2.4.10") { + because("version 2.4.8 has vulnerabilities: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-1370.") + } + } implementation(edc.azure.identity) implementation("com.azure:azure-security-keyvault-secrets:4.6.0") } diff --git a/edc-extensions/business-partner-validation/build.gradle.kts b/edc-extensions/business-partner-validation/build.gradle.kts index 53cb11e31..198886d9a 100644 --- a/edc-extensions/business-partner-validation/build.gradle.kts +++ b/edc-extensions/business-partner-validation/build.gradle.kts @@ -1,4 +1,3 @@ - plugins { `java-library` `maven-publish` @@ -7,5 +6,6 @@ plugins { dependencies { api(edc.spi.core) implementation(edc.spi.policy) + implementation(edc.spi.contract) implementation(edc.spi.policyengine) } diff --git a/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/BusinessPartnerValidationExtension.java b/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/BusinessPartnerValidationExtension.java index ee076406f..d88293a72 100644 --- a/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/BusinessPartnerValidationExtension.java +++ b/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/BusinessPartnerValidationExtension.java @@ -26,6 +26,7 @@ import org.eclipse.edc.policy.model.Permission; import org.eclipse.edc.policy.model.Prohibition; import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; @@ -37,60 +38,73 @@ public class BusinessPartnerValidationExtension implements ServiceExtension { - /** - * The key for business partner numbers constraints. Must be used as left operand when declaring - * constraints. - * - *

Example: - * - *

-   * {
-   *     "constraint": {
-   *         "leftOperand": "BusinessPartnerNumber",
-   *         "operator": "EQ",
-   *         "rightOperand": "BPNLCDQ90000X42KU"
-   *     }
-   * }
-   * 
- */ - public static final String BUSINESS_PARTNER_CONSTRAINT_KEY = "BusinessPartnerNumber"; - - public BusinessPartnerValidationExtension() {} - - public BusinessPartnerValidationExtension( - final RuleBindingRegistry ruleBindingRegistry, final PolicyEngine policyEngine) { - this.ruleBindingRegistry = ruleBindingRegistry; - this.policyEngine = policyEngine; - } - - @Inject private RuleBindingRegistry ruleBindingRegistry; - - @Inject private PolicyEngine policyEngine; - - @Override - public String name() { - return "Business Partner Validation Extension"; - } - - @Override - public void initialize(ServiceExtensionContext context) { - - final Monitor monitor = context.getMonitor(); - - final BusinessPartnerDutyFunction dutyFunction = new BusinessPartnerDutyFunction(monitor); - final BusinessPartnerPermissionFunction permissionFunction = - new BusinessPartnerPermissionFunction(monitor); - final BusinessPartnerProhibitionFunction prohibitionFunction = - new BusinessPartnerProhibitionFunction(monitor); - - ruleBindingRegistry.bind("USE", ALL_SCOPES); - ruleBindingRegistry.bind(BUSINESS_PARTNER_CONSTRAINT_KEY, ALL_SCOPES); - - policyEngine.registerFunction( - ALL_SCOPES, Duty.class, BUSINESS_PARTNER_CONSTRAINT_KEY, dutyFunction); - policyEngine.registerFunction( - ALL_SCOPES, Permission.class, BUSINESS_PARTNER_CONSTRAINT_KEY, permissionFunction); - policyEngine.registerFunction( - ALL_SCOPES, Prohibition.class, BUSINESS_PARTNER_CONSTRAINT_KEY, prohibitionFunction); - } + /** + * The key for business partner numbers constraints. Must be used as left operand when declaring + * constraints. + * + *

Example: + * + *

+     * {
+     *     "constraint": {
+     *         "leftOperand": "BusinessPartnerNumber",
+     *         "operator": "EQ",
+     *         "rightOperand": "BPNLCDQ90000X42KU"
+     *     }
+     * }
+     * 
+ */ + public static final String BUSINESS_PARTNER_CONSTRAINT_KEY = "BusinessPartnerNumber"; + + public static final String DEFAULT_LOG_AGREEMENT_EVALUATION = "true"; + + + @Setting(value = "Enable logging when evaluating the business partner constraints in the agreement validation", type = "boolean", defaultValue = DEFAULT_LOG_AGREEMENT_EVALUATION) + public static final String BUSINESS_PARTNER_VALIDATION_LOG_AGREEMENT_VALIDATION = "tractusx.businesspartnervalidation.log.agreement.validation"; + @Inject + private RuleBindingRegistry ruleBindingRegistry; + @Inject + private PolicyEngine policyEngine; + + public BusinessPartnerValidationExtension() { + } + + public BusinessPartnerValidationExtension( + final RuleBindingRegistry ruleBindingRegistry, final PolicyEngine policyEngine) { + this.ruleBindingRegistry = ruleBindingRegistry; + this.policyEngine = policyEngine; + } + + @Override + public String name() { + return "Business Partner Validation Extension"; + } + + @Override + public void initialize(ServiceExtensionContext context) { + + final Monitor monitor = context.getMonitor(); + + var logAgreementEvaluation = logAgreementEvaluationSetting(context); + + final BusinessPartnerDutyFunction dutyFunction = new BusinessPartnerDutyFunction(monitor, logAgreementEvaluation); + final BusinessPartnerPermissionFunction permissionFunction = + new BusinessPartnerPermissionFunction(monitor, logAgreementEvaluation); + final BusinessPartnerProhibitionFunction prohibitionFunction = + new BusinessPartnerProhibitionFunction(monitor, logAgreementEvaluation); + + ruleBindingRegistry.bind("USE", ALL_SCOPES); + ruleBindingRegistry.bind(BUSINESS_PARTNER_CONSTRAINT_KEY, ALL_SCOPES); + + policyEngine.registerFunction( + ALL_SCOPES, Duty.class, BUSINESS_PARTNER_CONSTRAINT_KEY, dutyFunction); + policyEngine.registerFunction( + ALL_SCOPES, Permission.class, BUSINESS_PARTNER_CONSTRAINT_KEY, permissionFunction); + policyEngine.registerFunction( + ALL_SCOPES, Prohibition.class, BUSINESS_PARTNER_CONSTRAINT_KEY, prohibitionFunction); + } + + private Boolean logAgreementEvaluationSetting(ServiceExtensionContext context) { + return Boolean.parseBoolean(context.getSetting(BUSINESS_PARTNER_VALIDATION_LOG_AGREEMENT_VALIDATION, DEFAULT_LOG_AGREEMENT_EVALUATION)); + } } diff --git a/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/AbstractBusinessPartnerValidation.java b/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/AbstractBusinessPartnerValidation.java index 55cb0d52b..ecb5b81ef 100644 --- a/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/AbstractBusinessPartnerValidation.java +++ b/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/AbstractBusinessPartnerValidation.java @@ -20,132 +20,147 @@ package org.eclipse.tractusx.edc.validation.businesspartner.functions; -import java.util.Map; -import java.util.Objects; +import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement; import org.eclipse.edc.policy.engine.spi.PolicyContext; import org.eclipse.edc.policy.model.Operator; import org.eclipse.edc.spi.agent.ParticipantAgent; import org.eclipse.edc.spi.monitor.Monitor; +import java.util.Map; +import java.util.Objects; + +import static java.lang.String.format; + /** * Abstract class for BusinessPartnerNumber validation. This class may be inherited from the EDC * policy enforcing functions for duties, permissions and prohibitions. */ public abstract class AbstractBusinessPartnerValidation { - // Developer Note: - // Problems reported to the policy context are not logged. Therefore, everything - // that is reported to the policy context should be logged, too. - - private static final String FAIL_EVALUATION_BECAUSE_RIGHT_VALUE_NOT_STRING = - "Failing evaluation because of invalid BusinessPartnerNumber constraint. For operator 'EQ' right value must be of type 'String'. Unsupported type: '%s'"; - private static final String FAIL_EVALUATION_BECAUSE_UNSUPPORTED_OPERATOR = - "Failing evaluation because of invalid BusinessPartnerNumber constraint. As operator only 'EQ' is supported. Unsupported operator: '%s'"; - - private final Monitor monitor; - - protected AbstractBusinessPartnerValidation(Monitor monitor) { - this.monitor = Objects.requireNonNull(monitor); - } - - /** - * Name of the claim that contains the Business Partner Number. - * - *

Please note: At the time of writing (April 2022) the business partner - * number is part of the 'referringConnector' claim in the IDS DAT token. This will probably - * change for the next release. - */ - private static final String REFERRING_CONNECTOR_CLAIM = "referringConnector"; - - /** - * Evaluation funtion to decide whether a claim belongs to a specific business partner. - * - * @param operator operator of the constraint - * @param rightValue right value fo the constraint, that contains the business partner number - * (e.g. BPNLCDQ90000X42KU) - * @param policyContext context of the policy with claims - * @return true if claims are from the constrained business partner - */ - protected boolean evaluate( - final Operator operator, final Object rightValue, final PolicyContext policyContext) { - - if (policyContext.hasProblems() && !policyContext.getProblems().isEmpty()) { - String problems = String.join(", ", policyContext.getProblems()); - String message = - String.format( - "BusinessPartnerNumberValidation: Rejecting PolicyContext with problems. Problems: %s", - problems); - monitor.debug(message); - return false; + // Developer Note: + // Problems reported to the policy context are not logged. Therefore, everything + // that is reported to the policy context should be logged, too. + + private static final String FAIL_EVALUATION_BECAUSE_RIGHT_VALUE_NOT_STRING = + "Failing evaluation because of invalid BusinessPartnerNumber constraint. For operator 'EQ' right value must be of type 'String'. Unsupported type: '%s'"; + private static final String FAIL_EVALUATION_BECAUSE_UNSUPPORTED_OPERATOR = + "Failing evaluation because of invalid BusinessPartnerNumber constraint. As operator only 'EQ' is supported. Unsupported operator: '%s'"; + /** + * Name of the claim that contains the Business Partner Number. + * + *

Please note: At the time of writing (April 2022) the business partner + * number is part of the 'referringConnector' claim in the IDS DAT token. This will probably + * change for the next release. + */ + private static final String REFERRING_CONNECTOR_CLAIM = "referringConnector"; + private final Monitor monitor; + private final boolean logAgreementEvaluation; + + protected AbstractBusinessPartnerValidation(Monitor monitor, boolean logAgreementEvaluation) { + this.monitor = Objects.requireNonNull(monitor); + this.logAgreementEvaluation = logAgreementEvaluation; } - final ParticipantAgent participantAgent = policyContext.getParticipantAgent(); - final Map claims = participantAgent.getClaims(); - - if (!claims.containsKey(REFERRING_CONNECTOR_CLAIM)) { - return false; + /** + * At the time of writing (11. April 2022) the business partner number is part of the + * 'referringConnector' claim, which contains a connector URL. As the CX projects are not further + * aligned about the URL formatting, the enforcement can only be done by checking whether the URL + * _contains_ the number. As this introduces some insecurities when validation business partner + * numbers, this should be addresses in the long term. + * + * @param referringConnectorClaim describing URL with business partner number + * @param businessPartnerNumber of the constraint + * @return true if claim contains the business partner number + */ + private static boolean isCorrectBusinessPartner( + String referringConnectorClaim, String businessPartnerNumber) { + return referringConnectorClaim.contains(businessPartnerNumber); } - Object referringConnectorClaimObject = claims.get(REFERRING_CONNECTOR_CLAIM); - String referringConnectorClaim = null; - - if (referringConnectorClaimObject instanceof String) { - referringConnectorClaim = (String) referringConnectorClaimObject; + public boolean isLogAgreementEvaluation() { + return logAgreementEvaluation; } - if (referringConnectorClaim == null || referringConnectorClaim.isEmpty()) { - return false; + /** + * Evaluation funtion to decide whether a claim belongs to a specific business partner. + * + * @param operator operator of the constraint + * @param rightValue right value fo the constraint, that contains the business partner number + * (e.g. BPNLCDQ90000X42KU) + * @param policyContext context of the policy with claims + * @return true if claims are from the constrained business partner + */ + protected boolean evaluate( + final Operator operator, final Object rightValue, final PolicyContext policyContext) { + + if (policyContext.hasProblems() && !policyContext.getProblems().isEmpty()) { + String problems = String.join(", ", policyContext.getProblems()); + String message = + format( + "BusinessPartnerNumberValidation: Rejecting PolicyContext with problems. Problems: %s", + problems); + monitor.debug(message); + return false; + } + + final ParticipantAgent participantAgent = policyContext.getParticipantAgent(); + final Map claims = participantAgent.getClaims(); + + if (!claims.containsKey(REFERRING_CONNECTOR_CLAIM)) { + return false; + } + + Object referringConnectorClaimObject = claims.get(REFERRING_CONNECTOR_CLAIM); + String referringConnectorClaim = null; + + if (referringConnectorClaimObject instanceof String) { + referringConnectorClaim = (String) referringConnectorClaimObject; + } + + if (referringConnectorClaim == null || referringConnectorClaim.isEmpty()) { + return false; + } + + if (operator == Operator.EQ) { + return isBusinessPartnerNumber(referringConnectorClaim, rightValue, policyContext); + } else { + final String message = format(FAIL_EVALUATION_BECAUSE_UNSUPPORTED_OPERATOR, operator); + monitor.warning(message); + policyContext.reportProblem(message); + return false; + } } - if (operator == Operator.EQ) { - return isBusinessPartnerNumber(referringConnectorClaim, rightValue, policyContext); - } else { - final String message = String.format(FAIL_EVALUATION_BECAUSE_UNSUPPORTED_OPERATOR, operator); - monitor.warning(message); - policyContext.reportProblem(message); - return false; - } - } - - /** - * @param referringConnectorClaim of the participant - * @param businessPartnerNumber object - * @return true if object is string and successfully evaluated against the claim - */ - private boolean isBusinessPartnerNumber( - String referringConnectorClaim, Object businessPartnerNumber, PolicyContext policyContext) { - if (businessPartnerNumber == null) { - final String message = String.format(FAIL_EVALUATION_BECAUSE_RIGHT_VALUE_NOT_STRING, "null"); - monitor.warning(message); - policyContext.reportProblem(message); - return false; + /** + * @param referringConnectorClaim of the participant + * @param businessPartnerNumber object + * @return true if object is string and successfully evaluated against the claim + */ + private boolean isBusinessPartnerNumber( + String referringConnectorClaim, Object businessPartnerNumber, PolicyContext policyContext) { + if (businessPartnerNumber == null) { + final String message = format(FAIL_EVALUATION_BECAUSE_RIGHT_VALUE_NOT_STRING, "null"); + monitor.warning(message); + policyContext.reportProblem(message); + return false; + } + if (!(businessPartnerNumber instanceof String)) { + final String message = + format( + FAIL_EVALUATION_BECAUSE_RIGHT_VALUE_NOT_STRING, + businessPartnerNumber.getClass().getName()); + monitor.warning(message); + policyContext.reportProblem(message); + return false; + } + + var businessPartnerNumberStr = (String) businessPartnerNumber; + var agreement = policyContext.getContextData(ContractAgreement.class); + var isCorrectBusinessPartner = isCorrectBusinessPartner(referringConnectorClaim, businessPartnerNumberStr); + + if (agreement != null && logAgreementEvaluation) { + monitor.info(format("Evaluated policy access for referringConnectorClaim: %s and contract id: %s with result: %s", referringConnectorClaim, agreement.getId(), isCorrectBusinessPartner)); + } + return isCorrectBusinessPartner; } - if (!(businessPartnerNumber instanceof String)) { - final String message = - String.format( - FAIL_EVALUATION_BECAUSE_RIGHT_VALUE_NOT_STRING, - businessPartnerNumber.getClass().getName()); - monitor.warning(message); - policyContext.reportProblem(message); - return false; - } - - return isCorrectBusinessPartner(referringConnectorClaim, (String) businessPartnerNumber); - } - - /** - * At the time of writing (11. April 2022) the business partner number is part of the - * 'referringConnector' claim, which contains a connector URL. As the CX projects are not further - * aligned about the URL formatting, the enforcement can only be done by checking whether the URL - * _contains_ the number. As this introduces some insecurities when validation business partner - * numbers, this should be addresses in the long term. - * - * @param referringConnectorClaim describing URL with business partner number - * @param businessPartnerNumber of the constraint - * @return true if claim contains the business partner number - */ - private static boolean isCorrectBusinessPartner( - String referringConnectorClaim, String businessPartnerNumber) { - return referringConnectorClaim.contains(businessPartnerNumber); - } } diff --git a/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/BusinessPartnerDutyFunction.java b/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/BusinessPartnerDutyFunction.java index f53ba3cbc..061d7fd7d 100644 --- a/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/BusinessPartnerDutyFunction.java +++ b/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/BusinessPartnerDutyFunction.java @@ -26,16 +26,18 @@ import org.eclipse.edc.policy.model.Operator; import org.eclipse.edc.spi.monitor.Monitor; -/** AtomicConstraintFunction to validate business partner numbers for edc duties. */ +/** + * AtomicConstraintFunction to validate business partner numbers for edc duties. + */ public class BusinessPartnerDutyFunction extends AbstractBusinessPartnerValidation - implements AtomicConstraintFunction { + implements AtomicConstraintFunction { - public BusinessPartnerDutyFunction(Monitor monitor) { - super(monitor); - } + public BusinessPartnerDutyFunction(Monitor monitor, boolean shouldLogOnAgreementEvaluation) { + super(monitor, shouldLogOnAgreementEvaluation); + } - @Override - public boolean evaluate(Operator operator, Object rightValue, Duty rule, PolicyContext context) { - return evaluate(operator, rightValue, context); - } + @Override + public boolean evaluate(Operator operator, Object rightValue, Duty rule, PolicyContext context) { + return evaluate(operator, rightValue, context); + } } diff --git a/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/BusinessPartnerPermissionFunction.java b/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/BusinessPartnerPermissionFunction.java index 07bda765e..b6713c477 100644 --- a/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/BusinessPartnerPermissionFunction.java +++ b/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/BusinessPartnerPermissionFunction.java @@ -26,17 +26,19 @@ import org.eclipse.edc.policy.model.Permission; import org.eclipse.edc.spi.monitor.Monitor; -/** AtomicConstraintFunction to validate business partner numbers for edc permissions. */ +/** + * AtomicConstraintFunction to validate business partner numbers for edc permissions. + */ public class BusinessPartnerPermissionFunction extends AbstractBusinessPartnerValidation - implements AtomicConstraintFunction { + implements AtomicConstraintFunction { - public BusinessPartnerPermissionFunction(Monitor monitor) { - super(monitor); - } + public BusinessPartnerPermissionFunction(Monitor monitor, boolean shouldLogOnAgreementEvaluation) { + super(monitor, shouldLogOnAgreementEvaluation); + } - @Override - public boolean evaluate( - Operator operator, Object rightValue, Permission rule, PolicyContext context) { - return evaluate(operator, rightValue, context); - } + @Override + public boolean evaluate( + Operator operator, Object rightValue, Permission rule, PolicyContext context) { + return evaluate(operator, rightValue, context); + } } diff --git a/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/BusinessPartnerProhibitionFunction.java b/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/BusinessPartnerProhibitionFunction.java index f3cddf9fe..79e318741 100644 --- a/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/BusinessPartnerProhibitionFunction.java +++ b/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/BusinessPartnerProhibitionFunction.java @@ -26,17 +26,19 @@ import org.eclipse.edc.policy.model.Prohibition; import org.eclipse.edc.spi.monitor.Monitor; -/** AtomicConstraintFunction to validate business partner numbers for edc prohibitions. */ +/** + * AtomicConstraintFunction to validate business partner numbers for edc prohibitions. + */ public class BusinessPartnerProhibitionFunction extends AbstractBusinessPartnerValidation - implements AtomicConstraintFunction { + implements AtomicConstraintFunction { - public BusinessPartnerProhibitionFunction(Monitor monitor) { - super(monitor); - } + public BusinessPartnerProhibitionFunction(Monitor monitor, boolean shouldLogOnAgreementEvaluation) { + super(monitor, shouldLogOnAgreementEvaluation); + } - @Override - public boolean evaluate( - Operator operator, Object rightValue, Prohibition rule, PolicyContext context) { - return evaluate(operator, rightValue, context); - } + @Override + public boolean evaluate( + Operator operator, Object rightValue, Prohibition rule, PolicyContext context) { + return evaluate(operator, rightValue, context); + } } diff --git a/edc-extensions/business-partner-validation/src/test/java/org/eclipse/tractusx/edc/validation/businesspartner/BusinessPartnerValidationExtensionTest.java b/edc-extensions/business-partner-validation/src/test/java/org/eclipse/tractusx/edc/validation/businesspartner/BusinessPartnerValidationExtensionTest.java index 0240dc9ef..dcea3be41 100644 --- a/edc-extensions/business-partner-validation/src/test/java/org/eclipse/tractusx/edc/validation/businesspartner/BusinessPartnerValidationExtensionTest.java +++ b/edc-extensions/business-partner-validation/src/test/java/org/eclipse/tractusx/edc/validation/businesspartner/BusinessPartnerValidationExtensionTest.java @@ -27,10 +27,13 @@ import org.eclipse.edc.policy.model.Prohibition; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.tractusx.edc.validation.businesspartner.functions.BusinessPartnerPermissionFunction; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; import org.mockito.Mockito; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -105,4 +108,24 @@ void testRegisterProhibitionFunction() { eq(BusinessPartnerValidationExtension.BUSINESS_PARTNER_CONSTRAINT_KEY), any()); } + + @Test + void testLogConfiguration() { + + when(serviceExtensionContext.getSetting(BusinessPartnerValidationExtension.BUSINESS_PARTNER_VALIDATION_LOG_AGREEMENT_VALIDATION, "true")).thenReturn("false"); + + var captor = ArgumentCaptor.forClass(BusinessPartnerPermissionFunction.class); + // invoke + extension.initialize(serviceExtensionContext); + + // verify + verify(policyEngine) + .registerFunction( + anyString(), + eq(Permission.class), + eq(BusinessPartnerValidationExtension.BUSINESS_PARTNER_CONSTRAINT_KEY), + captor.capture()); + + assertThat(captor.getValue().isLogAgreementEvaluation()).isFalse(); + } } diff --git a/edc-extensions/business-partner-validation/src/test/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/AbstractBusinessPartnerValidationTest.java b/edc-extensions/business-partner-validation/src/test/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/AbstractBusinessPartnerValidationTest.java index e8909c04e..2bc0738b0 100644 --- a/edc-extensions/business-partner-validation/src/test/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/AbstractBusinessPartnerValidationTest.java +++ b/edc-extensions/business-partner-validation/src/test/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/AbstractBusinessPartnerValidationTest.java @@ -20,10 +20,10 @@ package org.eclipse.tractusx.edc.validation.businesspartner.functions; -import java.util.Collections; -import java.util.List; +import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement; import org.eclipse.edc.policy.engine.spi.PolicyContext; import org.eclipse.edc.policy.model.Operator; +import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.spi.agent.ParticipantAgent; import org.eclipse.edc.spi.monitor.Monitor; import org.junit.jupiter.api.Assertions; @@ -31,143 +31,180 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.ArgumentCaptor; import org.mockito.Mockito; +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; + class AbstractBusinessPartnerValidationTest { - private AbstractBusinessPartnerValidation validation; + private AbstractBusinessPartnerValidation validation; + + // mocks + private Monitor monitor; + private PolicyContext policyContext; + private ParticipantAgent participantAgent; + + @BeforeEach + void BeforeEach() { + this.monitor = Mockito.mock(Monitor.class); + this.policyContext = Mockito.mock(PolicyContext.class); + this.participantAgent = Mockito.mock(ParticipantAgent.class); + + Mockito.when(policyContext.getParticipantAgent()).thenReturn(participantAgent); + + validation = new AbstractBusinessPartnerValidation(monitor, true) { + }; + } + + @ParameterizedTest + @EnumSource(Operator.class) + void testFailsOnUnsupportedOperations(Operator operator) { - // mocks - private Monitor monitor; - private PolicyContext policyContext; - private ParticipantAgent participantAgent; + if (operator == Operator.EQ) { // only allowed operator + return; + } - @BeforeEach - void BeforeEach() { - this.monitor = Mockito.mock(Monitor.class); - this.policyContext = Mockito.mock(PolicyContext.class); - this.participantAgent = Mockito.mock(ParticipantAgent.class); + // prepare + prepareContextProblems(null); + prepareBusinessPartnerClaim("yes"); - Mockito.when(policyContext.getParticipantAgent()).thenReturn(participantAgent); + // invoke & assert + Assertions.assertFalse(validation.evaluate(operator, "foo", policyContext)); + } + + @Test + void testFailsOnUnsupportedRightValue() { + + // prepare + prepareContextProblems(null); + prepareBusinessPartnerClaim("yes"); + + // invoke & assert + Assertions.assertFalse(validation.evaluate(Operator.EQ, 1, policyContext)); + } + + @Test + void testValidationFailsWhenClaimMissing() { - validation = new AbstractBusinessPartnerValidation(monitor) {}; - } + // prepare + prepareContextProblems(null); - @ParameterizedTest - @EnumSource(Operator.class) - void testFailsOnUnsupportedOperations(Operator operator) { + // invoke + final boolean isValid = validation.evaluate(Operator.EQ, "foo", policyContext); - if (operator == Operator.EQ) { // only allowed operator - return; + // assert + Assertions.assertFalse(isValid); } - // prepare - prepareContextProblems(null); - prepareBusinessPartnerClaim("yes"); + @Test + void testValidationSucceedsWhenClaimContainsValue() { - // invoke & assert - Assertions.assertFalse(validation.evaluate(operator, "foo", policyContext)); - } + // prepare + prepareContextProblems(null); - @Test - void testFailsOnUnsupportedRightValue() { + // prepare equals + prepareBusinessPartnerClaim("foo"); + final boolean isEqualsTrue = validation.evaluate(Operator.EQ, "foo", policyContext); - // prepare - prepareContextProblems(null); - prepareBusinessPartnerClaim("yes"); + // prepare contains + prepareBusinessPartnerClaim("foobar"); + final boolean isContainedTrue = validation.evaluate(Operator.EQ, "foo", policyContext); - // invoke & assert - Assertions.assertFalse(validation.evaluate(Operator.EQ, 1, policyContext)); - } + // assert + Assertions.assertTrue(isEqualsTrue); + Assertions.assertTrue(isContainedTrue); + } - @Test - void testValidationFailsWhenClaimMissing() { + @Test + void testValidationWhenParticipantHasProblems() { - // prepare - prepareContextProblems(null); + // prepare + prepareContextProblems(Collections.singletonList("big problem")); + prepareBusinessPartnerClaim("foo"); - // invoke - final boolean isValid = validation.evaluate(Operator.EQ, "foo", policyContext); + // invoke + final boolean isValid = validation.evaluate(Operator.EQ, "foo", policyContext); - // assert - Assertions.assertFalse(isValid); - } + // Mockito.verify(monitor.debug(Mockito.anyString()); + Assertions.assertFalse(isValid); + } - @Test - void testValidationSucceedsWhenClaimContainsValue() { + @Test + void testValidationWhenSingleParticipantIsValid() { - // prepare - prepareContextProblems(null); + // prepare + prepareContextProblems(null); + prepareBusinessPartnerClaim("foo"); - // prepare equals - prepareBusinessPartnerClaim("foo"); - final boolean isEqualsTrue = validation.evaluate(Operator.EQ, "foo", policyContext); + // invoke + final boolean isContainedTrue = validation.evaluate(Operator.EQ, "foo", policyContext); - // prepare contains - prepareBusinessPartnerClaim("foobar"); - final boolean isContainedTrue = validation.evaluate(Operator.EQ, "foo", policyContext); + // Mockito.verify(monitor.debug(Mockito.anyString()); + Assertions.assertTrue(isContainedTrue); + } - // assert - Assertions.assertTrue(isEqualsTrue); - Assertions.assertTrue(isContainedTrue); - } + @Test + void testValidationWhenSingleParticipantIsValidWithAgreement() { - @Test - void testValidationWhenParticipantHasProblems() { + // prepare + prepareContextProblems(null); + prepareBusinessPartnerClaim("foo"); - // prepare - prepareContextProblems(Collections.singletonList("big problem")); - prepareBusinessPartnerClaim("foo"); + var captor = ArgumentCaptor.forClass(String.class); - // invoke - final boolean isValid = validation.evaluate(Operator.EQ, "foo", policyContext); + var agreement = ContractAgreement.Builder.newInstance() + .id("agreementId") + .providerAgentId("provider") + .consumerAgentId("consumer") + .assetId("assetId") + .policy(Policy.Builder.newInstance().build()) + .build(); - // Mockito.verify(monitor.debug(Mockito.anyString()); - Assertions.assertFalse(isValid); - } + Mockito.when(policyContext.getContextData(eq(ContractAgreement.class))).thenReturn(agreement); - @Test - void testValidationWhenSingleParticipantIsValid() { + // invoke + final boolean isContainedTrue = validation.evaluate(Operator.EQ, "foo", policyContext); - // prepare - prepareContextProblems(null); - prepareBusinessPartnerClaim("foo"); + Assertions.assertTrue(isContainedTrue); - // invoke - final boolean isContainedTrue = validation.evaluate(Operator.EQ, "foo", policyContext); + Mockito.verify(monitor).info(captor.capture()); - // Mockito.verify(monitor.debug(Mockito.anyString()); - Assertions.assertTrue(isContainedTrue); - } + assertThat(captor.getValue()).contains(agreement.getId()).contains("foo"); + } - // In the past it was possible to use the 'IN' constraint with multiple BPNs as - // a list. This is no longer supported. - // The EDC must now always decline this kind of BPN format. - @Test - void testValidationForMultipleParticipants() { + // In the past it was possible to use the 'IN' constraint with multiple BPNs as + // a list. This is no longer supported. + // The EDC must now always decline this kind of BPN format. + @Test + void testValidationForMultipleParticipants() { - // prepare - prepareContextProblems(null); - prepareBusinessPartnerClaim("foo"); + // prepare + prepareContextProblems(null); + prepareBusinessPartnerClaim("foo"); - // invoke & verify - Assertions.assertFalse(validation.evaluate(Operator.IN, List.of("foo", "bar"), policyContext)); - Assertions.assertFalse(validation.evaluate(Operator.IN, List.of(1, "foo"), policyContext)); - Assertions.assertFalse(validation.evaluate(Operator.IN, List.of("bar", "bar"), policyContext)); - } + // invoke & verify + Assertions.assertFalse(validation.evaluate(Operator.IN, List.of("foo", "bar"), policyContext)); + Assertions.assertFalse(validation.evaluate(Operator.IN, List.of(1, "foo"), policyContext)); + Assertions.assertFalse(validation.evaluate(Operator.IN, List.of("bar", "bar"), policyContext)); + } - private void prepareContextProblems(List problems) { - Mockito.when(policyContext.getProblems()).thenReturn(problems); + private void prepareContextProblems(List problems) { + Mockito.when(policyContext.getProblems()).thenReturn(problems); - if (problems == null || problems.isEmpty()) { - Mockito.when(policyContext.hasProblems()).thenReturn(false); - } else { - Mockito.when(policyContext.hasProblems()).thenReturn(true); + if (problems == null || problems.isEmpty()) { + Mockito.when(policyContext.hasProblems()).thenReturn(false); + } else { + Mockito.when(policyContext.hasProblems()).thenReturn(true); + } } - } - private void prepareBusinessPartnerClaim(String businessPartnerNumber) { - Mockito.when(participantAgent.getClaims()) - .thenReturn(Collections.singletonMap("referringConnector", businessPartnerNumber)); - } + private void prepareBusinessPartnerClaim(String businessPartnerNumber) { + Mockito.when(participantAgent.getClaims()) + .thenReturn(Collections.singletonMap("referringConnector", businessPartnerNumber)); + } } diff --git a/edc-extensions/control-plane-adapter/build.gradle.kts b/edc-extensions/control-plane-adapter/build.gradle.kts index fe34a0866..c205d5cb0 100644 --- a/edc-extensions/control-plane-adapter/build.gradle.kts +++ b/edc-extensions/control-plane-adapter/build.gradle.kts @@ -8,7 +8,14 @@ plugins { dependencies { implementation(edc.spi.core) implementation(edc.spi.policy) + implementation(edc.api.management) + constraints { + implementation("org.yaml:snakeyaml:2.0") { + because("version 1.33 has vulnerabilities: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-1471.") + } + } + implementation(edc.spi.catalog) implementation(edc.spi.transactionspi) implementation(edc.spi.transaction.datasource) diff --git a/edc-extensions/postgresql-migration/build.gradle.kts b/edc-extensions/postgresql-migration/build.gradle.kts index 8d7b1fa05..cb04877c0 100644 --- a/edc-extensions/postgresql-migration/build.gradle.kts +++ b/edc-extensions/postgresql-migration/build.gradle.kts @@ -11,5 +11,5 @@ dependencies { implementation(edc.sql.assetindex) implementation(edc.sql.core) - implementation("org.flywaydb:flyway-core:9.15.2") + implementation("org.flywaydb:flyway-core:9.16.3") } diff --git a/edc-tests/cucumber/build.gradle.kts b/edc-tests/cucumber/build.gradle.kts index f2e40439d..2628ce71e 100644 --- a/edc-tests/cucumber/build.gradle.kts +++ b/edc-tests/cucumber/build.gradle.kts @@ -16,7 +16,7 @@ dependencies { implementation(project(":edc-extensions:transferprocess-sftp-provisioner")) - testImplementation("com.google.code.gson:gson:2.10") + testImplementation("com.google.code.gson:gson:2.10.1") testImplementation("org.apache.httpcomponents:httpclient:4.5.14") testImplementation("org.junit.platform:junit-platform-suite:1.9.2") testImplementation("io.cucumber:cucumber-java:7.11.2") diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/MultiRuntimeTest.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/MultiRuntimeTest.java index a28a2610d..5bf2a2417 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/MultiRuntimeTest.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/MultiRuntimeTest.java @@ -69,6 +69,7 @@ public class MultiRuntimeTest { put("edc.dataplane.selector.httpplane.destinationtypes", "HttpProxy"); put("edc.dataplane.selector.httpplane.properties", "{\"publicApiUrl\":\"http://localhost:" + SOKRATES_PUBLIC_API_PORT + "/api/public\"}"); put("edc.receiver.http.dynamic.endpoint", "http://localhost:" + SOKRATES_CONNECTOR_PORT + "/api/consumer/datareference"); + put("tractusx.businesspartnervalidation.log.agreement.validation", "true"); } }); @@ -98,6 +99,7 @@ public class MultiRuntimeTest { put("edc.dataplane.selector.httpplane.sourcetypes", "HttpData"); put("edc.dataplane.selector.httpplane.destinationtypes", "HttpProxy"); put("edc.dataplane.selector.httpplane.properties", "{\"publicApiUrl\":\"http://localhost:" + PLATO_PUBLIC_API_PORT + "/api/public\"}"); + put("tractusx.businesspartnervalidation.log.agreement.validation", "true"); } }); } diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/Participant.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/Participant.java index dd43d4f90..bd1546111 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/Participant.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/Participant.java @@ -26,6 +26,7 @@ import org.eclipse.edc.connector.api.management.transferprocess.model.TransferRequestDto; import org.eclipse.edc.connector.policy.spi.PolicyDefinition; import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; +import org.eclipse.edc.policy.model.PolicyRegistrationTypes; import org.eclipse.edc.spi.asset.AssetSelectorExpression; import org.eclipse.edc.spi.iam.IdentityService; import org.eclipse.edc.spi.system.ServiceExtension; @@ -76,6 +77,9 @@ public Participant(String moduleName, String runtimeName, Map pr this.bpn = runtimeName + "-BPN"; this.backend = properties.get("edc.receiver.http.dynamic.endpoint"); this.registerServiceMock(IdentityService.class, new MockDapsService(getBpn())); + + typeManager.registerTypes(PolicyRegistrationTypes.TYPES.toArray(Class[]::new)); + } @Override diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/HttpConsumerPullWithProxyTest.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/HttpConsumerPullWithProxyTest.java index 78f3d2013..1f93ae5e7 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/HttpConsumerPullWithProxyTest.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/HttpConsumerPullWithProxyTest.java @@ -38,7 +38,7 @@ import static org.awaitility.Awaitility.await; import static org.awaitility.pollinterval.FibonacciPollInterval.fibonacci; import static org.eclipse.edc.connector.transfer.dataplane.spi.TransferDataPlaneConstants.HTTP_PROXY; -import static org.eclipse.tractusx.edc.policy.PolicyHelperFunctions.noConstraintPolicy; +import static org.eclipse.tractusx.edc.policy.PolicyHelperFunctions.businessPartnerNumberPolicy; @EndToEndTest public class HttpConsumerPullWithProxyTest extends MultiRuntimeTest { @@ -61,8 +61,8 @@ void transferData_privateBackend() throws IOException, InterruptedException { .authKey(authCodeHeaderName) .authCode(authCode) .build()); - plato.createPolicy(noConstraintPolicy("policy-1")); - plato.createPolicy(noConstraintPolicy("policy-2")); + plato.createPolicy(businessPartnerNumberPolicy("policy-1", sokrates.getBpn())); + plato.createPolicy(businessPartnerNumberPolicy("policy-2", sokrates.getBpn())); plato.createContractDefinition(assetId, "def-1", "policy-1", "policy-2", ONE_WEEK); var negotiationId = sokrates.negotiateContract(plato, assetId); diff --git a/gradle.properties b/gradle.properties index 66b31427b..6b3bcce1e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ groupId=org.eclipse.tractusx.edc -version=0.3.3-SNAPSHOT +version=0.3.4-SNAPSHOT javaVersion=11 # configure the build: diff --git a/settings.gradle.kts b/settings.gradle.kts index e22d4aacc..a4158ef8c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -58,7 +58,7 @@ dependencyResolutionManagement { } // create version catalog for all EDC modules create("edc") { - version("edc", "0.0.1-20230220-SNAPSHOT") + version("edc", "0.0.1-20230220.patch1") library("spi-catalog", "org.eclipse.edc", "catalog-spi").versionRef("edc") library("spi-auth", "org.eclipse.edc", "auth-spi").versionRef("edc") library("spi-transfer", "org.eclipse.edc", "transfer-spi").versionRef("edc") @@ -137,34 +137,38 @@ dependencyResolutionManagement { library("micrometer-jersey", "org.eclipse.edc", "jersey-micrometer").versionRef("edc") library("micrometer-jetty", "org.eclipse.edc", "jetty-micrometer").versionRef("edc") library("monitor-jdklogger", "org.eclipse.edc", "monitor-jdk-logger").versionRef("edc") - library("transfer.dynamicreceiver", "org.eclipse.edc", "transfer-pull-http-dynamic-receiver").versionRef("edc") + library( + "transfer.dynamicreceiver", + "org.eclipse.edc", + "transfer-pull-http-dynamic-receiver" + ).versionRef("edc") library("transfer.receiver", "org.eclipse.edc", "transfer-pull-http-receiver").versionRef("edc") bundle( - "connector", - listOf("boot", "core-connector", "core-jersey", "core-controlplane", "api-observability") + "connector", + listOf("boot", "core-connector", "core-jersey", "core-controlplane", "api-observability") ) bundle( - "dpf", - listOf("dpf-transfer", "dpf-selector-core", "dpf-selector-client", "spi-dataplane-selector") + "dpf", + listOf("dpf-transfer", "dpf-selector-core", "dpf-selector-client", "spi-dataplane-selector") ) bundle( - "sqlstores", - listOf( - "sql-assetindex", - "sql-contract-definition", - "sql-contract-negotiation", - "sql-transferprocess", - "sql-policydef" - ) + "sqlstores", + listOf( + "sql-assetindex", + "sql-contract-definition", + "sql-contract-negotiation", + "sql-transferprocess", + "sql-policydef" + ) ) bundle( - "monitoring", - listOf("micrometer-core", "micrometer-jersey", "micrometer-jetty") + "monitoring", + listOf("micrometer-core", "micrometer-jersey", "micrometer-jetty") // listOf("micrometer-core", "micrometer-jersey", "micrometer-jetty", "monitor-jdklogger") ) }