From 568c5b7448f47935551524b129f5c83d8b170529 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 17 May 2023 15:34:52 +0200 Subject: [PATCH] Release version 0.3.4 (#367) * 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 * 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) * feat: Add/update documentation for connector kit (#138) Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) Co-authored-by: Stephan Bauer Co-authored-by: stephanbcbauer <84396022+stephanbcbauer@users.noreply.github.com> * feat: add GitHub workflow to automaticly add features to project (#264) * feature: refactor the main `tractusx-connector` chart (#230) * chore: Add 0.3.3 to, and fix markdown in CHANGELOG.md (#252) * feature: create new tractusx chart with Hashicorp and Postgres * lint * fix deployment test * updated urls * pr remarks * construct readiness URL directly in the test pod * Apply suggestions from code review Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * Update charts/tractusx-connector/templates/tests/test-controlplane-readiness.yaml Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) --------- Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * build(deps): Move Gradle dependencies constrains into root build.gradle.kts (#273) Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * chore(deps): bump com.azure:azure-security-keyvault-secrets from 4.6.0 to 4.6.1 (#272) * chore(deps): bump com.azure:azure-security-keyvault-secrets Bumps [com.azure:azure-security-keyvault-secrets](https://github.com/Azure/azure-sdk-for-java) from 4.6.0 to 4.6.1. - [Release notes](https://github.com/Azure/azure-sdk-for-java/releases) - [Commits](https://github.com/Azure/azure-sdk-for-java/compare/azure-cosmos_4.6.0...azure-messaging-eventgrid_4.6.1) --- updated-dependencies: - dependency-name: com.azure:azure-security-keyvault-secrets dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * trigger-ci --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Paul Latzelsperger * chore(deps): bump actions/checkout from 3.3.0 to 3.5.2 (#254) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.3.0 to 3.5.2. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3.3.0...v3.5.2) --- updated-dependencies: - dependency-name: actions/checkout 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> * build(deps): Move centralized dependency constrains to "allprojects" block within root build.gradle.kts (#274) * feat: delete add-to-project workflow (#276) This type of action doesn't work with the generated project. And there is a way to configure these kinds of workflows within the GitHub UI. * Update DEPENDENCIES file * Add license and copyright header to the charts * fix chart typo * fix charts * Update DEPENDENCIES file * Fix charts * Fix charts * Fix charts * Create new connector certificates * chore(test): use new certificate in the deployment test (#288) * chore(build): add GHA variables for sonar project and org (#287) * chore(build): add GHA variables for sonar project and org * trigger ci * chore(deps): bump org.junit:junit-bom from 5.9.2 to 5.9.3 (#290) Bumps [org.junit:junit-bom](https://github.com/junit-team/junit5) from 5.9.2 to 5.9.3. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.9.2...r5.9.3) --- updated-dependencies: - dependency-name: org.junit:junit-bom 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.junit.platform:junit-platform-suite (#291) Bumps [org.junit.platform:junit-platform-suite](https://github.com/junit-team/junit5) from 1.9.2 to 1.9.3. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/commits) --- updated-dependencies: - dependency-name: org.junit.platform:junit-platform-suite 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(build): remove CI-triggered sonar job (#292) * chore: prepare Changelog and Migr. Guide for 0.3.4 (#298) * chore: prepare Changelog and Migr. Guide for 0.3.4 * Update docs/migration/Version_0.3.3_0.3.4.md Co-authored-by: Tuncay Tunc (ZF Friedrichshafen AG) <100704677+tuncaytunc-zf@users.noreply.github.com> --------- Co-authored-by: Tuncay Tunc (ZF Friedrichshafen AG) <100704677+tuncaytunc-zf@users.noreply.github.com> * chore(deps): bump org.flywaydb:flyway-core from 9.16.3 to 9.17.0 (#294) Bumps [org.flywaydb:flyway-core](https://github.com/flyway/flyway) from 9.16.3 to 9.17.0. - [Release notes](https://github.com/flyway/flyway/releases) - [Commits](https://github.com/flyway/flyway/commits/flyway-9.17.0) --- updated-dependencies: - dependency-name: org.flywaydb:flyway-core 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> * refactor(chart): move test infrastructure into the test chart (#299) * chore(deps): bump io.cucumber:cucumber-junit-platform-engine (#300) Bumps [io.cucumber:cucumber-junit-platform-engine](https://github.com/cucumber/cucumber-jvm) from 7.11.2 to 7.12.0. - [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.2...v7.12.0) --- updated-dependencies: - dependency-name: io.cucumber:cucumber-junit-platform-engine 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 io.cucumber:cucumber-java from 7.11.2 to 7.12.0 (#301) Bumps [io.cucumber:cucumber-java](https://github.com/cucumber/cucumber-jvm) from 7.11.2 to 7.12.0. - [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.2...v7.12.0) --- updated-dependencies: - dependency-name: io.cucumber:cucumber-java 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: remove all printstacktrace statementsw (#304) * chore(build): use tractusx bot creds for release PRs etc. * feat(build): publish to OSSRH Snapshots and MavenCentral (#319) * docs: update code-of-conduct (#317) * chore(deps): bump alpine (#324) Bumps alpine from 3.17.3 to 3.18.0. --- updated-dependencies: - dependency-name: alpine 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 alpine (#325) Bumps alpine from 3.17.3 to 3.18.0. --- updated-dependencies: - dependency-name: alpine 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 alpine (#326) Bumps alpine from 3.17.3 to 3.18.0. --- updated-dependencies: - dependency-name: alpine 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 alpine (#323) Bumps alpine from 3.17.3 to 3.18.0. --- updated-dependencies: - dependency-name: alpine 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(build): use composite action for GPG import (#320) * chore(build): use composite action for GPG import * Update .github/actions/import-gpg-key/action.yml Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) --------- Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * chore: Add .tractusx metafile (#335) * feature: create helm chart using the Azure KeyVault variant (#279) * feat: add Helm chart that utilized Azure KeyVault + Postgres * pr remarks * Update charts/tractusx-connector-azure-vault/README.md.gotmpl Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * use cUrl instead of wget do satisfy SonarCloud --------- Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * chore(helm): fix typo in required value in cp deployment (#337) In the controlplane deploment a required value was misspelled. Signed-off-by: Marco Lecheler * chore(deps): bump org.testcontainers:vault from 1.18.0 to 1.18.1 (#339) Bumps [org.testcontainers:vault](https://github.com/testcontainers/testcontainers-java) from 1.18.0 to 1.18.1. - [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.18.0...1.18.1) --- updated-dependencies: - dependency-name: org.testcontainers:vault 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(security): use uppercase AS in Dockerfiles (#341) * chore(deps): bump org.testcontainers:junit-jupiter from 1.18.0 to 1.18.1 (#340) Bumps [org.testcontainers:junit-jupiter](https://github.com/testcontainers/testcontainers-java) from 1.18.0 to 1.18.1. - [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.18.0...1.18.1) --- updated-dependencies: - dependency-name: org.testcontainers:junit-jupiter 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> * feat: replace using hard-coded certs with dynamically generated ones (#342) * feat: replace using hard-coded certs with dynamically generated ones * added az login * set AZ KeyVault secrets before deploy test * allow no sub * escape command * avoid logging of sensitive info * chore(deps): bump org.flywaydb:flyway-core from 9.17.0 to 9.18.0 (#349) Bumps [org.flywaydb:flyway-core](https://github.com/flyway/flyway) from 9.17.0 to 9.18.0. - [Release notes](https://github.com/flyway/flyway/releases) - [Commits](https://github.com/flyway/flyway/compare/flyway-9.17.0...flyway-9.18.0) --- updated-dependencies: - dependency-name: org.flywaydb:flyway-core 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> * docs: add documentation about the helm charts (#352) * exclude charts/ from markdown lint, as the markdowns there are generated (#359) * chore(deps): bump helm/kind-action from 1.5.0 to 1.6.0 (#360) Bumps [helm/kind-action](https://github.com/helm/kind-action) from 1.5.0 to 1.6.0. - [Release notes](https://github.com/helm/kind-action/releases) - [Commits](https://github.com/helm/kind-action/compare/v1.5.0...v1.6.0) --- updated-dependencies: - dependency-name: helm/kind-action 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> * fix: add migration for transferprocess_properties default value (#313) * Add migration for transferprocess_properties default value Signed-off-by: Brendan Cronin * Update edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/transferprocess/V0_0_6__Snapshot_20230109_Update.sql Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * Move to new migration file Signed-off-by: Brendan Cronin * Copy+Paste=Bad Signed-off-by: Brendan Cronin * Empty commit * Update edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/transferprocess/V0_0_7__Default_Value_For_Properties.sql --------- Signed-off-by: Brendan Cronin Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * fix: Rename ingress endpoint from ids to protocol (#358) * Rename ingress endpoint from ids to protocol * Rename ingress endpoint from ids to protocol * fix service-runtime * fix renaming issues * Prepare release 0.3.4 * reformat changelog, adopt PR remarks/suggestions --------- Signed-off-by: dependabot[bot] Signed-off-by: Marco Lecheler Signed-off-by: Brendan Cronin 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> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: ndr_brt Co-authored-by: bcronin90 <90203222+bcronin90@users.noreply.github.com> Co-authored-by: stephanbcbauer <84396022+stephanbcbauer@users.noreply.github.com> Co-authored-by: Marco Lecheler Co-authored-by: eclipse-tractusx-bot --- .github/PULL_REQUEST_TEMPLATE.md | 13 + .github/actions/import-gpg-key/action.yml | 46 + .../actions/publish-docker-image/action.yml | 110 ++ .../actions/run-deployment-test/action.yml | 95 ++ .github/actions/setup-java/action.yml | 32 + .github/dependabot.yml | 37 +- .github/workflows/build.yaml | 243 +-- .github/workflows/business-tests.yaml | 28 +- .github/workflows/deployment-test.yaml | 153 ++ .github/workflows/draft-new-release.yaml | 38 +- .github/workflows/helm-chart-release.yaml | 9 +- .github/workflows/helm-lint.yaml | 30 +- .github/workflows/kics.yml | 26 +- .github/workflows/publish-docker.yaml | 66 + .github/workflows/publish-new-release.yml | 133 +- .github/workflows/trivy.yml | 67 +- .github/workflows/veracode.yaml | 135 +- .github/workflows/verify.yaml | 131 +- .gitignore | 4 + .markdownlint.yaml | 1 + .tractusx | 3 + CHANGELOG.md | 106 +- CODE_OF_CONDUCT.md | 98 +- DEPENDENCIES | 493 +++++-- NOTICE.md | 1 + README.md | 34 +- build.gradle.kts | 59 +- chart_schema.yaml | 19 + charts/edc-controlplane/Chart.yaml | 35 - charts/edc-controlplane/LICENSE | 202 --- charts/edc-controlplane/README.md | 106 -- charts/edc-controlplane/README.md.gotmpl | 26 - charts/edc-controlplane/templates/NOTES.txt | 74 - .../edc-controlplane/templates/_helpers.tpl | 72 - .../edc-controlplane/templates/configmap.yaml | 49 - .../templates/deployment.yaml | 154 -- .../templates/imagepullsecret.yaml | 35 - charts/edc-controlplane/values.yaml | 379 ----- charts/edc-dataplane/Chart.yaml | 35 - charts/edc-dataplane/LICENSE | 202 --- charts/edc-dataplane/README.md | 90 -- charts/edc-dataplane/README.md.gotmpl | 26 - charts/edc-dataplane/templates/NOTES.txt | 64 - charts/edc-dataplane/templates/configmap.yaml | 45 - .../edc-dataplane/templates/deployment.yaml | 142 -- .../templates/imagepullsecret.yaml | 35 - charts/edc-dataplane/values.yaml | 331 ----- .../tractusx-connector-azure-vault/Chart.yaml | 51 + .../tractusx-connector-azure-vault/README.md | 266 ++++ .../README.md.gotmpl | 59 + .../templates/NOTES.txt | 45 + .../templates/_helpers.tpl | 175 +++ .../templates/configmap-controlplane.yaml} | 12 +- .../templates/configmap-dataplane.yaml | 33 + .../templates/deployment-controlplane.yaml | 362 +++++ .../templates/deployment-dataplane.yaml | 223 +++ .../templates/hpa-controlplane.yaml | 49 + .../templates/hpa-dataplane.yaml | 48 + .../templates/ingress-controlplane.yaml} | 28 +- .../templates/ingress-dataplane.yaml | 96 ++ .../templates/service-controlplane.yaml} | 28 +- .../templates/service-dataplane.yaml | 52 + .../templates/serviceaccount.yaml | 36 + .../tests/test-controlplane-readiness.yaml | 35 + .../tests/test-dataplane-readiness.yaml | 35 + .../values.yaml | 530 +++++++ .../.helmignore | 0 charts/tractusx-connector-memory/Chart.yaml | 45 + charts/tractusx-connector-memory/README.md | 173 +++ .../README.md.gotmpl | 52 + charts/tractusx-connector-memory/example.yaml | 66 + .../templates/NOTES.txt | 22 + .../templates/_helpers.tpl | 157 ++ .../templates/configmap-runtime.yaml} | 7 +- .../templates/deployment-runtime.yaml | 308 ++++ .../templates/hpa-runtime.yaml} | 21 +- .../templates/ingress-runtime.yaml} | 23 +- .../templates/service-runtime.yaml} | 24 +- .../templates/serviceaccount.yaml | 9 +- .../templates/tests/test-readiness.yaml} | 20 +- charts/tractusx-connector-memory/values.yaml | 316 ++++ charts/tractusx-connector/Chart.yaml | 10 +- charts/tractusx-connector/README.md | 72 +- charts/tractusx-connector/README.md.gotmpl | 29 +- .../tractusx-connector/templates/_helpers.tpl | 12 +- .../templates/configmap-dataplane.yaml | 22 + .../templates/deployment-controlplane.yaml | 96 +- .../templates/deployment-dataplane.yaml | 75 +- .../templates/hpa-controlplane.yaml | 22 + .../templates/hpa-dataplane.yaml | 22 + .../templates/ingress-controlplane.yaml | 22 + .../templates/ingress-dataplane.yaml | 22 + .../templates/service-controlplane.yaml | 16 +- .../templates/service-dataplane.yaml | 26 + .../templates/serviceaccount.yaml | 23 + .../tests/test-controlplane-readiness.yaml | 35 + .../tests/test-dataplane-readiness.yaml | 35 + charts/tractusx-connector/values.yaml | 41 +- checkov.yaml | 19 + ct.yaml | 19 + docs/README.md | 14 +- docs/development/Release.md | 6 + .../2023-02-09-release-process/README.md | 18 +- .../2023-02-27_testing/README.md | 2 +- .../2023-03-23_remove_lombok/README.md | 4 +- .../2023-04-03_renaming_branches/README.md | 61 + .../2023-04-11_refactor_helmcharts/README.md | 112 ++ .../2023-04-20_conventional_commits/README.md | 43 + docs/development/postman/collection.json | 258 ++-- .../development/postman/images/screenshot.png | Bin 77083 -> 0 bytes .../kit/adoption-view/images/domain-model.png | Bin 0 -> 142498 bytes .../adoption-view/images/edc_architecture.png | Bin 0 -> 7768 bytes .../kit/adoption-view/images/edc_overview.png | Bin 0 -> 430994 bytes docs/kit/adoption-view/page_adoption-view.md | 48 + docs/kit/adoption-view/page_domain_model.md | 64 + .../page00_development_view.md | 24 + .../page01_eclipse_foundation.md | 35 + .../page02_repository_structure.md | 26 + .../page03_project_structure.md | 21 + .../operation-view/page00_operation_view.md | 24 + .../page02_technical_prerequisites.md | 43 + .../page03_local_setup_controlplane.md | 141 ++ .../page04_local_setup_dataplane.md | 98 ++ .../operation-view/page06_kubernetes_setup.md | 22 + docs/kit/operation-view/page08_api.md | 64 + docs/kit/operation-view/page09_upgrading.md | 20 + docs/kit/operation-view/page10_extensions.md | 44 + docs/migration/Version_0.3.1_0.3.2.md | 2 +- docs/migration/Version_0.3.3_0.3.4.md | 21 + edc-controlplane/build.gradle.kts | 22 +- .../edc-controlplane-base/build.gradle.kts | 19 + .../build.gradle.kts | 22 +- .../notice.md | 28 + .../src/main/docker/Dockerfile | 8 +- .../edc-controlplane-memory/build.gradle.kts | 25 - .../README.md | 6 +- .../build.gradle.kts | 50 + .../notice.md | 28 + .../src/main/docker/Dockerfile | 7 +- .../build.gradle.kts | 23 +- .../notice.md | 28 + .../src/main/docker/Dockerfile | 7 +- .../build.gradle.kts | 30 - .../README.md | 109 +- .../edc-runtime-memory/build.gradle.kts | 43 + edc-controlplane/edc-runtime-memory/notice.md | 28 + .../src/main/docker/Dockerfile | 24 +- .../edc/vault/memory/InMemoryVault.java | 53 + .../vault/memory/VaultMemoryExtension.java | 54 + ...rg.eclipse.edc.spi.system.ServiceExtension | 21 + .../edc/vault/memory/InMemoryVaultTest.java | 56 + .../memory/VaultMemoryExtensionTest.java | 52 + edc-dataplane/build.gradle.kts | 18 + .../build.gradle.kts | 22 +- .../edc-dataplane-azure-vault/notice.md | 28 + .../src/main/docker/Dockerfile | 7 +- .../edc-dataplane-base/build.gradle.kts | 41 +- .../build.gradle.kts | 20 +- .../edc-dataplane-hashicorp-vault/notice.md | 28 + .../src/main/docker/Dockerfile | 7 +- edc-extensions/build.gradle.kts | 18 + .../build.gradle.kts | 19 + .../BusinessPartnerValidationExtension.java | 126 +- .../AbstractBusinessPartnerValidation.java | 231 +-- .../BusinessPartnerDutyFunction.java | 20 +- .../BusinessPartnerPermissionFunction.java | 22 +- .../BusinessPartnerProhibitionFunction.java | 22 +- ...usinessPartnerValidationExtensionTest.java | 23 + ...AbstractBusinessPartnerValidationTest.java | 239 +-- .../control-plane-adapter/README.md | 10 +- .../control-plane-adapter/build.gradle.kts | 18 + .../ObjectStoreServiceInMemory.java | 5 +- .../objectstore/ObjectStoreServiceSql.java | 4 +- .../edc/cp/adapter/store/SqlObjectStore.java | 8 +- .../edc/cp/adapter/store/SqlQueueStore.java | 10 +- .../cp/adapter/service/ResultServiceTest.java | 2 +- edc-extensions/cx-oauth2/build.gradle.kts | 19 + .../data-encryption/build.gradle.kts | 18 + .../algorithms/aes/AesAlgorithm.java | 132 +- .../aes/AesInitializationVectorIterator.java | 63 +- .../algorithms/aes/ByteCounter.java | 102 +- .../data/CryptoDataFactoryImpl.java | 131 +- .../AesDataEncrypterConfiguration.java | 28 +- .../encrypter/AesDataEncrypterImpl.java | 137 +- .../encrypter/DataEncrypterFactory.java | 74 +- .../encryption/key/CryptoKeyFactoryImpl.java | 22 +- .../encryption/provider/AesKeyProvider.java | 73 +- .../provider/CachingKeyProvider.java | 105 +- .../algorithms/aes/AesAlgorithmTest.java | 102 +- .../AesInitializationVectorIteratorTest.java | 80 +- .../DataEncrypterAesComponentTest.java | 125 +- .../build.gradle.kts | 18 + .../hashicorp-vault/build.gradle.kts | 22 +- .../build.gradle.kts | 19 + .../postgresql-migration/build.gradle.kts | 20 +- ...CpAdapterPostgresqlMigrationExtension.java | 39 +- .../V0_0_7__Default_Value_For_Properties.sql | 15 + .../build.gradle.kts | 18 + .../build.gradle.kts | 18 + .../client/AbstractSftpClientWrapperIT.java | 409 ++--- .../build.gradle.kts | 21 +- .../build.gradle.kts | 18 + edc-tests/cucumber/build.gradle.kts | 31 +- .../deployment/helm/omejdn/Chart.yaml | 14 + .../helm/omejdn/templates/_helpers.tpl | 2 + .../helm/omejdn/templates/configmap.yaml | 54 +- .../helm/omejdn/templates/deployment.yaml | 93 +- .../deployment/helm/omejdn/templates/hpa.yaml | 19 + .../omejdn/templates/imagepullsecret.yaml | 18 + .../helm/omejdn/templates/service.yaml | 19 + .../helm/omejdn/templates/serviceaccount.yaml | 19 + .../deployment/helm/omejdn/values.yaml | 18 + .../helm/supporting-infrastructure/Chart.yaml | 20 +- .../supporting-infrastructure/values.yaml | 263 ++-- .../edc/tests/BackendDataService.java | 35 + .../edc/tests/BackendServiceBackendAPI.java | 270 ---- .../edc/tests/BackendServiceSteps.java | 31 +- .../eclipse/tractusx/edc/tests/Connector.java | 83 +- .../tractusx/edc/tests/ConnectorFactory.java | 27 +- .../eclipse/tractusx/edc/tests/Constants.java | 25 +- .../edc/tests/ControlPlaneAdapterSteps.java | 82 +- .../tractusx/edc/tests/DataManagementAPI.java | 1309 +++++++++-------- .../tractusx/edc/tests/Environment.java | 203 ++- .../edc/tests/HttpProxyTransferSteps.java | 185 ++- .../tractusx/edc/tests/NegotiationSteps.java | 89 +- .../tractusx/edc/tests/PolicyStepDefs.java | 65 +- .../edc/tests/S3FileTransferStepsDefs.java | 227 ++- .../tractusx/edc/tests/data/Asset.java | 26 +- .../data/BusinessPartnerNumberConstraint.java | 33 +- .../tractusx/edc/tests/data/Constraint.java | 22 +- .../edc/tests/data/ContractDefinition.java | 41 +- .../edc/tests/data/ContractNegotiation.java | 29 +- .../edc/tests/data/ContractOffer.java | 28 +- .../tests/data/HttpProxySinkDataAddress.java | 22 +- .../data/HttpProxySourceDataAddress.java | 79 +- .../tractusx/edc/tests/data/Negotiation.java | 76 +- .../edc/tests/data/NullDataAddress.java | 26 +- .../tractusx/edc/tests/data/OrConstraint.java | 34 +- .../edc/tests/data/PayMeConstraint.java | 14 +- .../tractusx/edc/tests/data/Permission.java | 29 +- .../tractusx/edc/tests/data/Policy.java | 22 +- .../edc/tests/data/S3DataAddress.java | 42 +- .../tractusx/edc/tests/data/Transfer.java | 65 +- .../edc/tests/data/TransferProcess.java | 21 +- .../edc/tests/util/DatabaseCleaner.java | 45 +- .../tractusx/edc/tests/util/S3Client.java | 166 +-- .../features/HttpProxyDataTransfer.feature | 18 - .../main/resources/helm/omejdn}/.helmignore | 6 - .../src/main/resources/helm/omejdn/Chart.yaml | 43 + .../src/main/resources/helm/omejdn/README.md | 21 + .../helm/omejdn}/templates/_helpers.tpl | 30 +- .../helm/omejdn/templates/configmap.yaml | 92 ++ .../helm/omejdn/templates/deployment.yaml | 168 +++ .../resources/helm/omejdn}/templates/hpa.yaml | 13 +- .../omejdn/templates/imagepullsecret.yaml | 31 + .../helm/omejdn/templates/service.yaml | 34 + .../helm/omejdn/templates/serviceaccount.yaml | 31 + .../main/resources/helm/omejdn/values.yaml | 109 ++ .../helm/test-infrastructure/.gitignore | 4 + .../helm/test-infrastructure}/.helmignore | 7 +- .../helm/test-infrastructure/Chart.yaml | 63 + .../helm/test-infrastructure/README.md | 54 + .../helm/test-infrastructure/values.yaml | 553 +++++++ .../tractusx-connector-azure-vault-test.yaml | 100 ++ .../helm/tractusx-connector-test.yaml | 81 + .../src/main/resources/prepare-test.sh | 45 + edc-tests/e2e-tests/build.gradle.kts | 8 +- .../edc/lifecycle/MultiRuntimeTest.java | 86 +- .../tractusx/edc/lifecycle/Participant.java | 169 ++- .../lifecycle/TestRuntimeConfiguration.java | 45 +- .../edc/policy/PolicyHelperFunctions.java | 13 + .../tractusx/edc/tests/CatalogTest.java | 25 +- .../tests/HttpConsumerPullWithProxyTest.java | 121 ++ edc-tests/runtime/build.gradle.kts | 16 +- .../ConsumerEdrHandlerController.java | 61 + .../lifecycle/ConsumerServicesExtension.java | 30 + ...rg.eclipse.edc.spi.system.ServiceExtension | 15 + gradle.properties | 2 +- lintconf.yaml | 19 + pr_etiquette.md | 13 +- settings.gradle.kts | 36 +- styleguide.md | 2 +- 282 files changed, 12902 insertions(+), 6353 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/actions/import-gpg-key/action.yml create mode 100644 .github/actions/publish-docker-image/action.yml create mode 100644 .github/actions/run-deployment-test/action.yml create mode 100644 .github/actions/setup-java/action.yml create mode 100644 .github/workflows/deployment-test.yaml create mode 100644 .github/workflows/publish-docker.yaml create mode 100644 .tractusx delete mode 100644 charts/edc-controlplane/Chart.yaml delete mode 100644 charts/edc-controlplane/LICENSE delete mode 100644 charts/edc-controlplane/README.md delete mode 100644 charts/edc-controlplane/README.md.gotmpl delete mode 100644 charts/edc-controlplane/templates/NOTES.txt delete mode 100644 charts/edc-controlplane/templates/_helpers.tpl delete mode 100644 charts/edc-controlplane/templates/configmap.yaml delete mode 100644 charts/edc-controlplane/templates/deployment.yaml delete mode 100644 charts/edc-controlplane/templates/imagepullsecret.yaml delete mode 100644 charts/edc-controlplane/values.yaml delete mode 100644 charts/edc-dataplane/Chart.yaml delete mode 100644 charts/edc-dataplane/LICENSE delete mode 100644 charts/edc-dataplane/README.md delete mode 100644 charts/edc-dataplane/README.md.gotmpl delete mode 100644 charts/edc-dataplane/templates/NOTES.txt delete mode 100644 charts/edc-dataplane/templates/configmap.yaml delete mode 100644 charts/edc-dataplane/templates/deployment.yaml delete mode 100644 charts/edc-dataplane/templates/imagepullsecret.yaml delete mode 100644 charts/edc-dataplane/values.yaml create mode 100644 charts/tractusx-connector-azure-vault/Chart.yaml create mode 100644 charts/tractusx-connector-azure-vault/README.md create mode 100644 charts/tractusx-connector-azure-vault/README.md.gotmpl create mode 100644 charts/tractusx-connector-azure-vault/templates/NOTES.txt create mode 100644 charts/tractusx-connector-azure-vault/templates/_helpers.tpl rename charts/{edc-controlplane/templates/configmap-env.yaml => tractusx-connector-azure-vault/templates/configmap-controlplane.yaml} (78%) create mode 100644 charts/tractusx-connector-azure-vault/templates/configmap-dataplane.yaml create mode 100644 charts/tractusx-connector-azure-vault/templates/deployment-controlplane.yaml create mode 100644 charts/tractusx-connector-azure-vault/templates/deployment-dataplane.yaml create mode 100644 charts/tractusx-connector-azure-vault/templates/hpa-controlplane.yaml create mode 100644 charts/tractusx-connector-azure-vault/templates/hpa-dataplane.yaml rename charts/{edc-controlplane/templates/ingress.yaml => tractusx-connector-azure-vault/templates/ingress-controlplane.yaml} (77%) create mode 100644 charts/tractusx-connector-azure-vault/templates/ingress-dataplane.yaml rename charts/{edc-dataplane/templates/service.yaml => tractusx-connector-azure-vault/templates/service-controlplane.yaml} (61%) create mode 100644 charts/tractusx-connector-azure-vault/templates/service-dataplane.yaml create mode 100644 charts/tractusx-connector-azure-vault/templates/serviceaccount.yaml create mode 100644 charts/tractusx-connector-azure-vault/templates/tests/test-controlplane-readiness.yaml create mode 100644 charts/tractusx-connector-azure-vault/templates/tests/test-dataplane-readiness.yaml create mode 100644 charts/tractusx-connector-azure-vault/values.yaml rename charts/{tractusx-connector => tractusx-connector-memory}/.helmignore (100%) create mode 100644 charts/tractusx-connector-memory/Chart.yaml create mode 100644 charts/tractusx-connector-memory/README.md create mode 100644 charts/tractusx-connector-memory/README.md.gotmpl create mode 100644 charts/tractusx-connector-memory/example.yaml create mode 100644 charts/tractusx-connector-memory/templates/NOTES.txt create mode 100644 charts/tractusx-connector-memory/templates/_helpers.tpl rename charts/{edc-dataplane/templates/configmap-env.yaml => tractusx-connector-memory/templates/configmap-runtime.yaml} (85%) create mode 100644 charts/tractusx-connector-memory/templates/deployment-runtime.yaml rename charts/{edc-controlplane/templates/hpa.yaml => tractusx-connector-memory/templates/hpa-runtime.yaml} (64%) rename charts/{edc-dataplane/templates/ingress.yaml => tractusx-connector-memory/templates/ingress-runtime.yaml} (84%) rename charts/{edc-controlplane/templates/service.yaml => tractusx-connector-memory/templates/service-runtime.yaml} (69%) rename charts/{edc-controlplane => tractusx-connector-memory}/templates/serviceaccount.yaml (84%) rename charts/{edc-dataplane/templates/serviceaccount.yaml => tractusx-connector-memory/templates/tests/test-readiness.yaml} (73%) create mode 100644 charts/tractusx-connector-memory/values.yaml create mode 100644 charts/tractusx-connector/templates/tests/test-controlplane-readiness.yaml create mode 100644 charts/tractusx-connector/templates/tests/test-dataplane-readiness.yaml create mode 100644 docs/development/decision-records/2023-04-03_renaming_branches/README.md create mode 100644 docs/development/decision-records/2023-04-11_refactor_helmcharts/README.md create mode 100644 docs/development/decision-records/2023-04-20_conventional_commits/README.md delete mode 100644 docs/development/postman/images/screenshot.png create mode 100644 docs/kit/adoption-view/images/domain-model.png create mode 100644 docs/kit/adoption-view/images/edc_architecture.png create mode 100644 docs/kit/adoption-view/images/edc_overview.png create mode 100644 docs/kit/adoption-view/page_adoption-view.md create mode 100644 docs/kit/adoption-view/page_domain_model.md create mode 100644 docs/kit/development-view/page00_development_view.md create mode 100644 docs/kit/development-view/page01_eclipse_foundation.md create mode 100644 docs/kit/development-view/page02_repository_structure.md create mode 100644 docs/kit/development-view/page03_project_structure.md create mode 100644 docs/kit/operation-view/page00_operation_view.md create mode 100644 docs/kit/operation-view/page02_technical_prerequisites.md create mode 100644 docs/kit/operation-view/page03_local_setup_controlplane.md create mode 100644 docs/kit/operation-view/page04_local_setup_dataplane.md create mode 100644 docs/kit/operation-view/page06_kubernetes_setup.md create mode 100644 docs/kit/operation-view/page08_api.md create mode 100644 docs/kit/operation-view/page09_upgrading.md create mode 100644 docs/kit/operation-view/page10_extensions.md create mode 100644 docs/migration/Version_0.3.3_0.3.4.md create mode 100644 edc-controlplane/edc-controlplane-memory-hashicorp-vault/notice.md delete mode 100644 edc-controlplane/edc-controlplane-memory/build.gradle.kts rename edc-controlplane/{edc-controlplane-postgresql => edc-controlplane-postgresql-azure-vault}/README.md (98%) create mode 100644 edc-controlplane/edc-controlplane-postgresql-azure-vault/build.gradle.kts create mode 100644 edc-controlplane/edc-controlplane-postgresql-azure-vault/notice.md rename edc-controlplane/{edc-controlplane-memory => edc-controlplane-postgresql-azure-vault}/src/main/docker/Dockerfile (89%) create mode 100644 edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/notice.md delete mode 100644 edc-controlplane/edc-controlplane-postgresql/build.gradle.kts rename edc-controlplane/{edc-controlplane-memory => edc-runtime-memory}/README.md (54%) create mode 100644 edc-controlplane/edc-runtime-memory/build.gradle.kts create mode 100644 edc-controlplane/edc-runtime-memory/notice.md rename edc-controlplane/{edc-controlplane-postgresql => edc-runtime-memory}/src/main/docker/Dockerfile (59%) create mode 100644 edc-controlplane/edc-runtime-memory/src/main/java/org/eclipse/tractusx/edc/vault/memory/InMemoryVault.java create mode 100644 edc-controlplane/edc-runtime-memory/src/main/java/org/eclipse/tractusx/edc/vault/memory/VaultMemoryExtension.java create mode 100644 edc-controlplane/edc-runtime-memory/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 edc-controlplane/edc-runtime-memory/src/test/java/org/eclipse/tractusx/edc/vault/memory/InMemoryVaultTest.java create mode 100644 edc-controlplane/edc-runtime-memory/src/test/java/org/eclipse/tractusx/edc/vault/memory/VaultMemoryExtensionTest.java create mode 100644 edc-dataplane/edc-dataplane-azure-vault/notice.md create mode 100644 edc-dataplane/edc-dataplane-hashicorp-vault/notice.md create mode 100644 edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/transferprocess/V0_0_7__Default_Value_For_Properties.sql create mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/BackendDataService.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/BackendServiceBackendAPI.java rename {charts/edc-controlplane => edc-tests/deployment/src/main/resources/helm/omejdn}/.helmignore (82%) create mode 100644 edc-tests/deployment/src/main/resources/helm/omejdn/Chart.yaml create mode 100644 edc-tests/deployment/src/main/resources/helm/omejdn/README.md rename {charts/edc-dataplane => edc-tests/deployment/src/main/resources/helm/omejdn}/templates/_helpers.tpl (66%) create mode 100644 edc-tests/deployment/src/main/resources/helm/omejdn/templates/configmap.yaml create mode 100644 edc-tests/deployment/src/main/resources/helm/omejdn/templates/deployment.yaml rename {charts/edc-dataplane => edc-tests/deployment/src/main/resources/helm/omejdn}/templates/hpa.yaml (75%) create mode 100644 edc-tests/deployment/src/main/resources/helm/omejdn/templates/imagepullsecret.yaml create mode 100644 edc-tests/deployment/src/main/resources/helm/omejdn/templates/service.yaml create mode 100644 edc-tests/deployment/src/main/resources/helm/omejdn/templates/serviceaccount.yaml create mode 100644 edc-tests/deployment/src/main/resources/helm/omejdn/values.yaml create mode 100644 edc-tests/deployment/src/main/resources/helm/test-infrastructure/.gitignore rename {charts/edc-dataplane => edc-tests/deployment/src/main/resources/helm/test-infrastructure}/.helmignore (82%) create mode 100644 edc-tests/deployment/src/main/resources/helm/test-infrastructure/Chart.yaml create mode 100644 edc-tests/deployment/src/main/resources/helm/test-infrastructure/README.md create mode 100644 edc-tests/deployment/src/main/resources/helm/test-infrastructure/values.yaml create mode 100644 edc-tests/deployment/src/main/resources/helm/tractusx-connector-azure-vault-test.yaml create mode 100644 edc-tests/deployment/src/main/resources/helm/tractusx-connector-test.yaml create mode 100755 edc-tests/deployment/src/main/resources/prepare-test.sh create mode 100644 edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/HttpConsumerPullWithProxyTest.java create mode 100644 edc-tests/runtime/src/main/java/org/eclipse/tractusx/edc/lifecycle/ConsumerEdrHandlerController.java create mode 100644 edc-tests/runtime/src/main/java/org/eclipse/tractusx/edc/lifecycle/ConsumerServicesExtension.java create mode 100644 edc-tests/runtime/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..fe4467a54 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,13 @@ +## WHAT + +_Briefly describe what your PR changes, which features it adds/modifies._ + +## WHY + +_Briefly state why the change was necessary._ + +## FURTHER NOTES + +_List other areas of code that have changed but are not necessarily linked to the main feature. This could be method signature changes, package declarations, bugs that were encountered and were fixed inline, etc._ + +Closes # <-- _insert Issue number if one exists_ diff --git a/.github/actions/import-gpg-key/action.yml b/.github/actions/import-gpg-key/action.yml new file mode 100644 index 000000000..ac2782601 --- /dev/null +++ b/.github/actions/import-gpg-key/action.yml @@ -0,0 +1,46 @@ +# +# 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: "Import GPG Key" +description: "Imports a GPG key given in the input" +inputs: + gpg-private-key: + required: true + description: "The GPG Private Key in plain text. Can be a sub-key." +runs: + using: "composite" + steps: + # this is necessary because it creates gpg.conf, etc. + - name: List Keys + shell: bash + run: | + gpg -K --keyid-format=long + + - name: Import GPG Private Key + shell: bash + run: | + echo "use-agent" >> ~/.gnupg/gpg.conf + echo "pinentry-mode loopback" >> ~/.gnupg/gpg.conf + echo -e "${{ inputs.gpg-private-key }}" | gpg --import --batch + for fpr in $(gpg --list-keys --with-colons | awk -F: '/fpr:/ {print $10}' | sort -u); + do + echo -e "5\\ny\\n" | gpg --batch --command-fd 0 --expert --edit-key $fpr trust; + done diff --git a/.github/actions/publish-docker-image/action.yml b/.github/actions/publish-docker-image/action.yml new file mode 100644 index 000000000..a252bf108 --- /dev/null +++ b/.github/actions/publish-docker-image/action.yml @@ -0,0 +1,110 @@ +# +# 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: "Publish Docker Image" +description: "Build and publish a Docker Image to DockerHub" +inputs: + rootDir: + required: true + description: "The directory where the notice.md file and the src/main/docker directory are located" + namespace: + required: false + default: "tractusx" + description: "The Docker image namespace" + imagename: + required: true + description: "the name of the image" + docker_user: + required: false + description: "DockerHub user name. No push is done if omitted" + 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: + - uses: actions/checkout@v3.3.0 + + ##################### + # Login to DockerHub + ##################### + - name: DockerHub login + uses: docker/login-action@v2 + with: + username: ${{ inputs.docker_user }} + password: ${{ inputs.docker_token }} + + ##################### + # Build JAR file + ##################### + - uses: ./.github/actions/setup-java + - name: Build Controlplane + shell: bash + run: |- + ./gradlew -p ${{ inputs.rootDir }} shadowJar + + ############################### + # Set metadata of docker image + ############################### + # Create SemVer or ref tags dependent of trigger event + - name: Docker meta + id: meta + uses: docker/metadata-action@v4 + with: + images: | + ${{ inputs.namespace }}/${{ inputs.imagename }} + tags: | + type=semver,pattern={{version}},value=${{ inputs.docker_tag }} + type=semver,pattern={{major}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{raw}} + type=match,pattern=\d.\d.\d + type=raw,value=latest,enable={{is_default_branch}} + type=sha + + ############################### + # Build and push the image + ############################### + - name: Build and push + uses: docker/build-push-action@v3 + with: + context: . + file: ${{ inputs.rootDir }}/src/main/docker/Dockerfile + build-args: | + JAR=${{ inputs.rootDir }}/build/libs/${{ inputs.imagename }}.jar + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + ############################### + # Update the description + # https://github.com/peter-evans/dockerhub-description + ############################### + - name: Update Docker Hub description + uses: peter-evans/dockerhub-description@v3 + with: + readme-filepath: ${{ inputs.rootDir }}/notice.md + username: ${{ inputs.docker_user }} + password: ${{ inputs.docker_token }} + repository: ${{ inputs.namespace }}/${{ inputs.imagename }} \ No newline at end of file diff --git a/.github/actions/run-deployment-test/action.yml b/.github/actions/run-deployment-test/action.yml new file mode 100644 index 000000000..fafff52b2 --- /dev/null +++ b/.github/actions/run-deployment-test/action.yml @@ -0,0 +1,95 @@ +# +# 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: "Run Deployment Test" +description: "Build and publish a Docker Image to DockerHub" +inputs: + imagename: + required: true + description: "name of the docker image, e.g. edc-runtime-memory" + + image_tag: + required: false + default: "latest" + description: "docker image tag, defaults to 'latest'" + + helm_command: + required: true + description: "command which is executed to install the chart. must also include verification commands, such as 'helm test'" + + rootDir: + required: true + description: "The directory that contains the docker file, e.g. edc-controlplane/edc-runtime-memory" + +runs: + using: "composite" + steps: + - uses: actions/checkout@v3.3.0 + - uses: ./.github/actions/setup-java + + - name: Build docker images + shell: bash + run: |- + ./gradlew -p ${{ inputs.rootDir }} dockerize + + - name: Setup Helm + uses: azure/setup-helm@v3.5 + with: + version: v3.8.1 + + - name: Setup Kubectl + uses: azure/setup-kubectl@v3.2 + + - name: Create k8s Kind Cluster + uses: helm/kind-action@v1.5.0 + + - name: Load images into KinD + shell: bash + run: | + kind get clusters | xargs -n1 kind load docker-image ${{ inputs.imagename }}:${{ inputs.image_tag }} --name + + ################################################### + # Install the test infrastructure + ################################################### + - name: "Generate test credentials" + shell: bash + run: |- + sh -c "edc-tests/deployment/src/main/resources/prepare-test.sh \ + edc-tests/deployment/src/main/resources/helm/test-infrastructure/values.yaml" + - name: Install Infrastructure + shell: bash + run: |- + helm install infra edc-tests/deployment/src/main/resources/helm/test-infrastructure \ + --wait-for-jobs --timeout=30s --dependency-update + + - name: Install Runtime + shell: bash + run: ${{ inputs.helm_command }} + + + ################# + ### Tear Down ### + ################# + - name: Destroy the kind cluster + if: always() + shell: bash + run: >- + kind get clusters | xargs -n1 kind delete cluster --name \ No newline at end of file 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/dependabot.yml b/.github/dependabot.yml index 16f8582cb..e3324e771 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,10 +1,29 @@ +# +# Copyright (c) 2021, 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 +# + --- version: 2 updates: # Maven - - package-ecosystem: "maven" - target-branch: develop + package-ecosystem: "gradle" + target-branch: main directory: / labels: - "dependabot" @@ -15,7 +34,7 @@ updates: # Github Actions - package-ecosystem: "github-actions" - target-branch: develop + target-branch: main directory: / labels: - "dependabot" @@ -26,7 +45,7 @@ updates: # Docker - package-ecosystem: "docker" - target-branch: develop + target-branch: main directory: ./edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/src/main/docker/ labels: - "dependabot" @@ -35,8 +54,8 @@ updates: interval: "daily" - package-ecosystem: "docker" - target-branch: develop - directory: ./edc-controlplane/edc-controlplane-postgresql/src/main/docker/ + target-branch: main + directory: ./edc-controlplane/edc-controlplane-postgresql-azure-vault/src/main/docker/ labels: - "dependabot" - "docker" @@ -44,7 +63,7 @@ updates: interval: "daily" - package-ecosystem: "docker" - target-branch: develop + target-branch: main directory: ./edc-controlplane/edc-controlplane-memory/src/main/docker/ labels: - "dependabot" @@ -53,7 +72,7 @@ updates: interval: "daily" - package-ecosystem: "docker" - target-branch: develop + target-branch: main directory: ./edc-dataplane/edc-dataplane-azure-vault/src/main/docker/ labels: - "dependabot" @@ -62,7 +81,7 @@ updates: interval: "daily" - package-ecosystem: "docker" - target-branch: develop + target-branch: main directory: ./edc-dataplane/edc-dataplane-hashicorp-vault/src/main/docker/ labels: - "dependabot" diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index b8ab83bba..8235015ed 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -27,7 +27,7 @@ on: push: branches: - main - - develop + - releases tags: - '[0-9]+.[0-9]+.[0-9]+' release: @@ -44,220 +44,89 @@ on: concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + # cancel only running jobs on pull requests + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: secret-presence: runs-on: ubuntu-latest outputs: - SONAR_TOKEN: ${{ steps.secret-presence.outputs.SONAR_TOKEN }} - GPG_PRIVATE_KEY: ${{ steps.secret-presence.outputs.GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ steps.secret-presence.outputs.GPG_PASSPHRASE }} + DOCKER_HUB_TOKEN: ${{ steps.secret-presence.outputs.DOCKER_HUB_TOKEN }} + HAS_OSSRH: ${{ steps.secret-presence.outputs.HAS_OSSRH }} steps: - - - name: Check whether secrets exist + - 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 "DOCKER_HUB_TOKEN=true" >> $GITHUB_OUTPUT + [ ! -z "${{ secrets.ORG_GPG_PASSPHRASE }}" ] && + [ ! -z "${{ secrets.ORG_GPG_PRIVATE_KEY }}" ] && + [ ! -z "${{ secrets.ORG_OSSRH_USERNAME }}" ] && + [ ! -z "${{ secrets.ORG_OSSRH_PASSWORD }}" ] && + echo "HAS_OSSRH=true" >> $GITHUB_OUTPUT exit 0 - build-extensions: + build-docker-images: + name: "Create Docker Images" runs-on: ubuntu-latest - needs: [ secret-presence] - steps: - # Set-Up - - - name: Checkout - uses: actions/checkout@v3.3.0 - - - name: Set up JDK 11 - uses: actions/setup-java@v3.10.0 - with: - java-version: '11' - distribution: 'temurin' - cache: 'gradle' - # Build - - - name: Build Extensions - run: |- - ./gradlew -p edc-extensions build - env: - GITHUB_PACKAGE_USERNAME: ${{ github.actor }} - GITHUB_PACKAGE_PASSWORD: ${{ secrets.GITHUB_TOKEN }} - - build-controlplane: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - needs: [ secret-presence] + needs: [ secret-presence ] + if: | + needs.secret-presence.outputs.DOCKER_HUB_TOKEN strategy: fail-fast: false matrix: - name: - - edc-controlplane-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: Login to GitHub Container Registry - if: | - github.event_name != 'pull_request' - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up JDK 11 - uses: actions/setup-java@v3.10.0 - with: - java-version: '11' - 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: edc-controlplane Docker Metadata - id: edc_controlplane_meta - uses: docker/metadata-action@v4 - with: - images: | - ghcr.io/${{ github.repository }}/${{ matrix.name }} - tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{raw}} - type=match,pattern=\d.\d.\d - type=sha - - - name: Build Docker Image - uses: docker/build-push-action@v4 - with: - context: . - file: edc-controlplane/${{ matrix.name }}/src/main/docker/Dockerfile - build-args: | - JAR=edc-controlplane/${{ matrix.name }}/build/libs/${{ matrix.name }}.jar - push: | - ${{ (github.event_name != 'pull_request' && 'true') || 'false' }} - tags: ${{ steps.edc_controlplane_meta.outputs.tags }} - labels: ${{ steps.edc_controlplane_meta.outputs.labels }} - - build-dataplane: - runs-on: ubuntu-latest + 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-azure-vault }, + { dir: edc-dataplane, img: edc-dataplane-azure-vault }, + { dir: edc-dataplane, img: edc-dataplane-hashicorp-vault } ] permissions: - contents: read - packages: write - needs: [ secret-presence] - strategy: - fail-fast: false - matrix: - name: - - edc-dataplane-azure-vault - - edc-dataplane-hashicorp-vault + contents: write steps: - # Set-Up - - - name: Checkout - uses: actions/checkout@v3.3.0 - - - name: Login to GitHub Container Registry - if: | - github.event_name != 'pull_request' - uses: docker/login-action@v2 + - uses: actions/checkout@v3.5.2 + - uses: ./.github/actions/publish-docker-image + name: Publish ${{ matrix.variant.img }} with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up JDK 11 - uses: actions/setup-java@v3.10.0 - with: - java-version: '11' - distribution: 'temurin' - cache: 'gradle' - # Build - - - name: Build Dataplane - run: |- - ./gradlew -p edc-dataplane/${{ matrix.name }} shadowJar - env: - GITHUB_PACKAGE_USERNAME: ${{ github.actor }} - GITHUB_PACKAGE_PASSWORD: ${{ secrets.GITHUB_TOKEN }} - - - name: edc-dataplane Docker Metadata - id: edc_dataplane_meta - uses: docker/metadata-action@v4 - with: - images: | - ghcr.io/${{ github.repository }}/${{ matrix.name }} - tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{raw}} - type=match,pattern=\d.\d.\d - type=sha - - - name: Build Docker Image - uses: docker/build-push-action@v4 - with: - context: . - file: edc-dataplane/${{ matrix.name }}/src/main/docker/Dockerfile - build-args: | - JAR=edc-dataplane/${{ matrix.name }}/build/libs/${{ matrix.name }}.jar - push: | - ${{ (github.event_name != 'pull_request' && 'true') || 'false' }} - tags: ${{ steps.edc_dataplane_meta.outputs.tags }} - labels: ${{ steps.edc_dataplane_meta.outputs.labels }} + 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 }} - publish-to-github-packages: + publish-to-sonatype: + name: "Publish artefacts to OSSRH Snapshots / MavenCentral" runs-on: ubuntu-latest permissions: contents: read packages: write - needs: [secret-presence, build-controlplane, build-dataplane, build-extensions] + needs: [ secret-presence ] - # do not run on PR branches, do not run on main + # do not run on PR branches, do not run on releases if: | - needs.secret-presence.outputs.GPG_PASSPHRASE && needs.secret-presence.outputs.GPG_PRIVATE_KEY && github.event_name != 'pull_request' && github.ref != 'refs/heads/main' + needs.secret-presence.outputs.HAS_OSSRH && 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.5.2 - - name: Set up JDK 11 - uses: actions/setup-java@v3.10.0 + # Import GPG Key + - uses: ./.github/actions/import-gpg-key + name: "Import GPG Key" with: - java-version: '11' - distribution: 'temurin' - cache: 'gradle' - - name: Import GPG Key - uses: crazy-max/ghaction-import-gpg@v1 - env: - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + gpg-private-key: ${{ secrets.ORG_GPG_PRIVATE_KEY }} - # publish snapshots - - name: Publish snapshot versions - run: |- - echo "Publishing Version $(grep -e "version" gradle.properties | cut -f2 -d"=") to Github Packages" - ./gradlew publishAllPublicationsToGitHubPackagesRepository + - uses: ./.github/actions/setup-java + # publish snapshots or releases + - name: Publish version env: - #REPO: ${{ github.repository }} - REPO: "catenax-ng/product-edc" - GITHUB_PACKAGE_USERNAME: ${{ secrets.TEMP_GHPKG_USER }} - GITHUB_PACKAGE_PASSWORD: ${{ secrets.TEMP_GHPKG_PASSWORD }} + OSSRH_PASSWORD: ${{ secrets.ORG_OSSRH_PASSWORD }} + OSSRH_USER: ${{ secrets.ORG_OSSRH_USERNAME }} + run: |- + VERSION=$(./gradlew properties -q | grep "version:" | awk '{print $2}') + cmd="" + if [[ $VERSION != *-SNAPSHOT ]] + then + cmd="closeAndReleaseSonatypeStagingRepository"; + fi + echo "Publishing Version $VERSION to Sonatype" + ./gradlew publishToSonatype ${cmd} --no-parallel -Pversion=$VERSION -Psigning.gnupg.executable=gpg -Psigning.gnupg.passphrase="${{ secrets.ORG_GPG_PASSPHRASE }}" \ No newline at end of file diff --git a/.github/workflows/business-tests.yaml b/.github/workflows/business-tests.yaml index 6a7cd2cbf..1bc3436bc 100644 --- a/.github/workflows/business-tests.yaml +++ b/.github/workflows/business-tests.yaml @@ -29,13 +29,14 @@ on: - 'docs/**' - '**/*.md' branches: - - develop + - releases - release/** - main workflow_dispatch: concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + # cancel only running jobs on pull requests + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: @@ -49,15 +50,9 @@ jobs: ### Set-Up ### ############## - - name: Checkout - uses: actions/checkout@v3.3.0 + uses: actions/checkout@v3.5.2 - - name: Set-Up JDK 11 - uses: actions/setup-java@v3.10.0 - with: - java-version: '11' - distribution: 'temurin' - cache: 'gradle' + uses: ./.github/actions/setup-java - name: Cache ContainerD Image Layers uses: actions/cache@v3 @@ -92,7 +87,7 @@ jobs: EOF - name: Create k8s Kind Cluster - uses: helm/kind-action@v1.5.0 + uses: helm/kind-action@v1.6.0 with: config: kind.config.yaml @@ -128,14 +123,14 @@ jobs: run: |- # Define endpoints echo "SOKRATES_DATA_MANAGEMENT_API_AUTH_KEY=password" | tee -a ${GITHUB_ENV} - echo "SOKRATES_DATA_MANAGEMENT_URL=http://sokrates-controlplane:8081/data" | tee -a ${GITHUB_ENV} + echo "SOKRATES_DATA_MANAGEMENT_URL=http://sokrates-controlplane:8081/management" | tee -a ${GITHUB_ENV} echo "SOKRATES_IDS_URL=http://sokrates-controlplane:8084/api/v1/ids" | tee -a ${GITHUB_ENV} echo "SOKRATES_DATA_PLANE_URL=http://sokrates-dataplane:8081/api/public/" | tee -a ${GITHUB_ENV} echo "SOKRATES_DATABASE_URL=jdbc:postgresql://plato-postgresql:5432/edc" | tee -a ${GITHUB_ENV} echo "SOKRATES_DATABASE_USER=user" | tee -a ${GITHUB_ENV} echo "SOKRATES_DATABASE_PASSWORD=password" | tee -a ${GITHUB_ENV} echo "PLATO_DATA_MANAGEMENT_API_AUTH_KEY=password" | tee -a ${GITHUB_ENV} - echo "PLATO_DATA_MANAGEMENT_URL=http://plato-controlplane:8081/data" | tee -a ${GITHUB_ENV} + echo "PLATO_DATA_MANAGEMENT_URL=http://plato-controlplane:8081/management" | tee -a ${GITHUB_ENV} echo "PLATO_IDS_URL=http://plato-controlplane:8084/api/v1/ids" | tee -a ${GITHUB_ENV} echo "PLATO_DATA_PLANE_URL=http://plato-dataplane:8081/api/public/" | tee -a ${GITHUB_ENV} echo "PLATO_DATABASE_URL=jdbc:postgresql://plato-postgresql:5432/edc" | tee -a ${GITHUB_ENV} @@ -165,7 +160,6 @@ jobs: sleep 5s # Wait for supporting infrastructure to become ready (control-/data-plane, backend service) - kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=backend --timeout=120s || ( kubectl logs -l app.kubernetes.io/name=backend --tail 500 && exit 1 ) kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=idsdaps --timeout=120s || ( kubectl logs -l app.kubernetes.io/name=idsdaps --tail 500 && exit 1 ) kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=vault --timeout=120s || ( kubectl logs -l app.kubernetes.io/name=vault --tail 500 && exit 1 ) kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=sokrates-postgresql --timeout=120s || ( kubectl logs -l app.kubernetes.io/name=sokrates-postgresql --tail 500 && exit 1 ) @@ -176,7 +170,7 @@ jobs: helm install plato charts/tractusx-connector \ --set fullnameOverride=plato \ --set controlplane.service.type=NodePort \ - --set controlplane.endpoints.data.authKey=password \ + --set controlplane.endpoints.management.authKey=password \ --set controlplane.image.tag=business-test \ --set controlplane.image.pullPolicy=Never \ --set controlplane.image.repository=docker.io/library/edc-controlplane-postgresql-hashicorp-vault \ @@ -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 \ @@ -209,7 +204,7 @@ jobs: helm install sokrates charts/tractusx-connector \ --set fullnameOverride=sokrates \ --set controlplane.service.type=NodePort \ - --set controlplane.endpoints.data.authKey=password \ + --set controlplane.endpoints.management.authKey=password \ --set controlplane.image.tag=business-test \ --set controlplane.image.pullPolicy=Never \ --set controlplane.image.repository=docker.io/library/edc-controlplane-postgresql-hashicorp-vault \ @@ -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 new file mode 100644 index 000000000..9474abcbe --- /dev/null +++ b/.github/workflows/deployment-test.yaml @@ -0,0 +1,153 @@ +# +# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH +# Copyright (c) 2021, 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: "Deployment Tests" + +on: + push: + branches: + - main + - develop + tags: + - '[0-9]+.[0-9]+.[0-9]+' + release: + types: + - published + pull_request: + paths-ignore: + - 'docs/**' + - '**/*.md' + branches: + - '*' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + + secret-presence: + runs-on: ubuntu-latest + outputs: + AZURE_KV_CREDS: ${{ steps.secret-presence.outputs.AZURE_KV_CREDS }} + steps: + - name: Check whether secrets exist + id: secret-presence + run: | + [ ! -z "${{ secrets.AZURE_TENANT_ID }}" ] && + [ ! -z "${{ secrets.AZURE_CLIENT_ID }}" ] && + [ ! -z "${{ secrets.AZURE_CLIENT_SECRET }}" ] && + [ ! -z "${{ secrets.AZURE_VAULT_NAME }}" ] && + echo "AZURE_KV_CREDS=true" >> $GITHUB_OUTPUT + exit 0 + + test-prepare: + runs-on: ubuntu-latest + steps: + - name: Cache ContainerD Image Layers + uses: actions/cache@v3 + with: + path: /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs + key: ${{ runner.os }}-io.containerd.snapshotter.v1.overlayfs + + test-in-memory: + runs-on: ubuntu-latest + needs: test-prepare + steps: + - uses: actions/checkout@v3.5.2 + - uses: ./.github/actions/run-deployment-test + name: "Run deployment test using KinD and Helm" + with: + imagename: edc-runtime-memory + rootDir: edc-controlplane/edc-runtime-memory + helm_command: |- + helm install tx-inmem charts/tractusx-connector-memory \ + -f charts/tractusx-connector-memory/example.yaml \ + --set vault.secrets="daps-crt:$(cat daps.cert);daps-key:$(cat daps.key)" \ + --wait-for-jobs --timeout=120s + + # wait for the pod to become ready + kubectl rollout status deployment tx-inmem + + # execute the helm test + helm test tx-inmem --logs + + test-hashicorp-postgres: + runs-on: ubuntu-latest + needs: test-prepare + steps: + - name: Checkout + uses: actions/checkout@v3.5.2 + - uses: ./.github/actions/run-deployment-test + name: "Run deployment test using KinD and Helm" + with: + imagename: "edc-controlplane-postgresql-hashicorp-vault edc-dataplane-hashicorp-vault" + rootDir: "." + helm_command: |- + helm install tx-prod charts/tractusx-connector \ + -f edc-tests/deployment/src/main/resources/helm/tractusx-connector-test.yaml \ + --dependency-update \ + --wait-for-jobs --timeout=120s + + # wait for the pod to become ready + kubectl rollout status deployment tx-prod-controlplane + kubectl rollout status deployment tx-prod-dataplane + + # execute the helm test + helm test tx-prod --logs + + test-azure-vault-postgres: + runs-on: ubuntu-latest + needs: [ test-prepare, secret-presence ] + if: | + needs.secret-presence.outputs.AZURE_KV_CREDS + steps: + - name: Checkout + uses: actions/checkout@v3.3.0 + - name: "Login to AZ CLI" + run: | + az login --service-principal -u="${{ secrets.AZURE_CLIENT_ID }}" --password="${{ secrets.AZURE_CLIENT_SECRET }}" --tenant="${{ secrets.AZURE_TENANT_ID }}" + - uses: ./.github/actions/run-deployment-test + name: "Run deployment test using KinD and Helm" + with: + imagename: "edc-controlplane-postgresql-azure-vault edc-dataplane-azure-vault" + rootDir: "." + helm_command: |- + az keyvault secret set --vault-name ${{ secrets.AZURE_VAULT_NAME }} --name daps-crt --value "$(cat daps.cert)" > /dev/null + az keyvault secret set --vault-name ${{ secrets.AZURE_VAULT_NAME }} --name daps-key --value "$(cat daps.key)" > /dev/null + az keyvault secret set --vault-name ${{ secrets.AZURE_VAULT_NAME }} --name aes-keys --value "$(cat aes.key)" > /dev/null + + helm install tx-prod charts/tractusx-connector-azure-vault \ + -f edc-tests/deployment/src/main/resources/helm/tractusx-connector-azure-vault-test.yaml \ + --dependency-update \ + --set vault.azure.name=${{ secrets.AZURE_VAULT_NAME }} \ + --set vault.azure.client=${{ secrets.AZURE_CLIENT_ID }} \ + --set vault.azure.secret=${{ secrets.AZURE_CLIENT_SECRET }} \ + --set vault.azure.tenant=${{ secrets.AZURE_TENANT_ID }} \ + --wait-for-jobs --timeout=120s + + # wait for the pod to become ready + kubectl rollout status deployment tx-prod-controlplane + kubectl rollout status deployment tx-prod-dataplane + + # execute the helm test + helm test tx-prod --logs diff --git a/.github/workflows/draft-new-release.yaml b/.github/workflows/draft-new-release.yaml index acb4412d9..1c080db15 100644 --- a/.github/workflows/draft-new-release.yaml +++ b/.github/workflows/draft-new-release.yaml @@ -1,3 +1,22 @@ +# +# Copyright (c) 2021, 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: "Draft new release" @@ -18,7 +37,7 @@ jobs: pages: write pull-requests: write steps: - - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v3.5.2 - name: Create release branch run: git checkout -b release/${{ github.event.inputs.version }} @@ -30,15 +49,10 @@ jobs: - name: Initialize mandatory git config run: | - git config user.name "GitHub actions" - git config user.email noreply@github.com + git config user.name "eclipse-tractusx-bot" + git config user.email "tractusx-bot@eclipse.org" - - name: Set up JDK 11 - uses: actions/setup-java@v3.10.0 - with: - java-version: '11' - distribution: 'temurin' - cache: 'gradle' + uses: ./.github/actions/setup-java - name: Bump version in gradle.properties run: |- @@ -49,7 +63,7 @@ jobs: GITHUB_PACKAGE_PASSWORD: ${{ secrets.GITHUB_TOKEN }} - name: Bump version in /charts - uses: mikefarah/yq@v4.31.2 + uses: mikefarah/yq@v4.33.3 with: cmd: |- find charts -name Chart.yaml | xargs -n1 yq -i '.appVersion = "${{ github.event.inputs.version }}" | .version = "${{ github.event.inputs.version }}"' @@ -68,7 +82,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 }} @@ -79,7 +93,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: head: release/${{ github.event.inputs.version }} - base: main + base: releases title: Release version ${{ github.event.inputs.version }} reviewers: ${{ github.actor }} body: |- diff --git a/.github/workflows/helm-chart-release.yaml b/.github/workflows/helm-chart-release.yaml index 819f4f0ec..0e282826f 100644 --- a/.github/workflows/helm-chart-release.yaml +++ b/.github/workflows/helm-chart-release.yaml @@ -23,7 +23,7 @@ on: paths: - 'charts/**' branches: - - main + - releases workflow_dispatch: jobs: @@ -38,15 +38,14 @@ jobs: steps: # fetch-depth: 0 is required to determine differences in chart(s) - - name: Checkout - uses: actions/checkout@v3 + - uses: actions/checkout@v3.5.2 with: fetch-depth: 0 - name: Configure Git run: | - git config user.name "$GITHUB_ACTOR" - git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + git config user.name "eclipse-tractusx-bot" + git config user.email "tractusx-bot@eclipse.org" - name: Install Helm uses: azure/setup-helm@v3 with: diff --git a/.github/workflows/helm-lint.yaml b/.github/workflows/helm-lint.yaml index 624607533..9bd8bc0ab 100644 --- a/.github/workflows/helm-lint.yaml +++ b/.github/workflows/helm-lint.yaml @@ -1,3 +1,22 @@ +# +# Copyright (c) 2021, 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: "Lint helm charts" @@ -5,7 +24,7 @@ on: push: branches: - main - - develop + - releases tags: - '[0-9]+.[0-9]+.[0-9]+' paths-ignore: @@ -26,8 +45,7 @@ jobs: ### Set-Up ### ############## - - name: Checkout - uses: actions/checkout@v3.3.0 + uses: actions/checkout@v3.5.2 with: fetch-depth: 0 - @@ -42,7 +60,7 @@ jobs: python-version: 3.7 - name: chart-testing (setup) - uses: helm/chart-testing-action@v2.3.1 + uses: helm/chart-testing-action@v2.4.0 ##################### ### Chart Testing ### ##################### @@ -50,9 +68,9 @@ jobs: name: chart-testing (list-changed) id: list-changed run: | - changed=$(ct list-changed --config ct.yaml --target-branch develop) + 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/kics.yml b/.github/workflows/kics.yml index c009ab2de..adb020c1f 100644 --- a/.github/workflows/kics.yml +++ b/.github/workflows/kics.yml @@ -1,10 +1,30 @@ +# +# Copyright (c) 2021, 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: "KICS" on: push: - branches: [main, master, develop] + branches: [main, releases] pull_request: - branches: [main, master, develop] + branches: [main, releases] workflow_dispatch: schedule: @@ -20,7 +40,7 @@ jobs: security-events: write steps: - - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v3.5.2 - name: KICS scan uses: checkmarx/kics-github-action@v1.5 diff --git a/.github/workflows/publish-docker.yaml b/.github/workflows/publish-docker.yaml new file mode 100644 index 000000000..da5787431 --- /dev/null +++ b/.github/workflows/publish-docker.yaml @@ -0,0 +1,66 @@ +# +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (c) 2021, 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: "Create Docker images" + +on: + workflow_dispatch: + inputs: + namespace: + 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 + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + create-docker-image: + name: "Create Docker Images for the ControlPlane" + runs-on: ubuntu-latest + 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-azure-vault }, + { dir: edc-dataplane, img: edc-dataplane-azure-vault }, + { dir: edc-dataplane, img: edc-dataplane-hashicorp-vault } ] + permissions: + contents: write + packages: write + steps: + - uses: actions/checkout@v3.5.2 + - 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 }} + namespace: ${{ inputs.namespace }} + docker_user: ${{ secrets.DOCKER_HUB_USER }} + docker_token: ${{ secrets.DOCKER_HUB_TOKEN }} diff --git a/.github/workflows/publish-new-release.yml b/.github/workflows/publish-new-release.yml index 56148b82a..9de73849f 100644 --- a/.github/workflows/publish-new-release.yml +++ b/.github/workflows/publish-new-release.yml @@ -1,10 +1,29 @@ +# +# Copyright (c) 2021, 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: "Publish new release" on: pull_request: branches: - - main + - releases - support/* types: - closed @@ -37,7 +56,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: @@ -49,36 +68,57 @@ jobs: packages: write if: github.event.pull_request.merged == true && needs.release-version.outputs.RELEASE_VERSION steps: - - - name: Export RELEASE_VERSION env + - name: Export RELEASE_VERSION env 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.10.0 + + # Set-Up + - uses: actions/checkout@v3.5.2 + - uses: ./.github/actions/setup-java + + # Import GPG Key + - uses: ./.github/actions/import-gpg-key + name: "Import GPG Key" with: - java-version: '11' - distribution: 'temurin' - cache: 'gradle' + gpg-private-key: ${{ secrets.ORG_GPG_PRIVATE_KEY }} - - name: Import GPG Key - uses: crazy-max/ghaction-import-gpg@v1 + # publish releases + - name: Publish version env: - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + OSSRH_PASSWORD: ${{ secrets.ORG_OSSRH_PASSWORD }} + OSSRH_USER: ${{ secrets.ORG_OSSRH_USERNAME }} + run: |- + echo "Publishing Version $RELEASE_VERSION to Sonatype/MavenCentral" + ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository --no-parallel -Pversion=$RELEASE_VERSION -Psigning.gnupg.executable=gpg -Psigning.gnupg.passphrase="${{ secrets.ORG_GPG_PASSPHRASE }}" + + 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 - - name: Publish release version - run: | - echo "Publishing Version $(grep -e "version" gradle.properties | cut -f2 -d"=") to Github Packages" - ./gradlew publishAllPublicationsToGithubPackagesRepository - env: - #REPO: ${{ github.repository }} - REPO: "catenax-ng/product-edc" - GITHUB_PACKAGE_USERNAME: ${{ secrets.TEMP_GHPKG_USER }} - GITHUB_PACKAGE_PASSWORD: ${{ secrets.TEMP_GHPKG_PASSWORD }} + 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-azure-vault}, + {dir: edc-dataplane, img: edc-dataplane-azure-vault}, + {dir: edc-dataplane, img: edc-dataplane-hashicorp-vault}] + + steps: + - uses: actions/checkout@v3.5.2 + - 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: @@ -97,8 +137,7 @@ jobs: run: | echo "RELEASE_VERSION=${{ needs.release-version.outputs.RELEASE_VERSION }}" >> $GITHUB_ENV - - name: Checkout - uses: actions/checkout@v3.3.0 + uses: actions/checkout@v3.5.2 with: fetch-depth: 0 - @@ -110,8 +149,8 @@ jobs: name: Package helm, update index.yaml and push to gh-pages run: | # Prepare git env - git config user.name "GitHub actions" - git config user.email noreply@github.com + git config user.name "eclipse-tractusx-bot" + git config user.email "tractusx-bot@eclipse.org" # Package all charts find charts -name Chart.yaml -not -path "./edc-tests/*" | xargs -n1 dirname | xargs -n1 helm package -u -d helm-charts @@ -128,7 +167,7 @@ jobs: git push origin gh-pages - # Release: GitHub tag & release; Merges back main into develop; Starts a new development cycle; + # Release: GitHub tag & release; Merges back releases into main; Starts a new development cycle; github-release: name: Publish new github release needs: [ release-version ] @@ -145,18 +184,17 @@ jobs: run: | echo "RELEASE_VERSION=${{ needs.release-version.outputs.RELEASE_VERSION }}" >> $GITHUB_ENV - - name: Checkout - uses: actions/checkout@v3.3.0 + uses: actions/checkout@v3.5.2 with: - # 0 to fetch the full history due to upcoming merge of main into develop branch + # 0 to fetch the full history due to upcoming merge of releases into main branch fetch-depth: 0 - name: Create Release Tag id: create_release_tag run: | # Prepare git env - git config user.name "$GITHUB_ACTOR" - git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + git config user.name "eclipse-tractusx-bot" + git config user.email "tractusx-bot@eclipse.org" # informative git branch -a @@ -178,22 +216,17 @@ jobs: draft: false prerelease: false - - name: Set up JDK 11 - uses: actions/setup-java@v3.10.0 - with: - java-version: '11' - distribution: 'temurin' - cache: 'gradle' + uses: ./.github/actions/setup-java - - name: Merge main back into develop and set new snapshot version - if: github.event.pull_request.base.ref == 'main' + name: Merge releases back into main and set new snapshot version + if: github.event.pull_request.base.ref == 'releases' run: | # Prepare git env - git config user.name "GitHub actions" - git config user.email noreply@github.com + git config user.name "eclipse-tractusx-bot" + git config user.email "tractusx-bot@eclipse.org" - # Merge main into develop - git checkout develop && git merge -X theirs main --no-commit --no-ff + # Merge releases into main + git checkout main && git merge -X theirs releases --no-commit --no-ff # Extract release version IFS=. read -r RELEASE_VERSION_MAJOR RELEASE_VERSION_MINOR RELEASE_VERSION_PATCH<<<"${{ env.RELEASE_VERSION }}" @@ -204,8 +237,8 @@ jobs: # Persist the "version" in the gradle.properties sed -i "s/version=.*/version=$SNAPSHOT_VERSION/g" gradle.properties - # Commit and push to origin develop + # Commit and push to origin main git add gradle.properties git commit --message "Introduce new snapshot version $SNAPSHOT_VERSION" - git push origin develop + git push origin main diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index b82acaf66..f53eb5271 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -1,3 +1,22 @@ +# +# Copyright (c) 2021, 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: "Trivy" @@ -6,10 +25,10 @@ on: - cron: "0 0 * * *" workflow_dispatch: workflow_run: - workflows: ["Build"] + workflows: [ "Build" ] branches: - main - - develop + - releases - release/* - hotfix/* tags: @@ -24,11 +43,10 @@ jobs: outputs: value: ${{ steps.git-sha7.outputs.SHA7 }} steps: - - - name: Resolve git 7-chars sha + - 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 @@ -37,11 +55,8 @@ jobs: contents: read security-events: write steps: - - - name: Checkout repository - uses: actions/checkout@v3.3.0 - - - name: Run Trivy vulnerability scanner in repo mode + - uses: actions/checkout@v3.5.2 + - name: Run Trivy vulnerability scanner in repo mode uses: aquasecurity/trivy-action@master with: scan-type: "config" @@ -51,8 +66,7 @@ jobs: format: "sarif" output: "trivy-results-config.sarif" severity: "CRITICAL,HIGH" - - - name: Upload Trivy scan results to GitHub Security tab + - name: Upload Trivy scan results to GitHub Security tab uses: github/codeql-action/upload-sarif@v2 if: always() with: @@ -69,30 +83,35 @@ jobs: fail-fast: false # continue scanning other images although if the other has been vulnerable matrix: image: - - edc-controlplane-memory + - edc-runtime-memory - edc-controlplane-memory-hashicorp-vault - - edc-controlplane-postgresql + - edc-controlplane-postgresql-azure-vault - edc-controlplane-postgresql-hashicorp-vault - edc-dataplane-azure-vault - edc-dataplane-hashicorp-vault steps: - - - name: Checkout - uses: actions/checkout@v3.3.0 - - - name: Run Trivy vulnerability scanner - if: always() + - uses: actions/checkout@v3.5.2 + + ## 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: success() && steps.imageCheck.outcome != 'failure' uses: aquasecurity/trivy-action@master with: - image-ref: "ghcr.io/${{ github.repository }}/${{ matrix.image }}:sha-${{ needs.git-sha7.outputs.value }}" + image-ref: "tractusx/${{ matrix.image }}:sha-${{ needs.git-sha7.outputs.value }}" format: "sarif" output: "trivy-results-${{ matrix.image }}.sarif" exit-code: "1" severity: "CRITICAL,HIGH" timeout: "10m0s" - - - name: Upload Trivy scan results to GitHub Security tab - if: always() + - name: Upload Trivy scan results to GitHub Security tab + 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 0bfaac8b5..78c4ae441 100644 --- a/.github/workflows/veracode.yaml +++ b/.github/workflows/veracode.yaml @@ -1,3 +1,22 @@ +# +# Copyright (c) 2021, 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: "Veracode" @@ -13,34 +32,24 @@ jobs: ORG_VERACODE_API_ID: ${{ steps.secret-presence.outputs.ORG_VERACODE_API_ID }} ORG_VERACODE_API_KEY: ${{ steps.secret-presence.outputs.ORG_VERACODE_API_KEY }} steps: - - - name: Check whether secrets exist + - 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.5.2 with: fetch-depth: 0 - - - name: Set up JDK 11 - uses: actions/setup-java@v3.10.0 - with: - java-version: '11' - distribution: 'temurin' - cache: 'gradle' - - - name: Verify proper formatting + - uses: ./.github/actions/setup-java + - name: Verify proper formatting run: ./gradlew spotlessCheck - build-controlplane: + build: runs-on: ubuntu-latest needs: [ secret-presence, verify-formatting ] permissions: @@ -48,95 +57,35 @@ jobs: strategy: fail-fast: false matrix: - name: - - edc-controlplane-memory - - edc-controlplane-memory-hashicorp-vault - - edc-controlplane-postgresql - - edc-controlplane-postgresql-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-azure-vault }, + { 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.10.0 - with: - java-version: '11' - distribution: 'temurin' - cache: 'gradle' + - uses: actions/checkout@v3.5.2 + - uses: ./.github/actions/setup-java # Build - - - name: Build Controlplane + - name: Build ${{ matrix.variant.name }} run: |- - ./gradlew -p edc-controlplane/${{ 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 + - 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 + 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-controlplane/${{ 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 }} - - build-dataplane: - runs-on: ubuntu-latest - needs: [ secret-presence, verify-formatting ] - permissions: - contents: read - strategy: - fail-fast: false - matrix: - name: - - edc-dataplane-azure-vault - - 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.10.0 - with: - java-version: '11' - distribution: 'temurin' - cache: 'gradle' - # Build - - - name: Build Dataplane - run: |- - ./gradlew -p edc-dataplane/${{ 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-dataplane/${{ matrix.name }}/build/libs/${{ matrix.name }}.tar.gz edc-dataplane/${{ 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-dataplane/${{ matrix.name }}/build/libs/${{ matrix.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 bac515157..c826f39b6 100644 --- a/.github/workflows/verify.yaml +++ b/.github/workflows/verify.yaml @@ -25,7 +25,7 @@ on: push: branches: - main - - develop + - releases tags: - '[0-9]+.[0-9]+.[0-9]+' release: @@ -34,45 +34,24 @@ on: pull_request: paths-ignore: - 'charts/**' - - 'docs/**' - - '**/*.md' branches: - '*' workflow_dispatch: concurrency: + # cancel older running jobs on the same branch group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: - secret-presence: - runs-on: ubuntu-latest - outputs: - SONAR_TOKEN: ${{ steps.secret-presence.outputs.SONAR_TOKEN }} - steps: - - - name: Check whether secrets exist - id: secret-presence - run: | - [ ! -z "${{ secrets.SONAR_TOKEN }}" ] && echo "::set-output name=SONAR_TOKEN::true" - exit 0 verify-formatting: runs-on: ubuntu-latest steps: - - - name: Checkout - uses: actions/checkout@v3.3.0 - - - - name: Set up JDK 11 - uses: actions/setup-java@v3.10.0 - with: - java-version: '11' - distribution: 'temurin' - cache: 'gradle' - - - name: Verify proper formatting + - uses: actions/checkout@v3.5.2 + + - uses: ./.github/actions/setup-java + - name: Verify proper formatting run: ./gradlew spotlessCheck - name: Run Checkstyle @@ -83,123 +62,55 @@ jobs: markdown-lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v3.5.2 - name: Install mardkdownlint run: npm install -g markdownlint-cli2 - name: Run markdownlint run: | - markdownlint-cli2-config .markdownlint.yaml "**/*.md" + markdownlint-cli2-config .markdownlint.yaml "**/*.md" "#.github" "#charts" unit-tests: runs-on: ubuntu-latest - needs: [verify-formatting] + needs: [ verify-formatting ] steps: - - name: Checkout - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v3.5.2 - - name: Set up JDK 11 - uses: actions/setup-java@v3.10.0 - with: - java-version: '11' - distribution: 'temurin' - cache: 'gradle' + - uses: ./.github/actions/setup-java - name: Run Unit tests run: ./gradlew test integration-tests: runs-on: ubuntu-latest - needs: [verify-formatting] + needs: [ verify-formatting ] steps: - - name: Checkout - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v3.5.2 - - name: Set up JDK 11 - uses: actions/setup-java@v3.10.0 - with: - java-version: '11' - distribution: 'temurin' - cache: 'gradle' + - uses: ./.github/actions/setup-java - name: Run Integration tests run: ./gradlew test -DincludeTags="ComponentTest" api-tests: runs-on: ubuntu-latest - needs: [verify-formatting] + needs: [ verify-formatting ] steps: - - name: Checkout - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v3.5.2 - - name: Set up JDK 11 - uses: actions/setup-java@v3.10.0 - with: - java-version: '11' - distribution: 'temurin' - cache: 'gradle' + - uses: ./.github/actions/setup-java - name: Run API tests run: ./gradlew test -DincludeTags="ApiTest" end-to-end-tests: runs-on: ubuntu-latest - needs: [verify-formatting] + needs: [ verify-formatting ] steps: - - name: Checkout - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v3.5.2 - - name: Set up JDK 11 - uses: actions/setup-java@v3.10.0 - with: - java-version: '11' - distribution: 'temurin' - cache: 'gradle' + - uses: ./.github/actions/setup-java - name: Run E2E tests - run: ./gradlew test -DincludeTags="EndToEndTest" - - sonar: - needs: [ secret-presence, verify-formatting ] - if: | - needs.secret-presence.outputs.SONAR_TOKEN - runs-on: ubuntu-latest - steps: - # Set-Up - - - name: Checkout - uses: actions/checkout@v3.3.0 - with: - fetch-depth: 0 - - - name: Set up JDK 11 - uses: actions/setup-java@v3.10.0 - with: - java-version: '11' - distribution: 'temurin' - cache: 'gradle' - - - name: Cache SonarCloud packages - uses: actions/cache@v3 - with: - path: ~/.sonar/cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - # Analyse - - - name: Build with Maven and analyze with Sonar - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - JACOCO: true - run: |- - ./gradlew sonar \ - -Pcoverage,failsafe \ - -Dsonar.projectKey=${GITHUB_REPOSITORY_OWNER}_product-edc \ - -Dsonar.organization=${GITHUB_REPOSITORY_OWNER} \ - -Dsonar.host.url=https://sonarcloud.io \ - -Dsonar.coverage.jacoco.xmlReportPaths=${GITHUB_WORKSPACE}/build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml \ - -Dsonar.verbose=true - - + run: ./gradlew :edc-tests:runtime:build test -DincludeTags="EndToEndTest" diff --git a/.gitignore b/.gitignore index cb53525fa..9f9952efe 100644 --- a/.gitignore +++ b/.gitignore @@ -105,3 +105,7 @@ buildNumber.properties .mvn/timing.properties # https://github.com/takari/maven-wrapper#usage-without-binary-jar .mvn/wrapper/maven-wrapper.jar + +### Helm +**/*.lock +**/*.tgz diff --git a/.markdownlint.yaml b/.markdownlint.yaml index ace38e3d4..d060f2264 100644 --- a/.markdownlint.yaml +++ b/.markdownlint.yaml @@ -19,6 +19,7 @@ "default": true # Do not restrict line length: https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#MD013 "MD013": false +"MD034": # Allow same content on headlines on siblings: https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#MD024 "MD024": "siblings_only": true diff --git a/.tractusx b/.tractusx new file mode 100644 index 000000000..e957e4d87 --- /dev/null +++ b/.tractusx @@ -0,0 +1,3 @@ +product: "Tractus-X EDC" +leadingRepository: "https://github.com/eclipse-tractusx/tractusx-edc" +repositories: [] diff --git a/CHANGELOG.md b/CHANGELOG.md index d3d528a32..842715f6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.4] - 2023-05-17 + +### Fixed + +- Added license headers to several files in the code base +- Refactoring of Helm charts - multiple charts instead of one dynamically assembled chart + +## [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 @@ -49,25 +67,6 @@ corresponding [documentation](/docs/migration/Version_0.1.x_0.3.x.md). - Local TXDC Setup Documentation (#618) - Feature: Sftp Provisioner and Client (#554) -- Add contract id to data source http call (#732) -- Support also support releases in ci pipeline -- Introduce typed object for oauth2 provisioning -- Add documentation -- Add test case -- Add client to omejdn -- add hydra deployment -- Configure dynamically HTTP Receiver callback endpoints. (#685) -- cp-adapter : code review, rollbacke name change (#664) -- Feature/cp adapter task 355 356 357 (#621) -- Add Validity Mapping in ContractDefinitionStepDefs class -- Add feature and create SendAnOfferwithoutConstraints method in class negotiationSteps -- Add validity attribute in class ContractDefinition -- Add Validity Mapping in ContractDefinitionStepDefs class -- Add feature and create SendAnOfferwithoutConstraints method in class negotiationSteps -- Add validity attribute in class ContractDefinition -- Local TXDC Setup Documentation (#618) -- Feature: Sftp Provisioner and Client (#554) - ### Changed - Support horizontal edc scaling in cp adapter extension (#678) @@ -88,27 +87,7 @@ corresponding [documentation](/docs/migration/Version_0.1.x_0.3.x.md). - update control plane docu (#623) - update postgresql version in Chart.yaml supporting-infrastructure (#622) - update link to edc logo in README.md (#612) -- update description of supporting infrastructure deployment (#616) - -- Support horizontal edc scaling in cp adapter extension (#678) -- Use upstream jackson version (#741) -- Replace provision-oauth2 with data-plane-http-oauth2 -- docs: Update sample documentation (#671) -- chore: Disable build ci pipeline if just docu was updated (#705) -- Increase trivy timeout -- Remove not useful anymore custom-jsonld extension (#683) -- update setup docu (#654) -- remove trailing slash (#652) -- update alpine from 3.17.0 to 3.17.1 for controlplane-memory-hashicorp-vault (#665) -- Feature/set charts deprecated (#628) -- update setup docu (#627) -- Feature/update txdc deployment downward capabilities (#625) -- remove git submodule (#619) -- Feature/update postman (#624) -- update control plane docu (#623) -- update postgresql version in Chart.yaml supporting-infrastructure (#622) -- update link to edc logo in README.md (#612) -- update description of supporting infrastructure deployment (#616) +- update description of supporting infrastructure deployment (#616) ### Fixed @@ -117,18 +96,13 @@ corresponding [documentation](/docs/migration/Version_0.1.x_0.3.x.md). - Fix not working docu link in README.md - Fix typo in control-plane adapter README -- bugfix: Fix slow AES encryption (#746) -- Fix typo in tractusx-connector values.yaml comment -- Fix not working docu link in README.md -- Fix typo in control-plane adapter README - ### Dependency updates - Bump EDC to 20220220 (#767) - Bump alpine (#749) - Bump alpine (#750) - Bump alpine (#752) -- Bump alpine in /edc-controlplane/edc-controlplane-memory/src/main/docker (#753) +- Bump alpine in /edc-controlplane/edc-runtime-memory/src/main/docker (#753) - Bump maven-deploy-plugin from 3.0.0 to 3.1.0 (#735) - Bump actions/setup-java from 3.9.0 to 3.10.0 (#730) - Bump s3 from 2.19.33 to 2.20.0 @@ -161,7 +135,7 @@ corresponding [documentation](/docs/migration/Version_0.1.x_0.3.x.md). - Bump s3 from 2.19.11 to 2.19.15 (#668) - Bump maven-surefire-plugin from 3.0.0-M7 to 3.0.0-M8 (#670) - Bump edc version to 0.0.1-20230109-SNAPSHOT (#666) -- Bump alpine in /edc-controlplane/edc-controlplane-memory/src/main/docker (#659) +- Bump alpine in /edc-controlplane/edc-runtime-memory/src/main/docker (#659) - Bump alpine in /edc-dataplane/edc-dataplane-azure-vault/src/main/docker (#660) - Bump alpine (#658) - Bump alpine (#661) @@ -215,7 +189,8 @@ corresponding [documentation](/docs/migration/Version_0.1.x_0.3.x.md). ## [0.1.1] - 2022-09-04 -**Important Note**: Please consolidate the migration documentation before updating your connector. [documentation](/docs/migration/Version_0.1.0_0.1.1.md). +**Important Note**: Please consolidate the migration documentation before updating your +connector. [documentation](/docs/migration/Version_0.1.0_0.1.1.md). ### Added @@ -228,7 +203,8 @@ corresponding [documentation](/docs/migration/Version_0.1.x_0.3.x.md). ### Fixed -- Connectors with Azure Vault extension are now starting again [link](https://github.com/eclipse-edc/Connector/issues/1892) +- Connectors with Azure Vault extension are now starting + again [link](https://github.com/eclipse-edc/Connector/issues/1892) ## [0.1.0] - 2022-08-19 @@ -237,11 +213,13 @@ corresponding [documentation](/docs/migration/Version_0.0.x_0.1.x.md). ### Added -- Control-Plane extension ([data-plane-selector-client](https://github.com/eclipse-edc/Connector/tree/v0.0.1-milestone-5/extensions/data-plane-selector/selector-client)) +- Control-Plane + extension ([data-plane-selector-client](https://github.com/eclipse-edc/Connector/tree/v0.0.1-milestone-5/extensions/data-plane-selector/selector-client)) - run the EDC with multiple data planes at once - Control-Plane extension ([dataplane-selector-configuration](edc-extensions/dataplane-selector-configuration)) - add data plane instances to the control plane by configuration -- Data-Plane extension ([s3-data-plane](https://github.com/eclipse-edc/Connector/tree/main/extensions/aws/data-plane-s3)) +- Data-Plane + extension ([s3-data-plane](https://github.com/eclipse-edc/Connector/tree/main/extensions/aws/data-plane-s3)) - transfer from and to AWS S3 buckets - Control-Plane extension ([data-encryption](edc-extensions/data-encryption)) - Data-Plane authentication attribute transmitted during data-plane-transfer can be encrypted symmetrically (AES) @@ -249,19 +227,27 @@ corresponding [documentation](/docs/migration/Version_0.0.x_0.1.x.md). ### Changed - Update setting name (`edc.dataplane.token.validation.endpoint` -> `edc.dataplane.token.validation.endpoint`) -- EDC has been updated to version [0.0.1-20220818-SNAPSHOT](https://oss.sonatype.org/#nexus-search;gav~org.eclipse.dataspaceconnector~~0.0.1-20220818-SNAPSHOT~~) - implications to the behavior of the connector have been covered in the [corresponding migration guide](docs/migration/Version_0.0.x_0.1.x.md) +- EDC has been updated to + version [0.0.1-20220818-SNAPSHOT](https://oss.sonatype.org/#nexus-search;gav~org.eclipse.dataspaceconnector~~0.0.1-20220818-SNAPSHOT~~) - + implications to the behavior of the connector have been covered in + the [corresponding migration guide](docs/migration/Version_0.0.x_0.1.x.md) ### Fixed -- Contract-Offer-Receiving-Connectors must also pass the ContractPolicy of the ContractDefinition before receiving offers([issue](https://github.com/eclipse-edc/Connector/issues/1331)) -- Deletion of Asset becomes impossible when Contract Negotiation exists([issue](https://github.com/eclipse-edc/Connector/issues/1403)) -- Deletion of Policy becomes impossible when Contract Definition exists([issue](https://github.com/eclipse-edc/Connector/issues/1410)) +- Contract-Offer-Receiving-Connectors must also pass the ContractPolicy of the ContractDefinition before receiving + offers([issue](https://github.com/eclipse-edc/Connector/issues/1331)) +- Deletion of Asset becomes impossible when Contract Negotiation + exists([issue](https://github.com/eclipse-edc/Connector/issues/1403)) +- Deletion of Policy becomes impossible when Contract Definition + exists([issue](https://github.com/eclipse-edc/Connector/issues/1410)) ## [0.0.6] - 2022-07-29 ### Fixed -- Fixes [release 0.0.5](https://github.com/eclipse-tractusx/tractusx-edc/releases/tag/0.0.5), which introduced classpath issues due to usage of [net.jodah:failsafe:2.4.3](https://search.maven.org/artifact/net.jodah/failsafe/2.4.3/jar) library +- Fixes [release 0.0.5](https://github.com/eclipse-tractusx/tractusx-edc/releases/tag/0.0.5), which introduced classpath + issues due to usage of [net.jodah:failsafe:2.4.3](https://search.maven.org/artifact/net.jodah/failsafe/2.4.3/jar) + library ## [0.0.5] - 2022-07-28 @@ -289,7 +275,7 @@ corresponding [documentation](/docs/migration/Version_0.0.x_0.1.x.md). ### Fixed - [#1515](https://github.com/eclipse-edc/Connector/issues/1515) SQL: Connector sends out 50 - contract offers max. + contract offers max. ### Removed @@ -302,9 +288,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/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 73b8aa525..4e32c4bd2 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,46 +1,94 @@ # Community Code of Conduct -**Version 1.2 -August 19, 2020** +**Version 2.0 +January 1, 2023** ## Our Pledge -In the interest of fostering an open and welcoming environment, we as community members, contributors, committers, and project leaders pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. +In the interest of fostering an open and welcoming environment, we as community members, contributors, Committers[^1], and Project Leads (collectively "Contributors") pledge to make participation in our projects and our community a harassment-free and inclusive experience for everyone. + +This Community Code of Conduct ("Code") outlines our behavior expectations as members of our community in all Eclipse Foundation activities, both offline and online. It is not intended to govern scenarios or behaviors outside of the scope of Eclipse Foundation activities. Nor is it intended to replace or supersede the protections offered to all our community members under the law. Please follow both the spirit and letter of this Code and encourage other Contributors to follow these principles into our work. Failure to read or acknowledge this Code does not excuse a Contributor from compliance with the Code. ## Our Standards -Examples of behavior that contributes to creating a positive environment include: +Examples of behavior that contribute to creating a positive and professional environment include: + +- Using welcoming and inclusive language; +- Actively encouraging all voices; +- Helping others bring their perspectives and listening actively. If you find yourself dominating a discussion, it is especially important to encourage other voices to join in; +- Being respectful of differing viewpoints and experiences; +- Gracefully accepting constructive criticism; +- Focusing on what is best for the community; +- Showing empathy towards other community members; +- Being direct but professional; and +- Leading by example by holding yourself and others accountable + +Examples of unacceptable behavior by Contributors include: + +- The use of sexualized language or imagery; +- Unwelcome sexual attention or advances; +- Trolling, insulting/derogatory comments, and personal or political attacks; +- Public or private harassment, repeated harassment; +- Publishing others' private information, such as a physical or electronic address, without explicit permission; +- Violent threats or language directed against another person; +- Sexist, racist, or otherwise discriminatory jokes and language; +- Posting sexually explicit or violent material; +- Sharing private content, such as emails sent privately or non-publicly, or unlogged forums such as IRC channel history; +- Personal insults, especially those using racist or sexist terms; +- Excessive or unnecessary profanity; +- Advocating for, or encouraging, any of the above behavior; and +- Other conduct which could reasonably be considered inappropriate in a professional setting -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +## Our Responsibilities -Examples of unacceptable behavior by participants include: +With the support of the Eclipse Foundation employees, consultants, officers, and directors (collectively, the "Staff"), Committers, and Project Leads, the Eclipse Foundation Conduct Committee (the "Conduct Committee") is responsible for clarifying the standards of acceptable behavior. The Conduct Committee takes appropriate and fair corrective action in response to any instances of unacceptable behavior. -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting +## Scope -## Our Responsibilities +This Code applies within all Project, Working Group, and Interest Group spaces and communication channels of the Eclipse Foundation (collectively, "Eclipse spaces"), within any Eclipse-organized event or meeting, and in public spaces when an individual is representing an Eclipse Foundation Project, Working Group, Interest Group, or their communities. Examples of representing a Project or community include posting via an official social media account, personal accounts, or acting as an appointed representative at an online or offline event. Representation of Projects, Working Groups, and Interest Groups may be further defined and clarified by Committers, Project Leads, or the Staff. -With the support of the Eclipse Foundation staff (the “Staff”), project committers and leaders are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. +## Enforcement -Project committers and leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the Conduct Committee via conduct@eclipse-foundation.org. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Without the explicit consent of the reporter, the Conduct Committee is obligated to maintain confidentiality with regard to the reporter of an incident. The Conduct Committee is further obligated to ensure that the respondent is provided with sufficient information about the complaint to reply. If such details cannot be provided while maintaining confidentiality, the Conduct Committee will take the respondent‘s inability to provide a defense into account in its deliberations and decisions. Further details of enforcement guidelines may be posted separately. -## Scope +Staff, Committers and Project Leads have the right to report, remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code, or to block temporarily or permanently any Contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. Any such actions will be reported to the Conduct Committee for transparency and record keeping. -This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the Eclipse Foundation project or its community in public spaces. Examples of representing a project or community include posting via an official social media account, or acting as a project representative at an online or offline event. Representation of a project may be further defined and clarified by project committers, leaders, or the EMO. +Any Staff (including officers and directors of the Eclipse Foundation), Committers, Project Leads, or Conduct Committee members who are the subject of a complaint to the Conduct Committee will be recused from the process of resolving any such complaint. -## Enforcement +## Responsibility + +The responsibility for administering this Code rests with the Conduct Committee, with oversight by the Executive Director and the Board of Directors. For additional information on the Conduct Committee and its process, please write to . + +## Investigation of Potential Code Violations + +All conflict is not bad as a healthy debate may sometimes be necessary to push us to do our best. It is, however, unacceptable to be disrespectful or offensive, or violate this Code. If you see someone engaging in objectionable behavior violating this Code, we encourage you to address the behavior directly with those involved. If for some reason, you are unable to resolve the matter or feel uncomfortable doing so, or if the behavior is threatening or harassing, please report it following the procedure laid out below. + +Reports should be directed to . It is the Conduct Committee’s role to receive and address reported violations of this Code and to ensure a fair and speedy resolution. + +The Eclipse Foundation takes all reports of potential Code violations seriously and is committed to confidentiality and a full investigation of all allegations. The identity of the reporter will be omitted from the details of the report supplied to the accused. Contributors who are being investigated for a potential Code violation will have an opportunity to be heard prior to any final determination. Those found to have violated the Code can seek reconsideration of the violation and disciplinary action decisions. Every effort will be made to have all matters disposed of within 60 days of the receipt of the complaint. + +## Actions + +Contributors who do not follow this Code in good faith may face temporary or permanent repercussions as determined by the Conduct Committee. + +This Code does not address all conduct. It works in conjunction with our [Communication Channel Guidelines](https://www.eclipse.org/org/documents/communication-channel-guidelines/), [Social Media Guidelines](https://www.eclipse.org/org/documents/social_media_guidelines.php), [Bylaws](https://www.eclipse.org/org/documents/eclipse-foundation-be-bylaws-en.pdf), and [Internal Rules](https://www.eclipse.org/org/documents/ef-be-internal-rules.pdf) which set out additional protections for, and obligations of, all contributors. The Foundation has additional policies that provide further guidance on other matters. + +It’s impossible to spell out every possible scenario that might be deemed a violation of this Code. Instead, we rely on one another’s good judgment to uphold a high standard of integrity within all Eclipse Spaces. Sometimes, identifying the right thing to do isn’t an easy call. In such a scenario, raise the issue as early as possible. + +## No Retaliation + +The Eclipse community relies upon and values the help of Contributors who identify potential problems that may need to be addressed within an Eclipse Space. Any retaliation against a Contributor who raises an issue honestly is a violation of this Code. That a Contributor has raised a concern honestly or participated in an investigation, cannot be the basis for any adverse action, including threats, harassment, or discrimination. If you work with someone who has raised a concern or provided information in an investigation, you should continue to treat the person with courtesy and respect. If you believe someone has retaliated against you, report the matter as described by this Code. Honest reporting does not mean that you have to be right when you raise a concern; you just have to believe that the information you are providing is accurate. + +False reporting, especially when intended to retaliate or exclude, is itself a violation of this Code and will not be accepted or tolerated. + +Everyone is encouraged to ask questions about this Code. Your feedback is welcome, and you will get a response within three business days. Write to . + +## Amendments -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the Staff at codeofconduct@eclipse.org. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The Staff is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. +The Eclipse Foundation Board of Directors may amend this Code from time to time and may vary the procedures it sets out where appropriate in a particular case. -Project committers or leaders who do not follow the Code of Conduct in good faith may face temporary or permanent repercussions as determined by the Staff. +### Attribution -## Attribution +This Code was inspired by the [Contributor Covenant](https://www.contributor-covenant.org/), version 1.4, available [here](https://www.contributor-covenant.org/version/1/4/code-of-conduct/). -This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct/) +[^1]: Capitalized terms used herein without definition shall have the meanings assigned to them in the Bylaws. diff --git a/DEPENDENCIES b/DEPENDENCIES index 3479de982..06cf6a2e9 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -1,148 +1,387 @@ -maven/mavencentral/com.azure/azure-core-http-netty/1.12.7, MIT, approved, clearlydefined -maven/mavencentral/com.azure/azure-core/1.34.0, MIT, approved, clearlydefined -maven/mavencentral/com.azure/azure-identity/1.7.0, MIT, approved, clearlydefined -maven/mavencentral/com.azure/azure-security-keyvault-secrets/4.5.2, , restricted, clearlydefined -maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.14.0-rc2, Apache-2.0, approved, #5303 -maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.14.0-rc2, Apache-2.0 AND MIT, approved, #4303 -maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.14.0-rc2, Apache-2.0, approved, #4105 -maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-toml/2.14.0-rc2, Apache-2.0, restricted, clearlydefined -maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.14.0-rc2, Apache-2.0, approved, #4300 -maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.14.0-rc2, Apache-2.0, approved, #4699 -maven/mavencentral/com.fasterxml.woodstox/woodstox-core/6.4.0, Apache-2.0, approved, #5309 +Invalid: com.azure:azure-identity:+, unknown, restricted, none +Invalid: net.minidev:json-smart:[1.3.3,2.4.8], unknown, restricted, none +maven/mavencentral/ch.randelshofer/fastdoubleparser/0.8.0, MIT AND (BSD-2-Clause AND MIT), approved, #7926 +maven/mavencentral/com.azure/azure-core-http-netty/1.13.2, MIT AND Apache-2.0, approved, #7948 +maven/mavencentral/com.azure/azure-core/1.38.0, MIT AND Apache-2.0, approved, #7939 +maven/mavencentral/com.azure/azure-identity/1.6.0, MIT, approved, clearlydefined +maven/mavencentral/com.azure/azure-json/1.0.0, MIT AND Apache-2.0, approved, #7933 +maven/mavencentral/com.azure/azure-security-keyvault-secrets/4.2.3, MIT, approved, clearlydefined +maven/mavencentral/com.azure/azure-security-keyvault-secrets/4.6.1, MIT, approved, #7940 +maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.10.3, Apache-2.0, approved, CQ21280 +maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.11.2, Apache-2.0, approved, CQ23491 +maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.13.3, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.13.5, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.14.2, Apache-2.0, approved, #5303 +maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.15.0, Apache-2.0, approved, #7947 +maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.13.3, Apache-2.0, approved, #2133 +maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.13.5, Apache-2.0, approved, #2133 +maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.14.2, Apache-2.0 AND MIT, approved, #4303 +maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.15.0, MIT AND Apache-2.0, approved, #7932 +maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.13.3, Apache-2.0, approved, #2134 +maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.13.4.2, Apache-2.0, approved, #2134 +maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.13.5, Apache-2.0, approved, #2134 +maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.14.2, Apache-2.0, approved, #4105 +maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.15.0, Apache-2.0, approved, #7934 +maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-toml/2.14.0, Apache-2.0 AND BSD-3-Clause AND MIT AND Apache-2.0, approved, #7943 +maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-toml/2.14.2, Apache-2.0 AND BSD-3-Clause AND MIT AND Apache-2.0, approved, #7944 +maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.14.2, Apache-2.0, approved, #4300 +maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.13.3, Apache-2.0, approved, #2566 +maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.14.2, Apache-2.0, approved, #5933 +maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.13.3, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.13.5, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.14.2, Apache-2.0, approved, #4699 +maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.15.0, None, restricted, #7930 +maven/mavencentral/com.fasterxml.jackson.jakarta.rs/jackson-jakarta-rs-base/2.14.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.fasterxml.jackson.jakarta.rs/jackson-jakarta-rs-json-provider/2.13.3, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.fasterxml.jackson.jakarta.rs/jackson-jakarta-rs-json-provider/2.14.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.fasterxml.jackson.module/jackson-module-jakarta-xmlbind-annotations/2.13.3, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.fasterxml.jackson.module/jackson-module-jakarta-xmlbind-annotations/2.14.2, Apache-2.0, approved, #5308 +maven/mavencentral/com.fasterxml.jackson/jackson-bom/2.14.2, Apache-2.0, approved, #7931 +maven/mavencentral/com.fasterxml.jackson/jackson-bom/2.15.0, Apache-2.0, approved, #7929 +maven/mavencentral/com.fasterxml.woodstox/woodstox-core/6.5.0, Apache-2.0, approved, #7950 +maven/mavencentral/com.fasterxml/classmate/1.5.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.github.docker-java/docker-java-api/3.3.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.github.docker-java/docker-java-transport-zerodep/3.3.0, Apache-2.0 AND (Apache-2.0 AND BSD-3-Clause), approved, #7946 +maven/mavencentral/com.github.docker-java/docker-java-transport/3.3.0, Apache-2.0, approved, #7942 +maven/mavencentral/com.github.spotbugs/spotbugs-annotations/4.7.3, LGPL-2.1, restricted, clearlydefined maven/mavencentral/com.github.stephenc.jcip/jcip-annotations/1.0-1, Apache-2.0, approved, CQ21949 +maven/mavencentral/com.google.code.findbugs/jsr305/3.0.2, Apache-2.0, approved, #20 +maven/mavencentral/com.google.errorprone/error_prone_annotations/2.7.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.google.guava/failureaccess/1.0.1, Apache-2.0, approved, CQ22654 +maven/mavencentral/com.google.guava/guava/31.0.1-jre, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.google.guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava, Apache-2.0, approved, CQ22657 +maven/mavencentral/com.google.j2objc/j2objc-annotations/1.3, Apache-2.0, approved, CQ21195 maven/mavencentral/com.microsoft.azure/msal4j-persistence-extension/1.1.0, MIT, approved, clearlydefined -maven/mavencentral/com.microsoft.azure/msal4j/1.13.3, MIT, approved, clearlydefined +maven/mavencentral/com.microsoft.azure/msal4j/1.13.7, MIT, approved, clearlydefined +maven/mavencentral/com.microsoft.azure/msal4j/1.4.0, MIT, approved, clearlydefined maven/mavencentral/com.nimbusds/content-type/2.2, Apache-2.0, approved, clearlydefined maven/mavencentral/com.nimbusds/lang-tag/1.6, Apache-2.0, approved, clearlydefined -maven/mavencentral/com.nimbusds/nimbus-jose-jwt/8.23, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.nimbusds/nimbus-jose-jwt/9.22, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.nimbusds/nimbus-jose-jwt/9.25, Apache-2.0, approved, clearlydefined maven/mavencentral/com.nimbusds/oauth2-oidc-sdk/9.35, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.puppycrawl.tools/checkstyle/10.0, LGPL-2.1-or-later AND (Apache-2.0 AND LGPL-2.1-or-later) AND (LGPL-2.1-or-later AND LicenseRef-scancode-proprietary-license) AND Apache-2.0, restricted, #7936 +maven/mavencentral/com.squareup.okhttp3/okhttp-dnsoverhttps/4.10.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.squareup.okhttp3/okhttp/4.10.0, Apache-2.0 AND MPL-2.0, approved, #3057 maven/mavencentral/com.squareup.okhttp3/okhttp/4.9.3, Apache-2.0 AND MPL-2.0, approved, #3225 -maven/mavencentral/com.squareup.okio/okio/2.8.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.squareup.okio/okio-jvm/3.0.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.squareup.okio/okio/3.0.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.sun.activation/jakarta.activation/2.0.1, EPL-2.0 OR BSD-3-Clause OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jaf +maven/mavencentral/commons-beanutils/commons-beanutils/1.9.4, Apache-2.0, approved, CQ12654 +maven/mavencentral/commons-codec/commons-codec/1.11, Apache-2.0 AND BSD-3-Clause, approved, CQ15971 +maven/mavencentral/commons-collections/commons-collections/3.2.2, Apache-2.0, approved, CQ10385 +maven/mavencentral/commons-logging/commons-logging/1.2, Apache-2.0, approved, CQ10162 maven/mavencentral/de.fraunhofer.iais.eis.ids.infomodel/java/4.1.3, Apache-2.0, approved, #3779 maven/mavencentral/de.fraunhofer.iais.eis.infomodel/util/4.1.3, Apache-2.0, approved, #3780 +maven/mavencentral/dev.failsafe/failsafe-okhttp/3.2.4, Apache-2.0, approved, clearlydefined maven/mavencentral/dev.failsafe/failsafe/3.2.4, Apache-2.0, approved, clearlydefined +maven/mavencentral/info.picocli/picocli/4.6.3, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.github.classgraph/classgraph/4.8.138, MIT, approved, CQ22530 maven/mavencentral/io.micrometer/micrometer-core/1.8.2, Apache-2.0, approved, clearlydefined -maven/mavencentral/io.netty/netty-buffer/4.1.82.Final, Apache-2.0, approved, CQ21842 -maven/mavencentral/io.netty/netty-codec-dns/4.1.81.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 -maven/mavencentral/io.netty/netty-codec-http/4.1.82.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 -maven/mavencentral/io.netty/netty-codec-http2/4.1.82.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 -maven/mavencentral/io.netty/netty-codec-socks/4.1.82.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 -maven/mavencentral/io.netty/netty-codec/4.1.82.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 -maven/mavencentral/io.netty/netty-common/4.1.82.Final, Apache-2.0 AND MIT AND CC0-1.0, approved, CQ21843 -maven/mavencentral/io.netty/netty-handler-proxy/4.1.82.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 -maven/mavencentral/io.netty/netty-handler/4.1.82.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 -maven/mavencentral/io.netty/netty-resolver-dns-classes-macos/4.1.81.Final, Apache-2.0, approved, clearlydefined -maven/mavencentral/io.netty/netty-resolver-dns-native-macos/4.1.81.Final, Apache-2.0, approved, clearlydefined -maven/mavencentral/io.netty/netty-resolver-dns/4.1.81.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 -maven/mavencentral/io.netty/netty-resolver/4.1.82.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 -maven/mavencentral/io.netty/netty-tcnative-boringssl-static/2.0.54.Final, Apache-2.0 OR LicenseRef-Public-Domain OR BSD-2-Clause OR MIT, approved, CQ15280 -maven/mavencentral/io.netty/netty-tcnative-classes/2.0.54.Final, Apache-2.0, approved, clearlydefined -maven/mavencentral/io.netty/netty-transport-classes-epoll/4.1.82.Final, Apache-2.0, approved, clearlydefined -maven/mavencentral/io.netty/netty-transport-classes-kqueue/4.1.82.Final, Apache-2.0, approved, #4107 -maven/mavencentral/io.netty/netty-transport-native-epoll/4.1.82.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 -maven/mavencentral/io.netty/netty-transport-native-kqueue/4.1.82.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 -maven/mavencentral/io.netty/netty-transport-native-unix-common/4.1.82.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 -maven/mavencentral/io.netty/netty-transport/4.1.82.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 -maven/mavencentral/io.projectreactor.netty/reactor-netty-core/1.0.23, Apache-2.0, approved, clearlydefined -maven/mavencentral/io.projectreactor.netty/reactor-netty-http/1.0.23, Apache-2.0, approved, clearlydefined -maven/mavencentral/io.projectreactor/reactor-core/3.4.23, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.netty/netty-buffer/4.1.86.Final, Apache-2.0, approved, CQ21842 +maven/mavencentral/io.netty/netty-buffer/4.1.89.Final, Apache-2.0, approved, CQ21842 +maven/mavencentral/io.netty/netty-codec-dns/4.1.89.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-codec-http/4.1.86.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-codec-http/4.1.89.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-codec-http2/4.1.86.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-codec-http2/4.1.89.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-codec-socks/4.1.89.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-codec/4.1.86.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-codec/4.1.89.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-common/4.1.86.Final, Apache-2.0 AND MIT AND CC0-1.0, approved, CQ21843 +maven/mavencentral/io.netty/netty-common/4.1.89.Final, Apache-2.0 AND MIT AND CC0-1.0, approved, CQ21843 +maven/mavencentral/io.netty/netty-handler-proxy/4.1.89.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-handler/4.1.86.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-handler/4.1.89.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-resolver-dns-classes-macos/4.1.89.Final, Apache-2.0, approved, #6367 +maven/mavencentral/io.netty/netty-resolver-dns-native-macos/4.1.89.Final, Apache-2.0, approved, #7004 +maven/mavencentral/io.netty/netty-resolver-dns/4.1.89.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-resolver/4.1.89.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-tcnative-boringssl-static/2.0.56.Final, Apache-2.0 OR LicenseRef-Public-Domain OR BSD-2-Clause OR MIT, approved, CQ15280 +maven/mavencentral/io.netty/netty-tcnative-classes/2.0.56.Final, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.netty/netty-transport-classes-epoll/4.1.86.Final, Apache-2.0, approved, #6366 +maven/mavencentral/io.netty/netty-transport-classes-epoll/4.1.89.Final, Apache-2.0, approved, #6366 +maven/mavencentral/io.netty/netty-transport-classes-kqueue/4.1.89.Final, Apache-2.0, approved, #4107 +maven/mavencentral/io.netty/netty-transport-native-epoll/4.1.89.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-transport-native-kqueue/4.1.89.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-transport-native-unix-common/4.1.89.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-transport/4.1.86.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-transport/4.1.89.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.opentelemetry/opentelemetry-api/1.12.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.opentelemetry/opentelemetry-context/1.12.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.opentelemetry/opentelemetry-extension-annotations/1.12.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.projectreactor.netty/reactor-netty-core/1.0.28, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.projectreactor.netty/reactor-netty-http/1.0.28, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.projectreactor/reactor-core/3.4.27, Apache-2.0, approved, #7517 +maven/mavencentral/io.swagger.core.v3/swagger-annotations-jakarta/2.2.2, Apache-2.0, approved, #5947 +maven/mavencentral/io.swagger.core.v3/swagger-core-jakarta/2.2.2, Apache-2.0, approved, #5929 +maven/mavencentral/io.swagger.core.v3/swagger-integration-jakarta/2.2.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.swagger.core.v3/swagger-jaxrs2-jakarta/2.2.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.swagger.core.v3/swagger-models-jakarta/2.2.2, Apache-2.0, approved, #5919 +maven/mavencentral/jakarta.annotation/jakarta.annotation-api/2.0.0, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.ca +maven/mavencentral/jakarta.annotation/jakarta.annotation-api/2.1.1, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.ca +maven/mavencentral/jakarta.el/jakarta.el-api/4.0.0, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.el +maven/mavencentral/jakarta.inject/jakarta.inject-api/2.0.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/jakarta.transaction/jakarta.transaction-api/2.0.0, EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0, approved, #7697 +maven/mavencentral/jakarta.validation/jakarta.validation-api/3.0.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/jakarta.validation/jakarta.validation-api/3.0.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/jakarta.ws.rs/jakarta.ws.rs-api/3.0.0, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.rest maven/mavencentral/jakarta.ws.rs/jakarta.ws.rs-api/3.1.0, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.rest +maven/mavencentral/jakarta.xml.bind/jakarta.xml.bind-api/3.0.0, BSD-3-Clause, approved, ee4j.jaxb +maven/mavencentral/jakarta.xml.bind/jakarta.xml.bind-api/3.0.1, BSD-3-Clause, approved, ee4j.jaxb maven/mavencentral/javax.validation/validation-api/2.0.1.Final, Apache-2.0, approved, CQ15302 +maven/mavencentral/junit/junit/4.13.2, EPL-2.0, approved, CQ23636 +maven/mavencentral/net.bytebuddy/byte-buddy-agent/1.12.4, Apache-2.0, approved, #1810 +maven/mavencentral/net.bytebuddy/byte-buddy/1.12.4, Apache-2.0 AND BSD-3-Clause, approved, #1811 +maven/mavencentral/net.java.dev.jna/jna-platform/5.5.0, Apache-2.0 AND (Apache-2.0 AND LGPL-2.1-or-later), approved, #1514 maven/mavencentral/net.java.dev.jna/jna-platform/5.6.0, Apache-2.0 OR LGPL-2.1-or-later, approved, CQ22390 +maven/mavencentral/net.java.dev.jna/jna/5.12.1, Apache-2.0 OR LGPL-2.1-or-later, approved, #3217 maven/mavencentral/net.java.dev.jna/jna/5.5.0, Apache-2.0 or LGPL-2.1, approved, #1508 -maven/mavencentral/net.minidev/accessors-smart/2.4.7, Apache-2.0, approved, clearlydefined -maven/mavencentral/net.minidev/json-smart/2.4.7, Apache-2.0, approved, #3288 +maven/mavencentral/net.java.dev.jna/jna/5.6.0, Apache-2.0 AND LGPL-2.1-or-later, approved, CQ22391 +maven/mavencentral/net.minidev/accessors-smart/2.4.9, Apache-2.0, approved, #7515 +maven/mavencentral/net.minidev/json-smart/2.4.10, Apache-2.0, approved, #3288 +maven/mavencentral/net.sf.saxon/Saxon-HE/10.6, LicenseRef-scancode-iso-8879 AND (W3C AND W3C-19980720), restricted, #7945 +maven/mavencentral/org.antlr/antlr4-runtime/4.9.3, BSD-3-Clause, approved, #322 +maven/mavencentral/org.apache.commons/commons-compress/1.22, Apache-2.0 AND BSD-3-Clause, approved, #4299 +maven/mavencentral/org.apache.commons/commons-lang3/3.12.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.apache.commons/commons-pool2/2.11.1, Apache-2.0, approved, CQ23795 +maven/mavencentral/org.apache.commons/commons-text/1.10.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.apache.httpcomponents/httpclient/4.5.13, Apache-2.0 AND LicenseRef-Public-Domain, approved, CQ23527 +maven/mavencentral/org.apache.httpcomponents/httpcore/4.4.13, Apache-2.0, approved, CQ23528 +maven/mavencentral/org.apache.sshd/sshd-common/2.9.2, Apache-2.0 AND ISC, approved, #3224 +maven/mavencentral/org.apache.sshd/sshd-core/2.9.2, Apache-2.0, approved, #3222 +maven/mavencentral/org.apache.sshd/sshd-sftp/2.9.2, Apache-2.0, approved, #6273 +maven/mavencentral/org.apiguardian/apiguardian-api/1.1.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.assertj/assertj-core/3.22.0, Apache-2.0, approved, clearlydefined maven/mavencentral/org.bouncycastle/bcpkix-jdk15on/1.70, MIT, approved, clearlydefined +maven/mavencentral/org.bouncycastle/bcpkix-jdk18on/1.72, MIT, approved, #3789 maven/mavencentral/org.bouncycastle/bcprov-jdk15on/1.70, MIT, approved, #1712 +maven/mavencentral/org.bouncycastle/bcprov-jdk18on/1.72, MIT AND CC0-1.0, approved, #3538 maven/mavencentral/org.bouncycastle/bcutil-jdk15on/1.70, MIT, approved, clearlydefined +maven/mavencentral/org.bouncycastle/bcutil-jdk18on/1.72, MIT, approved, #3790 +maven/mavencentral/org.checkerframework/checker-qual/3.12.0, MIT, approved, clearlydefined +maven/mavencentral/org.checkerframework/checker-qual/3.5.0, MIT, approved, clearlydefined maven/mavencentral/org.codehaus.woodstox/stax2-api/4.2.1, BSD-2-Clause, approved, #2670 -maven/mavencentral/org.eclipse.edc/aggregate-service-spi/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/api-core/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/api-observability/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/asset-api/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/asset-index-sql/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/auth-spi/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/auth-tokenbased/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/boot/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/catalog-api/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/catalog-spi/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/configuration-filesystem/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/connector-core/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/contract-agreement-api/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/contract-definition-api/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/contract-definition-store-sql/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/contract-negotiation-api/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/contract-negotiation-store-sql/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/contract-spi/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/control-plane-api-client-spi/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/control-plane-core/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/control-plane-spi/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/core-spi/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/data-plane-api/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/data-plane-aws-s3/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/data-plane-framework/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/data-plane-http/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/data-plane-selector-client/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/data-plane-selector-core/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/data-plane-selector-spi/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/data-plane-spi/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/data-plane-transfer-client/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/data-plane-transfer-spi/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/data-plane-transfer-sync/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/http-receiver/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/http/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/ids-api-configuration/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/ids-api-multipart-dispatcher-v1/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/ids-api-multipart-endpoint-v1/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/ids-core/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/ids-spi/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/ids-transform-v1/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/jersey-core/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/jersey-micrometer/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/jetty-core/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/jetty-micrometer/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/jwt-spi/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/management-api-configuration/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/management-api/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/micrometer-core/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/monitor-jdk-logger/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/oauth2-core/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/oauth2-daps/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/oauth2-spi/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/policy-definition-api/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/policy-definition-store-sql/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/policy-engine-spi/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/policy-model/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/policy-spi/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/runtime-metamodel/0.0.1-20221208.055231-48, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/sql-core/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/sql-pool-apache-commons/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/transaction-datasource-spi/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/transaction-local/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/transaction-spi/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/transfer-process-api/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/transfer-process-store-sql/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/transfer-spi/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/transform-spi/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/util/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/vault-azure/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/web-spi/0.0.1-20221201-20221201.072646-1, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.tractusx.edc.extensions/business-partner-validation/0.1.4-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx.edc.extensions/cx-oauth2/0.1.4-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx.edc.extensions/data-encryption/0.1.4-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx.edc.extensions/dataplane-selector-configuration/0.1.4-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx.edc.extensions/hashicorp-vault/0.1.4-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx.edc.extensions/postgresql-migration/0.1.4-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx.edc/edc-controlplane-base/0.1.4-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx.edc/edc-dataplane-base/0.1.4-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.flywaydb/flyway-core/9.8.3, , restricted, clearlydefined +maven/mavencentral/org.eclipse.edc/aggregate-service-spi/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/api-core/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/asset-api/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/asset-index-sql/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/auth-spi/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/auth-tokenbased/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/autodoc-processor/0.0.1-SNAPSHOT, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/aws-s3-core/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/boot/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/catalog-api/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/catalog-spi/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/configuration-filesystem/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/connector-core/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/contract-agreement-api/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/contract-core/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/contract-definition-api/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/contract-definition-store-sql/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/contract-negotiation-api/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/contract-negotiation-store-sql/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/contract-spi/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/control-api-configuration/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/control-plane-aggregate-services/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/control-plane-api-client-spi/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/control-plane-core/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/control-plane-spi/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/core-spi/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-api/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-aws-s3/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-client/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-core/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-framework/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-http-oauth2-core/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-http-oauth2/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-http-spi/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-http/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-selector-client/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-selector-core/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-selector-spi/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-spi/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-util/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/http-spi/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/http/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/ids-api-configuration/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/ids-api-multipart-dispatcher-v1/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/ids-api-multipart-endpoint-v1/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/ids-core/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/ids-jsonld-serdes/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/ids-spi/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/ids-transform-v1/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/ids/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/jersey-core/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/jersey-micrometer/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/jetty-core/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/jetty-micrometer/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/junit/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/jwt-core/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/jwt-spi/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/management-api-configuration/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/management-api/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/micrometer-core/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/oauth2-client/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/oauth2-core/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/oauth2-daps/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/oauth2-spi/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/policy-definition-api/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/policy-definition-store-sql/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/policy-engine-spi/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/policy-engine/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/policy-evaluator/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/policy-model/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/policy-spi/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/runtime-metamodel/0.0.1-20230220-SNAPSHOT, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/runtime-metamodel/0.0.1-SNAPSHOT, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/sql-core/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/sql-lease/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/sql-pool-apache-commons/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/state-machine/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/transaction-datasource-spi/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/transaction-local/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/transaction-spi/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/transfer-core/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/transfer-data-plane-spi/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/transfer-data-plane/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/transfer-process-api/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/transfer-process-store-sql/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/transfer-pull-http-dynamic-receiver/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/transfer-spi/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/transform-spi/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/util/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/vault-azure/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/web-spi/0.0.1-20230220.patch1, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.jetty.toolchain/jetty-jakarta-servlet-api/5.0.2, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.toolchain/jetty-jakarta-websocket-api/2.0.0, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.websocket/websocket-core-client/11.0.12, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.websocket/websocket-core-common/11.0.12, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.websocket/websocket-core-server/11.0.12, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.websocket/websocket-jakarta-client/11.0.12, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.websocket/websocket-jakarta-common/11.0.12, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.websocket/websocket-jakarta-server/11.0.12, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.websocket/websocket-servlet/11.0.12, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-alpn-client/11.0.12, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-annotations/11.0.12, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-client/11.0.12, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-http/11.0.12, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-io/11.0.12, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-jndi/11.0.12, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-plus/11.0.12, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-security/11.0.12, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-server/11.0.12, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-servlet/11.0.12, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-util/11.0.12, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-webapp/11.0.12, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-xml/11.0.12, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.flywaydb/flyway-core/9.16.3, Apache-2.0, approved, #7935 +maven/mavencentral/org.glassfish.hk2.external/aopalliance-repackaged/3.0.3, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.glassfish +maven/mavencentral/org.glassfish.hk2/hk2-api/3.0.3, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.glassfish +maven/mavencentral/org.glassfish.hk2/hk2-locator/3.0.3, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.glassfish +maven/mavencentral/org.glassfish.hk2/hk2-utils/3.0.3, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.glassfish +maven/mavencentral/org.glassfish.hk2/osgi-resource-locator/1.0.3, CDDL-1.0, approved, CQ10889 +maven/mavencentral/org.glassfish.jersey.containers/jersey-container-servlet-core/3.0.8, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey +maven/mavencentral/org.glassfish.jersey.containers/jersey-container-servlet/3.0.8, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey +maven/mavencentral/org.glassfish.jersey.core/jersey-client/3.0.8, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey +maven/mavencentral/org.glassfish.jersey.core/jersey-common/3.0.8, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey +maven/mavencentral/org.glassfish.jersey.core/jersey-server/3.0.8, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey +maven/mavencentral/org.glassfish.jersey.ext/jersey-bean-validation/3.0.8, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey +maven/mavencentral/org.glassfish.jersey.ext/jersey-entity-filtering/3.0.8, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey +maven/mavencentral/org.glassfish.jersey.inject/jersey-hk2/3.0.8, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey +maven/mavencentral/org.glassfish.jersey.media/jersey-media-json-jackson/3.0.8, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey +maven/mavencentral/org.glassfish.jersey.media/jersey-media-multipart/3.0.8, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey +maven/mavencentral/org.glassfish/jakarta.el/4.0.2, EPL-2.0 AND (GPL-2.0-only WITH Classpath-exception-2.0) AND (EPL-2.0 AND (GPL-2.0-only WITH Classpath-exception-2.0) AND LicenseRef-scancode-generic-export-compliance), restricted, #7937 +maven/mavencentral/org.hamcrest/hamcrest-core/1.3, BSD-2-Clause, approved, CQ11429 maven/mavencentral/org.hdrhistogram/HdrHistogram/2.1.12, , approved, CQ13192 -maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.0, Apache-2.0, approved, clearlydefined -maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib/1.4.10, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.hibernate.validator/hibernate-validator/7.0.1.Final, Apache-2.0, approved, CQ22746 +maven/mavencentral/org.jacoco/org.jacoco.agent/0.8.8, EPL-2.0, approved, CQ23285 +maven/mavencentral/org.jacoco/org.jacoco.ant/0.8.8, EPL-2.0, approved, #1068 +maven/mavencentral/org.jacoco/org.jacoco.core/0.8.8, EPL-2.0, approved, CQ23283 +maven/mavencentral/org.jacoco/org.jacoco.report/0.8.8, EPL-2.0 AND Apache-2.0, approved, CQ23284 +maven/mavencentral/org.javassist/javassist/3.25.0-GA, MPL-1.1 OR LGPL-2.1-or-later OR Apache-2.0, approved, CQ19885 +maven/mavencentral/org.javassist/javassist/3.28.0-GA, Apache-2.0 OR LGPL-2.1-or-later OR MPL-1.1, approved, #327 +maven/mavencentral/org.jboss.logging/jboss-logging/3.4.1.Final, Apache-2.0, approved, CQ21255 +maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib-common/1.5.31, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib-common/1.6.20, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib-jdk7/1.6.10, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.5.31, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.6.10, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib/1.6.10, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib/1.6.20, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.jetbrains/annotations/13.0, Apache-2.0, approved, clearlydefined maven/mavencentral/org.jetbrains/annotations/15.0, Apache-2.0, approved, clearlydefined -maven/mavencentral/org.ow2.asm/asm/9.1, BSD-3-Clause, approved, CQ23029 -maven/mavencentral/org.ow2.asm/asm/9.3, BSD-3-Clause, approved, CQ24052 -maven/mavencentral/org.postgresql/postgresql/42.5.1, BSD-2-Clause, approved, #3416 -maven/mavencentral/org.projectlombok/lombok/1.18.24, MIT AND LicenseRef-Public-Domain, approved, CQ23907 +maven/mavencentral/org.jetbrains/annotations/17.0.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.jetbrains/annotations/24.0.1, Apache-2.0, approved, #7417 +maven/mavencentral/org.junit-pioneer/junit-pioneer/2.0.0, EPL-2.0, approved, clearlydefined +maven/mavencentral/org.junit.jupiter/junit-jupiter-api/5.8.2, EPL-2.0, approved, #1291 +maven/mavencentral/org.junit.jupiter/junit-jupiter-api/5.9.0, EPL-2.0, approved, #3133 +maven/mavencentral/org.junit.jupiter/junit-jupiter-api/5.9.2, EPL-2.0, approved, #3133 +maven/mavencentral/org.junit.jupiter/junit-jupiter-engine/5.8.2, EPL-2.0, approved, #1292 +maven/mavencentral/org.junit.jupiter/junit-jupiter-engine/5.9.0, EPL-2.0, approved, #3125 +maven/mavencentral/org.junit.jupiter/junit-jupiter-engine/5.9.2, EPL-2.0, approved, #3125 +maven/mavencentral/org.junit.jupiter/junit-jupiter-params/5.8.2, EPL-2.0, approved, #1488 +maven/mavencentral/org.junit.jupiter/junit-jupiter-params/5.9.0, EPL-2.0, approved, #3134 +maven/mavencentral/org.junit.jupiter/junit-jupiter-params/5.9.2, EPL-2.0, approved, #3134 +maven/mavencentral/org.junit.platform/junit-platform-commons/1.9.0, EPL-2.0, approved, #3130 +maven/mavencentral/org.junit.platform/junit-platform-commons/1.9.2, EPL-2.0, approved, #3130 +maven/mavencentral/org.junit.platform/junit-platform-engine/1.9.0, EPL-2.0, approved, #3128 +maven/mavencentral/org.junit.platform/junit-platform-engine/1.9.2, EPL-2.0, approved, #3128 +maven/mavencentral/org.junit.platform/junit-platform-launcher/1.9.0, EPL-2.0, approved, #3132 +maven/mavencentral/org.junit/junit-bom/5.9.0, EPL-2.0, approved, #4711 +maven/mavencentral/org.junit/junit-bom/5.9.2, EPL-2.0, approved, #4711 +maven/mavencentral/org.jvnet.mimepull/mimepull/1.9.13, CDDL-1.1 OR GPL-2.0-only WITH Classpath-exception-2.0, approved, CQ21484 +maven/mavencentral/org.latencyutils/LatencyUtils/2.0.3, BSD-2-Clause, approved, CQ17408 +maven/mavencentral/org.mockito/mockito-core/4.2.0, MIT, approved, clearlydefined +maven/mavencentral/org.objenesis/objenesis/3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.opentest4j/opentest4j/1.2.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.ow2.asm/asm-analysis/9.2, BSD-3-Clause, approved, clearlydefined +maven/mavencentral/org.ow2.asm/asm-analysis/9.3, BSD-3-Clause, approved, clearlydefined +maven/mavencentral/org.ow2.asm/asm-commons/9.2, BSD-3-Clause, approved, clearlydefined +maven/mavencentral/org.ow2.asm/asm-commons/9.3, BSD-3-Clause, approved, clearlydefined +maven/mavencentral/org.ow2.asm/asm-tree/9.2, BSD-3-Clause, approved, clearlydefined +maven/mavencentral/org.ow2.asm/asm-tree/9.3, BSD-3-Clause, approved, clearlydefined +maven/mavencentral/org.ow2.asm/asm/9.2, BSD-3-Clause, approved, CQ23635 +maven/mavencentral/org.ow2.asm/asm/9.3, BSD-3-Clause, approved, clearlydefined +maven/mavencentral/org.postgresql/postgresql/42.4.0, BSD-2-Clause, approved, #3112 +maven/mavencentral/org.projectlombok/lombok/1.18.26, MIT AND LicenseRef-Public-Domain, approved, CQ23907 +maven/mavencentral/org.reactivestreams/reactive-streams/1.0.3, CC0-1.0, approved, CQ16332 maven/mavencentral/org.reactivestreams/reactive-streams/1.0.4, CC0-1.0, approved, CQ16332 -maven/mavencentral/org.slf4j/slf4j-api/2.0.3, MIT, approved, CQ24285 +maven/mavencentral/org.reflections/reflections/0.10.2, Apache-2.0 AND WTFPL, approved, clearlydefined +maven/mavencentral/org.rnorth.duct-tape/duct-tape/1.0.8, MIT, approved, clearlydefined +maven/mavencentral/org.slf4j/jcl-over-slf4j/1.7.32, Apache-2.0, approved, CQ12843 +maven/mavencentral/org.slf4j/slf4j-api/1.7.25, MIT, approved, CQ13368 +maven/mavencentral/org.slf4j/slf4j-api/1.7.30, MIT, approved, CQ13368 +maven/mavencentral/org.slf4j/slf4j-api/1.7.32, MIT, approved, CQ13368 +maven/mavencentral/org.slf4j/slf4j-api/1.7.35, MIT, approved, CQ13368 +maven/mavencentral/org.slf4j/slf4j-api/1.7.36, MIT, approved, CQ13368 +maven/mavencentral/org.slf4j/slf4j-api/1.7.7, MIT, approved, CQ9827 +maven/mavencentral/org.slf4j/slf4j-api/2.0.0, MIT, approved, #5915 +maven/mavencentral/org.slf4j/slf4j-api/2.0.0-alpha7, MIT, approved, #5915 +maven/mavencentral/org.slf4j/slf4j-api/2.0.7, MIT, approved, #5915 +maven/mavencentral/org.testcontainers/junit-jupiter/1.18.0, None, restricted, #7941 +maven/mavencentral/org.testcontainers/testcontainers/1.18.0, None, restricted, #7938 +maven/mavencentral/org.testcontainers/vault/1.18.0, None, restricted, #7927 +maven/mavencentral/org.yaml/snakeyaml/1.33, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.yaml/snakeyaml/2.0, Apache-2.0 AND (Apache-2.0 OR BSD-3-Clause OR EPL-1.0 OR GPL-2.0-or-later OR LGPL-2.1-or-later), approved, #7275 +maven/mavencentral/software.amazon.awssdk/annotations/2.19.26, Apache-2.0, approved, clearlydefined +maven/mavencentral/software.amazon.awssdk/apache-client/2.19.26, Apache-2.0, approved, clearlydefined +maven/mavencentral/software.amazon.awssdk/arns/2.19.26, Apache-2.0, approved, clearlydefined +maven/mavencentral/software.amazon.awssdk/auth/2.19.26, Apache-2.0, approved, clearlydefined +maven/mavencentral/software.amazon.awssdk/aws-core/2.19.26, Apache-2.0, approved, clearlydefined +maven/mavencentral/software.amazon.awssdk/aws-query-protocol/2.19.26, Apache-2.0, approved, clearlydefined +maven/mavencentral/software.amazon.awssdk/aws-xml-protocol/2.19.26, Apache-2.0, approved, clearlydefined +maven/mavencentral/software.amazon.awssdk/endpoints-spi/2.19.26, Apache-2.0, approved, clearlydefined +maven/mavencentral/software.amazon.awssdk/http-client-spi/2.19.26, Apache-2.0, approved, clearlydefined +maven/mavencentral/software.amazon.awssdk/iam/2.19.26, Apache-2.0, approved, clearlydefined +maven/mavencentral/software.amazon.awssdk/json-utils/2.19.26, Apache-2.0, approved, clearlydefined +maven/mavencentral/software.amazon.awssdk/metrics-spi/2.19.26, Apache-2.0, approved, clearlydefined +maven/mavencentral/software.amazon.awssdk/netty-nio-client/2.19.26, Apache-2.0, approved, clearlydefined +maven/mavencentral/software.amazon.awssdk/profiles/2.19.26, Apache-2.0, approved, clearlydefined +maven/mavencentral/software.amazon.awssdk/protocol-core/2.19.26, Apache-2.0, approved, clearlydefined +maven/mavencentral/software.amazon.awssdk/regions/2.19.26, Apache-2.0, approved, clearlydefined +maven/mavencentral/software.amazon.awssdk/s3/2.19.26, Apache-2.0, approved, clearlydefined +maven/mavencentral/software.amazon.awssdk/sdk-core/2.19.26, Apache-2.0, approved, #7928 +maven/mavencentral/software.amazon.awssdk/sts/2.19.26, Apache-2.0, approved, clearlydefined +maven/mavencentral/software.amazon.awssdk/third-party-jackson-core/2.19.26, Apache-2.0, approved, #7949 +maven/mavencentral/software.amazon.awssdk/utils/2.19.26, Apache-2.0, approved, clearlydefined +maven/mavencentral/software.amazon.eventstream/eventstream/1.0.1, Apache-2.0, approved, clearlydefined diff --git a/NOTICE.md b/NOTICE.md index 24a59602c..4223c64f3 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -26,6 +26,7 @@ SPDX-License-Identifier: Apache-2.0 ## Source Code +The project maintains the following source code repositories in the GitHub organization : * diff --git a/README.md b/README.md index 566e42f5a..08664b5a8 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ [![Apache 2.0 License][license-shield]][license-url] [![Latest Release][release-shield]][release-url] +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=eclipse-tractusx_tractusx-edc&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=eclipse-tractusx_tractusx-edc) + Container images and deployments of the Eclipse Dataspace Components for the Tractus-X project. Please also refer to: @@ -16,24 +18,25 @@ Please also refer to: ## About The Project -The project provides pre-built control- and data-plane [docker](https://www.docker.com/) images and [helm](https://helm.sh/) charts of the [Eclipse DataSpaceConnector Project](https://github.com/eclipse-edc/Connector). +The project provides pre-built control- and data-plane [docker](https://www.docker.com/) images +and [helm](https://helm.sh/) charts of +the [Eclipse DataSpaceConnector Project](https://github.com/eclipse-edc/Connector). ## Inventory -The eclipse data space connector is split up into Control-Plane and Data-Plane, whereas the Control-Plane functions as administration layer -and has responsibility of resource management, contract negotiation and administer data transfer. +The eclipse data space connector is split up into Control-Plane and Data-Plane, whereas the Control-Plane functions as +administration layer and has responsibility of resource management, contract negotiation and administer data transfer. The Data-Plane does the heavy lifting of transferring and receiving data streams. Depending on your environment there are different derivatives of the control-plane prepared: -- [edc-controlplane-memory](edc-controlplane/edc-controlplane-memory) with dependency onto - - [Azure Key Vault](https://azure.microsoft.com/en-us/services/key-vault/#product-overview) -- [edc-controlplane-postgresql](edc-controlplane/edc-controlplane-postgresql) with dependency onto +- [edc-controlplane-postgresql-azure-vault](edc-controlplane/edc-controlplane-postgresql-azure-vault) with dependency onto - [Azure Key Vault](https://azure.microsoft.com/en-us/services/key-vault/#product-overview) - [PostgreSQL 8.2 or newer](https://www.postgresql.org/) -- [edc-controlplane-postgresql-hashicorp-vault](edc-controlplane/edc-controlplane-postgresql-hashicorp-vault) with dependency onto +- [edc-controlplane-postgresql-hashicorp-vault](edc-controlplane/edc-controlplane-postgresql-hashicorp-vault) with + dependency onto - [Hashicorp Vault](https://www.vaultproject.io/) - -[PostgreSQL 8.2 or newer](https://www.postgresql.org/) + - [PostgreSQL 8.2 or newer](https://www.postgresql.org/) Derivatives of the Data-Plane can be found here @@ -42,6 +45,10 @@ Derivatives of the Data-Plane can be found here - [edc-dataplane-hashicorp-vault](edc-dataplane/edc-dataplane-hashicorp-vault) with dependency onto - [Hashicorp Vault](https://www.vaultproject.io/) +For testing/development purposes: + +- [edc-runtime-memory](edc-controlplane/edc-runtime-memory) + ## Getting Started ### Build @@ -54,15 +61,24 @@ Build Tractus-X EDC together with its Container Images ## License -Distributed under the Apache 2.0 License. See [LICENSE](https://github.com/eclipse-tractusx/tractusx-edc/blob/main/LICENSE) for more information. +Distributed under the Apache 2.0 License. +See [LICENSE](https://github.com/eclipse-tractusx/tractusx-edc/blob/main/LICENSE) for more information. + [contributors-shield]: https://img.shields.io/github/contributors/eclipse-tractusx/tractusx-edc.svg?style=for-the-badge + [contributors-url]: https://github.com/eclipse-tractusx/tractusx-edc/graphs/contributors + [stars-shield]: https://img.shields.io/github/stars/eclipse-tractusx/tractusx-edc.svg?style=for-the-badge + [stars-url]: https://github.com/eclipse-tractusx/tractusx-edc/stargazers + [license-shield]: https://img.shields.io/github/license/eclipse-tractusx/tractusx-edc.svg?style=for-the-badge + [license-url]: https://github.com/eclipse-tractusx/tractusx-edc/blob/main/LICENSE + [release-shield]: https://img.shields.io/github/v/release/eclipse-tractusx/tractusx-edc.svg?style=for-the-badge + [release-url]: https://github.com/eclipse-tractusx/tractusx-edc/releases diff --git a/build.gradle.kts b/build.gradle.kts index a9e1f992d..953e3b4ab 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,22 @@ +/* + * 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 + */ + import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage import com.github.jengelman.gradle.plugins.shadow.ShadowJavaPlugin @@ -5,11 +24,10 @@ plugins { `java-library` `maven-publish` `jacoco-report-aggregation` - id("io.freefair.lombok") version "6.6.2" - id("com.diffplug.spotless") version "6.15.0" - id("com.github.johnrengelman.shadow") version "8.0.0" - id("com.bmuschko.docker-remote-api") version "9.2.1" - id("org.sonarqube") version "4.0.0.2929" + id("io.freefair.lombok") version "8.0.1" + id("com.diffplug.spotless") version "6.18.0" + id("com.github.johnrengelman.shadow") version "8.1.1" + id("com.bmuschko.docker-remote-api") version "9.3.1" } val javaVersion: String by project @@ -37,25 +55,28 @@ project.subprojects.forEach { } } -// make sure the test report aggregation is done before reporting to sonar -tasks.sonar { - dependsOn(tasks.named("testCodeCoverageReport")) -} - allprojects { apply(plugin = "org.eclipse.edc.edc-build") apply(plugin = "io.freefair.lombok") - apply(plugin = "org.sonarqube") repositories { mavenCentral() } dependencies { implementation("org.projectlombok:lombok:1.18.26") - implementation("org.slf4j:slf4j-api:2.0.5") + implementation("org.slf4j:slf4j-api:2.0.7") // this is used to counter version conflicts between the JUnit version pulled in by the plugin, // and the one expected by IntelliJ - testImplementation(platform("org.junit:junit-bom:5.9.2")) + testImplementation(platform("org.junit:junit-bom:5.9.3")) + + 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("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.") + } + } } // configure which version of the annotation processor to use. defaults to the same version as the plugin @@ -100,7 +121,6 @@ allprojects { } - // this is a temporary workaround until we're fully moved to TractusX: // publishing to OSSRH is handled by the build plugin, but publishing to GH packages // must be configured separately publishing { @@ -141,8 +161,9 @@ subprojects { dockerFile.set(file("${project.projectDir}/src/main/docker/Dockerfile")) images.add("${project.name}:${project.version}") images.add("${project.name}:latest") - // uncomment the following line if building on Apple Silicon - // platform.set("linux/x86_64") + // specify platform with the -Dplatform flag: + if (System.getProperty("platform") != null) + platform.set(System.getProperty("platform")) buildArgs.put("JAR", "build/libs/${project.name}.jar") inputDir.set(file(project.projectDir)) } @@ -151,10 +172,4 @@ subprojects { dockerTask.dependsOn(tasks.findByName(ShadowJavaPlugin.SHADOW_JAR_TASK_NAME)) } } - - sonarqube { - properties { - property("sonar.moduleKey", "${project.group}-${project.name}") - } - } } diff --git a/chart_schema.yaml b/chart_schema.yaml index 6581ef7e8..13d62dee7 100644 --- a/chart_schema.yaml +++ b/chart_schema.yaml @@ -1,3 +1,22 @@ +# +# Copyright (c) 2021, 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: str() home: str(required=False) diff --git a/charts/edc-controlplane/Chart.yaml b/charts/edc-controlplane/Chart.yaml deleted file mode 100644 index ffd77bd4d..000000000 --- a/charts/edc-controlplane/Chart.yaml +++ /dev/null @@ -1,35 +0,0 @@ -# -# Copyright (c) 2023 ZF Friedrichshafen AG -# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH -# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) -# Copyright (c) 2021, 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 -# - ---- -apiVersion: v2 -name: edc-controlplane -description: >- - EDC Control-Plane - The Eclipse DataSpaceConnector administration layer with responsibility of resource management and govern contracts and data transfers -home: https://github.com/eclipse-tractusx/tractusx-edc/tree/main/charts/edc-controlplane -type: application -appVersion: "0.3.2" -version: 0.3.2 -deprecated: true -maintainers: [] -sources: - - https://github.com/eclipse-tractusx/tractusx-edc/tree/main/charts/edc-controlplane diff --git a/charts/edc-controlplane/LICENSE b/charts/edc-controlplane/LICENSE deleted file mode 100644 index d64569567..000000000 --- a/charts/edc-controlplane/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/charts/edc-controlplane/README.md b/charts/edc-controlplane/README.md deleted file mode 100644 index 9984db480..000000000 --- a/charts/edc-controlplane/README.md +++ /dev/null @@ -1,106 +0,0 @@ -# edc-controlplane - -> **:exclamation: This Helm Chart is deprecated!** - -![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) - -EDC Control-Plane - The Eclipse DataSpaceConnector administration layer with responsibility of resource management and govern contracts and data transfers - -**Homepage:** - -## TL;DR - -```shell -helm repo add tractusx-edc https://eclipse-tractusx.github.io/charts/dev -helm install my-release tractusx-edc/edc-controlplane --version 0.3.2 -``` - -## Source Code - -* - -## Values - -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| affinity | object | `{}` | [Affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity) constrains which nodes the Pod can be scheduled on based on node labels. | -| automountServiceAccountToken | bool | `false` | Whether to [automount kubernetes API credentials](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#use-the-default-service-account-to-access-the-api-server) into the pod | -| 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/) | -| autoscaling.maxReplicas | int | `100` | Maximum replicas if resource consumption exceeds resource threshholds | -| autoscaling.minReplicas | int | `1` | Minimal replicas if resource consumption falls below resource threshholds | -| autoscaling.targetCPUUtilizationPercentage | int | `80` | targetAverageUtilization of cpu provided to a pod | -| autoscaling.targetMemoryUtilizationPercentage | int | `80` | targetAverageUtilization of memory provided to a pod | -| configuration.properties | string | `"# edc.api.auth.key=\n# edc.atomikos.checkpoint.interval=\n# edc.atomikos.directory=\n# edc.atomikos.logging=\n# edc.atomikos.threaded2pc=\n# edc.atomikos.timeout=\n# edc.aws.access.key=\n# edc.aws.provision.retry.retries.max=\n# edc.aws.provision.role.duration.session.max=\n# edc.aws.secret.access.key=\n# edc.blobstore.endpoint=\n# edc.dataplane.token.validation.endpoint=\n# edc.core.retry.backoff.max=\n# edc.core.retry.backoff.min=\n# edc.core.retry.retries.max=\n# edc.core.system.health.check.liveness-period=\n# edc.core.system.health.check.readiness-period=\n# edc.core.system.health.check.startup-period=\n# edc.core.system.health.check.threadpool-size=\n# edc.dataplane.queue.capacity=\n# edc.dataplane.wait=\n# edc.dataplane.workers=\n# edc.datasource.asset.name=\"default\"\n# edc.datasource.contractdefinition.name=\"default\"\n# edc.datasource.contractnegotiation.name=\"default\"\n# edc.datasource.policy.name=\"default\"\n# edc.datasource.transferprocess.name=\"default\"\n# edc.datasource.default.pool.maxIdleConnections=\n# edc.datasource.default.pool.maxTotalConnections=\n# edc.datasource.default.pool.minIdleConnections=\n# edc.datasource.default.pool.testConnectionOnBorrow=\n# edc.datasource.default.pool.testConnectionOnCreate=\n# edc.datasource.default.pool.testConnectionOnReturn=\n# edc.datasource.default.pool.testConnectionWhileIdle=\n# edc.datasource.default.pool.testQuery=\n# edc.datasource.default.url=\n# edc.datasource.default.user=\n# edc.datasource.default.password=\n# edc.dpf.selector.url=\n# edc.events.topic.endpoint=\n# edc.events.topic.name=\n# edc.fs.config=\n# edc.hostname=\n# edc.identity.did.url=\n# edc.ids.catalog.id=\n# edc.ids.curator=\n# edc.ids.description=\n# edc.ids.endpoint=\n# edc.ids.id=\n# edc.ids.maintainer=\n# edc.ids.security.profile=\n# edc.ids.title=\n# edc.ids.validation.referringconnector=\n# edc.ion.crawler.did-type=\n# edc.ion.crawler.interval-minutes=\n# edc.ion.crawler.ion.url=\n# edc.metrics.enabled=\n# edc.metrics.executor.enabled=\n# edc.metrics.jersey.enabled=\n# edc.metrics.jetty.enabled=\n# edc.metrics.okhttp.enabled=\n# edc.metrics.system.enabled=\n# edc.negotiation.consumer.state-machine.batch-size=\n# edc.negotiation.provider.state-machine.batch-size=\n# edc.oauth.client.id=\n# edc.oauth.private.key.alias=\n# edc.oauth.provider.audience=\n# edc.oauth.provider.jwks.refresh=\n# edc.oauth.provider.jwks.url=\n# edc.oauth.public.key.alias=\n# edc.oauth.token.url=\n# edc.oauth.validation.nbf.leeway=\n# edc.receiver.http.auth-code=\n# edc.receiver.http.auth-key=\n# edc.receiver.http.endpoint=\n# edc.transfer.proxy.endpoint=\n# edc.transfer.proxy.token.validity.seconds=\n# edc.transfer.proxy.token.signer.privatekey.alias=\n# edc.transfer.functions.check.endpoint=\n# edc.transfer.functions.enabled.protocols=\n# edc.transfer.functions.transfer.endpoint=\n# edc.transfer-process-store.database.name=\n# edc.transfer.state-machine.batch-size=\n# edc.vault=\n# edc.vault.certificate=\n# edc.vault.clientid=\n# edc.vault.clientsecret=\n# edc.vault.name=\n# edc.vault.tenantid=\n# edc.vault.hashicorp.url=\n# edc.vault.hashicorp.token=\n# edc.vault.hashicorp.timeout.seconds=\n# edc.webdid.doh.url=\n# edc.web.rest.cors.enabled=\n# edc.web.rest.cors.headers=\n# edc.web.rest.cors.methods=\n# edc.web.rest.cors.origins=\n# ids.webhook.address="` | EDC configuration.properties configuring aspects of the [eclipse-dataspaceconnector](https://github.com/eclipse-edc/Connector) | -| customLabels | object | `{}` | Additional custom Labels to add | -| edc.endpoints.control.path | string | `"/api/controlplane/control"` | The path mapping the "control" api is going to be exposed at | -| edc.endpoints.control.port | string | `"9999"` | The network port, which the "control" api is going to be exposed by the container, pod and service | -| edc.endpoints.data.path | string | `"/data"` | The path mapping the "data" management api is going to be exposed at | -| edc.endpoints.data.port | string | `"8181"` | The network port, which the "data" management api is going to be exposed by the container, pod and service | -| edc.endpoints.default.path | string | `"/api"` | The path mapping the "default" api is going to be exposed at | -| edc.endpoints.default.port | string | `"8080"` | The network port, which the "default" api is going to be exposed by the container, pod and service | -| edc.endpoints.ids.path | string | `"/api/v1/ids"` | The path mapping the "ids" multipart api is going to be exposed at | -| edc.endpoints.ids.port | string | `"8282"` | The network port, which the "ids" multipart api is going to be exposed by the container, pod and service | -| edc.endpoints.metrics.path | string | `"/metrics"` | The path mapping the prometheus metrics are going to be exposed at | -| edc.endpoints.metrics.port | string | `"9090"` | The network port, which the prometheus metrics are going to be exposed by the container, pod and service | -| edc.endpoints.validation.path | string | `"/validation"` | The path mapping the "validation" api is going to be exposed at | -| edc.endpoints.validation.port | string | `"8182"` | The network port, which the "validation" api is going to be exposed by the container, pod and service | -| env | object | `{}` | Container environment variables e.g. for configuring [JAVA_TOOL_OPTIONS](https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/envvars002.html) Ex.: JAVA_TOOL_OPTIONS: > -Dhttp.proxyHost=proxy -Dhttp.proxyPort=80 -Dhttp.nonProxyHosts="localhost|127.*|[::1]" -Dhttps.proxyHost=proxy -Dhttps.proxyPort=443 | -| envSecretName | string | `nil` | [Kubernetes Secret Resource](https://kubernetes.io/docs/concepts/configuration/secret/) name to load environment variables from | -| fullnameOverride | string | `""` | Overrides the releases full name | -| image.pullPolicy | string | `"IfNotPresent"` | [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) to use | -| image.repository | string | `"ghcr.io/eclipse-tractusx/tractusx-edc/edc-controlplane-postgresql-hashicorp-vault"` | Which derivate of the edc control-plane to use. One of: [ghcr.io/eclipse-tractusx/tractusx-edc/edc-controlplane-postgresql-hashicorp-vault, ghcr.io/eclipse-tractusx/tractusx-edc/edc-controlplane-postgresql, ghcr.io/eclipse-tractusx/tractusx-edc/edc-controlplane-memory] | -| image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. | -| imagePullSecret.dockerconfigjson | string | `""` | Image pull secret to create to [obtain the container image from private registries](https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry) Note: This value needs to adhere to the [(base64 encoded) .dockerconfigjson format](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/#registry-secret-existing-credentials). Furthermore, if 'imagePullSecret.dockerconfigjson' is defined, it takes precedence over 'imagePullSecrets'. | -| 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) | -| ingresses[0].annotations | object | `{}` | Additional ingress annotations to add | -| ingresses[0].certManager.clusterIssuer | string | `""` | If preset enables certificate generation via cert-manager cluster-wide issuer | -| ingresses[0].certManager.issuer | string | `""` | If preset enables certificate generation via cert-manager namespace scoped issuer | -| ingresses[0].className | string | `""` | Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use | -| ingresses[0].enabled | bool | `true` | | -| ingresses[0].endpoints | list | `["ids"]` | EDC endpoints exposed by this ingress resource | -| ingresses[0].hostname | string | `"edc-controlplane.local"` | The hostname to be used to precisely map incoming traffic onto the underlying network service | -| ingresses[0].tls | object | `{"enabled":false,"secretName":""}` | TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource | -| ingresses[0].tls.enabled | bool | `false` | Enables TLS on the ingress resource | -| ingresses[0].tls.secretName | string | `""` | If present overwrites the default secret name | -| ingresses[1].annotations | object | `{}` | Additional ingress annotations to add | -| ingresses[1].certManager.clusterIssuer | string | `""` | If preset enables certificate generation via cert-manager cluster-wide issuer | -| ingresses[1].certManager.issuer | string | `""` | If preset enables certificate generation via cert-manager namespace scoped issuer | -| ingresses[1].className | string | `""` | Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use | -| ingresses[1].enabled | bool | `false` | | -| ingresses[1].endpoints | list | `["data","control"]` | EDC endpoints exposed by this ingress resource | -| ingresses[1].hostname | string | `"edc-controlplane.intranet"` | The hostname to be used to precisely map incoming traffic onto the underlying network service | -| ingresses[1].tls | object | `{"enabled":false,"secretName":""}` | TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource | -| ingresses[1].tls.enabled | bool | `false` | Enables TLS on the ingress resource | -| ingresses[1].tls.secretName | string | `""` | If present overwrites the default secret name | -| livenessProbe.enabled | bool | `true` | Whether to enable kubernetes [liveness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | -| logging.properties | 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"` | EDC logging.properties configuring the [java.util.logging subsystem](https://docs.oracle.com/javase/7/docs/technotes/guides/logging/overview.html#a1.8) | -| nameOverride | string | `""` | Overrides the charts name | -| nodeSelector | object | `{}` | [Node-Selector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector) to constrain the Pod to nodes with specific labels. | -| opentelemetry.properties | string | `"otel.javaagent.enabled=true\notel.javaagent.debug=false"` | opentelemetry.properties configuring the [opentelemetry agent](https://opentelemetry.io/docs/instrumentation/java/automatic/agent-config/) | -| podAnnotations | object | `{}` | [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) added to deployed [pods](https://kubernetes.io/docs/concepts/workloads/pods/) | -| podSecurityContext.fsGroup | int | `10001` | The owner for volumes and any files created within volumes will belong to this guid | -| podSecurityContext.runAsGroup | int | `10001` | Processes within a pod will belong to this guid | -| podSecurityContext.runAsUser | int | `10001` | Runs all processes within a pod with a special uid | -| podSecurityContext.seccompProfile.type | string | `"RuntimeDefault"` | Restrict a Container's Syscalls with seccomp | -| readinessProbe.enabled | bool | `true` | Whether to enable kubernetes readiness-probes | -| replicaCount | int | `1` | Specifies how many replicas of a deployed pod shall be created during the deployment Note: If horizontal pod autoscaling is enabled this setting has no effect | -| resources | object | `{}` | [Resource management](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) applied to the deployed pod | -| 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 | -| securityContext.capabilities.add | list | `[]` | Specifies which capabilities to add to issue specialized syscalls | -| securityContext.capabilities.drop | list | `["ALL"]` | Specifies which capabilities to drop to reduce syscall attack surface | -| securityContext.readOnlyRootFilesystem | bool | `true` | Whether the root filesystem is mounted in read-only mode | -| securityContext.runAsNonRoot | bool | `true` | Requires the container to run without root privileges | -| securityContext.runAsUser | int | `10001` | The container's process will run with the specified uid | -| 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. | -| serviceAccount.annotations | object | `{}` | [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) to add to the service account | -| serviceAccount.create | bool | `true` | Specifies whether a [service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) should be created per release | -| serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the release's fullname template | -| startupProbe.enabled | bool | `true` | Whether to enable kubernetes startup-probes | -| startupProbe.failureThreshold | int | `12` | Minimum consecutive failures for the probe to be considered failed after having succeeded | -| startupProbe.initialDelaySeconds | int | `10` | Number of seconds after the container has started before liveness probes are initiated. | -| tolerations | list | `[]` | [Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) are applied to Pods to schedule onto nodes with matching taints. | -| volumeMounts | list | `[]` | Additional volumeMounts to the controlplane main container | -| volumes | list | `[]` | Additional volumes to the controlplane pod | - ----------------------------------------------- -Autogenerated from chart metadata using [helm-docs v1.10.0](https://github.com/norwoodj/helm-docs/releases/v1.10.0) diff --git a/charts/edc-controlplane/README.md.gotmpl b/charts/edc-controlplane/README.md.gotmpl deleted file mode 100644 index aa70ec6fc..000000000 --- a/charts/edc-controlplane/README.md.gotmpl +++ /dev/null @@ -1,26 +0,0 @@ -{{ template "chart.header" . }} - -{{ template "chart.deprecationWarning" . }} - -{{ template "chart.badgesSection" . }} - -{{ template "chart.description" . }} - -{{ template "chart.homepageLine" . }} - -## TL;DR - -```shell -helm repo add tractusx-edc https://eclipse-tractusx.github.io/charts/dev -helm install my-release tractusx-edc/edc-controlplane --version {{ .Version }} -``` - -{{ template "chart.maintainersSection" . }} - -{{ template "chart.sourcesSection" . }} - -{{ template "chart.requirementsSection" . }} - -{{ template "chart.valuesSection" . }} - -{{ template "helm-docs.versionFooter" . }} diff --git a/charts/edc-controlplane/templates/NOTES.txt b/charts/edc-controlplane/templates/NOTES.txt deleted file mode 100644 index 6758c6bdf..000000000 --- a/charts/edc-controlplane/templates/NOTES.txt +++ /dev/null @@ -1,74 +0,0 @@ - -CHART NAME: {{ .Chart.Name }} -CHART VERSION: {{ .Chart.Version }} -APP VERSION: {{ .Chart.AppVersion }} - -Logs can be accessed by running this command: - - kubectl logs --tail 100 -f \ - --namespace {{ .Release.Namespace }} \ - -l "app.kubernetes.io/name={{ include "edc-controlplane.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" - -{{- if .Values.ingresses }} - -Following ingress URLS are available: - {{- $edcEndpoints := .Values.edc.endpoints }} - {{- range .Values.ingresses }} - {{- if .enabled }} - {{- $ingressEdcEndpoints := .endpoints }} - {{- $hostname := .hostname }} - {{- $tls := .tls }} - {{- range $name, $mapping := $edcEndpoints }} - {{- if (has $name $ingressEdcEndpoints) }} - Visit http{{ if $tls }}s{{ end }}://{{ $hostname }}{{ $mapping.path }} to access the {{ $name }} api - {{- end }} - {{- end }} - {{- end }} - {{- end }} - -{{- else if contains "NodePort" .Values.service.type }} -Get the application URLs by running these commands: - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") - - export NODE_PORT_DEFAULT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "edc-controlplane.fullname" . }}}") - export NODE_PORT_DATA=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[1].nodePort}" services {{ include "edc-controlplane.fullname" . }}}") - export NODE_PORT_VALIDATION=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[2].nodePort}" services {{ include "edc-controlplane.fullname" . }}}") - export NODE_PORT_CONTROL=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[3].nodePort}" services {{ include "edc-controlplane.fullname" . }}}") - export NODE_PORT_IDS=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[4].nodePort}" services {{ include "edc-controlplane.fullname" . }}}") - export NODE_PORT_METRICS=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[5].nodePort}" services {{ include "edc-controlplane.fullname" . }}}") - - echo "Visit http://$NODE_IP:$NODE_PORT_DEFAULT to access the default api" - echo "Visit http://$NODE_IP:$NODE_PORT_DATA to access the data management api" - echo "Visit http://$NODE_IP:$NODE_PORT_VALIDATION to access the data transfer validation api" - echo "Visit http://$NODE_IP:$NODE_PORT_CONTROL to access the control api" - echo "Visit http://$NODE_IP:$NODE_PORT_IDS to access the IDS api" - echo "Visit http://$NODE_IP:$NODE_PORT_METRICS to access the metrics api" - -{{- else if contains "ClusterIP" .Values.service.type }} -Get the application URL by running these commands: - - export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "edc-controlplane.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") - - export CONTAINER_PORT_DEFAULT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") - export CONTAINER_PORT_DATA=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[1].containerPort}") - export CONTAINER_PORT_VALIDATION=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[2].containerPort}") - export CONTAINER_PORT_CONTROL=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[3].containerPort}") - export CONTAINER_PORT_IDS=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[4].containerPort}") - export CONTAINER_PORT_METRICS=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[5].containerPort}") - - echo "Visit http://127.0.0.1:8080 to access the default api" - echo "Visit http://127.0.0.1:8182 to access the data management api" - echo "Visit http://127.0.0.1:8182 to access the data transfer validation api" - echo "Visit http://127.0.0.1:9999 to access the control api" - echo "Visit http://127.0.0.1:8282 to access the IDS api" - echo "Visit http://127.0.0.1:9090 to access the metrics api" - - kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME \ - 8080:$CONTAINER_PORT_DEFAULT \ - 8182:$CONTAINER_PORT_DATA \ - 8182:$CONTAINER_PORT_VALIDATION \ - 9999:$CONTAINER_PORT_CONTROL \ - 8282:$CONTAINER_PORT_IDS \ - 9090:$CONTAINER_PORT_METRICS - -{{- end }} diff --git a/charts/edc-controlplane/templates/_helpers.tpl b/charts/edc-controlplane/templates/_helpers.tpl deleted file mode 100644 index 272a0f27d..000000000 --- a/charts/edc-controlplane/templates/_helpers.tpl +++ /dev/null @@ -1,72 +0,0 @@ -{{/* -Expand the name of the chart. -*/}} -{{- define "edc-controlplane.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "edc-controlplane.fullname" -}} -{{- if .Values.fullnameOverride }} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- $name := default .Chart.Name .Values.nameOverride }} -{{- if contains $name .Release.Name }} -{{- .Release.Name | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} -{{- end }} -{{- end }} -{{- end }} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "edc-controlplane.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Common labels -*/}} -{{- define "edc-controlplane.labels" -}} -helm.sh/chart: {{ include "edc-controlplane.chart" . }} -{{ include "edc-controlplane.selectorLabels" . }} -{{ include "edc-controlplane.customLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} - -{{/* -Selector labels -*/}} -{{- define "edc-controlplane.selectorLabels" -}} -app.kubernetes.io/name: {{ include "edc-controlplane.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} - -{{/* -Custom labels -*/}} -{{- define "edc-controlplane.customLabels" -}} -{{- with .Values.customLabels }} -{{ toYaml . }} -{{- end }} -{{- end }} - -{{/* -Create the name of the service account to use -*/}} -{{- define "edc-controlplane.serviceAccountName" -}} -{{- if .Values.serviceAccount.create }} -{{- default (include "edc-controlplane.fullname" .) .Values.serviceAccount.name }} -{{- else }} -{{- default "default" .Values.serviceAccount.name }} -{{- end }} -{{- end }} diff --git a/charts/edc-controlplane/templates/configmap.yaml b/charts/edc-controlplane/templates/configmap.yaml deleted file mode 100644 index 863ac5e83..000000000 --- a/charts/edc-controlplane/templates/configmap.yaml +++ /dev/null @@ -1,49 +0,0 @@ -# -# Copyright (c) 2023 ZF Friedrichshafen AG -# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH -# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) -# Copyright (c) 2021, 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 -# - ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "edc-controlplane.fullname" . }}-configmap - namespace: {{ .Release.Namespace | default "default" | quote }} - labels: - {{- include "edc-controlplane.labels" . | nindent 4 }} -data: - configuration.properties: |- - web.http.default.port={{ .Values.edc.endpoints.default.port }} - web.http.default.path={{ .Values.edc.endpoints.default.path }} - web.http.data.port={{ .Values.edc.endpoints.data.port }} - web.http.data.path={{ .Values.edc.endpoints.data.path }} - web.http.validation.port={{ .Values.edc.endpoints.validation.port }} - web.http.validation.path={{ .Values.edc.endpoints.validation.path }} - web.http.control.port={{ .Values.edc.endpoints.control.port }} - web.http.control.path={{ .Values.edc.endpoints.control.path }} - web.http.ids.port={{ .Values.edc.endpoints.ids.port }} - web.http.ids.path={{ .Values.edc.endpoints.ids.path }} - {{- .Values.configuration.properties | nindent 4 }} - - opentelemetry.properties: |- - {{- .Values.opentelemetry.properties | nindent 4 }} - - logging.properties: |- - {{- .Values.logging.properties | nindent 4 }} diff --git a/charts/edc-controlplane/templates/deployment.yaml b/charts/edc-controlplane/templates/deployment.yaml deleted file mode 100644 index 4fd762d0b..000000000 --- a/charts/edc-controlplane/templates/deployment.yaml +++ /dev/null @@ -1,154 +0,0 @@ -# -# Copyright (c) 2023 ZF Friedrichshafen AG -# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH -# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) -# Copyright (c) 2021, 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 -# - ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "edc-controlplane.fullname" . }} - namespace: {{ .Release.Namespace | default "default" | quote }} - labels: - {{- include "edc-controlplane.labels" . | nindent 4 }} -spec: - {{- if not .Values.autoscaling.enabled }} - replicas: {{ .Values.replicaCount }} - {{- end }} - selector: - matchLabels: - {{- include "edc-controlplane.selectorLabels" . | nindent 6 }} - template: - metadata: - annotations: - checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} - checksum/env-config: {{ include (print $.Template.BasePath "/configmap-env.yaml") . | sha256sum }} - {{- with .Values.podAnnotations }} - {{- toYaml . | nindent 8 }} - {{- end }} - labels: - {{- include "edc-controlplane.selectorLabels" . | nindent 8 }} - spec: - {{- if .Values.imagePullSecret.dockerconfigjson }} - imagePullSecrets: - - name: {{ include "edc-controlplane.fullname" . }}-imagepullsecret - {{- else }} - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- end }} - serviceAccountName: {{ include "edc-controlplane.serviceAccountName" . }} - automountServiceAccountToken: {{ if .Values.automountServiceAccountToken }}true{{ else }}false{{ end }} - securityContext: - {{- toYaml .Values.podSecurityContext | nindent 8 }} - containers: - - name: {{ .Chart.Name }} - securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - ports: - - name: default - containerPort: {{ .Values.edc.endpoints.default.port }} - protocol: TCP - - name: data - containerPort: {{ .Values.edc.endpoints.data.port }} - protocol: TCP - - name: validation - containerPort: {{ .Values.edc.endpoints.validation.port }} - protocol: TCP - - name: control - containerPort: {{ .Values.edc.endpoints.control.port }} - protocol: TCP - - name: ids - containerPort: {{ .Values.edc.endpoints.ids.port }} - protocol: TCP - - name: metrics - containerPort: {{ .Values.edc.endpoints.metrics.port }} - protocol: TCP - {{- if .Values.livenessProbe.enabled }} - livenessProbe: - httpGet: - path: {{ .Values.edc.endpoints.default.path }}/check/liveness - port: default - {{- end }} - {{- if .Values.readinessProbe.enabled }} - readinessProbe: - httpGet: - path: {{ .Values.edc.endpoints.default.path }}/check/readiness - port: default - {{- end }} - {{- if .Values.startupProbe.enabled }} - startupProbe: - httpGet: - path: {{ .Values.edc.endpoints.default.path }}/check/startup - port: default - failureThreshold: {{ .Values.startupProbe.failureThreshold }} - initialDelaySeconds: {{ .Values.startupProbe.initialDelaySeconds }} - {{- end }} - envFrom: - - configMapRef: - name: {{ include "edc-controlplane.fullname" . }}-env - {{- if .Values.envSecretName }} - - secretRef: - name: {{ .Values.envSecretName | quote }} - {{- end }} - resources: - {{- toYaml .Values.resources | nindent 12 }} - volumeMounts: - - name: configuration - mountPath: /app/configuration.properties - subPath: configuration.properties - - name: configuration - mountPath: /app/opentelemetry.properties - subPath: opentelemetry.properties - - name: configuration - mountPath: /app/logging.properties - subPath: logging.properties - {{- with .Values.volumeMounts }} - {{- toYaml . | nindent 12 }} - {{- end }} - volumes: - - name: configuration - configMap: - name: {{ include "edc-controlplane.fullname" . }}-configmap - items: - - key: configuration.properties - path: configuration.properties - - key: opentelemetry.properties - path: opentelemetry.properties - - key: logging.properties - path: logging.properties - {{- with .Values.volumes }} - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} diff --git a/charts/edc-controlplane/templates/imagepullsecret.yaml b/charts/edc-controlplane/templates/imagepullsecret.yaml deleted file mode 100644 index 6b6e29ace..000000000 --- a/charts/edc-controlplane/templates/imagepullsecret.yaml +++ /dev/null @@ -1,35 +0,0 @@ -# -# Copyright (c) 2023 ZF Friedrichshafen AG -# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH -# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) -# Copyright (c) 2021, 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 -# - -{{- if .Values.imagePullSecret.dockerconfigjson }} ---- -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "edc-controlplane.fullname" . }}-imagepullsecret - namespace: {{ .Release.Namespace | default "default" | quote }} - labels: - {{- include "edc-controlplane.labels" . | nindent 4 }} -data: - .dockerconfigjson: {{ .Values.imagePullSecret.dockerconfigjson }} -type: kubernetes.io/dockerconfigjson -{{- end }} diff --git a/charts/edc-controlplane/values.yaml b/charts/edc-controlplane/values.yaml deleted file mode 100644 index b43d67a35..000000000 --- a/charts/edc-controlplane/values.yaml +++ /dev/null @@ -1,379 +0,0 @@ -# -# Copyright (c) 2023 ZF Friedrichshafen AG -# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH -# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) -# Copyright (c) 2021, 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 -# - ---- -# Default values for edc-controlplane. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. - -# -- Specifies how many replicas of a deployed pod shall be created during the deployment -# Note: If horizontal pod autoscaling is enabled this setting has no effect -replicaCount: 1 - -image: - # -- Which derivate of the edc control-plane to use. - # One of: [ghcr.io/eclipse-tractusx/tractusx-edc/edc-controlplane-postgresql-hashicorp-vault, ghcr.io/eclipse-tractusx/tractusx-edc/edc-controlplane-postgresql, ghcr.io/eclipse-tractusx/tractusx-edc/edc-controlplane-memory] - repository: ghcr.io/eclipse-tractusx/tractusx-edc/edc-controlplane-postgresql-hashicorp-vault - # -- [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) to use - pullPolicy: IfNotPresent - # -- Overrides the image tag whose default is the chart appVersion. - tag: "" - -imagePullSecret: - # -- Image pull secret to create to [obtain the container image from private registries](https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry) - # Note: This value needs to adhere to the [(base64 encoded) .dockerconfigjson format](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/#registry-secret-existing-credentials). - # Furthermore, if 'imagePullSecret.dockerconfigjson' is defined, it takes precedence over 'imagePullSecrets'. - dockerconfigjson: "" - -# -- 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) -imagePullSecrets: [] - -# -- Overrides the charts name -nameOverride: "" - -# -- Overrides the releases full name -fullnameOverride: "" - -# -- Additional custom Labels to add -customLabels: {} - -serviceAccount: - # -- Specifies whether a [service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) should be created per release - create: true - # -- [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) to add to the service account - annotations: {} - # -- The name of the service account to use. If not set and create is true, a name is generated using the release's fullname template - name: "" - -# -- Whether to [automount kubernetes API credentials](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#use-the-default-service-account-to-access-the-api-server) into the pod -automountServiceAccountToken: false - -# -- [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) added to deployed [pods](https://kubernetes.io/docs/concepts/workloads/pods/) -podAnnotations: {} - -# 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 -podSecurityContext: - seccompProfile: - # -- Restrict a Container's Syscalls with seccomp - type: RuntimeDefault - # -- Runs all processes within a pod with a special uid - runAsUser: 10001 - # -- Processes within a pod will belong to this guid - runAsGroup: 10001 - # -- The owner for volumes and any files created within volumes will belong to this guid - fsGroup: 10001 - -# The [container security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container) defines privilege and access control settings for a Container within a pod -securityContext: - capabilities: - # -- Specifies which capabilities to drop to reduce syscall attack surface - drop: - - ALL - # -- Specifies which capabilities to add to issue specialized syscalls - add: [] - # -- Whether the root filesystem is mounted in read-only mode - readOnlyRootFilesystem: true - # -- Controls [Privilege Escalation](https://kubernetes.io/docs/concepts/security/pod-security-policy/#privilege-escalation) enabling setuid binaries changing the effective user ID - allowPrivilegeEscalation: false - # -- Requires the container to run without root privileges - runAsNonRoot: true - # -- The container's process will run with the specified uid - runAsUser: 10001 - -livenessProbe: - # -- Whether to enable kubernetes [liveness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) - enabled: true - -readinessProbe: - # -- Whether to enable kubernetes readiness-probes - enabled: true - -startupProbe: - # -- Whether to enable kubernetes startup-probes - enabled: true - # -- Minimum consecutive failures for the probe to be considered failed after having succeeded - failureThreshold: 12 - # -- Number of seconds after the container has started before liveness probes are initiated. - initialDelaySeconds: 10 - -# -- Additional volumeMounts to the controlplane main container -volumeMounts: [] - -# -- Additional volumes to the controlplane pod -volumes: [] - -## EDC endpoints exposed by the control-plane -edc: - endpoints: - ## Default api exposing health checks etc - default: - # -- The network port, which the "default" api is going to be exposed by the container, pod and service - port: "8080" - # -- The path mapping the "default" api is going to be exposed at - path: /api - ## Data management API - data: - # -- The network port, which the "data" management api is going to be exposed by the container, pod and service - port: "8181" - # -- The path mapping the "data" management api is going to be exposed at - path: /data - ## Validation API - validation: - # -- The network port, which the "validation" api is going to be exposed by the container, pod and service - port: "8182" - # -- The path mapping the "validation" api is going to be exposed at - path: /validation - ## Control API - control: - # -- The network port, which the "control" api is going to be exposed by the container, pod and service - port: "9999" - # -- The path mapping the "control" api is going to be exposed at - path: /api/controlplane/control - ## IDS endpoints - ids: - # -- The network port, which the "ids" multipart api is going to be exposed by the container, pod and service - port: "8282" - # -- The path mapping the "ids" multipart api is going to be exposed at - path: /api/v1/ids - ## Prometheus endpoint - metrics: - # -- The network port, which the prometheus metrics are going to be exposed by the container, pod and service - port: "9090" - # -- The path mapping the prometheus metrics are going to be exposed at - path: /metrics - -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 - -## Ingress declaration to expose the network service. -ingresses: - ## Public / Internet facing Ingress - - enabled: true - # -- The hostname to be used to precisely map incoming traffic onto the underlying network service - hostname: "edc-controlplane.local" - # -- Additional ingress annotations to add - annotations: {} - # -- EDC endpoints exposed by this ingress resource - endpoints: - - ids - # -- Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use - className: "" - # -- TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource - tls: - # -- Enables TLS on the ingress resource - enabled: false - # -- If present overwrites the default secret name - secretName: "" - ## Adds [cert-manager](https://cert-manager.io/docs/) annotations to the ingress resource - certManager: - # -- If preset enables certificate generation via cert-manager namespace scoped issuer - issuer: "" - # -- If preset enables certificate generation via cert-manager cluster-wide issuer - clusterIssuer: "" - - ## Private / Intranet facing Ingress - - enabled: false - # -- The hostname to be used to precisely map incoming traffic onto the underlying network service - hostname: "edc-controlplane.intranet" - # -- Additional ingress annotations to add - annotations: {} - # -- EDC endpoints exposed by this ingress resource - endpoints: - - data - - control - # -- Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use - className: "" - # -- TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource - tls: - # -- Enables TLS on the ingress resource - enabled: false - # -- If present overwrites the default secret name - secretName: "" - ## Adds [cert-manager](https://cert-manager.io/docs/) annotations to the ingress resource - certManager: - # -- If preset enables certificate generation via cert-manager namespace scoped issuer - issuer: "" - # -- If preset enables certificate generation via cert-manager cluster-wide issuer - clusterIssuer: "" - -# -- [Resource management](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) applied to the deployed pod -resources: - {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - -autoscaling: - # -- Enables [horizontal pod autoscaling](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) - enabled: false - # -- Minimal replicas if resource consumption falls below resource threshholds - minReplicas: 1 - # -- Maximum replicas if resource consumption exceeds resource threshholds - maxReplicas: 100 - # -- targetAverageUtilization of cpu provided to a pod - targetCPUUtilizationPercentage: 80 - # -- targetAverageUtilization of memory provided to a pod - targetMemoryUtilizationPercentage: 80 - -# -- [Node-Selector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector) to constrain the Pod to nodes with specific labels. -nodeSelector: {} - -# -- [Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) are applied to Pods to schedule onto nodes with matching taints. -tolerations: [] - -# -- [Affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity) constrains which nodes the Pod can be scheduled on based on node labels. -affinity: {} - -# -- Container environment variables e.g. for configuring [JAVA_TOOL_OPTIONS](https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/envvars002.html) -# Ex.: -# JAVA_TOOL_OPTIONS: > -# -Dhttp.proxyHost=proxy -Dhttp.proxyPort=80 -Dhttp.nonProxyHosts="localhost|127.*|[::1]" -Dhttps.proxyHost=proxy -Dhttps.proxyPort=443 -env: {} - -# -- [Kubernetes Secret Resource](https://kubernetes.io/docs/concepts/configuration/secret/) name to load environment variables from -envSecretName: - -logging: - # -- EDC logging.properties configuring the [java.util.logging subsystem](https://docs.oracle.com/javase/7/docs/technotes/guides/logging/overview.html#a1.8) - properties: |- - .level=INFO - org.eclipse.edc.level=ALL - handlers=java.util.logging.ConsoleHandler - java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter - java.util.logging.ConsoleHandler.level=ALL - java.util.logging.SimpleFormatter.format=[%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS] [%4$-7s] %5$s%6$s%n - -opentelemetry: - # -- opentelemetry.properties configuring the [opentelemetry agent](https://opentelemetry.io/docs/instrumentation/java/automatic/agent-config/) - properties: |- - otel.javaagent.enabled=true - otel.javaagent.debug=false - -configuration: - # -- EDC configuration.properties configuring aspects of the [eclipse-dataspaceconnector](https://github.com/eclipse-edc/Connector) - properties: |- - # edc.api.auth.key= - # edc.atomikos.checkpoint.interval= - # edc.atomikos.directory= - # edc.atomikos.logging= - # edc.atomikos.threaded2pc= - # edc.atomikos.timeout= - # edc.aws.access.key= - # edc.aws.provision.retry.retries.max= - # edc.aws.provision.role.duration.session.max= - # edc.aws.secret.access.key= - # edc.blobstore.endpoint= - # edc.dataplane.token.validation.endpoint= - # edc.core.retry.backoff.max= - # edc.core.retry.backoff.min= - # edc.core.retry.retries.max= - # edc.core.system.health.check.liveness-period= - # edc.core.system.health.check.readiness-period= - # edc.core.system.health.check.startup-period= - # edc.core.system.health.check.threadpool-size= - # edc.dataplane.queue.capacity= - # edc.dataplane.wait= - # edc.dataplane.workers= - # edc.datasource.asset.name="default" - # edc.datasource.contractdefinition.name="default" - # edc.datasource.contractnegotiation.name="default" - # edc.datasource.policy.name="default" - # edc.datasource.transferprocess.name="default" - # edc.datasource.default.pool.maxIdleConnections= - # edc.datasource.default.pool.maxTotalConnections= - # edc.datasource.default.pool.minIdleConnections= - # edc.datasource.default.pool.testConnectionOnBorrow= - # edc.datasource.default.pool.testConnectionOnCreate= - # edc.datasource.default.pool.testConnectionOnReturn= - # edc.datasource.default.pool.testConnectionWhileIdle= - # edc.datasource.default.pool.testQuery= - # edc.datasource.default.url= - # edc.datasource.default.user= - # edc.datasource.default.password= - # edc.dpf.selector.url= - # edc.events.topic.endpoint= - # edc.events.topic.name= - # edc.fs.config= - # edc.hostname= - # edc.identity.did.url= - # edc.ids.catalog.id= - # edc.ids.curator= - # edc.ids.description= - # edc.ids.endpoint= - # edc.ids.id= - # edc.ids.maintainer= - # edc.ids.security.profile= - # edc.ids.title= - # edc.ids.validation.referringconnector= - # edc.ion.crawler.did-type= - # edc.ion.crawler.interval-minutes= - # edc.ion.crawler.ion.url= - # edc.metrics.enabled= - # edc.metrics.executor.enabled= - # edc.metrics.jersey.enabled= - # edc.metrics.jetty.enabled= - # edc.metrics.okhttp.enabled= - # edc.metrics.system.enabled= - # edc.negotiation.consumer.state-machine.batch-size= - # edc.negotiation.provider.state-machine.batch-size= - # edc.oauth.client.id= - # edc.oauth.private.key.alias= - # edc.oauth.provider.audience= - # edc.oauth.provider.jwks.refresh= - # edc.oauth.provider.jwks.url= - # edc.oauth.public.key.alias= - # edc.oauth.token.url= - # edc.oauth.validation.nbf.leeway= - # edc.receiver.http.auth-code= - # edc.receiver.http.auth-key= - # edc.receiver.http.endpoint= - # edc.transfer.proxy.endpoint= - # edc.transfer.proxy.token.validity.seconds= - # edc.transfer.proxy.token.signer.privatekey.alias= - # edc.transfer.functions.check.endpoint= - # edc.transfer.functions.enabled.protocols= - # edc.transfer.functions.transfer.endpoint= - # edc.transfer-process-store.database.name= - # edc.transfer.state-machine.batch-size= - # edc.vault= - # edc.vault.certificate= - # edc.vault.clientid= - # edc.vault.clientsecret= - # edc.vault.name= - # edc.vault.tenantid= - # edc.vault.hashicorp.url= - # edc.vault.hashicorp.token= - # edc.vault.hashicorp.timeout.seconds= - # edc.webdid.doh.url= - # edc.web.rest.cors.enabled= - # edc.web.rest.cors.headers= - # edc.web.rest.cors.methods= - # edc.web.rest.cors.origins= - # ids.webhook.address= diff --git a/charts/edc-dataplane/Chart.yaml b/charts/edc-dataplane/Chart.yaml deleted file mode 100644 index 96d5598fa..000000000 --- a/charts/edc-dataplane/Chart.yaml +++ /dev/null @@ -1,35 +0,0 @@ -# -# Copyright (c) 2023 ZF Friedrichshafen AG -# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH -# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) -# Copyright (c) 2021, 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 -# - ---- -apiVersion: v2 -name: edc-dataplane -description: >- - EDC Data-Plane - The Eclipse DataSpaceConnector data layer with responsibility of transferring and receiving data streams -home: https://github.com/eclipse-tractusx/tractusx-edc/tree/main/charts/edc-dataplane -type: application -appVersion: "0.3.2" -version: 0.3.2 -deprecated: true -maintainers: [] -sources: - - https://github.com/eclipse-tractusx/tractusx-edc/tree/main/charts/edc-dataplane diff --git a/charts/edc-dataplane/LICENSE b/charts/edc-dataplane/LICENSE deleted file mode 100644 index d64569567..000000000 --- a/charts/edc-dataplane/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/charts/edc-dataplane/README.md b/charts/edc-dataplane/README.md deleted file mode 100644 index 6085ccbbc..000000000 --- a/charts/edc-dataplane/README.md +++ /dev/null @@ -1,90 +0,0 @@ -# edc-dataplane - -> **:exclamation: This Helm Chart is deprecated!** - -![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) - -EDC Data-Plane - The Eclipse DataSpaceConnector data layer with responsibility of transferring and receiving data streams - -**Homepage:** - -## TL;DR - -```shell -helm repo add tractusx-edc https://eclipse-tractusx.github.io/charts/dev -helm install my-release tractusx-edc/edc-dataplane --version 0.3.2 -``` - -## Source Code - -* - -## Values - -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| affinity | object | `{}` | [Affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity) constrains which nodes the Pod can be scheduled on based on node labels. | -| automountServiceAccountToken | bool | `false` | Whether to [automount kubernetes API credentials](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#use-the-default-service-account-to-access-the-api-server) into the pod | -| 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/) | -| autoscaling.maxReplicas | int | `100` | Maximum replicas if resource consumption exceeds resource threshholds | -| autoscaling.minReplicas | int | `1` | Minimal replicas if resource consumption falls below resource threshholds | -| autoscaling.targetCPUUtilizationPercentage | int | `80` | targetAverageUtilization of cpu provided to a pod | -| autoscaling.targetMemoryUtilizationPercentage | int | `80` | targetAverageUtilization of memory provided to a pod | -| configuration.properties | string | `"# edc.atomikos.checkpoint.interval=\n# edc.atomikos.directory=\n# edc.atomikos.logging=\n# edc.atomikos.threaded2pc=\n# edc.atomikos.timeout=\n# edc.aws.access.key=\n# edc.aws.provision.retry.retries.max=\n# edc.aws.provision.role.duration.session.max=\n# edc.aws.secret.access.key=\n# edc.blobstore.endpoint=\n# edc.dataplane.token.validation.endpoint=\n# edc.core.retry.backoff.max=\n# edc.core.retry.backoff.min=\n# edc.core.retry.retries.max=\n# edc.core.system.health.check.liveness-period=\n# edc.core.system.health.check.readiness-period=\n# edc.core.system.health.check.startup-period=\n# edc.core.system.health.check.threadpool-size=\n# edc.dataplane.queue.capacity=\n# edc.dataplane.wait=\n# edc.dataplane.workers=\n# edc.datasource.asset.name=\"default\"\n# edc.datasource.contractdefinition.name=\"default\"\n# edc.datasource.contractnegotiation.name=\"default\"\n# edc.datasource.policy.name=\"default\"\n# edc.datasource.transferprocess.name=\"default\"\n# edc.datasource.default.pool.maxIdleConnections=\n# edc.datasource.default.pool.maxTotalConnections=\n# edc.datasource.default.pool.minIdleConnections=\n# edc.datasource.default.pool.testConnectionOnBorrow=\n# edc.datasource.default.pool.testConnectionOnCreate=\n# edc.datasource.default.pool.testConnectionOnReturn=\n# edc.datasource.default.pool.testConnectionWhileIdle=\n# edc.datasource.default.pool.testQuery=\n# edc.datasource.default.url=\n# edc.datasource.default.user=\n# edc.datasource.default.password=\n# edc.dpf.selector.url=\n# edc.events.topic.endpoint=\n# edc.events.topic.name=\n# edc.fs.config=\n# edc.hostname=\n# edc.identity.did.url=\n# edc.ids.catalog.id=\n# edc.ids.curator=\n# edc.ids.description=\n# edc.ids.endpoint=\n# edc.ids.endpoint.audience=\n# edc.ids.id=\n# edc.ids.maintainer=\n# edc.ids.security.profile=\n# edc.ids.title=\n# edc.ids.validation.referringconnector=\n# edc.ion.crawler.did-type=\n# edc.ion.crawler.interval-minutes=\n# edc.ion.crawler.ion.url=\n# edc.metrics.enabled=\n# edc.metrics.executor.enabled=\n# edc.metrics.jersey.enabled=\n# edc.metrics.jetty.enabled=\n# edc.metrics.okhttp.enabled=\n# edc.metrics.system.enabled=\n# edc.negotiation.consumer.state-machine.batch-size=\n# edc.negotiation.provider.state-machine.batch-size=\n# edc.oauth.client.id=\n# edc.oauth.private.key.alias=\n# edc.oauth.provider.jwks.refresh=\n# edc.oauth.provider.jwks.url=\n# edc.oauth.public.key.alias=\n# edc.oauth.token.url=\n# edc.oauth.validation.nbf.leeway=\n# edc.receiver.http.auth-code=\n# edc.receiver.http.auth-key=\n# edc.receiver.http.endpoint=\n# edc.transfer.functions.check.endpoint=\n# edc.transfer.functions.enabled.protocols=\n# edc.transfer.functions.transfer.endpoint=\n# edc.transfer-process-store.database.name=\n# edc.transfer.state-machine.batch-size=\n# edc.vault=\n# edc.vault.certificate=\n# edc.vault.clientid=\n# edc.vault.clientsecret=\n# edc.vault.name=\n# edc.vault.tenantid=\n# edc.vault.hashicorp.url=\n# edc.vault.hashicorp.token=\n# edc.vault.hashicorp.timeout.seconds=\n# edc.webdid.doh.url=\n# edc.web.rest.cors.enabled=\n# edc.web.rest.cors.headers=\n# edc.web.rest.cors.methods=\n# edc.web.rest.cors.origins="` | EDC configuration.properties configuring aspects of the [eclipse-dataspaceconnector](https://github.com/eclipse-edc/Connector) | -| customLabels | object | `{}` | Additional custom Labels to add | -| edc.endpoints.control.path | string | `"/api/dataplane/control"` | The path mapping the "control" api is going to be exposed by | -| edc.endpoints.control.port | string | `"9999"` | The network port, which the "control" api is going to be exposed by the container, pod and service | -| edc.endpoints.default.path | string | `"/api"` | The path mapping the "default" api is going to be exposed by | -| edc.endpoints.default.port | string | `"8080"` | The network port, which the "default" api is going to be exposed by the container, pod and service | -| edc.endpoints.metrics.path | string | `"/metrics"` | The path mapping the prometheus metrics are going to be exposed at | -| edc.endpoints.metrics.port | string | `"9090"` | The network port, which the prometheus metrics are going to be exposed by the container, pod and service | -| edc.endpoints.public.path | string | `"/api/public"` | The path mapping the "public" api is going to be exposed by | -| edc.endpoints.public.port | string | `"8185"` | The network port, which the "public" api is going to be exposed by the container, pod and service | -| env | object | `{}` | Container environment variables e.g. for configuring [JAVA_TOOL_OPTIONS](https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/envvars002.html) Ex.: JAVA_TOOL_OPTIONS: > -Dhttp.proxyHost=proxy -Dhttp.proxyPort=80 -Dhttp.nonProxyHosts="localhost|127.*|[::1]" -Dhttps.proxyHost=proxy -Dhttps.proxyPort=443 | -| envSecretName | string | `nil` | [Kubernetes Secret Resource](https://kubernetes.io/docs/concepts/configuration/secret/) name to load environment variables from | -| fullnameOverride | string | `""` | Overrides the releases full name | -| image.pullPolicy | string | `"IfNotPresent"` | [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) to use | -| image.repository | string | `"ghcr.io/eclipse-tractusx/tractusx-edc/edc-dataplane-hashicorp-vault"` | Which derivate of the edc data-plane to use. One of: [ghcr.io/eclipse-tractusx/tractusx-edc/edc-dataplane-hashicorp-vault, ghcr.io/eclipse-tractusx/tractusx-edc/edc-dataplane-azure-vault] | -| image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion | -| imagePullSecret.dockerconfigjson | string | `""` | Image pull secret to create to [obtain the container image from private registries](https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry) Note: This value needs to adhere to the [(base64 encoded) .dockerconfigjson format](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/#registry-secret-existing-credentials). Furthermore, if 'imagePullSecret.dockerconfigjson' is defined, it takes precedence over 'imagePullSecrets'. | -| 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) | -| ingresses[0].annotations | object | `{}` | Additional ingress annotations to add | -| ingresses[0].certManager.clusterIssuer | string | `""` | If preset enables certificate generation via cert-manager cluster-wide issuer | -| ingresses[0].certManager.issuer | string | `""` | If preset enables certificate generation via cert-manager namespace scoped issuer | -| ingresses[0].className | string | `""` | Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use | -| ingresses[0].enabled | bool | `true` | | -| ingresses[0].endpoints | list | `["public"]` | EDC endpoints exposed by this ingress resource | -| ingresses[0].hostname | string | `"edc-dataplane.local"` | The hostname to be used to precisely map incoming traffic onto the underlying network service | -| ingresses[0].tls | object | `{"enabled":false,"secretName":""}` | TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource | -| ingresses[0].tls.enabled | bool | `false` | Enables TLS on the ingress resource | -| ingresses[0].tls.secretName | string | `""` | If present overwrites the default secret name | -| livenessProbe.enabled | bool | `true` | Whether to enable kubernetes [liveness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | -| logging.properties | 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"` | EDC logging.properties configuring the [java.util.logging subsystem](https://docs.oracle.com/javase/7/docs/technotes/guides/logging/overview.html#a1.8) | -| nameOverride | string | `""` | Overrides the charts name | -| nodeSelector | object | `{}` | [Node-Selector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector) to constrain the Pod to nodes with specific labels. | -| opentelemetry.properties | string | `"otel.javaagent.enabled=true\notel.javaagent.debug=false"` | opentelemetry.properties configuring the [opentelemetry agent](https://opentelemetry.io/docs/instrumentation/java/automatic/agent-config/) | -| podAnnotations | object | `{}` | [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) added to deployed [pods](https://kubernetes.io/docs/concepts/workloads/pods/) | -| podSecurityContext.fsGroup | int | `10001` | The owner for volumes and any files created within volumes will belong to this guid | -| podSecurityContext.runAsGroup | int | `10001` | Processes within a pod will belong to this guid | -| podSecurityContext.runAsUser | int | `10001` | Runs all processes within a pod with a special uid | -| podSecurityContext.seccompProfile.type | string | `"RuntimeDefault"` | Restrict a Container's Syscalls with seccomp | -| readinessProbe.enabled | bool | `true` | Whether to enable kubernetes readiness-probes | -| replicaCount | int | `1` | Specifies how many replicas of a deployed pod shall be created during the deployment Note: If horizontal pod autoscaling is enabled this setting has no effect | -| resources | object | `{}` | [Resource management](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) applied to the deployed pod | -| 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 | -| securityContext.capabilities.add | list | `[]` | Specifies which capabilities to add to issue specialized syscalls | -| securityContext.capabilities.drop | list | `["ALL"]` | Specifies which capabilities to drop to reduce syscall attack surface | -| securityContext.readOnlyRootFilesystem | bool | `true` | Whether the root filesystem is mounted in read-only mode | -| securityContext.runAsNonRoot | bool | `true` | Requires the container to run without root privileges | -| securityContext.runAsUser | int | `10001` | The container's process will run with the specified uid | -| 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. | -| serviceAccount.annotations | object | `{}` | [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) to add to the service account | -| serviceAccount.create | bool | `true` | Specifies whether a [service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) should be created per release | -| serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the release's fullname template | -| startupProbe.enabled | bool | `true` | Whether to enable kubernetes startup-probes | -| startupProbe.failureThreshold | int | `12` | Minimum consecutive failures for the probe to be considered failed after having succeeded | -| startupProbe.initialDelaySeconds | int | `10` | Number of seconds after the container has started before liveness probes are initiated. | -| tolerations | list | `[]` | [Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) are applied to Pods to schedule onto nodes with matching taints. | - ----------------------------------------------- -Autogenerated from chart metadata using [helm-docs v1.10.0](https://github.com/norwoodj/helm-docs/releases/v1.10.0) diff --git a/charts/edc-dataplane/README.md.gotmpl b/charts/edc-dataplane/README.md.gotmpl deleted file mode 100644 index c94d26d50..000000000 --- a/charts/edc-dataplane/README.md.gotmpl +++ /dev/null @@ -1,26 +0,0 @@ -{{ template "chart.header" . }} - -{{ template "chart.deprecationWarning" . }} - -{{ template "chart.badgesSection" . }} - -{{ template "chart.description" . }} - -{{ template "chart.homepageLine" . }} - -## TL;DR - -```shell -helm repo add tractusx-edc https://eclipse-tractusx.github.io/charts/dev -helm install my-release tractusx-edc/edc-dataplane --version {{ .Version }} -``` - -{{ template "chart.maintainersSection" . }} - -{{ template "chart.sourcesSection" . }} - -{{ template "chart.requirementsSection" . }} - -{{ template "chart.valuesSection" . }} - -{{ template "helm-docs.versionFooter" . }} diff --git a/charts/edc-dataplane/templates/NOTES.txt b/charts/edc-dataplane/templates/NOTES.txt deleted file mode 100644 index 454b250eb..000000000 --- a/charts/edc-dataplane/templates/NOTES.txt +++ /dev/null @@ -1,64 +0,0 @@ - -CHART NAME: {{ .Chart.Name }} -CHART VERSION: {{ .Chart.Version }} -APP VERSION: {{ .Chart.AppVersion }} - -Logs can be accessed by running this command: - - kubectl logs --tail 100 -f \ - --namespace {{ .Release.Namespace }} \ - -l "app.kubernetes.io/name={{ include "edc-dataplane.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" - -{{- if .Values.ingresses }} - -Following ingress URLS are available: - {{- $edcEndpoints := .Values.edc.endpoints }} - {{- range .Values.ingresses }} - {{- if .enabled }} - {{- $ingressEdcEndpoints := .endpoints }} - {{- $hostname := .hostname }} - {{- $tls := .tls }} - {{- range $name, $mapping := $edcEndpoints }} - {{- if (has $name $ingressEdcEndpoints) }} - Visit http{{ if $tls }}s{{ end }}://{{ $hostname }}{{ $mapping.path }} to access the {{ $name }} api - {{- end }} - {{- end }} - {{- end }} - {{- end }} - -{{- else if contains "NodePort" .Values.service.type }} -Get the application URLs by running these commands: - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") - - export NODE_PORT_DEFAULT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "edc-dataplane.fullname" . }}}") - export NODE_PORT_PUBLIC=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[1].nodePort}" services {{ include "edc-dataplane.fullname" . }}}") - export NODE_PORT_CONTROL=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[2].nodePort}" services {{ include "edc-dataplane.fullname" . }}}") - export NODE_PORT_METRICS=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[3].nodePort}" services {{ include "edc-dataplane.fullname" . }}}") - - echo "Visit http://$NODE_IP:$NODE_PORT_DEFAULT to access the default api" - echo "Visit http://$NODE_IP:$NODE_PORT_PUBLIC to access the public data transfer api" - echo "Visit http://$NODE_IP:$NODE_PORT_CONTROL to access the control api" - echo "Visit http://$NODE_IP:$NODE_PORT_METRICS to access the metrics api" - -{{- else if contains "ClusterIP" .Values.service.type }} -Get the application URL by running these commands: - - export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "edc-dataplane.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") - - export CONTAINER_PORT_DEFAULT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") - export CONTAINER_PORT_PUBLIC=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[1].containerPort}") - export CONTAINER_PORT_CONTROL=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[2].containerPort}") - export CONTAINER_PORT_METRICS=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[3].containerPort}") - - echo "Visit http://127.0.0.1:8080 to access the default api" - echo "Visit http://127.0.0.1:8185 to access the public data transfer api" - echo "Visit http://127.0.0.1:9999 to access the control api" - echo "Visit http://127.0.0.1:9090 to access the metrics api" - - kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME \ - 8080:$CONTAINER_PORT_DEFAULT \ - 8185:$CONTAINER_PORT_PUBLIC \ - 9999:$CONTAINER_PORT_CONTROL \ - 9090:$CONTAINER_PORT_METRICS - -{{- end }} diff --git a/charts/edc-dataplane/templates/configmap.yaml b/charts/edc-dataplane/templates/configmap.yaml deleted file mode 100644 index c7daa322f..000000000 --- a/charts/edc-dataplane/templates/configmap.yaml +++ /dev/null @@ -1,45 +0,0 @@ -# -# Copyright (c) 2023 ZF Friedrichshafen AG -# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH -# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) -# Copyright (c) 2021, 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 -# - ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "edc-dataplane.fullname" . }}-configmap - namespace: {{ .Release.Namespace | default "default" | quote }} - labels: - {{- include "edc-dataplane.labels" . | nindent 4 }} -data: - configuration.properties: |- - web.http.default.port={{ .Values.edc.endpoints.default.port }} - web.http.default.path={{ .Values.edc.endpoints.default.path }} - web.http.public.port={{ .Values.edc.endpoints.public.port }} - web.http.public.path={{ .Values.edc.endpoints.public.path }} - web.http.control.port={{ .Values.edc.endpoints.control.port }} - web.http.control.path={{ .Values.edc.endpoints.control.path }} - {{- .Values.configuration.properties | nindent 4 }} - - opentelemetry.properties: |- - {{- .Values.opentelemetry.properties | nindent 4 }} - - logging.properties: |- - {{- .Values.logging.properties | nindent 4 }} diff --git a/charts/edc-dataplane/templates/deployment.yaml b/charts/edc-dataplane/templates/deployment.yaml deleted file mode 100644 index 474b04650..000000000 --- a/charts/edc-dataplane/templates/deployment.yaml +++ /dev/null @@ -1,142 +0,0 @@ -# -# Copyright (c) 2023 ZF Friedrichshafen AG -# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH -# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) -# Copyright (c) 2021, 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 -# - ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "edc-dataplane.fullname" . }} - namespace: {{ .Release.Namespace | default "default" | quote }} - labels: - {{- include "edc-dataplane.labels" . | nindent 4 }} -spec: - {{- if not .Values.autoscaling.enabled }} - replicas: {{ .Values.replicaCount }} - {{- end }} - selector: - matchLabels: - {{- include "edc-dataplane.selectorLabels" . | nindent 6 }} - template: - metadata: - annotations: - checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} - checksum/env-config: {{ include (print $.Template.BasePath "/configmap-env.yaml") . | sha256sum }} - {{- with .Values.podAnnotations }} - {{- toYaml . | nindent 8 }} - {{- end }} - labels: - {{- include "edc-dataplane.selectorLabels" . | nindent 8 }} - spec: - {{- if .Values.imagePullSecret.dockerconfigjson }} - imagePullSecrets: - - name: {{ include "edc-dataplane.fullname" . }}-imagepullsecret - {{- else }} - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- end }} - serviceAccountName: {{ include "edc-dataplane.serviceAccountName" . }} - automountServiceAccountToken: {{ if .Values.automountServiceAccountToken }}true{{ else }}false{{ end }} - securityContext: - {{- toYaml .Values.podSecurityContext | nindent 8 }} - containers: - - name: {{ .Chart.Name }} - securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - ports: - - name: default - containerPort: {{ .Values.edc.endpoints.default.port }} - protocol: TCP - - name: public - containerPort: {{ .Values.edc.endpoints.public.port }} - protocol: TCP - - name: control - containerPort: {{ .Values.edc.endpoints.control.port }} - protocol: TCP - - name: metrics - containerPort: {{ .Values.edc.endpoints.metrics.port }} - protocol: TCP - {{- if .Values.livenessProbe.enabled }} - livenessProbe: - httpGet: - path: {{ .Values.edc.endpoints.default.path }}/check/liveness - port: default - {{- end }} - {{- if .Values.readinessProbe.enabled }} - readinessProbe: - httpGet: - path: {{ .Values.edc.endpoints.default.path }}/check/readiness - port: default - {{- end }} - {{- if .Values.startupProbe.enabled }} - startupProbe: - httpGet: - path: {{ .Values.edc.endpoints.default.path }}/check/startup - port: default - failureThreshold: {{ .Values.startupProbe.failureThreshold }} - initialDelaySeconds: {{ .Values.startupProbe.initialDelaySeconds }} - {{- end }} - envFrom: - - configMapRef: - name: {{ include "edc-dataplane.fullname" . }}-env - {{- if .Values.envSecretName }} - - secretRef: - name: {{ .Values.envSecretName | quote }} - {{- end }} - resources: - {{- toYaml .Values.resources | nindent 12 }} - volumeMounts: - - name: configuration - mountPath: /app/configuration.properties - subPath: configuration.properties - - name: configuration - mountPath: /app/opentelemetry.properties - subPath: opentelemetry.properties - - name: configuration - mountPath: /app/logging.properties - subPath: logging.properties - volumes: - - name: configuration - configMap: - name: {{ include "edc-dataplane.fullname" . }}-configmap - items: - - key: configuration.properties - path: configuration.properties - - key: opentelemetry.properties - path: opentelemetry.properties - - key: logging.properties - path: logging.properties - {{- with .Values.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} diff --git a/charts/edc-dataplane/templates/imagepullsecret.yaml b/charts/edc-dataplane/templates/imagepullsecret.yaml deleted file mode 100644 index 11961674b..000000000 --- a/charts/edc-dataplane/templates/imagepullsecret.yaml +++ /dev/null @@ -1,35 +0,0 @@ -# -# Copyright (c) 2023 ZF Friedrichshafen AG -# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH -# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) -# Copyright (c) 2021, 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 -# - -{{- if .Values.imagePullSecret.dockerconfigjson }} ---- -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "edc-dataplane.fullname" . }}-imagepullsecret - namespace: {{ .Release.Namespace | default "default" | quote }} - labels: - {{- include "edc-dataplane.labels" . | nindent 4 }} -data: - .dockerconfigjson: {{ .Values.imagePullSecret.dockerconfigjson }} -type: kubernetes.io/dockerconfigjson -{{- end }} diff --git a/charts/edc-dataplane/values.yaml b/charts/edc-dataplane/values.yaml deleted file mode 100644 index 9a049cb1f..000000000 --- a/charts/edc-dataplane/values.yaml +++ /dev/null @@ -1,331 +0,0 @@ -# -# Copyright (c) 2023 ZF Friedrichshafen AG -# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH -# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) -# Copyright (c) 2021, 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 -# - ---- -# Default values for edc-dataplane. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. - -# -- Specifies how many replicas of a deployed pod shall be created during the deployment -# Note: If horizontal pod autoscaling is enabled this setting has no effect -replicaCount: 1 - -image: - # -- Which derivate of the edc data-plane to use. - # One of: [ghcr.io/eclipse-tractusx/tractusx-edc/edc-dataplane-hashicorp-vault, ghcr.io/eclipse-tractusx/tractusx-edc/edc-dataplane-azure-vault] - repository: ghcr.io/eclipse-tractusx/tractusx-edc/edc-dataplane-hashicorp-vault - # -- [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) to use - pullPolicy: IfNotPresent - # -- Overrides the image tag whose default is the chart appVersion - tag: "" - -imagePullSecret: - # -- Image pull secret to create to [obtain the container image from private registries](https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry) - # Note: This value needs to adhere to the [(base64 encoded) .dockerconfigjson format](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/#registry-secret-existing-credentials). - # Furthermore, if 'imagePullSecret.dockerconfigjson' is defined, it takes precedence over 'imagePullSecrets'. - dockerconfigjson: "" - -# -- 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) -imagePullSecrets: [] - -# -- Overrides the charts name -nameOverride: "" - -# -- Overrides the releases full name -fullnameOverride: "" - -# -- Additional custom Labels to add -customLabels: {} - -serviceAccount: - # -- Specifies whether a [service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) should be created per release - create: true - # -- [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) to add to the service account - annotations: {} - # -- The name of the service account to use. If not set and create is true, a name is generated using the release's fullname template - name: "" - -# -- Whether to [automount kubernetes API credentials](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#use-the-default-service-account-to-access-the-api-server) into the pod -automountServiceAccountToken: false - -# -- [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) added to deployed [pods](https://kubernetes.io/docs/concepts/workloads/pods/) -podAnnotations: {} - -# 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 -podSecurityContext: - seccompProfile: - # -- Restrict a Container's Syscalls with seccomp - type: RuntimeDefault - # -- Runs all processes within a pod with a special uid - runAsUser: 10001 - # -- Processes within a pod will belong to this guid - runAsGroup: 10001 - # -- The owner for volumes and any files created within volumes will belong to this guid - fsGroup: 10001 - -# The [container security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container) defines privilege and access control settings for a Container within a pod -securityContext: - capabilities: - # -- Specifies which capabilities to drop to reduce syscall attack surface - drop: - - ALL - # -- Specifies which capabilities to add to issue specialized syscalls - add: [] - # -- Whether the root filesystem is mounted in read-only mode - readOnlyRootFilesystem: true - # -- Controls [Privilege Escalation](https://kubernetes.io/docs/concepts/security/pod-security-policy/#privilege-escalation) enabling setuid binaries changing the effective user ID - allowPrivilegeEscalation: false - # -- Requires the container to run without root privileges - runAsNonRoot: true - # -- The container's process will run with the specified uid - runAsUser: 10001 - -livenessProbe: - # -- Whether to enable kubernetes [liveness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) - enabled: true - -readinessProbe: - # -- Whether to enable kubernetes readiness-probes - enabled: true - -startupProbe: - # -- Whether to enable kubernetes startup-probes - enabled: true - # -- Minimum consecutive failures for the probe to be considered failed after having succeeded - failureThreshold: 12 - # -- Number of seconds after the container has started before liveness probes are initiated. - initialDelaySeconds: 10 - -## EDC endpoints exposed by the data-plane -edc: - endpoints: - ## Default api exposing health checks etc - default: - # -- The network port, which the "default" api is going to be exposed by the container, pod and service - port: "8080" - # -- The path mapping the "default" api is going to be exposed by - path: /api - ## Public endpoint for data transfer - public: - # -- The network port, which the "public" api is going to be exposed by the container, pod and service - port: "8185" - # -- The path mapping the "public" api is going to be exposed by - path: /api/public - ## Control API - control: - # -- The network port, which the "control" api is going to be exposed by the container, pod and service - port: "9999" - # -- The path mapping the "control" api is going to be exposed by - path: /api/dataplane/control - ## Prometheus endpoint - metrics: - # -- The network port, which the prometheus metrics are going to be exposed by the container, pod and service - port: "9090" - # -- The path mapping the prometheus metrics are going to be exposed at - path: /metrics - -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 - -## Ingress declaration to expose the network service. -ingresses: - ## Public / Internet facing Ingress - - enabled: true - # -- The hostname to be used to precisely map incoming traffic onto the underlying network service - hostname: "edc-dataplane.local" - # -- Additional ingress annotations to add - annotations: {} - # -- EDC endpoints exposed by this ingress resource - endpoints: - - public - # -- Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use - className: "" - # -- TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource - tls: - # -- Enables TLS on the ingress resource - enabled: false - # -- If present overwrites the default secret name - secretName: "" - ## Adds [cert-manager](https://cert-manager.io/docs/) annotations to the ingress resource - certManager: - # -- If preset enables certificate generation via cert-manager namespace scoped issuer - issuer: "" - # -- If preset enables certificate generation via cert-manager cluster-wide issuer - clusterIssuer: "" - -# -- [Resource management](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) applied to the deployed pod -resources: - {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - -autoscaling: - # -- Enables [horizontal pod autoscaling](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) - enabled: false - # -- Minimal replicas if resource consumption falls below resource threshholds - minReplicas: 1 - # -- Maximum replicas if resource consumption exceeds resource threshholds - maxReplicas: 100 - # -- targetAverageUtilization of cpu provided to a pod - targetCPUUtilizationPercentage: 80 - # -- targetAverageUtilization of memory provided to a pod - targetMemoryUtilizationPercentage: 80 - -# -- [Node-Selector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector) to constrain the Pod to nodes with specific labels. -nodeSelector: {} - -# -- [Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) are applied to Pods to schedule onto nodes with matching taints. -tolerations: [] - -# -- [Affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity) constrains which nodes the Pod can be scheduled on based on node labels. -affinity: {} - -# -- Container environment variables e.g. for configuring [JAVA_TOOL_OPTIONS](https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/envvars002.html) -# Ex.: -# JAVA_TOOL_OPTIONS: > -# -Dhttp.proxyHost=proxy -Dhttp.proxyPort=80 -Dhttp.nonProxyHosts="localhost|127.*|[::1]" -Dhttps.proxyHost=proxy -Dhttps.proxyPort=443 -env: {} - -# -- [Kubernetes Secret Resource](https://kubernetes.io/docs/concepts/configuration/secret/) name to load environment variables from -envSecretName: - -logging: - # -- EDC logging.properties configuring the [java.util.logging subsystem](https://docs.oracle.com/javase/7/docs/technotes/guides/logging/overview.html#a1.8) - properties: |- - .level=INFO - org.eclipse.edc.level=ALL - handlers=java.util.logging.ConsoleHandler - java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter - java.util.logging.ConsoleHandler.level=ALL - java.util.logging.SimpleFormatter.format=[%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS] [%4$-7s] %5$s%6$s%n - -opentelemetry: - # -- opentelemetry.properties configuring the [opentelemetry agent](https://opentelemetry.io/docs/instrumentation/java/automatic/agent-config/) - properties: |- - otel.javaagent.enabled=true - otel.javaagent.debug=false - -configuration: - # -- EDC configuration.properties configuring aspects of the [eclipse-dataspaceconnector](https://github.com/eclipse-edc/Connector) - properties: |- - # edc.atomikos.checkpoint.interval= - # edc.atomikos.directory= - # edc.atomikos.logging= - # edc.atomikos.threaded2pc= - # edc.atomikos.timeout= - # edc.aws.access.key= - # edc.aws.provision.retry.retries.max= - # edc.aws.provision.role.duration.session.max= - # edc.aws.secret.access.key= - # edc.blobstore.endpoint= - # edc.dataplane.token.validation.endpoint= - # edc.core.retry.backoff.max= - # edc.core.retry.backoff.min= - # edc.core.retry.retries.max= - # edc.core.system.health.check.liveness-period= - # edc.core.system.health.check.readiness-period= - # edc.core.system.health.check.startup-period= - # edc.core.system.health.check.threadpool-size= - # edc.dataplane.queue.capacity= - # edc.dataplane.wait= - # edc.dataplane.workers= - # edc.datasource.asset.name="default" - # edc.datasource.contractdefinition.name="default" - # edc.datasource.contractnegotiation.name="default" - # edc.datasource.policy.name="default" - # edc.datasource.transferprocess.name="default" - # edc.datasource.default.pool.maxIdleConnections= - # edc.datasource.default.pool.maxTotalConnections= - # edc.datasource.default.pool.minIdleConnections= - # edc.datasource.default.pool.testConnectionOnBorrow= - # edc.datasource.default.pool.testConnectionOnCreate= - # edc.datasource.default.pool.testConnectionOnReturn= - # edc.datasource.default.pool.testConnectionWhileIdle= - # edc.datasource.default.pool.testQuery= - # edc.datasource.default.url= - # edc.datasource.default.user= - # edc.datasource.default.password= - # edc.dpf.selector.url= - # edc.events.topic.endpoint= - # edc.events.topic.name= - # edc.fs.config= - # edc.hostname= - # edc.identity.did.url= - # edc.ids.catalog.id= - # edc.ids.curator= - # edc.ids.description= - # edc.ids.endpoint= - # edc.ids.endpoint.audience= - # edc.ids.id= - # edc.ids.maintainer= - # edc.ids.security.profile= - # edc.ids.title= - # edc.ids.validation.referringconnector= - # edc.ion.crawler.did-type= - # edc.ion.crawler.interval-minutes= - # edc.ion.crawler.ion.url= - # edc.metrics.enabled= - # edc.metrics.executor.enabled= - # edc.metrics.jersey.enabled= - # edc.metrics.jetty.enabled= - # edc.metrics.okhttp.enabled= - # edc.metrics.system.enabled= - # edc.negotiation.consumer.state-machine.batch-size= - # edc.negotiation.provider.state-machine.batch-size= - # edc.oauth.client.id= - # edc.oauth.private.key.alias= - # edc.oauth.provider.jwks.refresh= - # edc.oauth.provider.jwks.url= - # edc.oauth.public.key.alias= - # edc.oauth.token.url= - # edc.oauth.validation.nbf.leeway= - # edc.receiver.http.auth-code= - # edc.receiver.http.auth-key= - # edc.receiver.http.endpoint= - # edc.transfer.functions.check.endpoint= - # edc.transfer.functions.enabled.protocols= - # edc.transfer.functions.transfer.endpoint= - # edc.transfer-process-store.database.name= - # edc.transfer.state-machine.batch-size= - # edc.vault= - # edc.vault.certificate= - # edc.vault.clientid= - # edc.vault.clientsecret= - # edc.vault.name= - # edc.vault.tenantid= - # edc.vault.hashicorp.url= - # edc.vault.hashicorp.token= - # edc.vault.hashicorp.timeout.seconds= - # edc.webdid.doh.url= - # edc.web.rest.cors.enabled= - # edc.web.rest.cors.headers= - # edc.web.rest.cors.methods= - # edc.web.rest.cors.origins= diff --git a/charts/tractusx-connector-azure-vault/Chart.yaml b/charts/tractusx-connector-azure-vault/Chart.yaml new file mode 100644 index 000000000..1fa9d3e3a --- /dev/null +++ b/charts/tractusx-connector-azure-vault/Chart.yaml @@ -0,0 +1,51 @@ +# +# Copyright (c) 2023 ZF Friedrichshafen AG +# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (c) 2021, 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 +# + +--- +apiVersion: v2 +name: tractusx-connector-azure-vault +description: | + A Helm chart for Tractus-X Eclipse Data Space Connector. The connector deployment consists of two runtime consists of a + Control Plane and a Data Plane. Note that _no_ external dependencies such as a PostgreSQL database and Azure KeyVault are included. + + This chart is intended for use with an _existing_ PostgreSQL database and an _existing_ Azure KeyVault. +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +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.4 +# 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.4" +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-azure-vault/README.md b/charts/tractusx-connector-azure-vault/README.md new file mode 100644 index 000000000..003e75320 --- /dev/null +++ b/charts/tractusx-connector-azure-vault/README.md @@ -0,0 +1,266 @@ +# tractusx-connector-azure-vault + +![Version: 0.3.4](https://img.shields.io/badge/Version-0.3.4-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.3.4](https://img.shields.io/badge/AppVersion-0.3.4-informational?style=flat-square) + +A Helm chart for Tractus-X Eclipse Data Space Connector. The connector deployment consists of two runtime consists of a +Control Plane and a Data Plane. Note that _no_ external dependencies such as a PostgreSQL database and Azure KeyVault are included. + +This chart is intended for use with an _existing_ PostgreSQL database and an _existing_ Azure KeyVault. + +**Homepage:** + +This chart uses Azure KeyVault, which is expected to contain the following secrets on application start: + +- `daps-cert`: contains the x509 certificate of the connector. +- `daps-key`: the private key of the x509 certificate +- `aes-keys`: a 128bit, 256bit or 512bit string used to encrypt data. Must be stored in base64 format. + +These must be obtained from a DAPS instance, the process of which is out of the scope of this document. Alternatively, +self-signed certificates can be used for testing: + +```shell +openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout daps.key -out daps.cert -subj "/CN=test" +export DAPS_KEY="$(cat daps.key)" +export DAPS_CERT="$(cat daps.cert)" +``` + +## Launching the application + +The following requirements must be met before launching the application: + +- Write access to an Azure KeyVault instance is required to run this chart +- Secrets are seeded in advance +- The vault's client id, client secret, tenant id and vault name (not the url!) are known + +Please also consider using [this example configuration](https://github.com/eclipse-tractusx/tractusx-edc/blob/main/edc-tests/deployment/src/main/resources/helm/tractusx-connector-azure-vault-test.yaml) +to launch the application. +Combined, run this shell command to start the in-memory Tractus-X EDC runtime: + +```shell +helm repo add tractusx-edc https://eclipse-tractusx.github.io/charts/dev +helm install my-release tractusx-edc/tractusx-connector-azure-vault --version 0.3.4 \ + -f /tractusx-connector-azure-vault-test.yaml \ + --set vault.azure.name=$AZURE_VAULT_NAME \ + --set vault.azure.client=$AZURE_CLIENT_ID \ + --set vault.azure.secret=$AZURE_CLIENT_SECRET \ + --set vault.azure.tenant=$AZURE_TENANT_ID +``` + +Note that `DAPS_CERT` contains the x509 certificate, `DAPS_KEY` contains the private key. + +## Source Code + +* + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| backendService.httpProxyTokenReceiverUrl | string | `""` | | +| controlplane.affinity | object | `{}` | | +| controlplane.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/) | +| controlplane.autoscaling.maxReplicas | int | `100` | Maximum replicas if resource consumption exceeds resource threshholds | +| 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},"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.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.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 | +| controlplane.endpoints.observability | object | `{"insecure":true,"path":"/observability","port":8085}` | observability api with unsecured access, must not be internet facing | +| 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.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 | `[]` | | +| controlplane.envValueFrom | object | `{}` | | +| controlplane.image.pullPolicy | string | `"IfNotPresent"` | [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) to use | +| controlplane.image.repository | string | `""` | Which derivate of the control plane to use. when left empty the deployment will select the correct image automatically | +| controlplane.image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion | +| controlplane.ingresses[0].annotations | object | `{}` | Additional ingress annotations to add | +| controlplane.ingresses[0].certManager.clusterIssuer | string | `""` | If preset enables certificate generation via cert-manager cluster-wide issuer | +| controlplane.ingresses[0].certManager.issuer | string | `""` | If preset enables certificate generation via cert-manager namespace scoped issuer | +| controlplane.ingresses[0].className | string | `""` | Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use | +| controlplane.ingresses[0].enabled | bool | `false` | | +| controlplane.ingresses[0].endpoints | list | `["ids"]` | EDC endpoints exposed by this ingress resource | +| controlplane.ingresses[0].hostname | string | `"edc-control.local"` | The hostname to be used to precisely map incoming traffic onto the underlying network service | +| controlplane.ingresses[0].tls | object | `{"enabled":false,"secretName":""}` | TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource | +| controlplane.ingresses[0].tls.enabled | bool | `false` | Enables TLS on the ingress resource | +| controlplane.ingresses[0].tls.secretName | string | `""` | If present overwrites the default secret name | +| controlplane.ingresses[1].annotations | object | `{}` | Additional ingress annotations to add | +| controlplane.ingresses[1].certManager.clusterIssuer | string | `""` | If preset enables certificate generation via cert-manager cluster-wide issuer | +| 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 | `["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 | +| controlplane.ingresses[1].tls.secretName | string | `""` | If present overwrites the default secret name | +| controlplane.initContainers | list | `[]` | | +| controlplane.internationalDataSpaces.catalogId | string | `"TXDC-Catalog"` | | +| controlplane.internationalDataSpaces.curator | string | `""` | | +| controlplane.internationalDataSpaces.description | string | `"Tractus-X Eclipse IDS Data Space Connector"` | | +| controlplane.internationalDataSpaces.id | string | `"TXDC"` | | +| controlplane.internationalDataSpaces.maintainer | string | `""` | | +| controlplane.internationalDataSpaces.title | string | `""` | | +| controlplane.livenessProbe.enabled | bool | `true` | Whether to enable kubernetes [liveness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | +| controlplane.livenessProbe.failureThreshold | int | `6` | when a probe fails kubernetes will try 6 times before giving up | +| controlplane.livenessProbe.initialDelaySeconds | int | `30` | seconds to wait before performing the first liveness check | +| controlplane.livenessProbe.periodSeconds | int | `10` | this fields specifies that kubernetes should perform a liveness check every 10 seconds | +| controlplane.livenessProbe.successThreshold | int | `1` | number of consecutive successes for the probe to be considered successful after having failed | +| controlplane.livenessProbe.timeoutSeconds | int | `5` | number of seconds after which the probe times out | +| controlplane.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) | +| controlplane.nodeSelector | object | `{}` | | +| controlplane.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 | +| controlplane.podAnnotations | object | `{}` | additional annotations for the pod | +| controlplane.podLabels | object | `{}` | additional labels for the pod | +| controlplane.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 | +| controlplane.podSecurityContext.fsGroup | int | `10001` | The owner for volumes and any files created within volumes will belong to this guid | +| controlplane.podSecurityContext.runAsGroup | int | `10001` | Processes within a pod will belong to this guid | +| controlplane.podSecurityContext.runAsUser | int | `10001` | Runs all processes within a pod with a special uid | +| controlplane.podSecurityContext.seccompProfile.type | string | `"RuntimeDefault"` | Restrict a Container's Syscalls with seccomp | +| controlplane.readinessProbe.enabled | bool | `true` | Whether to enable kubernetes [readiness-probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | +| controlplane.readinessProbe.failureThreshold | int | `6` | when a probe fails kubernetes will try 6 times before giving up | +| controlplane.readinessProbe.initialDelaySeconds | int | `30` | seconds to wait before performing the first readiness check | +| controlplane.readinessProbe.periodSeconds | int | `10` | this fields specifies that kubernetes should perform a readiness check every 10 seconds | +| controlplane.readinessProbe.successThreshold | int | `1` | number of consecutive successes for the probe to be considered successful after having failed | +| controlplane.readinessProbe.timeoutSeconds | int | `5` | number of seconds after which the probe times out | +| controlplane.replicaCount | int | `1` | | +| controlplane.resources | object | `{}` | [resource management](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) for the container | +| controlplane.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 | +| controlplane.securityContext.capabilities.add | list | `[]` | Specifies which capabilities to add to issue specialized syscalls | +| controlplane.securityContext.capabilities.drop | list | `["ALL"]` | Specifies which capabilities to drop to reduce syscall attack surface | +| controlplane.securityContext.readOnlyRootFilesystem | bool | `true` | Whether the root filesystem is mounted in read-only mode | +| controlplane.securityContext.runAsNonRoot | bool | `true` | Requires the container to run without root privileges | +| controlplane.securityContext.runAsUser | int | `10001` | The container's process will run with the specified uid | +| controlplane.service.annotations | object | `{}` | | +| controlplane.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. | +| controlplane.tolerations | list | `[]` | | +| controlplane.url.ids | string | `""` | Explicitly declared url for reaching the ids api (e.g. if ingresses not used) | +| controlplane.volumeMounts | list | `[]` | declare where to mount [volumes](https://kubernetes.io/docs/concepts/storage/volumes/) into the container | +| controlplane.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.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.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.name | string | `""` | | +| vault.azure.secret | string | `nil` | | +| vault.azure.tenant | 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"` | | + +---------------------------------------------- +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-azure-vault/README.md.gotmpl b/charts/tractusx-connector-azure-vault/README.md.gotmpl new file mode 100644 index 000000000..c90617416 --- /dev/null +++ b/charts/tractusx-connector-azure-vault/README.md.gotmpl @@ -0,0 +1,59 @@ +{{ template "chart.header" . }} + +{{ template "chart.deprecationWarning" . }} + +{{ template "chart.badgesSection" . }} + +{{ template "chart.description" . }} + +{{ template "chart.homepageLine" . }} + +This chart uses Azure KeyVault, which is expected to contain the following secrets on application start: + +- `daps-cert`: contains the x509 certificate of the connector. +- `daps-key`: the private key of the x509 certificate +- `aes-keys`: a 128bit, 256bit or 512bit string used to encrypt data. Must be stored in base64 format. + +These must be obtained from a DAPS instance, the process of which is out of the scope of this document. Alternatively, +self-signed certificates can be used for testing: + +```shell +openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout daps.key -out daps.cert -subj "/CN=test" +export DAPS_KEY="$(cat daps.key)" +export DAPS_CERT="$(cat daps.cert)" +``` + +## Launching the application + +The following requirements must be met before launching the application: + +- Write access to an Azure KeyVault instance is required to run this chart +- Secrets are seeded in advance +- The vault's client id, client secret, tenant id and vault name (not the url!) are known + +Please also consider using [this example configuration](https://github.com/eclipse-tractusx/tractusx-edc/blob/main/edc-tests/deployment/src/main/resources/helm/tractusx-connector-azure-vault-test.yaml) +to launch the application. +Combined, run this shell command to start the in-memory Tractus-X EDC runtime: + +```shell +helm repo add tractusx-edc https://eclipse-tractusx.github.io/charts/dev +helm install my-release tractusx-edc/tractusx-connector-azure-vault --version {{ .Version }} \ + -f /tractusx-connector-azure-vault-test.yaml \ + --set vault.azure.name=$AZURE_VAULT_NAME \ + --set vault.azure.client=$AZURE_CLIENT_ID \ + --set vault.azure.secret=$AZURE_CLIENT_SECRET \ + --set vault.azure.tenant=$AZURE_TENANT_ID +``` + +Note that `DAPS_CERT` contains the x509 certificate, `DAPS_KEY` contains the private key. + + +{{ template "chart.maintainersSection" . }} + +{{ template "chart.sourcesSection" . }} + +{{ template "chart.requirementsSection" . }} + +{{ template "chart.valuesSection" . }} + +{{ template "helm-docs.versionFooter" . }} diff --git a/charts/tractusx-connector-azure-vault/templates/NOTES.txt b/charts/tractusx-connector-azure-vault/templates/NOTES.txt new file mode 100644 index 000000000..254cf9c67 --- /dev/null +++ b/charts/tractusx-connector-azure-vault/templates/NOTES.txt @@ -0,0 +1,45 @@ +1. Get the control plane URL by running these commands: +{{ with index .Values.controlplane.ingresses 0}} +{{- if .enabled }} +{{- range .paths }} + http{{ if .tls }}s{{ end }}://{{ .hostname }}{{ .path }} +{{- end }} +{{- else if contains "NodePort" $.Values.controlplane.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ $.Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "txdc.fullname" $ }}) + export NODE_IP=$(kubectl get nodes --namespace {{ $.Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" $.Values.controlplane.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "txdc.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "txdc.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ $.Values.controlplane.service.port }} +{{- else if contains "ClusterIP" $.Values.controlplane.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ $.Release.Namespace }} -l "app.kubernetes.io/name={{ include "txdc.name" $ }},app.kubernetes.io/instance={{ $.Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ $.Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ $.Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} +{{- end }} + +2. Get the data plane URL by running these commands: +{{ with index .Values.controlplane.ingresses 0}} +{{- if .enabled }} +{{- range .paths }} + http{{ if .tls }}s{{ end }}://{{ .hostname }}{{ .path }} +{{- end }} +{{- else if contains "NodePort" $.Values.dataplane.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ $.Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "txdc.fullname" $ }}) + export NODE_IP=$(kubectl get nodes --namespace {{ $.Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" $.Values.dataplane.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ $.Release.Namespace }} svc -w {{ include "txdc.fullname" $ }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "txdc.fullname" $ }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" $.Values.dataplane.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ $.Release.Namespace }} -l "app.kubernetes.io/name={{ include "txdc.name" $ }},app.kubernetes.io/instance={{ $.Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ $.Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ $.Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} +{{- end }} diff --git a/charts/tractusx-connector-azure-vault/templates/_helpers.tpl b/charts/tractusx-connector-azure-vault/templates/_helpers.tpl new file mode 100644 index 000000000..701e6fc75 --- /dev/null +++ b/charts/tractusx-connector-azure-vault/templates/_helpers.tpl @@ -0,0 +1,175 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "txdc.name" -}} +{{- default .Chart.Name .Values.nameOverride | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "txdc.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "txdc.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Control Common labels +*/}} +{{- define "txdc.labels" -}} +helm.sh/chart: {{ include "txdc.chart" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Control Common labels +*/}} +{{- define "txdc.controlplane.labels" -}} +helm.sh/chart: {{ include "txdc.chart" . }} +{{ include "txdc.controlplane.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/component: edc-controlplane +app.kubernetes.io/part-of: edc +{{- end }} + +{{/* +Data Common labels +*/}} +{{- define "txdc.dataplane.labels" -}} +helm.sh/chart: {{ include "txdc.chart" . }} +{{ include "txdc.dataplane.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/component: edc-dataplane +app.kubernetes.io/part-of: edc +{{- end }} + +{{/* +Control Selector labels +*/}} +{{- define "txdc.controlplane.selectorLabels" -}} +app.kubernetes.io/name: {{ include "txdc.name" . }}-controlplane +app.kubernetes.io/instance: {{ .Release.Name }}-controlplane +{{- end }} + +{{/* +Data Selector labels +*/}} +{{- define "txdc.dataplane.selectorLabels" -}} +app.kubernetes.io/name: {{ include "txdc.name" . }}-dataplane +app.kubernetes.io/instance: {{ .Release.Name }}-dataplane +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "txdc.controlplane.serviceaccount.name" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "txdc.fullname" . ) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "txdc.dataplane.serviceaccount.name" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "txdc.fullname" . ) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Control IDS URL +*/}} +{{- define "txdc.controlplane.url.protocol" -}} +{{- if .Values.controlplane.url.protocol }}{{/* if ids api url has been specified explicitly */}} +{{- .Values.controlplane.url.protocol }} +{{- else }}{{/* else when ids api url has not been specified explicitly */}} +{{- with (index .Values.controlplane.ingresses 0) }} +{{- if .enabled }}{{/* if ingress enabled */}} +{{- if .tls.enabled }}{{/* if TLS enabled */}} +{{- printf "https://%s" .hostname -}} +{{- else }}{{/* else when TLS not enabled */}} +{{- printf "http://%s" .hostname -}} +{{- end }}{{/* end if tls */}} +{{- else }}{{/* else when ingress not enabled */}} +{{- printf "http://%s-controlplane:%v" ( include "txdc.fullname" $ ) $.Values.controlplane.endpoints.protocol.port -}} +{{- end }}{{/* end if ingress */}} +{{- end }}{{/* end with ingress */}} +{{- end }}{{/* end if .Values.controlplane.url.protocol */}} +{{- end }} + +{{/* +Validation URL +*/}} +{{- define "txdc.controlplane.url.validation" -}} +{{- printf "http://%s-controlplane:%v%s/token" ( include "txdc.fullname" $ ) $.Values.controlplane.endpoints.control.port $.Values.controlplane.endpoints.control.path -}} +{{- end }} + +{{/* +Data Control URL +*/}} +{{- define "txdc.dataplane.url.control" -}} +{{- printf "http://%s-dataplane:%v%s" (include "txdc.fullname" . ) .Values.dataplane.endpoints.control.port .Values.dataplane.endpoints.control.path -}} +{{- end }} + +{{/* +Data Public URL +*/}} +{{- define "txdc.dataplane.url.public" -}} +{{- if .Values.dataplane.url.public }}{{/* if public api url has been specified explicitly */}} +{{- .Values.dataplane.url.public }} +{{- else }}{{/* else when public api url has not been specified explicitly */}} +{{- with (index .Values.dataplane.ingresses 0) }} +{{- if .enabled }}{{/* if ingress enabled */}} +{{- if .tls.enabled }}{{/* if TLS enabled */}} +{{- printf "https://%s%s" .hostname $.Values.dataplane.endpoints.public.path -}} +{{- else }}{{/* else when TLS not enabled */}} +{{- printf "http://%s%s" .hostname $.Values.dataplane.endpoints.public.path -}} +{{- end }}{{/* end if tls */}} +{{- else }}{{/* else when ingress not enabled */}} +{{- printf "http://%s-dataplane:%v%s" (include "txdc.fullname" $ ) $.Values.dataplane.endpoints.public.port $.Values.dataplane.endpoints.public.path -}} +{{- end }}{{/* end if ingress */}} +{{- end }}{{/* end with ingress */}} +{{- end }}{{/* end if .Values.dataplane.url.public */}} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "txdc.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "txdc.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/charts/edc-controlplane/templates/configmap-env.yaml b/charts/tractusx-connector-azure-vault/templates/configmap-controlplane.yaml similarity index 78% rename from charts/edc-controlplane/templates/configmap-env.yaml rename to charts/tractusx-connector-azure-vault/templates/configmap-controlplane.yaml index d33071a58..42f2a493f 100644 --- a/charts/edc-controlplane/templates/configmap-env.yaml +++ b/charts/tractusx-connector-azure-vault/templates/configmap-controlplane.yaml @@ -19,14 +19,18 @@ # # SPDX-License-Identifier: Apache-2.0 # - + --- apiVersion: v1 kind: ConfigMap metadata: - name: {{ include "edc-controlplane.fullname" . }}-env + name: {{ include "txdc.fullname" . }}-controlplane namespace: {{ .Release.Namespace | default "default" | quote }} labels: - {{- include "edc-controlplane.labels" . | nindent 4 }} + {{- include "txdc.controlplane.labels" . | nindent 4 }} data: - {{- toYaml .Values.env | nindent 2 }} + opentelemetry.properties: |- + {{- .Values.controlplane.opentelemetry | nindent 4 }} + + logging.properties: |- + {{- .Values.controlplane.logging | nindent 4 }} diff --git a/charts/tractusx-connector-azure-vault/templates/configmap-dataplane.yaml b/charts/tractusx-connector-azure-vault/templates/configmap-dataplane.yaml new file mode 100644 index 000000000..c399f4301 --- /dev/null +++ b/charts/tractusx-connector-azure-vault/templates/configmap-dataplane.yaml @@ -0,0 +1,33 @@ +# + # 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 + # + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "txdc.fullname" . }}-dataplane + namespace: {{ .Release.Namespace | default "default" | quote }} + labels: + {{- include "txdc.dataplane.labels" . | nindent 4 }} +data: + opentelemetry.properties: |- + {{- .Values.dataplane.opentelemetry | nindent 4 }} + + logging.properties: |- + {{- .Values.dataplane.logging | nindent 4 }} diff --git a/charts/tractusx-connector-azure-vault/templates/deployment-controlplane.yaml b/charts/tractusx-connector-azure-vault/templates/deployment-controlplane.yaml new file mode 100644 index 000000000..b5bd4968e --- /dev/null +++ b/charts/tractusx-connector-azure-vault/templates/deployment-controlplane.yaml @@ -0,0 +1,362 @@ +# +# Copyright (c) 2023 ZF Friedrichshafen AG +# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (c) 2021, 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 +# + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "txdc.fullname" . }}-controlplane + labels: + {{- include "txdc.controlplane.labels" . | nindent 4 }} +spec: + {{- if not .Values.controlplane.autoscaling.enabled }} + replicas: {{ .Values.controlplane.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "txdc.controlplane.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.controlplane.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "txdc.controlplane.selectorLabels" . | nindent 8 }} + {{- with .Values.controlplane.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "txdc.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.controlplane.podSecurityContext | nindent 8 }} + initContainers: + {{- toYaml .Values.controlplane.initContainers | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.controlplane.securityContext | nindent 12 }} + + # either use the specified image, or use the default one + {{- if .Values.controlplane.image.repository }} + image: "{{ .Values.controlplane.image.repository }}:{{ .Values.controlplane.image.tag | default .Chart.AppVersion }}" + {{- else }} + image: "tractusx/edc-controlplane-postgresql-azure-vault:{{ .Values.controlplane.image.tag | default .Chart.AppVersion }}" + {{- end }} + imagePullPolicy: {{ .Values.controlplane.image.pullPolicy }} + ports: + {{- range $key,$value := .Values.controlplane.endpoints }} + - name: {{ $key }} + containerPort: {{ $value.port }} + protocol: TCP + {{- end }} + {{- if .Values.controlplane.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: {{ .Values.controlplane.endpoints.observability.path }}/check/liveness + port: {{ .Values.controlplane.endpoints.observability.port }} + initialDelaySeconds: {{ .Values.controlplane.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.controlplane.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.controlplane.livenessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.controlplane.livenessProbe.failureThreshold }} + successThreshold: {{ .Values.controlplane.livenessProbe.successThreshold }} + {{- end }} + {{- if .Values.controlplane.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: {{ .Values.controlplane.endpoints.observability.path }}/check/readiness + port: {{ .Values.controlplane.endpoints.observability.port }} + initialDelaySeconds: {{ .Values.controlplane.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.controlplane.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.controlplane.readinessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.controlplane.readinessProbe.failureThreshold }} + successThreshold: {{ .Values.controlplane.readinessProbe.successThreshold }} + {{- end }} + resources: + {{- toYaml .Values.controlplane.resources | nindent 12 }} + env: + {{- if .Values.controlplane.debug.enabled }} + - name: "JAVA_TOOL_OPTIONS" + {{- if .Values.controlplane.debug.suspendOnStart }} + value: >- + {{ printf "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=%v" .Values.controlplane.debug.port }} + {{- else }} + value: >- + {{ printf "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=%v" .Values.controlplane.debug.port }} + {{- end }} + {{- end }} + + ######################## + ## DAPS CONFIGURATION ## + ######################## + + # see extension https://github.com/eclipse-edc/Connector/tree/main/extensions/iam/oauth2/oauth2-core + - name: EDC_OAUTH_CLIENT_ID + value: {{ .Values.daps.clientId | required ".Values.daps.clientId is required" | quote }} + - name: EDC_OAUTH_PROVIDER_JWKS_URL + value: {{ printf "%s%s" .Values.daps.url .Values.daps.paths.jwks }} + - name: EDC_OAUTH_TOKEN_URL + value: {{ printf "%s%s" .Values.daps.url .Values.daps.paths.token }} + - name: EDC_OAUTH_PRIVATE_KEY_ALIAS + value: {{ .Values.vault.secretNames.dapsPrivateKey | required ".Values.vault.secretNames.dapsPrivateKey is required" | quote }} + - name: EDC_OAUTH_CERTIFICATE_ALIAS + value: {{ .Values.vault.secretNames.dapsPublicKey | required ".Values.vault.secretNames.dapsPublicKey is required" | quote }} + + ####### + # API # + ####### + - name: "EDC_API_AUTH_KEY" + value: {{ .Values.controlplane.endpoints.management.authKey | required ".Values.controlplane.endpoints.mangement.authKey is required" | quote }} + - name: "WEB_HTTP_DEFAULT_PORT" + value: {{ .Values.controlplane.endpoints.default.port | quote }} + - name: "WEB_HTTP_DEFAULT_PATH" + value: {{ .Values.controlplane.endpoints.default.path | quote }} + - name: "WEB_HTTP_MANAGEMENT_PORT" + value: {{ .Values.controlplane.endpoints.management.port | quote }} + - name: "WEB_HTTP_MANAGEMENT_PATH" + value: {{ .Values.controlplane.endpoints.management.path | quote }} + - name: "WEB_HTTP_CONTROL_PORT" + value: {{ .Values.controlplane.endpoints.control.port | quote }} + - name: "WEB_HTTP_CONTROL_PATH" + value: {{ .Values.controlplane.endpoints.control.path | quote }} + - name: "WEB_HTTP_PROTOCOL_PORT" + value: {{ .Values.controlplane.endpoints.protocol.port | quote }} + - name: "WEB_HTTP_PROTOCOL_PATH" + value: {{ .Values.controlplane.endpoints.protocol.path | quote }} + - name: "WEB_HTTP_OBSERVABILITY_PORT" + value: {{ .Values.controlplane.endpoints.observability.port | quote}} + - name: "WEB_HTTP_OBSERVABILITY_PATH" + value: {{ .Values.controlplane.endpoints.observability.path | quote}} + - name: "TRACTUSX_API_OBSERVABILITY_ALLOW-INSECURE" + value: {{ .Values.controlplane.endpoints.observability.insecure | quote }} + + ######### + ## IDS ## + ######### + - name: "IDS_WEBHOOK_ADDRESS" + value: {{ include "txdc.controlplane.url.protocol" . | quote }} + - name: "EDC_IDS_ENDPOINT" + value: {{ printf "%s%s" (include "txdc.controlplane.url.protocol" .) .Values.controlplane.endpoints.protocol.path | quote }} + - name: "EDC_IDS_ID" + value: {{ printf "urn:connector:%s" (lower .Values.controlplane.internationalDataSpaces.id) | quote }} + - name: "EDC_IDS_DESCRIPTION" + value: {{ .Values.controlplane.internationalDataSpaces.description | quote }} + - name: "EDC_IDS_TITLE" + value: {{ .Values.controlplane.internationalDataSpaces.title | quote }} + - name: "EDC_IDS_MAINTAINER" + value: {{ .Values.controlplane.internationalDataSpaces.maintainer | quote }} + - name: "EDC_IDS_CURATOR" + value: {{ .Values.controlplane.internationalDataSpaces.curator | quote }} + - name: "EDC_IDS_CATALOG_ID" + value: {{ printf "urn:catalog:%s" (lower .Values.controlplane.internationalDataSpaces.catalogId) | quote }} + - name: "EDC_OAUTH_PROVIDER_AUDIENCE" + value: "idsc:IDS_CONNECTORS_ALL" + - name: "EDC_OAUTH_ENDPOINT_AUDIENCE" + value: {{ printf "%s%s%s" (include "txdc.controlplane.url.protocol" . ) .Values.controlplane.endpoints.protocol.path "/data" | quote }} + # this is the old setting name for 'EDC_OAUTH_ENDPOINT_AUDIENCE' and is mandatory for Produce EDC v0.1.2 and older + - name: "EDC_IDS_ENDPOINT_AUDIENCE" + value: {{ printf "%s%s%s" (include "txdc.controlplane.url.protocol" . ) .Values.controlplane.endpoints.protocol.path "/data" | quote }} + + ################ + ## POSTGRESQL ## + ################ + + # see extension https://github.com/eclipse-edc/Connector/tree/main/extensions/control-plane/store/sql/asset-index-sql + - name: "EDC_DATASOURCE_ASSET_NAME" + value: "asset" + - name: "EDC_DATASOURCE_ASSET_USER" + value: {{ .Values.postgresql.username | required ".Values.postgresql.username is required" | quote }} + - name: "EDC_DATASOURCE_ASSET_PASSWORD" + value: {{ .Values.postgresql.password | required ".Values.postgresql.password is required" | quote }} + - name: "EDC_DATASOURCE_ASSET_URL" + value: {{ .Values.postgresql.jdbcUrl | required ".Values.postgresql.jdbcUrl is required" | quote }} + + # see extension https://github.com/eclipse-edc/Connector/tree/main/extensions/control-plane/store/sql/contract-definition-store-sql + - name: "EDC_DATASOURCE_CONTRACTDEFINITION_NAME" + value: "contractdefinition" + - name: "EDC_DATASOURCE_CONTRACTDEFINITION_USER" + value: {{ .Values.postgresql.username | required ".Values.postgresql.username is required" | quote }} + - name: "EDC_DATASOURCE_CONTRACTDEFINITION_PASSWORD" + value: {{ .Values.postgresql.password | required ".Values.postgresql.password is required" | quote }} + - name: "EDC_DATASOURCE_CONTRACTDEFINITION_URL" + value: {{ .Values.postgresql.jdbcUrl | required ".Values.postgresql.jdbcUrl is required" | quote }} + + # see extension https://github.com/eclipse-edc/Connector/tree/main/extensions/control-plane/store/sql/contract-negotiation-store-sql + - name: "EDC_DATASOURCE_CONTRACTNEGOTIATION_NAME" + value: "contractnegotiation" + - name: "EDC_DATASOURCE_CONTRACTNEGOTIATION_USER" + value: {{ .Values.postgresql.username | required ".Values.postgresql.username is required" | quote }} + - name: "EDC_DATASOURCE_CONTRACTNEGOTIATION_PASSWORD" + value: {{ .Values.postgresql.password | required ".Values.postgresql.password is required" | quote }} + - name: "EDC_DATASOURCE_CONTRACTNEGOTIATION_URL" + value: {{ .Values.postgresql.jdbcUrl | required ".Values.postgresql.jdbcUrl is required" | quote }} + + # see extension https://github.com/eclipse-edc/Connector/tree/main/extensions/control-plane/store/sql/policy-store-sql + - name: "EDC_DATASOURCE_POLICY_NAME" + value: "policy" + - name: "EDC_DATASOURCE_POLICY_USER" + value: {{ .Values.postgresql.username | required ".Values.postgresql.username is required" | quote }} + - name: "EDC_DATASOURCE_POLICY_PASSWORD" + value: {{ .Values.postgresql.password | required ".Values.postgresql.password is required" | quote }} + - name: "EDC_DATASOURCE_POLICY_URL" + value: {{ .Values.postgresql.jdbcUrl | required ".Values.postgresql.jdbcUrl is required" | quote }} + + # see extension https://github.com/eclipse-edc/Connector/tree/main/extensions/control-plane/store/sql/transfer-process-store-sql + - name: "EDC_DATASOURCE_TRANSFERPROCESS_NAME" + value: "transferprocess" + - name: "EDC_DATASOURCE_TRANSFERPROCESS_USER" + value: {{ .Values.postgresql.username | required ".Values.postgresql.username is required" | quote }} + - name: "EDC_DATASOURCE_TRANSFERPROCESS_PASSWORD" + value: {{ .Values.postgresql.password | required ".Values.postgresql.password is required" | quote }} + - name: "EDC_DATASOURCE_TRANSFERPROCESS_URL" + value: {{ .Values.postgresql.jdbcUrl | required ".Values.postgresql.jdbcUrl is required" | quote }} + + ################ + ## DATA PLANE ## + ################ + + # see extension https://github.com/eclipse-tractusx/tractusx-edc/tree/main/edc-extensions/dataplane-selector-configuration + - name: "EDC_DATAPLANE_SELECTOR_DEFAULTPLANE_URL" + value: {{ include "txdc.dataplane.url.control" . }}/transfer + - name: "EDC_DATAPLANE_SELECTOR_DEFAULTPLANE_SOURCETYPES" + value: "HttpData,AmazonS3" + - name: "EDC_DATAPLANE_SELECTOR_DEFAULTPLANE_DESTINATIONTYPES" + value: "HttpProxy,AmazonS3" + - name: "EDC_DATAPLANE_SELECTOR_DEFAULTPLANE_PROPERTIES" + value: |- + {{ printf "{ \"publicApiUrl\": \"%s\" }" (include "txdc.dataplane.url.public" . ) }} + + # see extension https://github.com/eclipse-edc/Connector/tree/main/extensions/control-plane/data-plane-transfer + - name: "EDC_TRANSFER_PROXY_ENDPOINT" + value: {{ include "txdc.dataplane.url.public" . }} + - name: "EDC_TRANSFER_PROXY_TOKEN_SIGNER_PRIVATEKEY_ALIAS" + value: {{ .Values.vault.secretNames.transferProxyTokenSignerPrivateKey | quote }} + - name: "EDC_TRANSFER_PROXY_TOKEN_VERIFIER_PUBLICKEY_ALIAS" + value: {{ .Values.vault.secretNames.transferProxyTokenSignerPublicKey | quote }} + + # see extension https://github.com/eclipse-edc/Connector/tree/main/extensions/control-plane/transfer/transfer-pull-http-dynamic-receiver + + - name: "EDC_RECEIVER_HTTP_DYNAMIC_ENDPOINT" + value: {{ .Values.backendService.httpProxyTokenReceiverUrl | required ".Values.backendService.httpProxyTokenReceiverUrl is required" | quote }} + + ########### + ## VAULT ## + ########### + + - name: "EDC_VAULT_CLIENTID" + value: {{ .Values.vault.azure.client | required ".Values.vault.azure.client is required" | quote }} + - name: "EDC_VAULT_TENANTID" + value: {{ .Values.vault.azure.tenant | required ".Values.vault.azure.tenant is required" | quote }} + - name: "EDC_VAULT_NAME" + value: {{ .Values.vault.azure.name | required ".Values.vault.azure.name is required" | quote }} + # only set the env var if config value not null + {{- if .Values.vault.azure.secret }} + - name: "EDC_VAULT_CLIENTSECRET" + value: {{ .Values.vault.azure.secret | quote }} + {{- end }} + # only set the env var if config value not null + {{- if .Values.vault.azure.certificate }} + - name: "EDC_VAULT_CERTIFICATE" + value: {{ .Values.vault.azure.certificate | quote }} + {{- end }} + + ##################### + ## DATA ENCRYPTION ## + ##################### + + # see extension https://github.com/eclipse-tractusx/tractusx-edc/tree/main/edc-extensions/data-encryption + - name: "EDC_DATA_ENCRYPTION_KEYS_ALIAS" + value: {{ .Values.vault.secretNames.transferProxyTokenEncryptionAesKey | quote }} + - name: "EDC_DATA_ENCRYPTION_ALGORITHM" + value: "AES" + + ########################### + ## AAS WRAPPER EXTENSION ## + ########################### + - name: "EDC_CP_ADAPTER_CACHE_CATALOG_EXPIRE_AFTER" + value: "0" + - 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 ## + ###################################### + - name: "EDC_CONNECTOR_NAME" + value: {{ include "txdc.fullname" .}}-controlplane + {{- range $key, $value := .Values.controlplane.envValueFrom }} + - name: {{ $key | quote }} + valueFrom: + {{- tpl (toYaml $value) $ | nindent 16 }} + {{- end }} + {{- range $key, $value := .Values.controlplane.env }} + - name: {{ $key | quote }} + value: {{ $value | quote }} + {{- end }} + {{- if and (or .Values.controlplane.envSecretNames .Values.controlplane.envConfigMapNames) (or (gt (len .Values.controlplane.envSecretNames) 0) (gt (len .Values.controlplane.envConfigMapNames) 0)) }} + envFrom: + {{- range $value := .Values.controlplane.envSecretNames }} + - secretRef: + name: {{ $value | quote }} + {{- end }} + {{- range $value := .Values.controlplane.envConfigMapNames }} + - configMapRef: + name: {{ $value | quote }} + {{- end }} + {{- end }} + volumeMounts: + - name: "configuration" + mountPath: "/app/opentelemetry.properties" + subPath: "opentelemetry.properties" + - name: "configuration" + mountPath: "/app/logging.properties" + subPath: "logging.properties" + volumes: + - name: "configuration" + configMap: + name: {{ include "txdc.fullname" . }}-controlplane + items: + - key: "opentelemetry.properties" + path: "opentelemetry.properties" + - key: "logging.properties" + path: "logging.properties" + {{- with .Values.controlplane.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.controlplane.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.controlplane.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/tractusx-connector-azure-vault/templates/deployment-dataplane.yaml b/charts/tractusx-connector-azure-vault/templates/deployment-dataplane.yaml new file mode 100644 index 000000000..a745e6778 --- /dev/null +++ b/charts/tractusx-connector-azure-vault/templates/deployment-dataplane.yaml @@ -0,0 +1,223 @@ +# +# Copyright (c) 2023 ZF Friedrichshafen AG +# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (c) 2021, 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 +# + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "txdc.fullname" . }}-dataplane + labels: + {{- include "txdc.dataplane.labels" . | nindent 4 }} +spec: + {{- if not .Values.dataplane.autoscaling.enabled }} + replicas: {{ .Values.dataplane.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "txdc.dataplane.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.dataplane.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "txdc.dataplane.selectorLabels" . | nindent 8 }} + {{- with .Values.dataplane.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "txdc.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.dataplane.podSecurityContext | nindent 8 }} + initContainers: + {{- toYaml .Values.dataplane.initContainers | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.dataplane.securityContext | nindent 12 }} + {{- if .Values.dataplane.image.repository }} + image: "{{ .Values.dataplane.image.repository }}:{{ .Values.dataplane.image.tag | default .Chart.AppVersion }}" + {{- else }} + image: "tractusx/edc-dataplane-azure-vault:{{ .Values.dataplane.image.tag | default .Chart.AppVersion }}" + {{- end }} + imagePullPolicy: {{ .Values.dataplane.image.pullPolicy }} + ports: + {{- range $key,$value := .Values.dataplane.endpoints }} + - name: {{ $key }} + containerPort: {{ $value.port }} + protocol: TCP + {{- end }} + {{- if .Values.dataplane.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: {{ .Values.dataplane.endpoints.observability.path }}/check/liveness + port: {{ .Values.dataplane.endpoints.observability.port }} + initialDelaySeconds: {{ .Values.dataplane.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.dataplane.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.dataplane.livenessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.dataplane.livenessProbe.failureThreshold }} + successThreshold: {{ .Values.dataplane.livenessProbe.successThreshold }} + {{- end }} + {{- if .Values.dataplane.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: {{ .Values.dataplane.endpoints.observability.path }}/check/readiness + port: {{ .Values.dataplane.endpoints.observability.port }} + initialDelaySeconds: {{ .Values.dataplane.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.dataplane.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.dataplane.readinessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.dataplane.readinessProbe.failureThreshold }} + successThreshold: {{ .Values.dataplane.readinessProbe.successThreshold }} + {{- end }} + resources: + {{- toYaml .Values.dataplane.resources | nindent 12 }} + env: + {{- if .Values.dataplane.debug.enabled }} + - name: "JAVA_TOOL_OPTIONS" + {{- if .Values.dataplane.debug.suspendOnStart }} + value: >- + {{ printf "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=%v" .Values.dataplane.debug.port }} + {{- else }} + value: >- + {{ printf "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=%v" .Values.dataplane.debug.port }} + {{- end }} + {{- end }} + + ####### + # API # + ####### + - name: "WEB_HTTP_DEFAULT_PORT" + value: {{ .Values.dataplane.endpoints.default.port | quote }} + - name: "WEB_HTTP_DEFAULT_PATH" + value: {{ .Values.dataplane.endpoints.default.path | quote }} + - name: "WEB_HTTP_CONTROL_PORT" + value: {{ .Values.dataplane.endpoints.control.port | quote }} + - name: "WEB_HTTP_CONTROL_PATH" + value: {{ .Values.dataplane.endpoints.control.path | quote }} + - name: "WEB_HTTP_PUBLIC_PORT" + value: {{ .Values.dataplane.endpoints.public.port | quote }} + - name: "WEB_HTTP_PUBLIC_PATH" + value: {{ .Values.dataplane.endpoints.public.path | quote }} + - name: "EDC_DATAPLANE_TOKEN_VALIDATION_ENDPOINT" + value: {{ include "txdc.controlplane.url.validation" .}} + - name: "WEB_HTTP_OBSERVABILITY_PORT" + value: {{ .Values.dataplane.endpoints.observability.port | quote }} + - name: "WEB_HTTP_OBSERVABILITY_PATH" + value: {{ .Values.dataplane.endpoints.observability.path | quote }} + - name: "TRACTUSX_API_OBSERVABILITY_ALLOW-INSECURE" + value: {{ .Values.dataplane.endpoints.observability.insecure | quote }} + + ####### + # AWS # + ####### + {{- if .Values.dataplane.aws.endpointOverride }} + - name: "EDC_AWS_ENDPOINT_OVERRIDE" + value: {{ .Values.dataplane.aws.endpointOverride | quote }} + {{- end }} + {{- if .Values.dataplane.aws.secretAccessKey }} + - name: "AWS_SECRET_ACCESS_KEY" + value: {{ .Values.dataplane.aws.secretAccessKey | quote }} + {{- end }} + {{- if .Values.dataplane.aws.accessKeyId }} + - name: "AWS_ACCESS_KEY_ID" + value: {{ .Values.dataplane.aws.accessKeyId | quote }} + {{- end }} + + ########### + ## VAULT ## + ########### + + - name: "EDC_VAULT_CLIENTID" + value: {{ .Values.vault.azure.client | quote }} + - name: "EDC_VAULT_TENANTID" + value: {{ .Values.vault.azure.tenant | quote }} + - name: "EDC_VAULT_NAME" + value: {{ .Values.vault.azure.name | quote }} + # only set the env var if config value not null + {{- if .Values.vault.azure.secret }} + - name: "EDC_VAULT_CLIENTSECRET" + value: {{ .Values.vault.azure.secret | quote }} + {{- end }} + # only set the env var if config value not null + {{- if .Values.vault.azure.certificate }} + - name: "EDC_VAULT_CERTIFICATE" + value: {{ .Values.vault.azure.certificate | quote }} + {{- end }} + + ###################################### + ## Additional environment variables ## + ###################################### + - name: "EDC_CONNECTOR_NAME" + value: {{ include "txdc.fullname" .}}-dataplane + {{- range $key, $value := .Values.dataplane.envValueFrom }} + - name: {{ $key | quote }} + valueFrom: + {{- tpl (toYaml $value) $ | nindent 16 }} + {{- end }} + {{- range $key, $value := .Values.dataplane.env }} + - name: {{ $key | quote }} + value: {{ $value | quote }} + {{- end }} + {{- if and (or .Values.dataplane.envSecretNames .Values.dataplane.envConfigMapNames) (or (gt (len .Values.dataplane.envSecretNames) 0) (gt (len .Values.dataplane.envConfigMapNames) 0)) }} + envFrom: + {{- range $value := .Values.dataplane.envSecretNames }} + - secretRef: + name: {{ $value | quote }} + {{- end }} + {{- range $value := .Values.dataplane.envConfigMapNames }} + - configMapRef: + name: {{ $value | quote }} + {{- end }} + {{- end }} + volumeMounts: + - name: "configuration" + mountPath: "/app/opentelemetry.properties" + subPath: "opentelemetry.properties" + - name: "configuration" + mountPath: "/app/logging.properties" + subPath: "logging.properties" + volumes: + - name: "configuration" + configMap: + name: {{ include "txdc.fullname" . }}-dataplane + items: + - key: "opentelemetry.properties" + path: "opentelemetry.properties" + - key: "logging.properties" + path: "logging.properties" + {{- with .Values.dataplane.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.dataplane.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.dataplane.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/tractusx-connector-azure-vault/templates/hpa-controlplane.yaml b/charts/tractusx-connector-azure-vault/templates/hpa-controlplane.yaml new file mode 100644 index 000000000..0c7d95272 --- /dev/null +++ b/charts/tractusx-connector-azure-vault/templates/hpa-controlplane.yaml @@ -0,0 +1,49 @@ +# + # 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 + # + + +{{- if .Values.controlplane.autoscaling.enabled }} +--- +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "txdc.fullname" . }}-controlplane + labels: + {{- include "txdc.controlplane.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "txdc.fullname" . }}-controlplane + minReplicas: {{ .Values.controlplane.autoscaling.minReplicas }} + maxReplicas: {{ .Values.controlplane.autoscaling.maxReplicas }} + metrics: + {{- if .Values.controlplane.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.controlplane.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.controlplane.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.controlplane.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/charts/tractusx-connector-azure-vault/templates/hpa-dataplane.yaml b/charts/tractusx-connector-azure-vault/templates/hpa-dataplane.yaml new file mode 100644 index 000000000..ddbee3823 --- /dev/null +++ b/charts/tractusx-connector-azure-vault/templates/hpa-dataplane.yaml @@ -0,0 +1,48 @@ +# + # 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 + # + +{{- if .Values.controlplane.autoscaling.enabled }} +--- +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "txdc.fullname" . }}-dataplane + labels: + {{- include "txdc.dataplane.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "txdc.fullname" . }}-dataplane + minReplicas: {{ .Values.dataplane.autoscaling.minReplicas }} + maxReplicas: {{ .Values.dataplane.autoscaling.maxReplicas }} + metrics: + {{- if .Values.dataplane.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.dataplane.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.dataplane.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.dataplane.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/charts/edc-controlplane/templates/ingress.yaml b/charts/tractusx-connector-azure-vault/templates/ingress-controlplane.yaml similarity index 77% rename from charts/edc-controlplane/templates/ingress.yaml rename to charts/tractusx-connector-azure-vault/templates/ingress-controlplane.yaml index cb58b5ac9..d70d00413 100644 --- a/charts/edc-controlplane/templates/ingress.yaml +++ b/charts/tractusx-connector-azure-vault/templates/ingress-controlplane.yaml @@ -1,8 +1,5 @@ # -# Copyright (c) 2023 ZF Friedrichshafen AG - # Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH - # Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - # Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + # Copyright (c) 2023 Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. @@ -20,14 +17,15 @@ # SPDX-License-Identifier: Apache-2.0 # -{{- $fullName := include "edc-controlplane.fullname" . }} -{{- $labels := include "edc-controlplane.labels" . | nindent 4 }} +{{- $fullName := include "txdc.fullname" . }} +{{- $controlLabels := include "txdc.controlplane.labels" . | nindent 4 }} +{{- $controlEdcEndpoints := .Values.controlplane.endpoints }} {{- $gitVersion := .Capabilities.KubeVersion.GitVersion }} -{{- $edcEndpoints := .Values.edc.endpoints }} {{- $namespace := .Release.Namespace }} -{{- range .Values.ingresses }} + +{{- range .Values.controlplane.ingresses }} {{- if and .enabled .endpoints }} -{{- $ingressName := printf "%s-%s" $fullName .hostname }} +{{- $controlIngressName := printf "%s-controlplane-%s" $fullName .hostname }} --- {{- if semverCompare ">=1.19-0" $gitVersion }} apiVersion: networking.k8s.io/v1 @@ -38,10 +36,10 @@ apiVersion: extensions/v1beta1 {{- end }} kind: Ingress metadata: - name: {{ $ingressName }} + name: {{ $controlIngressName }} namespace: {{ $namespace | default "default" | quote }} labels: - {{- $labels | nindent 2 }} + {{- $controlLabels | nindent 2 }} annotations: {{- if and .className (not (semverCompare ">=1.18-0" $gitVersion)) }} {{- if not (hasKey .annotations "kubernetes.io/ingress.class") }} @@ -71,7 +69,7 @@ spec: {{- if .tls.secretName }} secretName: {{ .tls.secretName }} {{- else }} - secretName: {{ $ingressName }}-tls + secretName: {{ $controlIngressName }}-tls {{- end }} {{- end }} rules: @@ -79,19 +77,17 @@ spec: http: paths: {{- $ingressEdcEndpoints := .endpoints }} - {{- range $name, $mapping := $edcEndpoints }} + {{- range $name, $mapping := $controlEdcEndpoints }} {{- if (has $name $ingressEdcEndpoints) }} - path: {{ $mapping.path }} pathType: Prefix backend: {{- if semverCompare ">=1.19-0" $gitVersion }} service: - name: {{ $fullName }} + name: {{ $fullName }}-controlplane port: number: {{ $mapping.port }} {{- else }} - serviceName: {{ $fullName }} - servicePort: {{ $mapping.port }} {{- end }} {{- end }} {{- end }} diff --git a/charts/tractusx-connector-azure-vault/templates/ingress-dataplane.yaml b/charts/tractusx-connector-azure-vault/templates/ingress-dataplane.yaml new file mode 100644 index 000000000..20788d85f --- /dev/null +++ b/charts/tractusx-connector-azure-vault/templates/ingress-dataplane.yaml @@ -0,0 +1,96 @@ +# + # 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 + # + +{{- $fullName := include "txdc.fullname" . }} +{{- $dataLabels := include "txdc.dataplane.labels" . | nindent 4 }} +{{- $dataEdcEndpoints := .Values.dataplane.endpoints }} +{{- $gitVersion := .Capabilities.KubeVersion.GitVersion }} +{{- $namespace := .Release.Namespace }} + +{{- range .Values.dataplane.ingresses }} +{{- if and .enabled .endpoints }} +{{- $dataIngressName := printf "%s-dataplane-%s" $fullName .hostname }} +--- +{{- if semverCompare ">=1.19-0" $gitVersion }} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" $gitVersion }} +apiVersion: networking.k8s.io/v1beta1 +{{- else }} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $dataIngressName }} + namespace: {{ $namespace | default "default" | quote }} + labels: + {{- $dataLabels | nindent 2 }} + annotations: + {{- if and .className (not (semverCompare ">=1.18-0" $gitVersion)) }} + {{- if not (hasKey .annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .annotations "kubernetes.io/ingress.class" .className}} + {{- end }} + {{- end }} + {{- if .certManager }} + {{- if .certManager.issuer }} + {{- $_ := set .annotations "cert-manager.io/issuer" .certManager.issuer}} + {{- end }} + {{- if .certManager.clusterIssuer }} + {{- $_ := set .annotations "cert-manager.io/cluster-issuer" .certManager.clusterIssuer}} + {{- end }} + {{- end }} + {{- with .annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .className (semverCompare ">=1.18-0" $gitVersion) }} + ingressClassName: {{ .className }} + {{- end }} + {{- if .hostname }} + {{- if .tls.enabled }} + tls: + - hosts: + - {{ .hostname }} + {{- if .tls.secretName }} + secretName: {{ .tls.secretName }} + {{- else }} + secretName: {{ $dataIngressName }}-tls + {{- end }} + {{- end }} + rules: + - host: {{ .hostname }} + http: + paths: + {{- $ingressEdcEndpoints := .endpoints }} + {{- range $name, $mapping := $dataEdcEndpoints }} + {{- if (has $name $ingressEdcEndpoints) }} + - path: {{ $mapping.path }} + pathType: Prefix + backend: + {{- if semverCompare ">=1.19-0" $gitVersion }} + service: + name: {{ $fullName }}-dataplane + port: + number: {{ $mapping.port }} + {{- else }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} +{{- end }}{{- /* end: if .enabled */}} +{{- end }}{{- /* end: range .Values.ingresses */}} diff --git a/charts/edc-dataplane/templates/service.yaml b/charts/tractusx-connector-azure-vault/templates/service-controlplane.yaml similarity index 61% rename from charts/edc-dataplane/templates/service.yaml rename to charts/tractusx-connector-azure-vault/templates/service-controlplane.yaml index e4d081776..acab58343 100644 --- a/charts/edc-dataplane/templates/service.yaml +++ b/charts/tractusx-connector-azure-vault/templates/service-controlplane.yaml @@ -24,28 +24,36 @@ apiVersion: v1 kind: Service metadata: - name: {{ include "edc-dataplane.fullname" . }} + name: {{ include "txdc.fullname" . }}-controlplane namespace: {{ .Release.Namespace | default "default" | quote }} labels: - {{- include "edc-dataplane.labels" . | nindent 4 }} + {{- include "txdc.controlplane.labels" . | nindent 4 }} spec: - type: {{ .Values.service.type }} + type: {{ .Values.controlplane.service.type }} ports: - - port: {{ .Values.edc.endpoints.default.port }} + - port: {{ .Values.controlplane.endpoints.default.port }} targetPort: default protocol: TCP name: default - - port: {{ .Values.edc.endpoints.control.port }} + - port: {{ .Values.controlplane.endpoints.control.port }} targetPort: control protocol: TCP name: control - - port: {{ .Values.edc.endpoints.public.port }} - targetPort: public + - port: {{ .Values.controlplane.endpoints.management.port }} + targetPort: management protocol: TCP - name: public - - port: {{ .Values.edc.endpoints.metrics.port }} + name: management + - port: {{ .Values.controlplane.endpoints.protocol.port }} + targetPort: protocol + protocol: TCP + name: protocol + - port: {{ .Values.controlplane.endpoints.metrics.port }} targetPort: metrics protocol: TCP name: metrics + - port: {{ .Values.controlplane.endpoints.observability.port}} + targetPort: observability + protocol: TCP + name: observability selector: - {{- include "edc-dataplane.selectorLabels" . | nindent 4 }} + {{- include "txdc.controlplane.selectorLabels" . | nindent 4 }} diff --git a/charts/tractusx-connector-azure-vault/templates/service-dataplane.yaml b/charts/tractusx-connector-azure-vault/templates/service-dataplane.yaml new file mode 100644 index 000000000..65df635da --- /dev/null +++ b/charts/tractusx-connector-azure-vault/templates/service-dataplane.yaml @@ -0,0 +1,52 @@ +# + # 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 + # + +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "txdc.fullname" . }}-dataplane + namespace: {{ .Release.Namespace | default "default" | quote }} + labels: + {{- include "txdc.dataplane.labels" . | nindent 4 }} +spec: + type: {{ .Values.dataplane.service.type }} + ports: + - port: {{ .Values.dataplane.endpoints.default.port }} + targetPort: default + protocol: TCP + name: default + - port: {{ .Values.dataplane.endpoints.control.port }} + targetPort: control + protocol: TCP + name: control + - port: {{ .Values.dataplane.endpoints.public.port }} + targetPort: public + protocol: TCP + name: public + - port: {{ .Values.dataplane.endpoints.observability.port }} + targetPort: observability + protocol: TCP + name: observability + - port: {{ .Values.dataplane.endpoints.metrics.port }} + targetPort: metrics + protocol: TCP + name: metrics + selector: + {{- include "txdc.dataplane.selectorLabels" . | nindent 4 }} diff --git a/charts/tractusx-connector-azure-vault/templates/serviceaccount.yaml b/charts/tractusx-connector-azure-vault/templates/serviceaccount.yaml new file mode 100644 index 000000000..fad069665 --- /dev/null +++ b/charts/tractusx-connector-azure-vault/templates/serviceaccount.yaml @@ -0,0 +1,36 @@ +# +# 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 +# + +{{- if .Values.serviceAccount.create -}} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "txdc.serviceAccountName" . }} + labels: + {{- include "txdc.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- with .Values.serviceAccount.imagePullSecrets }} +imagePullSecrets: + {{- toYaml . | nindent 2 }} +{{- end }} +{{- end }} diff --git a/charts/tractusx-connector-azure-vault/templates/tests/test-controlplane-readiness.yaml b/charts/tractusx-connector-azure-vault/templates/tests/test-controlplane-readiness.yaml new file mode 100644 index 000000000..db4765c0c --- /dev/null +++ b/charts/tractusx-connector-azure-vault/templates/tests/test-controlplane-readiness.yaml @@ -0,0 +1,35 @@ +# + # 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 + # + +--- +apiVersion: v1 +kind: Pod +metadata: + name: "{{include "txdc.fullname" .}}test-controlplane-readiness" + labels: + {{- include "txdc.controlplane.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: curlimages/curl + command: [ 'curl' ] + args: [ '{{- printf "http://%s-controlplane:%v%s/check/readiness" (include "txdc.fullname" $ ) $.Values.controlplane.endpoints.observability.port $.Values.controlplane.endpoints.observability.path -}}' ] + restartPolicy: Never diff --git a/charts/tractusx-connector-azure-vault/templates/tests/test-dataplane-readiness.yaml b/charts/tractusx-connector-azure-vault/templates/tests/test-dataplane-readiness.yaml new file mode 100644 index 000000000..0aed7c112 --- /dev/null +++ b/charts/tractusx-connector-azure-vault/templates/tests/test-dataplane-readiness.yaml @@ -0,0 +1,35 @@ +# + # 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 + # + +--- +apiVersion: v1 +kind: Pod +metadata: + name: "{{include "txdc.fullname" .}}test-dataplane-readiness" + labels: + {{- include "txdc.dataplane.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: curlimages/curl + command: [ 'curl' ] + args: [ '{{- printf "http://%s-dataplane:%v%s/check/readiness" (include "txdc.fullname" $ ) $.Values.dataplane.endpoints.observability.port $.Values.dataplane.endpoints.observability.path -}}' ] + restartPolicy: Never diff --git a/charts/tractusx-connector-azure-vault/values.yaml b/charts/tractusx-connector-azure-vault/values.yaml new file mode 100644 index 000000000..a2a80bcb3 --- /dev/null +++ b/charts/tractusx-connector-azure-vault/values.yaml @@ -0,0 +1,530 @@ +# +# Copyright (c) 2023 ZF Friedrichshafen AG +# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (c) 2021, 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 +# + + +--- +# Default values for eclipse-dataspace-connector. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +fullnameOverride: "" +nameOverride: "" + +# -- 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) +imagePullSecrets: [] + +customLabels: {} + +controlplane: + image: + # -- Which derivate of the control plane to use. when left empty the deployment will select the correct image automatically + repository: "" + # -- [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) to use + pullPolicy: IfNotPresent + # -- Overrides the image tag whose default is the chart appVersion + tag: "" + initContainers: [] + debug: + enabled: false + port: 1044 + suspendOnStart: false + internationalDataSpaces: + id: TXDC + description: Tractus-X Eclipse IDS Data Space Connector + title: "" + maintainer: "" + curator: "" + catalogId: TXDC-Catalog + livenessProbe: + # -- Whether to enable kubernetes [liveness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) + enabled: true + # -- seconds to wait before performing the first liveness check + initialDelaySeconds: 30 + # -- this fields specifies that kubernetes should perform a liveness check every 10 seconds + periodSeconds: 10 + # -- number of seconds after which the probe times out + timeoutSeconds: 5 + # -- when a probe fails kubernetes will try 6 times before giving up + failureThreshold: 6 + # -- number of consecutive successes for the probe to be considered successful after having failed + successThreshold: 1 + readinessProbe: + # -- Whether to enable kubernetes [readiness-probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) + enabled: true + # -- seconds to wait before performing the first readiness check + initialDelaySeconds: 30 + # -- this fields specifies that kubernetes should perform a readiness check every 10 seconds + periodSeconds: 10 + # -- number of seconds after which the probe times out + timeoutSeconds: 5 + # -- when a probe fails kubernetes will try 6 times before giving up + failureThreshold: 6 + # -- number of consecutive successes for the probe to be considered successful after having failed + successThreshold: 1 + # -- endpoints of the control plane + endpoints: + # -- default api for health checks, should not be added to any ingress + default: + # -- port for incoming api calls + port: 8080 + # -- path for incoming api calls + path: /api + # -- data management api, used by internal users, can be added to an ingress and must not be internet facing + management: + # -- port for incoming api calls + port: 8081 + # -- path for incoming api calls + path: /management + # -- authentication key, must be attached to each 'X-Api-Key' request header + authKey: "" + # -- control api, used for internal control calls. can be added to the internal ingress, but should probably not + control: + # -- port for incoming api calls + port: 8083 + # -- path for incoming api calls + path: /control + # -- ids api, used for inter connector communication and must be internet facing + protocol: + # -- port for incoming api calls + port: 8084 + # -- path for incoming api calls + path: /api/v1/ids + # -- metrics api, used for application metrics, must not be internet facing + metrics: + # -- port for incoming api calls + port: 9090 + # -- path for incoming api calls + path: /metrics + # -- observability api with unsecured access, must not be internet facing + observability: + # -- port for incoming API calls + port: 8085 + # -- observability api, provides /health /readiness and /liveness endpoints + 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 + annotations: {} + # -- additional labels for the pod + podLabels: {} + # -- additional annotations for the pod + podAnnotations: {} + # -- 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 + podSecurityContext: + seccompProfile: + # -- Restrict a Container's Syscalls with seccomp + type: RuntimeDefault + # -- Runs all processes within a pod with a special uid + runAsUser: 10001 + # -- Processes within a pod will belong to this guid + runAsGroup: 10001 + # -- The owner for volumes and any files created within volumes will belong to this guid + fsGroup: 10001 + # The [container security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container) defines privilege and access control settings for a Container within a pod + securityContext: + capabilities: + # -- Specifies which capabilities to drop to reduce syscall attack surface + drop: + - ALL + # -- Specifies which capabilities to add to issue specialized syscalls + add: [] + # -- Whether the root filesystem is mounted in read-only mode + readOnlyRootFilesystem: true + # -- Controls [Privilege Escalation](https://kubernetes.io/docs/concepts/security/pod-security-policy/#privilege-escalation) enabling setuid binaries changing the effective user ID + allowPrivilegeEscalation: false + # -- Requires the container to run without root privileges + runAsNonRoot: true + # -- The container's process will run with the specified uid + runAsUser: 10001 + # Extra environment variables that will be pass onto deployment pods + env: {} + # ENV_NAME: value + + # "valueFrom" environment variable references that will be added to deployment pods. Name is templated. + # ref: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#envvarsource-v1-core + envValueFrom: {} + # ENV_NAME: + # configMapKeyRef: + # name: configmap-name + # key: value_key + # secretKeyRef: + # name: secret-name + # key: value_key + + # [Kubernetes Secret Resource](https://kubernetes.io/docs/concepts/configuration/secret/) names to load environment variables from + envSecretNames: [] + # - first-secret + # - second-secret + + # [Kubernetes ConfigMap Resource](https://kubernetes.io/docs/concepts/configuration/configmap/) names to load environment variables from + envConfigMapNames: [] + # - first-config-map + # - second-config-map + + ## Ingress declaration to expose the network service. + ingresses: + ## Public / Internet facing Ingress + - enabled: false + # -- The hostname to be used to precisely map incoming traffic onto the underlying network service + hostname: "edc-control.local" + # -- Additional ingress annotations to add + annotations: {} + # -- EDC endpoints exposed by this ingress resource + endpoints: + - ids + # -- Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use + className: "" + # -- TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource + tls: + # -- Enables TLS on the ingress resource + enabled: false + # -- If present overwrites the default secret name + secretName: "" + ## Adds [cert-manager](https://cert-manager.io/docs/) annotations to the ingress resource + certManager: + # -- If preset enables certificate generation via cert-manager namespace scoped issuer + issuer: "" + # -- If preset enables certificate generation via cert-manager cluster-wide issuer + clusterIssuer: "" + ## Private / Intranet facing Ingress + - enabled: false + # -- The hostname to be used to precisely map incoming traffic onto the underlying network service + hostname: "edc-control.intranet" + # -- Additional ingress annotations to add + annotations: {} + # -- EDC endpoints exposed by this ingress resource + endpoints: + - management + - control + # -- Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use + className: "" + # -- TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource + tls: + # -- Enables TLS on the ingress resource + enabled: false + # -- If present overwrites the default secret name + secretName: "" + ## Adds [cert-manager](https://cert-manager.io/docs/) annotations to the ingress resource + certManager: + # -- If preset enables certificate generation via cert-manager namespace scoped issuer + issuer: "" + # -- If preset enables certificate generation via cert-manager cluster-wide issuer + clusterIssuer: "" + # -- declare where to mount [volumes](https://kubernetes.io/docs/concepts/storage/volumes/) into the container + volumeMounts: [] + # -- [volume](https://kubernetes.io/docs/concepts/storage/volumes/) directories + volumes: [] + # -- [resource management](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) for the container + resources: + {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + replicaCount: 1 + autoscaling: + # -- Enables [horizontal pod autoscaling](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) + enabled: false + # -- Minimal replicas if resource consumption falls below resource threshholds + minReplicas: 1 + # -- Maximum replicas if resource consumption exceeds resource threshholds + maxReplicas: 100 + # -- targetAverageUtilization of cpu provided to a pod + targetCPUUtilizationPercentage: 80 + # -- targetAverageUtilization of memory provided to a pod + targetMemoryUtilizationPercentage: 80 + # -- configuration of the [Open Telemetry Agent](https://opentelemetry.io/docs/instrumentation/java/automatic/agent-config/) to collect and expose metrics + opentelemetry: |- + otel.javaagent.enabled=false + otel.javaagent.debug=false + # -- configuration of the [Java Util Logging Facade](https://docs.oracle.com/javase/7/docs/technotes/guides/logging/overview.html) + logging: |- + .level=INFO + org.eclipse.edc.level=ALL + handlers=java.util.logging.ConsoleHandler + java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter + java.util.logging.ConsoleHandler.level=ALL + java.util.logging.SimpleFormatter.format=[%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS] [%4$-7s] %5$s%6$s%n + + # [node selector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector) to constrain pods to nodes + nodeSelector: {} + # [tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) to configure preferred nodes + tolerations: [] + # [affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity) to configure which nodes the pods can be scheduled on + affinity: {} + + url: + # -- Explicitly declared url for reaching the ids api (e.g. if ingresses not used) + ids: "" + +dataplane: + image: + # -- Which derivate of the data plane to use. when left empty the deployment will select the correct image automatically + repository: "" + # -- [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) to use + pullPolicy: IfNotPresent + # -- Overrides the image tag whose default is the chart appVersion + tag: "" + initContainers: [] + debug: + enabled: false + port: 1044 + suspendOnStart: false + livenessProbe: + # -- Whether to enable kubernetes [liveness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) + enabled: true + # -- seconds to wait before performing the first liveness check + initialDelaySeconds: 30 + # -- this fields specifies that kubernetes should perform a liveness check every 10 seconds + periodSeconds: 10 + # -- number of seconds after which the probe times out + timeoutSeconds: 5 + # -- when a probe fails kubernetes will try 6 times before giving up + failureThreshold: 6 + # -- number of consecutive successes for the probe to be considered successful after having failed + successThreshold: 1 + readinessProbe: + # -- Whether to enable kubernetes [readiness-probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) + enabled: true + # -- seconds to wait before performing the first readiness check + initialDelaySeconds: 30 + # -- this fields specifies that kubernetes should perform a liveness check every 10 seconds + periodSeconds: 10 + # -- number of seconds after which the probe times out + timeoutSeconds: 5 + # -- when a probe fails kubernetes will try 6 times before giving up + failureThreshold: 6 + # -- number of consecutive successes for the probe to be considered successful after having failed + successThreshold: 1 + 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 + port: 80 + endpoints: + default: + port: 8080 + path: /api + public: + port: 8081 + path: /api/public + control: + port: 8083 + path: /api/dataplane/control + observability: + # -- port for incoming API calls + port: 8085 + # -- observability api, provides /health /readiness and /liveness endpoints + path: /observability + # -- allow or disallow insecure access, i.e. access without authentication + insecure: true + metrics: + port: 9090 + path: /metrics + aws: + endpointOverride: "" + accessKeyId: "" + secretAccessKey: "" + # -- additional labels for the pod + podLabels: {} + # -- additional annotations for the pod + podAnnotations: {} + # -- 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 + podSecurityContext: + seccompProfile: + # -- Restrict a Container's Syscalls with seccomp + type: RuntimeDefault + # -- Runs all processes within a pod with a special uid + runAsUser: 10001 + # -- Processes within a pod will belong to this guid + runAsGroup: 10001 + # -- The owner for volumes and any files created within volumes will belong to this guid + fsGroup: 10001 + # The [container security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container) defines privilege and access control settings for a Container within a pod + securityContext: + capabilities: + # -- Specifies which capabilities to drop to reduce syscall attack surface + drop: + - ALL + # -- Specifies which capabilities to add to issue specialized syscalls + add: [] + # -- Whether the root filesystem is mounted in read-only mode + readOnlyRootFilesystem: true + # -- Controls [Privilege Escalation](https://kubernetes.io/docs/concepts/security/pod-security-policy/#privilege-escalation) enabling setuid binaries changing the effective user ID + allowPrivilegeEscalation: false + # -- Requires the container to run without root privileges + runAsNonRoot: true + # -- The container's process will run with the specified uid + runAsUser: 10001 + # Extra environment variables that will be pass onto deployment pods + env: {} + # ENV_NAME: value + + # "valueFrom" environment variable references that will be added to deployment pods. Name is templated. + # ref: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#envvarsource-v1-core + envValueFrom: {} + # ENV_NAME: + # configMapKeyRef: + # name: configmap-name + # key: value_key + # secretKeyRef: + # name: secret-name + # key: value_key + + # [Kubernetes Secret Resource](https://kubernetes.io/docs/concepts/configuration/secret/) names to load environment variables from + envSecretNames: [] + # - first-secret + # - second-secret + + # [Kubernetes ConfigMap Resource](https://kubernetes.io/docs/concepts/configuration/configmap/) names to load environment variables from + envConfigMapNames: [] + # - first-config-map + # - second-config-map + + ## Ingress declaration to expose the network service. + ingresses: + ## Public / Internet facing Ingress + - enabled: false + # -- The hostname to be used to precisely map incoming traffic onto the underlying network service + hostname: "edc-data.local" + # -- Additional ingress annotations to add + annotations: {} + # -- EDC endpoints exposed by this ingress resource + endpoints: + - public + # -- Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use + className: "" + # -- TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource + tls: + # -- Enables TLS on the ingress resource + enabled: false + # -- If present overwrites the default secret name + secretName: "" + ## Adds [cert-manager](https://cert-manager.io/docs/) annotations to the ingress resource + certManager: + # -- If preset enables certificate generation via cert-manager namespace scoped issuer + issuer: "" + # -- If preset enables certificate generation via cert-manager cluster-wide issuer + clusterIssuer: "" + # -- declare where to mount [volumes](https://kubernetes.io/docs/concepts/storage/volumes/) into the container + volumeMounts: [] + # -- [volume](https://kubernetes.io/docs/concepts/storage/volumes/) directories + volumes: [] + # -- [resource management](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) for the container + resources: + {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + replicaCount: 1 + autoscaling: + # -- Enables [horizontal pod autoscaling](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) + enabled: false + # -- Minimal replicas if resource consumption falls below resource threshholds + minReplicas: 1 + # -- Maximum replicas if resource consumption exceeds resource threshholds + maxReplicas: 100 + # -- targetAverageUtilization of cpu provided to a pod + targetCPUUtilizationPercentage: 80 + # -- targetAverageUtilization of memory provided to a pod + targetMemoryUtilizationPercentage: 80 + # -- configuration of the [Open Telemetry Agent](https://opentelemetry.io/docs/instrumentation/java/automatic/agent-config/) to collect and expose metrics + opentelemetry: |- + otel.javaagent.enabled=false + otel.javaagent.debug=false + # -- configuration of the [Java Util Logging Facade](https://docs.oracle.com/javase/7/docs/technotes/guides/logging/overview.html) + logging: |- + .level=INFO + org.eclipse.edc.level=ALL + handlers=java.util.logging.ConsoleHandler + java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter + java.util.logging.ConsoleHandler.level=ALL + java.util.logging.SimpleFormatter.format=[%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS] [%4$-7s] %5$s%6$s%n + # [node selector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector) to constrain pods to nodes + nodeSelector: {} + # [tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) to configure preferred nodes + tolerations: [] + # [affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity) to configure which nodes the pods can be scheduled on + affinity: {} + + url: + # -- Explicitly declared url for reaching the public api (e.g. if ingresses not used) + public: "" + +postgresql: + enabled: false + jdbcUrl: "" + username: "" + password: "" + +vault: + azure: + name: "" + client: "" + tenant: "" + secret: + certificate: + + secretNames: + transferProxyTokenSignerPrivateKey: transfer-proxy-token-signer-private-key + transferProxyTokenSignerPublicKey: transfer-proxy-token-signer-public-key + transferProxyTokenEncryptionAesKey: transfer-proxy-token-encryption-aes-key + dapsPrivateKey: daps-private-key + dapsPublicKey: daps-public-key + +daps: + url: "" + clientId: "" + paths: + jwks: /jwks.json + token: /token + +backendService: + httpProxyTokenReceiverUrl: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + # -- 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) + imagePullSecrets: [] diff --git a/charts/tractusx-connector/.helmignore b/charts/tractusx-connector-memory/.helmignore similarity index 100% rename from charts/tractusx-connector/.helmignore rename to charts/tractusx-connector-memory/.helmignore diff --git a/charts/tractusx-connector-memory/Chart.yaml b/charts/tractusx-connector-memory/Chart.yaml new file mode 100644 index 000000000..354d8a7a9 --- /dev/null +++ b/charts/tractusx-connector-memory/Chart.yaml @@ -0,0 +1,45 @@ +# +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (c) 2021, 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 +# + +--- +apiVersion: v2 +name: tractusx-connector-memory +description: A Helm chart for Tractus-X Eclipse Data Space Connector based on memory. Please only use this for development or testing purposes, never in production workloads! +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +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.4 +# 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.4" +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 new file mode 100644 index 000000000..654b28900 --- /dev/null +++ b/charts/tractusx-connector-memory/README.md @@ -0,0 +1,173 @@ +# tractusx-connector-memory + +![Version: 0.3.4](https://img.shields.io/badge/Version-0.3.4-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.3.4](https://img.shields.io/badge/AppVersion-0.3.4-informational?style=flat-square) + +A Helm chart for Tractus-X Eclipse Data Space Connector based on memory. Please only use this for development or testing purposes, never in production workloads! + +**Homepage:** + +This chart uses an in-memory secrets vault, which is required to contain the following secrets on application start: + +- `daps-cert`: contains the x509 certificate of the connector. +- `daps-key`: the private key of the x509 certificate + +These must be obtained from a DAPS instance, the process of which is out of the scope of this document. Alternatively, +self-signed certificates can be used for testing: + +```shell +openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout daps.key -out daps.cert -subj "/CN=test" +export DAPS_KEY="$(cat daps.key)" +export DAPS_CERT="$(cat daps.cert)" +``` + +## Launching the application + +The in-memory vault can be seeded directly with secrets that are passed in `:;:;...` format. +This config value can be passed to the runtime using the `vault.secrets` parameter. In addition, the runtime requires a +couple of configuration parameters, all of which can be found in the section below. Please also consider using +[this example configuration](https://github.com/eclipse-tractusx/tractusx-edc/blob/main/charts/tractusx-connector-memory/example.yaml) +to launch the application. + +Combined, run this shell command to start the in-memory Tractus-X EDC runtime: + +```shell +helm repo add tractusx-edc https://eclipse-tractusx.github.io/charts/dev +helm install my-release tractusx-edc/tractusx-connector-memory --version 0.3.4 \ + -f /example.yaml \ + --set vault.secrets="daps-cert:$DAPS_CERT;daps-key:$DAPS_KEY" \ +``` + +Note that `DAPS_CERT` contains the x509 certificate, `DAPS_KEY` contains the private key. + +## Source Code + +* + +## Values + +| 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},"observability":{"insecure":true,"path":"/observability","port":8085},"protocol":{"path":"/api/v1/ids","port":8084},"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.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.protocol | object | `{"path":"/api/v1/ids","port":8084}` | ids api, used for inter connector communication and must be internet facing | +| runtime.endpoints.protocol.path | string | `"/api/v1/ids"` | path for incoming api calls | +| runtime.endpoints.protocol.port | int | `8084` | 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 | `["protocol"]` | 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 new file mode 100644 index 000000000..630e63377 --- /dev/null +++ b/charts/tractusx-connector-memory/README.md.gotmpl @@ -0,0 +1,52 @@ +{{ template "chart.header" . }} + +{{ template "chart.deprecationWarning" . }} + +{{ template "chart.badgesSection" . }} + +{{ template "chart.description" . }} + +{{ template "chart.homepageLine" . }} + +This chart uses an in-memory secrets vault, which is required to contain the following secrets on application start: + +- `daps-cert`: contains the x509 certificate of the connector. +- `daps-key`: the private key of the x509 certificate + +These must be obtained from a DAPS instance, the process of which is out of the scope of this document. Alternatively, +self-signed certificates can be used for testing: + +```shell +openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout daps.key -out daps.cert -subj "/CN=test" +export DAPS_KEY="$(cat daps.key)" +export DAPS_CERT="$(cat daps.cert)" +``` + +## Launching the application + +The in-memory vault can be seeded directly with secrets that are passed in `:;:;...` format. +This config value can be passed to the runtime using the `vault.secrets` parameter. In addition, the runtime requires a +couple of configuration parameters, all of which can be found in the section below. Please also consider using +[this example configuration](https://github.com/eclipse-tractusx/tractusx-edc/blob/main/charts/tractusx-connector-memory/example.yaml) +to launch the application. + +Combined, run this shell command to start the in-memory Tractus-X EDC runtime: + +```shell +helm repo add tractusx-edc https://eclipse-tractusx.github.io/charts/dev +helm install my-release tractusx-edc/tractusx-connector-memory --version {{ .Version }} \ + -f /example.yaml \ + --set vault.secrets="daps-cert:$DAPS_CERT;daps-key:$DAPS_KEY" \ +``` + +Note that `DAPS_CERT` contains the x509 certificate, `DAPS_KEY` contains the private key. + +{{ template "chart.maintainersSection" . }} + +{{ template "chart.sourcesSection" . }} + +{{ template "chart.requirementsSection" . }} + +{{ template "chart.valuesSection" . }} + +{{ template "helm-docs.versionFooter" . }} diff --git a/charts/tractusx-connector-memory/example.yaml b/charts/tractusx-connector-memory/example.yaml new file mode 100644 index 000000000..2eb672a74 --- /dev/null +++ b/charts/tractusx-connector-memory/example.yaml @@ -0,0 +1,66 @@ +# +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (c) 2021, 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 +# + +## This file can be used to verify that the chart is working properly. It provides an exemplary configuration +## that is intended to be used with the supporting infrastructure. +## 1. install DAPS: +## helm install infrastructure edc-tests/deployment/src/main/resources/helm/test-infrastructure \ ─╯ +## --wait-for-jobs +## +## 2. install in-mem runtime. Note that the key and crt must match exactly the DAPS setup, c.f. edc-tests/deployment/src/main/resources/helm/test-infrastructure/values.yaml +## export DAPSKEY="" +## export DAPSCRT="" +## export YOUR_VAULT_SECRETS="daps-key:$DAPSKEY;daps-crt:$DAPSCRT" +## helm install trudy charts/tractusx-connector-memory -f charts/tractusx-connector-memory/example.yaml --set vault.secrets=$YOUR_VAULT_SECRETS + +--- +fullnameOverride: tx-inmem +runtime: + service: + type: NodePort + endpoints: + data: + authKey: password + image: + pullPolicy: Never + tag: "latest" + repository: "edc-runtime-memory" + securityContext: + # avoids some errors in the log: cannot write temp files of large multipart requests when R/O + readOnlyRootFilesystem: false + +vault: + secretNames: + transferProxyTokenSignerPublicKey: daps-crt + transferProxyTokenSignerPrivateKey: daps-key + transferProxyTokenEncryptionAesKey: aes-keysc + dapsPrivateKey: daps-key + dapsPublicKey: daps-crt + + # this must be set through CLI args: --set vault.secrets=$YOUR_VAULT_SECRETS where YOUR_VAULT_SECRETS should + # be a string in the format "key1:secret1;key2:secret2;..." + secrets: + +daps: + url: "http://ids-daps:4567" + clientId: "99:83:A7:17:86:FF:98:93:CE:A0:DD:A1:F1:36:FA:F6:0F:75:0A:23:keyid:99:83:A7:17:86:FF:98:93:CE:A0:DD:A1:F1:36:FA:F6:0F:75:0A:23" + +backendService: + httpProxyTokenReceiverUrl: "http://backend:8080" diff --git a/charts/tractusx-connector-memory/templates/NOTES.txt b/charts/tractusx-connector-memory/templates/NOTES.txt new file mode 100644 index 000000000..cd49a4d15 --- /dev/null +++ b/charts/tractusx-connector-memory/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the runtime URL by running these commands: +{{ with index .Values.runtime.ingresses 0}} +{{- if .enabled }} +{{- range .paths }} + http{{ if .tls }}s{{ end }}://{{ .hostname }}{{ .path }} +{{- end }} +{{- else if contains "NodePort" $.Values.runtime.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ $.Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "txdc.fullname" $ }}) + export NODE_IP=$(kubectl get nodes --namespace {{ $.Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" $.Values.runtime.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "txdc.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "txdc.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ $.Values.runtime.service.port }} +{{- else if contains "ClusterIP" $.Values.runtime.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ $.Release.Namespace }} -l "app.kubernetes.io/name={{ include "txdc.name" $ }},app.kubernetes.io/instance={{ $.Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ $.Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ $.Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} +{{- end }} diff --git a/charts/tractusx-connector-memory/templates/_helpers.tpl b/charts/tractusx-connector-memory/templates/_helpers.tpl new file mode 100644 index 000000000..b30831421 --- /dev/null +++ b/charts/tractusx-connector-memory/templates/_helpers.tpl @@ -0,0 +1,157 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "txdc.name" -}} +{{- default .Chart.Name .Values.nameOverride | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "txdc.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "txdc.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Control Common labels +*/}} +{{- define "txdc.labels" -}} +helm.sh/chart: {{ include "txdc.chart" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Control Common labels +*/}} +{{- define "txdc.runtime.labels" -}} +helm.sh/chart: {{ include "txdc.chart" . }} +{{ include "txdc.runtime.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/component: edc-runtime +app.kubernetes.io/part-of: edc +{{- end }} + +{{/* +Control Selector labels +*/}} +{{- define "txdc.runtime.selectorLabels" -}} +app.kubernetes.io/name: {{ include "txdc.name" . }}-runtime +app.kubernetes.io/instance: {{ .Release.Name }}-runtime +{{- end }} + +{{/* +Data Selector labels +*/}} +{{- define "txdc.dataplane.selectorLabels" -}} +app.kubernetes.io/name: {{ include "txdc.name" . }}-dataplane +app.kubernetes.io/instance: {{ .Release.Name }}-dataplane +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "txdc.runtime.serviceaccount.name" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "txdc.fullname" . ) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Control IDS URL +*/}} +{{- define "txdc.runtime.url.protocol" -}} +{{- if .Values.runtime.url.protocol }}{{/* if ids api url has been specified explicitly */}} +{{- .Values.runtime.url.protocol }} +{{- else }}{{/* else when ids api url has not been specified explicitly */}} +{{- with (index .Values.runtime.ingresses 0) }} +{{- if .enabled }}{{/* if ingress enabled */}} +{{- if .tls.enabled }}{{/* if TLS enabled */}} +{{- printf "https://%s" .hostname -}} +{{- else }}{{/* else when TLS not enabled */}} +{{- printf "http://%s" .hostname -}} +{{- end }}{{/* end if tls */}} +{{- else }}{{/* else when ingress not enabled */}} +{{- printf "http://%s-runtime:%v" ( include "txdc.fullname" $ ) $.Values.runtime.endpoints.protocol.port -}} +{{- end }}{{/* end if ingress */}} +{{- end }}{{/* end with ingress */}} +{{- end }}{{/* end if .Values.runtime.url.protocol */}} +{{- end }} + +{{/* +Observability URL +*/}} +{{- define "tdxc.runtime.url.readiness" -}} +{{- printf "http://%s-runtime:%v%s/check/readiness" (include "txdc.fullname" $ ) $.Values.runtime.endpoints.observability.port $.Values.runtime.endpoints.observability.path -}} +{{- end }} + +{{/* +Validation URL +*/}} +{{- define "txdc.runtime.url.validation" -}} +{{- printf "http://%s-runtime:%v%s/token" ( include "txdc.fullname" $ ) $.Values.runtime.endpoints.validation.port $.Values.runtime.endpoints.validation.path -}} +{{- end }} + +{{/* +Data Control URL +*/}} +{{- define "txdc.dataplane.url.control" -}} +{{- printf "http://%s-dataplane:%v%s" (include "txdc.fullname" . ) .Values.runtime.endpoints.control.port .Values.runtime.endpoints.control.path -}} +{{- end }} + +{{/* +Data Public URL +*/}} +{{- define "txdc.dataplane.url.public" -}} +{{- if .Values.runtime.url.public }}{{/* if public api url has been specified explicitly */}} +{{- .Values.runtime.url.public }} +{{- else }}{{/* else when public api url has not been specified explicitly */}} +{{- with (index .Values.runtime.ingresses 0) }} +{{- if .enabled }}{{/* if ingress enabled */}} +{{- if .tls.enabled }}{{/* if TLS enabled */}} +{{- printf "https://%s%s" .hostname $.Values.runtime.endpoints.public.path -}} +{{- else }}{{/* else when TLS not enabled */}} +{{- printf "http://%s%s" .hostname $.Values.runtime.endpoints.public.path -}} +{{- end }}{{/* end if tls */}} +{{- else }}{{/* else when ingress not enabled */}} +{{- printf "http://%s-dataplane:%v%s" (include "txdc.fullname" $ ) $.Values.runtime.endpoints.public.port $.Values.runtime.endpoints.public.path -}} +{{- end }}{{/* end if ingress */}} +{{- end }}{{/* end with ingress */}} +{{- end }}{{/* end if .Values.dataplane.url.public */}} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "txdc.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "txdc.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/charts/edc-dataplane/templates/configmap-env.yaml b/charts/tractusx-connector-memory/templates/configmap-runtime.yaml similarity index 85% rename from charts/edc-dataplane/templates/configmap-env.yaml rename to charts/tractusx-connector-memory/templates/configmap-runtime.yaml index 0e021734a..eddd69c55 100644 --- a/charts/edc-dataplane/templates/configmap-env.yaml +++ b/charts/tractusx-connector-memory/templates/configmap-runtime.yaml @@ -24,9 +24,10 @@ apiVersion: v1 kind: ConfigMap metadata: - name: {{ include "edc-dataplane.fullname" . }}-env + name: {{ include "txdc.fullname" . }}-runtime namespace: {{ .Release.Namespace | default "default" | quote }} labels: - {{- include "edc-dataplane.labels" . | nindent 4 }} + {{- include "txdc.runtime.labels" . | nindent 4 }} data: - {{- toYaml .Values.env | nindent 2 }} + logging.properties: |- + {{- .Values.runtime.logging | nindent 4 }} diff --git a/charts/tractusx-connector-memory/templates/deployment-runtime.yaml b/charts/tractusx-connector-memory/templates/deployment-runtime.yaml new file mode 100644 index 000000000..462a592d6 --- /dev/null +++ b/charts/tractusx-connector-memory/templates/deployment-runtime.yaml @@ -0,0 +1,308 @@ +# +# Copyright (c) 2023 ZF Friedrichshafen AG +# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (c) 2021, 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 +# + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "txdc.fullname" . }} + labels: + {{- include "txdc.runtime.labels" . | nindent 4 }} +spec: + {{- if not .Values.runtime.autoscaling.enabled }} + replicas: {{ .Values.runtime.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "txdc.runtime.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.runtime.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "txdc.runtime.selectorLabels" . | nindent 8 }} + {{- with .Values.runtime.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "txdc.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.runtime.podSecurityContext | nindent 8 }} + initContainers: + {{- toYaml .Values.runtime.initContainers | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.runtime.securityContext | nindent 12 }} + # either use the specified image, or use the default one + {{- if .Values.runtime.image.repository }} + image: "{{ .Values.runtime.image.repository }}:{{ .Values.runtime.image.tag | default .Chart.AppVersion }}" + {{- else }} + image: "tractusx/edc-runtime-memory:{{ .Values.runtime.image.tag | default .Chart.AppVersion }}" + {{- end }} + + imagePullPolicy: {{ .Values.runtime.image.pullPolicy }} + ports: + {{- range $key,$value := .Values.runtime.endpoints }} + - name: {{ $key }} + containerPort: {{ $value.port }} + protocol: TCP + {{- end }} + {{- if .Values.runtime.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: {{ .Values.runtime.endpoints.observability.path }}/check/liveness + port: {{ .Values.runtime.endpoints.observability.port }} + initialDelaySeconds: {{ .Values.runtime.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.runtime.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.runtime.livenessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.runtime.livenessProbe.failureThreshold }} + successThreshold: {{ .Values.runtime.livenessProbe.successThreshold }} + {{- end }} + {{- if .Values.runtime.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: {{ .Values.runtime.endpoints.observability.path }}/check/readiness + port: {{ .Values.runtime.endpoints.observability.port }} + initialDelaySeconds: {{ .Values.runtime.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.runtime.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.runtime.readinessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.runtime.readinessProbe.failureThreshold }} + successThreshold: {{ .Values.runtime.readinessProbe.successThreshold }} + {{- end }} + resources: + {{- toYaml .Values.runtime.resources | nindent 12 }} + env: + {{- if .Values.runtime.debug.enabled }} + - name: "JAVA_TOOL_OPTIONS" + {{- if .Values.runtime.debug.suspendOnStart }} + value: >- + {{ printf "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=%v" .Values.runtime.debug.port }} + {{- else }} + value: >- + {{ printf "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=%v" .Values.runtime.debug.port }} + {{- end }} + {{- end }} + + ######################## + ## DAPS CONFIGURATION ## + ######################## + + # see extension https://github.com/eclipse-edc/Connector/tree/main/extensions/iam/oauth2/oauth2-core + - name: EDC_OAUTH_CLIENT_ID + value: {{ .Values.daps.clientId | required ".Values.daps.clientId is required" | quote }} + - name: EDC_OAUTH_PROVIDER_JWKS_URL + value: {{ printf "%s%s" .Values.daps.url .Values.daps.paths.jwks }} + - name: EDC_OAUTH_TOKEN_URL + value: {{ printf "%s%s" .Values.daps.url .Values.daps.paths.token }} + - name: EDC_OAUTH_PRIVATE_KEY_ALIAS + value: {{ .Values.vault.secretNames.dapsPrivateKey | required ".Values.vault.secretNames.dapsPrivateKey is required" | quote }} + - name: EDC_OAUTH_PUBLIC_KEY_ALIAS + value: {{ .Values.vault.secretNames.dapsPublicKey | required ".Values.vault.secretNames.dapsPublicKey is required" | quote }} + + ####### + # API # + ####### + - name: "EDC_API_AUTH_KEY" + value: {{ .Values.runtime.endpoints.data.authKey | required ".Values.runtime.endpoints.data.authKey is required" | quote }} + - name: "WEB_HTTP_DEFAULT_PORT" + value: {{ .Values.runtime.endpoints.default.port | quote }} + - name: "WEB_HTTP_DEFAULT_PATH" + value: {{ .Values.runtime.endpoints.default.path | quote }} + {{- if or (eq (substr 0 3 .Values.runtime.image.tag) "0.1") (eq (substr 0 3 .Values.runtime.image.tag) "0.2") }} + # WEB_HTTP_DATA_PORT is renamed to WEB_HTTP_MANAGEMENT_PORT from version 0.2.1 and newer + # we will keep both settings for downward capabilities + - name: "WEB_HTTP_DATA_PORT" + value: {{ .Values.runtime.endpoints.data.port | quote }} + # WEB_HTTP_DATA_PATH is renamed to WEB_HTTP_MANAGEMENT_PATH from version 0.2.1 and newer + # we will keep both settings for downward capabilities + - name: "WEB_HTTP_DATA_PATH" + value: {{ .Values.runtime.endpoints.data.path | quote }} + {{- else }} + - name: "WEB_HTTP_MANAGEMENT_PORT" + value: {{ .Values.runtime.endpoints.data.port | quote }} + - name: "WEB_HTTP_MANAGEMENT_PATH" + value: {{ .Values.runtime.endpoints.data.path | quote }} + {{- end }} + - name: "WEB_HTTP_VALIDATION_PORT" + value: {{ .Values.runtime.endpoints.validation.port | quote }} + - name: "WEB_HTTP_VALIDATION_PATH" + value: {{ .Values.runtime.endpoints.validation.path | quote }} + - name: "WEB_HTTP_CONTROL_PORT" + value: {{ .Values.runtime.endpoints.control.port | quote }} + - name: "WEB_HTTP_CONTROL_PATH" + value: {{ .Values.runtime.endpoints.control.path | quote }} + - name: "WEB_HTTP_IDS_PORT" + value: {{ .Values.runtime.endpoints.protocol.port | quote }} + - name: "WEB_HTTP_IDS_PATH" + value: {{ .Values.runtime.endpoints.protocol.path | quote }} + - name: "WEB_HTTP_OBSERVABILITY_PORT" + value: {{ .Values.runtime.endpoints.observability.port | quote}} + - name: "WEB_HTTP_OBSERVABILITY_PATH" + value: {{ .Values.runtime.endpoints.observability.path | quote}} + - name: "TRACTUSX_API_OBSERVABILITY_ALLOW-INSECURE" + value: {{ .Values.runtime.endpoints.observability.insecure | quote }} + - name: "WEB_HTTP_PUBLIC_PORT" + value: {{ .Values.runtime.endpoints.public.port | quote }} + - name: "WEB_HTTP_PUBLIC_PATH" + value: {{ .Values.runtime.endpoints.public.path | quote }} + - name: "EDC_DATAPLANE_TOKEN_VALIDATION_ENDPOINT" + value: {{ include "txdc.runtime.url.validation" .}} + + ######### + ## IDS ## + ######### + - name: "IDS_WEBHOOK_ADDRESS" + value: {{ include "txdc.runtime.url.protocol" . | quote }} + - name: "EDC_IDS_ENDPOINT" + value: {{ printf "%s%s" (include "txdc.runtime.url.protocol" .) .Values.runtime.endpoints.protocol.path | quote }} + - name: "EDC_IDS_ID" + value: {{ printf "urn:connector:%s" (lower .Values.runtime.internationalDataSpaces.id) | quote }} + - name: "EDC_IDS_DESCRIPTION" + value: {{ .Values.runtime.internationalDataSpaces.description | quote }} + - name: "EDC_IDS_TITLE" + value: {{ .Values.runtime.internationalDataSpaces.title | quote }} + - name: "EDC_IDS_MAINTAINER" + value: {{ .Values.runtime.internationalDataSpaces.maintainer | quote }} + - name: "EDC_IDS_CURATOR" + value: {{ .Values.runtime.internationalDataSpaces.curator | quote }} + - name: "EDC_IDS_CATALOG_ID" + value: {{ printf "urn:catalog:%s" (lower .Values.runtime.internationalDataSpaces.catalogId) | quote }} + - name: "EDC_OAUTH_PROVIDER_AUDIENCE" + value: "idsc:IDS_CONNECTORS_ALL" + - name: "EDC_OAUTH_ENDPOINT_AUDIENCE" + value: {{ printf "%s%s%s" (include "txdc.runtime.url.protocol" . ) .Values.runtime.endpoints.protocol.path "/data" | quote }} + # this is the old setting name for 'EDC_OAUTH_ENDPOINT_AUDIENCE' and is mandatory for Produce EDC v0.1.2 and older + - name: "EDC_IDS_ENDPOINT_AUDIENCE" + value: {{ printf "%s%s%s" (include "txdc.runtime.url.protocol" . ) .Values.runtime.endpoints.protocol.path "/data" | quote }} + + ################ + ## DATA PLANE ## + ################ + + # see extension https://github.com/eclipse-tractusx/tractusx-edc/tree/develop/edc-extensions/dataplane-selector-configuration + - name: "EDC_DATAPLANE_SELECTOR_DEFAULTPLANE_URL" + value: {{ include "txdc.dataplane.url.control" . }}/transfer + - name: "EDC_DATAPLANE_SELECTOR_DEFAULTPLANE_SOURCETYPES" + value: "HttpData,AmazonS3" + - name: "EDC_DATAPLANE_SELECTOR_DEFAULTPLANE_DESTINATIONTYPES" + value: "HttpProxy,AmazonS3" + - name: "EDC_DATAPLANE_SELECTOR_DEFAULTPLANE_PROPERTIES" + value: |- + {{ printf "{ \"publicApiUrl\": \"%s\" }" (include "txdc.dataplane.url.public" . ) }} + + # see extension https://github.com/eclipse-edc/Connector/tree/main/extensions/control-plane/data-plane-transfer + - name: "EDC_TRANSFER_PROXY_ENDPOINT" + value: {{ include "txdc.dataplane.url.public" . }} + - name: "EDC_TRANSFER_PROXY_TOKEN_SIGNER_PRIVATEKEY_ALIAS" + value: {{ .Values.vault.secretNames.transferProxyTokenSignerPrivateKey | quote }} + - name: "EDC_TRANSFER_PROXY_TOKEN_VERIFIER_PUBLICKEY_ALIAS" + value: {{ .Values.vault.secretNames.transferProxyTokenSignerPublicKey | quote }} + + # see extension https://github.com/eclipse-edc/Connector/tree/main/extensions/control-plane/http-receiver + - name: "EDC_RECEIVER_HTTP_ENDPOINT" + value: {{ .Values.backendService.httpProxyTokenReceiverUrl | required ".Values.backendService.httpProxyTokenReceiverUrl is required" | quote }} + + ########### + ## VAULT ## + ########### + + # see extension https://github.com/eclipse-tractusx/tractusx-edc/tree/develop/edc-extensions/hashicorp-vault + - name: "SECRETS" + value: {{ .Values.vault.secrets | quote}} + + ##################### + ## DATA ENCRYPTION ## + ##################### + + # see extension https://github.com/eclipse-tractusx/tractusx-edc/tree/develop/edc-extensions/data-encryption + - name: "EDC_DATA_ENCRYPTION_KEYS_ALIAS" + value: {{ .Values.vault.secretNames.transferProxyTokenEncryptionAesKey | quote }} + - name: "EDC_DATA_ENCRYPTION_ALGORITHM" + value: "AES" + + ########################### + ## AAS WRAPPER EXTENSION ## + ########################### + - name: "EDC_CP_ADAPTER_CACHE_CATALOG_EXPIRE_AFTER" + 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 ## + ###################################### + {{- range $key, $value := .Values.runtime.envValueFrom }} + - name: {{ $key | quote }} + valueFrom: + {{- tpl (toYaml $value) $ | nindent 16 }} + {{- end }} + {{- range $key, $value := .Values.runtime.env }} + - name: {{ $key | quote }} + value: {{ $value | quote }} + {{- end }} + {{- if and (or .Values.runtime.envSecretNames .Values.runtime.envConfigMapNames) (or (gt (len .Values.runtime.envSecretNames) 0) (gt (len .Values.runtime.envConfigMapNames) 0)) }} + envFrom: + {{- range $value := .Values.runtime.envSecretNames }} + - secretRef: + name: {{ $value | quote }} + {{- end }} + {{- range $value := .Values.runtime.envConfigMapNames }} + - configMapRef: + name: {{ $value | quote }} + {{- end }} + {{- end }} + volumeMounts: + - name: "configuration" + mountPath: "/app/logging.properties" + subPath: "logging.properties" + volumes: + - name: "configuration" + configMap: + name: {{ include "txdc.fullname" . }}-runtime + items: + - key: "logging.properties" + path: "logging.properties" + {{- with .Values.runtime.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.runtime.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.runtime.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/edc-controlplane/templates/hpa.yaml b/charts/tractusx-connector-memory/templates/hpa-runtime.yaml similarity index 64% rename from charts/edc-controlplane/templates/hpa.yaml rename to charts/tractusx-connector-memory/templates/hpa-runtime.yaml index bc75d097a..2cce3c6f2 100644 --- a/charts/edc-controlplane/templates/hpa.yaml +++ b/charts/tractusx-connector-memory/templates/hpa-runtime.yaml @@ -20,33 +20,32 @@ # SPDX-License-Identifier: Apache-2.0 # -{{- if .Values.autoscaling.enabled }} +{{- if .Values.runtime.autoscaling.enabled }} --- apiVersion: autoscaling/v2beta1 kind: HorizontalPodAutoscaler metadata: - name: {{ include "edc-controlplane.fullname" . }} - namespace: {{ .Release.Namespace | default "default" | quote }} + name: {{ include "txdc.fullname" . }}-runtime labels: - {{- include "edc-controlplane.labels" . | nindent 4 }} + {{- include "txdc.runtime.labels" . | nindent 4 }} spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment - name: {{ include "edc-controlplane.fullname" . }} - minReplicas: {{ .Values.autoscaling.minReplicas }} - maxReplicas: {{ .Values.autoscaling.maxReplicas }} + name: {{ include "txdc.fullname" . }}-runtime + minReplicas: {{ .Values.runtime.autoscaling.minReplicas }} + maxReplicas: {{ .Values.runtime.autoscaling.maxReplicas }} metrics: - {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- if .Values.runtime.autoscaling.targetCPUUtilizationPercentage }} - type: Resource resource: name: cpu - targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + targetAverageUtilization: {{ .Values.runtime.autoscaling.targetCPUUtilizationPercentage }} {{- end }} - {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- if .Values.runtime.autoscaling.targetMemoryUtilizationPercentage }} - type: Resource resource: name: memory - targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + targetAverageUtilization: {{ .Values.runtime.autoscaling.targetMemoryUtilizationPercentage }} {{- end }} {{- end }} diff --git a/charts/edc-dataplane/templates/ingress.yaml b/charts/tractusx-connector-memory/templates/ingress-runtime.yaml similarity index 84% rename from charts/edc-dataplane/templates/ingress.yaml rename to charts/tractusx-connector-memory/templates/ingress-runtime.yaml index 716ac3d1f..a5209ebbf 100644 --- a/charts/edc-dataplane/templates/ingress.yaml +++ b/charts/tractusx-connector-memory/templates/ingress-runtime.yaml @@ -20,14 +20,15 @@ # SPDX-License-Identifier: Apache-2.0 # -{{- $fullName := include "edc-dataplane.fullname" . }} -{{- $labels := include "edc-dataplane.labels" . | nindent 4 }} +{{- $fullName := include "txdc.fullname" . }} +{{- $controlLabels := include "txdc.runtime.labels" . | nindent 4 }} +{{- $controlEdcEndpoints := .Values.runtime.endpoints }} {{- $gitVersion := .Capabilities.KubeVersion.GitVersion }} -{{- $edcEndpoints := .Values.edc.endpoints }} {{- $namespace := .Release.Namespace }} -{{- range .Values.ingresses }} + +{{- range .Values.runtime.ingresses }} {{- if and .enabled .endpoints }} -{{- $ingressName := printf "%s-%s" $fullName .hostname }} +{{- $controlIngressName := printf "%s-runtime-%s" $fullName .hostname }} --- {{- if semverCompare ">=1.19-0" $gitVersion }} apiVersion: networking.k8s.io/v1 @@ -38,10 +39,10 @@ apiVersion: extensions/v1beta1 {{- end }} kind: Ingress metadata: - name: {{ $ingressName }} + name: {{ $controlIngressName }} namespace: {{ $namespace | default "default" | quote }} labels: - {{- $labels | nindent 2 }} + {{- $controlLabels | nindent 2 }} annotations: {{- if and .className (not (semverCompare ">=1.18-0" $gitVersion)) }} {{- if not (hasKey .annotations "kubernetes.io/ingress.class") }} @@ -71,7 +72,7 @@ spec: {{- if .tls.secretName }} secretName: {{ .tls.secretName }} {{- else }} - secretName: {{ $ingressName }}-tls + secretName: {{ $controlIngressName }}-tls {{- end }} {{- end }} rules: @@ -79,19 +80,17 @@ spec: http: paths: {{- $ingressEdcEndpoints := .endpoints }} - {{- range $name, $mapping := $edcEndpoints }} + {{- range $name, $mapping := $controlEdcEndpoints }} {{- if (has $name $ingressEdcEndpoints) }} - path: {{ $mapping.path }} pathType: Prefix backend: {{- if semverCompare ">=1.19-0" $gitVersion }} service: - name: {{ $fullName }} + name: {{ $fullName }}-runtime port: number: {{ $mapping.port }} {{- else }} - serviceName: {{ $fullName }} - servicePort: {{ $mapping.port }} {{- end }} {{- end }} {{- end }} diff --git a/charts/edc-controlplane/templates/service.yaml b/charts/tractusx-connector-memory/templates/service-runtime.yaml similarity index 69% rename from charts/edc-controlplane/templates/service.yaml rename to charts/tractusx-connector-memory/templates/service-runtime.yaml index 18bc8bd55..d227ceb53 100644 --- a/charts/edc-controlplane/templates/service.yaml +++ b/charts/tractusx-connector-memory/templates/service-runtime.yaml @@ -24,36 +24,36 @@ apiVersion: v1 kind: Service metadata: - name: {{ include "edc-controlplane.fullname" . }} + name: {{ include "txdc.fullname" . }}-runtime namespace: {{ .Release.Namespace | default "default" | quote }} labels: - {{- include "edc-controlplane.labels" . | nindent 4 }} + {{- include "txdc.runtime.labels" . | nindent 4 }} spec: - type: {{ .Values.service.type }} + type: {{ .Values.runtime.service.type }} ports: - - port: {{ .Values.edc.endpoints.default.port }} + - port: {{ .Values.runtime.endpoints.default.port }} targetPort: default protocol: TCP name: default - - port: {{ .Values.edc.endpoints.control.port }} + - port: {{ .Values.runtime.endpoints.control.port }} targetPort: control protocol: TCP name: control - - port: {{ .Values.edc.endpoints.data.port }} + - port: {{ .Values.runtime.endpoints.data.port }} targetPort: data protocol: TCP name: data - - port: {{ .Values.edc.endpoints.validation.port }} + - port: {{ .Values.runtime.endpoints.validation.port }} targetPort: validation protocol: TCP name: validation - - port: {{ .Values.edc.endpoints.ids.port }} + - port: {{ .Values.runtime.endpoints.protocol.port }} targetPort: ids protocol: TCP name: ids - - port: {{ .Values.edc.endpoints.metrics.port }} - targetPort: metrics + - port: {{ .Values.runtime.endpoints.observability.port}} + targetPort: observability protocol: TCP - name: metrics + name: observability selector: - {{- include "edc-controlplane.selectorLabels" . | nindent 4 }} + {{- include "txdc.runtime.selectorLabels" . | nindent 4 }} diff --git a/charts/edc-controlplane/templates/serviceaccount.yaml b/charts/tractusx-connector-memory/templates/serviceaccount.yaml similarity index 84% rename from charts/edc-controlplane/templates/serviceaccount.yaml rename to charts/tractusx-connector-memory/templates/serviceaccount.yaml index 1f9d5045b..4a6e1ac07 100644 --- a/charts/edc-controlplane/templates/serviceaccount.yaml +++ b/charts/tractusx-connector-memory/templates/serviceaccount.yaml @@ -25,12 +25,15 @@ apiVersion: v1 kind: ServiceAccount metadata: - name: {{ include "edc-controlplane.serviceAccountName" . }} - namespace: {{ .Release.Namespace | default "default" | quote }} + name: {{ include "txdc.serviceAccountName" . }} labels: - {{- include "edc-controlplane.labels" . | nindent 4 }} + {{- include "txdc.labels" . | nindent 4 }} {{- with .Values.serviceAccount.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} +{{- with .Values.serviceAccount.imagePullSecrets }} +imagePullSecrets: + {{- toYaml . | nindent 2 }} +{{- end }} {{- end }} diff --git a/charts/edc-dataplane/templates/serviceaccount.yaml b/charts/tractusx-connector-memory/templates/tests/test-readiness.yaml similarity index 73% rename from charts/edc-dataplane/templates/serviceaccount.yaml rename to charts/tractusx-connector-memory/templates/tests/test-readiness.yaml index 39a44d35e..497e619a1 100644 --- a/charts/edc-dataplane/templates/serviceaccount.yaml +++ b/charts/tractusx-connector-memory/templates/tests/test-readiness.yaml @@ -20,17 +20,19 @@ # SPDX-License-Identifier: Apache-2.0 # -{{- if .Values.serviceAccount.create -}} --- apiVersion: v1 -kind: ServiceAccount +kind: Pod metadata: - name: {{ include "edc-dataplane.serviceAccountName" . }} - namespace: {{ .Release.Namespace | default "default" | quote }} + name: "{{ include "txdc.fullname" . }}-test-readiness" labels: - {{- include "edc-dataplane.labels" . | nindent 4 }} - {{- with .Values.serviceAccount.annotations }} + {{- include "txdc.runtime.labels" . | nindent 4 }} annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -{{- end }} + "helm.sh/hook": test +spec: + containers: + - name: wget + image: curlimages/curl + command: ['curl'] + args: ['{{ include "tdxc.runtime.url.readiness" . }}'] + restartPolicy: Never diff --git a/charts/tractusx-connector-memory/values.yaml b/charts/tractusx-connector-memory/values.yaml new file mode 100644 index 000000000..7df670250 --- /dev/null +++ b/charts/tractusx-connector-memory/values.yaml @@ -0,0 +1,316 @@ +# +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (c) 2021, 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 +# + +--- +# Default values for eclipse-dataspace-connector. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +fullnameOverride: "" +nameOverride: "" + +# -- 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) +imagePullSecrets: [] + +customLabels: {} + +runtime: + image: + repository: "" + # -- [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) to use + pullPolicy: IfNotPresent + # -- Overrides the image tag whose default is the chart appVersion + tag: "" + initContainers: [] + debug: + enabled: false + port: 1044 + suspendOnStart: false + internationalDataSpaces: + id: TXDC + description: Tractus-X Eclipse IDS Data Space Connector + title: "" + maintainer: "" + curator: "" + catalogId: TXDC-Catalog + livenessProbe: + # -- Whether to enable kubernetes [liveness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) + enabled: true + # -- seconds to wait before performing the first liveness check + initialDelaySeconds: 30 + # -- this fields specifies that kubernetes should perform a liveness check every 10 seconds + periodSeconds: 10 + # -- number of seconds after which the probe times out + timeoutSeconds: 5 + # -- when a probe fails kubernetes will try 6 times before giving up + failureThreshold: 6 + # -- number of consecutive successes for the probe to be considered successful after having failed + successThreshold: 1 + readinessProbe: + # -- Whether to enable kubernetes [readiness-probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) + enabled: true + # -- seconds to wait before performing the first readiness check + initialDelaySeconds: 30 + # -- this fields specifies that kubernetes should perform a readiness check every 10 seconds + periodSeconds: 10 + # -- number of seconds after which the probe times out + timeoutSeconds: 5 + # -- when a probe fails kubernetes will try 6 times before giving up + failureThreshold: 6 + # -- number of consecutive successes for the probe to be considered successful after having failed + successThreshold: 1 + # -- endpoints of the control plane + endpoints: + # -- default api for health checks, should not be added to any ingress + default: + # -- port for incoming api calls + port: 8080 + # -- path for incoming api calls + path: /api + # -- data management api, used by internal users, can be added to an ingress and must not be internet facing + data: + # -- port for incoming api calls + port: 8081 + # -- path for incoming api calls + path: /data + # -- authentication key, must be attached to each 'X-Api-Key' request header + authKey: "" + # -- validation api, only used by the data plane and should not be added to any ingress + validation: + # -- port for incoming api calls + port: 8082 + # -- path for incoming api calls + path: /validation + # -- control api, used for internal control calls. can be added to the internal ingress, but should probably not + control: + # -- port for incoming api calls + port: 8083 + # -- path for incoming api calls + path: /control + # -- ids api, used for inter connector communication and must be internet facing + protocol: + # -- port for incoming api calls + port: 8084 + # -- path for incoming api calls + path: /api/v1/ids + # -- observability api with unsecured access, must not be internet facing + observability: + # -- port for incoming API calls + port: 8085 + # -- observability api, provides /health /readiness and /liveness endpoints + path: /observability + # -- allow or disallow insecure access, i.e. access without authentication + insecure: true + 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 + annotations: {} + # -- additional labels for the pod + podLabels: {} + # -- additional annotations for the pod + podAnnotations: {} + # -- 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 + podSecurityContext: + seccompProfile: + # -- Restrict a Container's Syscalls with seccomp + type: RuntimeDefault + # -- Runs all processes within a pod with a special uid + runAsUser: 10001 + # -- Processes within a pod will belong to this guid + runAsGroup: 10001 + # -- The owner for volumes and any files created within volumes will belong to this guid + fsGroup: 10001 + # The [container security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container) defines privilege and access control settings for a Container within a pod + securityContext: + capabilities: + # -- Specifies which capabilities to drop to reduce syscall attack surface + drop: + - ALL + # -- Specifies which capabilities to add to issue specialized syscalls + add: [] + # -- Whether the root filesystem is mounted in read-only mode + readOnlyRootFilesystem: true + # -- Controls [Privilege Escalation](https://kubernetes.io/docs/concepts/security/pod-security-policy/#privilege-escalation) enabling setuid binaries changing the effective user ID + allowPrivilegeEscalation: false + # -- Requires the container to run without root privileges + runAsNonRoot: true + # -- The container's process will run with the specified uid + runAsUser: 10001 + # Extra environment variables that will be pass onto deployment pods + env: {} + # ENV_NAME: value + + # "valueFrom" environment variable references that will be added to deployment pods. Name is templated. + # ref: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#envvarsource-v1-core + envValueFrom: {} + # ENV_NAME: + # configMapKeyRef: + # name: configmap-name + # key: value_key + # secretKeyRef: + # name: secret-name + # key: value_key + + # [Kubernetes Secret Resource](https://kubernetes.io/docs/concepts/configuration/secret/) names to load environment variables from + envSecretNames: [] + # - first-secret + # - second-secret + + # [Kubernetes ConfigMap Resource](https://kubernetes.io/docs/concepts/configuration/configmap/) names to load environment variables from + envConfigMapNames: [] + # - first-config-map + # - second-config-map + + ## Ingress declaration to expose the network service. + ingresses: + ## Public / Internet facing Ingress + - enabled: false + # -- The hostname to be used to precisely map incoming traffic onto the underlying network service + hostname: "edc-control.local" + # -- Additional ingress annotations to add + annotations: {} + # -- EDC endpoints exposed by this ingress resource + endpoints: + - protocol + # -- Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use + className: "" + # -- TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource + tls: + # -- Enables TLS on the ingress resource + enabled: false + # -- If present overwrites the default secret name + secretName: "" + ## Adds [cert-manager](https://cert-manager.io/docs/) annotations to the ingress resource + certManager: + # -- If preset enables certificate generation via cert-manager namespace scoped issuer + issuer: "" + # -- If preset enables certificate generation via cert-manager cluster-wide issuer + clusterIssuer: "" + ## Private / Intranet facing Ingress + - enabled: false + # -- The hostname to be used to precisely map incoming traffic onto the underlying network service + hostname: "edc-control.intranet" + # -- Additional ingress annotations to add + annotations: {} + # -- EDC endpoints exposed by this ingress resource + endpoints: + - data + - control + # -- Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use + className: "" + # -- TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource + tls: + # -- Enables TLS on the ingress resource + enabled: false + # -- If present overwrites the default secret name + secretName: "" + ## Adds [cert-manager](https://cert-manager.io/docs/) annotations to the ingress resource + certManager: + # -- If preset enables certificate generation via cert-manager namespace scoped issuer + issuer: "" + # -- If preset enables certificate generation via cert-manager cluster-wide issuer + clusterIssuer: "" + # -- declare where to mount [volumes](https://kubernetes.io/docs/concepts/storage/volumes/) into the container + volumeMounts: [] + # -- [volume](https://kubernetes.io/docs/concepts/storage/volumes/) directories + volumes: [] + # -- [resource management](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) for the container + resources: + {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + replicaCount: 1 + autoscaling: + # -- Enables [horizontal pod autoscaling](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) + enabled: false + # -- Minimal replicas if resource consumption falls below resource threshholds + minReplicas: 1 + # -- Maximum replicas if resource consumption exceeds resource threshholds + maxReplicas: 100 + # -- targetAverageUtilization of cpu provided to a pod + targetCPUUtilizationPercentage: 80 + # -- targetAverageUtilization of memory provided to a pod + targetMemoryUtilizationPercentage: 80 + # -- configuration of the [Java Util Logging Facade](https://docs.oracle.com/javase/7/docs/technotes/guides/logging/overview.html) + logging: |- + .level=INFO + org.eclipse.edc.level=ALL + handlers=java.util.logging.ConsoleHandler + java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter + java.util.logging.ConsoleHandler.level=ALL + java.util.logging.SimpleFormatter.format=[%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS] [%4$-7s] %5$s%6$s%n + + # [node selector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector) to constrain pods to nodes + nodeSelector: {} + # [tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) to configure preferred nodes + tolerations: [] + # [affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity) to configure which nodes the pods can be scheduled on + affinity: {} + + url: + # -- Explicitly declared url for reaching the ids api (e.g. if ingresses not used) + ids: "" + public: "" + readiness: "" + +vault: + # secrets can be seeded by supplying them in a comma separated list key1:secret2,key2:secret2 + secrets: "" + secretNames: + transferProxyTokenSignerPrivateKey: transfer-proxy-token-signer-private-key + transferProxyTokenSignerPublicKey: transfer-proxy-token-signer-public-key + transferProxyTokenEncryptionAesKey: transfer-proxy-token-encryption-aes-key + dapsPrivateKey: daps-private-key + dapsPublicKey: daps-public-key + +daps: + url: "" + clientId: "" + paths: + jwks: /jwks.json + token: /token + +backendService: + httpProxyTokenReceiverUrl: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + # -- 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) + imagePullSecrets: [] diff --git a/charts/tractusx-connector/Chart.yaml b/charts/tractusx-connector/Chart.yaml index f9e4322c6..b13ff5d01 100644 --- a/charts/tractusx-connector/Chart.yaml +++ b/charts/tractusx-connector/Chart.yaml @@ -23,7 +23,11 @@ --- apiVersion: v2 name: tractusx-connector -description: A Helm chart for Tractus-X Eclipse Data Space Connector +description: | + A Helm chart for Tractus-X Eclipse Data Space Connector. The connector deployment consists of two runtime consists of a + Control Plane and a Data Plane. Note that _no_ external dependencies such as a PostgreSQL database and HashiCorp Vault are included. + + This chart is intended for use with an _existing_ PostgreSQL database and an _existing_ HashiCorp Vault. # A chart can be either an 'application' or a 'library' chart. # # Application charts are a collection of templates that can be packaged into versioned archives @@ -36,12 +40,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.4 # 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.4" 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..436d347d6 100644 --- a/charts/tractusx-connector/README.md +++ b/charts/tractusx-connector/README.md @@ -1,16 +1,44 @@ # 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.4](https://img.shields.io/badge/Version-0.3.4-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.3.4](https://img.shields.io/badge/AppVersion-0.3.4-informational?style=flat-square) -A Helm chart for Tractus-X Eclipse Data Space Connector +A Helm chart for Tractus-X Eclipse Data Space Connector. The connector deployment consists of two runtime consists of a +Control Plane and a Data Plane. Note that _no_ external dependencies such as a PostgreSQL database and HashiCorp Vault are included. + +This chart is intended for use with an _existing_ PostgreSQL database and an _existing_ HashiCorp Vault. **Homepage:** -## TL;DR +This chart uses Hashicorp Vault, which is expected to contain the following secrets on application start: + +- `daps-cert`: contains the x509 certificate of the connector. +- `daps-key`: the private key of the x509 certificate +- `aes-keys`: a 128bit, 256bit or 512bit string used to encrypt data. Must be stored in base64 format. + +These must be obtained from a DAPS instance, the process of which is out of the scope of this document. Alternatively, +self-signed certificates can be used for testing: + +```shell +openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout daps.key -out daps.cert -subj "/CN=test" +export DAPS_KEY="$(cat daps.key)" +export DAPS_CERT="$(cat daps.cert)" +``` + +## Launching the application + +The following requirements must be met before launching the application: + +- Write access to a HashiCorp Vault instance is required to run this chart +- Secrets are seeded in advance + +Please also consider using [this example configuration](https://github.com/eclipse-tractusx/tractusx-edc/blob/main/edc-tests/deployment/src/main/resources/helm/tractusx-connector-test.yaml) +to launch the application. +Combined, run this shell command to start the in-memory Tractus-X EDC runtime: ```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-azure-vault --version 0.3.4 \ + -f /tractusx-connector-test.yaml ``` ## Source Code @@ -28,23 +56,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 +78,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 | `[]` | | @@ -67,7 +93,7 @@ helm install my-release tractusx-edc/tractusx-connector --version 0.3.2 | controlplane.ingresses[0].certManager.issuer | string | `""` | If preset enables certificate generation via cert-manager namespace scoped issuer | | controlplane.ingresses[0].className | string | `""` | Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use | | controlplane.ingresses[0].enabled | bool | `false` | | -| controlplane.ingresses[0].endpoints | list | `["ids"]` | EDC endpoints exposed by this ingress resource | +| controlplane.ingresses[0].endpoints | list | `["protocol"]` | EDC endpoints exposed by this ingress resource | | controlplane.ingresses[0].hostname | string | `"edc-control.local"` | The hostname to be used to precisely map incoming traffic onto the underlying network service | | controlplane.ingresses[0].tls | object | `{"enabled":false,"secretName":""}` | TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource | | controlplane.ingresses[0].tls.enabled | bool | `false` | Enables TLS on the ingress resource | @@ -77,7 +103,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 +174,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 | `[]` | | @@ -217,13 +244,6 @@ helm install my-release tractusx-edc/tractusx-connector --version 0.3.2 | 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"` | | diff --git a/charts/tractusx-connector/README.md.gotmpl b/charts/tractusx-connector/README.md.gotmpl index b1671f5a2..267a294f3 100644 --- a/charts/tractusx-connector/README.md.gotmpl +++ b/charts/tractusx-connector/README.md.gotmpl @@ -8,11 +8,36 @@ {{ template "chart.homepageLine" . }} -## TL;DR +This chart uses Hashicorp Vault, which is expected to contain the following secrets on application start: + +- `daps-cert`: contains the x509 certificate of the connector. +- `daps-key`: the private key of the x509 certificate +- `aes-keys`: a 128bit, 256bit or 512bit string used to encrypt data. Must be stored in base64 format. + +These must be obtained from a DAPS instance, the process of which is out of the scope of this document. Alternatively, +self-signed certificates can be used for testing: + +```shell +openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout daps.key -out daps.cert -subj "/CN=test" +export DAPS_KEY="$(cat daps.key)" +export DAPS_CERT="$(cat daps.cert)" +``` + +## Launching the application + +The following requirements must be met before launching the application: + +- Write access to a HashiCorp Vault instance is required to run this chart +- Secrets are seeded in advance + +Please also consider using [this example configuration](https://github.com/eclipse-tractusx/tractusx-edc/blob/main/edc-tests/deployment/src/main/resources/helm/tractusx-connector-test.yaml) +to launch the application. +Combined, run this shell command to start the in-memory Tractus-X EDC runtime: ```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-azure-vault --version {{ .Version }} \ + -f /tractusx-connector-test.yaml ``` {{ template "chart.maintainersSection" . }} diff --git a/charts/tractusx-connector/templates/_helpers.tpl b/charts/tractusx-connector/templates/_helpers.tpl index ecc8ff1d2..701e6fc75 100644 --- a/charts/tractusx-connector/templates/_helpers.tpl +++ b/charts/tractusx-connector/templates/_helpers.tpl @@ -110,9 +110,9 @@ Create the name of the service account to use {{/* Control IDS URL */}} -{{- define "txdc.controlplane.url.ids" -}} -{{- if .Values.controlplane.url.ids }}{{/* if ids api url has been specified explicitly */}} -{{- .Values.controlplane.url.ids }} +{{- define "txdc.controlplane.url.protocol" -}} +{{- if .Values.controlplane.url.protocol }}{{/* if ids api url has been specified explicitly */}} +{{- .Values.controlplane.url.protocol }} {{- else }}{{/* else when ids api url has not been specified explicitly */}} {{- with (index .Values.controlplane.ingresses 0) }} {{- if .enabled }}{{/* if ingress enabled */}} @@ -122,17 +122,17 @@ Control IDS URL {{- printf "http://%s" .hostname -}} {{- end }}{{/* end if tls */}} {{- else }}{{/* else when ingress not enabled */}} -{{- printf "http://%s-controlplane:%v" ( include "txdc.fullname" $ ) $.Values.controlplane.endpoints.ids.port -}} +{{- printf "http://%s-controlplane:%v" ( include "txdc.fullname" $ ) $.Values.controlplane.endpoints.protocol.port -}} {{- end }}{{/* end if ingress */}} {{- end }}{{/* end with ingress */}} -{{- end }}{{/* end if .Values.controlplane.url.ids */}} +{{- end }}{{/* end if .Values.controlplane.url.protocol */}} {{- end }} {{/* Validation URL */}} {{- define "txdc.controlplane.url.validation" -}} -{{- printf "http://%s-controlplane:%v%s/token" ( include "txdc.fullname" $ ) $.Values.controlplane.endpoints.validation.port $.Values.controlplane.endpoints.validation.path -}} +{{- printf "http://%s-controlplane:%v%s/token" ( include "txdc.fullname" $ ) $.Values.controlplane.endpoints.control.port $.Values.controlplane.endpoints.control.path -}} {{- end }} {{/* diff --git a/charts/tractusx-connector/templates/configmap-dataplane.yaml b/charts/tractusx-connector/templates/configmap-dataplane.yaml index 4f4c1a456..87fd401c3 100644 --- a/charts/tractusx-connector/templates/configmap-dataplane.yaml +++ b/charts/tractusx-connector/templates/configmap-dataplane.yaml @@ -1,3 +1,25 @@ +# +# Copyright (c) 2023 ZF Friedrichshafen AG +# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (c) 2021, 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 +# + --- apiVersion: v1 kind: ConfigMap diff --git a/charts/tractusx-connector/templates/deployment-controlplane.yaml b/charts/tractusx-connector/templates/deployment-controlplane.yaml index 691047c0f..64e8fa5f8 100644 --- a/charts/tractusx-connector/templates/deployment-controlplane.yaml +++ b/charts/tractusx-connector/templates/deployment-controlplane.yaml @@ -59,18 +59,12 @@ spec: - name: {{ .Chart.Name }} securityContext: {{- toYaml .Values.controlplane.securityContext | nindent 12 }} + + # either use the specified image, or use the default one {{- if .Values.controlplane.image.repository }} image: "{{ .Values.controlplane.image.repository }}:{{ .Values.controlplane.image.tag | default .Chart.AppVersion }}" - {{- else if and .Values.postgresql.enabled .Values.vault.hashicorp.enabled }} - image: "ghcr.io/catenax-ng/tx-tractusx-edc/edc-controlplane-postgresql-hashicorp-vault:{{ .Values.controlplane.image.tag | default .Chart.AppVersion }}" - {{- else if and .Values.postgresql.enabled .Values.vault.azure.enabled }} - image: "ghcr.io/catenax-ng/tx-tractusx-edc/edc-controlplane-postgresql:{{ .Values.controlplane.image.tag | default .Chart.AppVersion }}" - {{- else if .Values.vault.hashicorp.enabled }} - image: "ghcr.io/catenax-ng/tx-tractusx-edc/edc-controlplane-memory-hashicorp-vault:{{ .Values.controlplane.image.tag | default .Chart.AppVersion }}" - {{- else if .Values.vault.azure.enabled }} - image: "ghcr.io/catenax-ng/tx-tractusx-edc/edc-controlplane-memory:{{ .Values.controlplane.image.tag | default .Chart.AppVersion }}" {{- else }} - {{- fail "cannot choose control-plane image automatically based on configuration" }} + image: "tractusx/edc-controlplane-postgresql-hashicorp-vault:{{ .Values.controlplane.image.tag | default .Chart.AppVersion }}" {{- end }} imagePullPolicy: {{ .Values.controlplane.image.pullPolicy }} ports: @@ -106,7 +100,7 @@ spec: env: {{- if .Values.controlplane.debug.enabled }} - name: "JAVA_TOOL_OPTIONS" - {{- if and .Values.controlplane.debug.enabled .Values.controlplane.debug.suspendOnStart }} + {{- if .Values.controlplane.debug.suspendOnStart }} value: >- {{ printf "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=%v" .Values.controlplane.debug.port }} {{- else }} @@ -128,45 +122,30 @@ spec: value: {{ printf "%s%s" .Values.daps.url .Values.daps.paths.token }} - name: EDC_OAUTH_PRIVATE_KEY_ALIAS value: {{ .Values.vault.secretNames.dapsPrivateKey | required ".Values.vault.secretNames.dapsPrivateKey is required" | quote }} - - name: EDC_OAUTH_PUBLIC_KEY_ALIAS + - name: EDC_OAUTH_CERTIFICATE_ALIAS value: {{ .Values.vault.secretNames.dapsPublicKey | required ".Values.vault.secretNames.dapsPublicKey is required" | quote }} ####### # API # ####### - name: "EDC_API_AUTH_KEY" - value: {{ .Values.controlplane.endpoints.data.authKey | required ".Values.controlplane.endpoints.data.authKey is required" | quote }} + value: {{ .Values.controlplane.endpoints.management.authKey | required ".Values.controlplane.endpoints.management.authKey is required" | quote }} - name: "WEB_HTTP_DEFAULT_PORT" value: {{ .Values.controlplane.endpoints.default.port | quote }} - name: "WEB_HTTP_DEFAULT_PATH" value: {{ .Values.controlplane.endpoints.default.path | quote }} - {{- if or (eq (substr 0 3 .Values.controlplane.image.tag) "0.1") (eq (substr 0 3 .Values.controlplane.image.tag) "0.2") }} - # WEB_HTTP_DATA_PORT is renamed to WEB_HTTP_MANAGEMENT_PORT from version 0.2.1 and newer - # we will keep both settings for downward capabilities - - name: "WEB_HTTP_DATA_PORT" - value: {{ .Values.controlplane.endpoints.data.port | quote }} - # WEB_HTTP_DATA_PATH is renamed to WEB_HTTP_MANAGEMENT_PATH from version 0.2.1 and newer - # we will keep both settings for downward capabilities - - name: "WEB_HTTP_DATA_PATH" - value: {{ .Values.controlplane.endpoints.data.path | quote }} - {{- else }} - name: "WEB_HTTP_MANAGEMENT_PORT" - value: {{ .Values.controlplane.endpoints.data.port | quote }} + value: {{ .Values.controlplane.endpoints.management.port | quote }} - name: "WEB_HTTP_MANAGEMENT_PATH" - value: {{ .Values.controlplane.endpoints.data.path | quote }} - {{- end }} - - name: "WEB_HTTP_VALIDATION_PORT" - value: {{ .Values.controlplane.endpoints.validation.port | quote }} - - name: "WEB_HTTP_VALIDATION_PATH" - value: {{ .Values.controlplane.endpoints.validation.path | quote }} + value: {{ .Values.controlplane.endpoints.management.path | quote }} - name: "WEB_HTTP_CONTROL_PORT" value: {{ .Values.controlplane.endpoints.control.port | quote }} - name: "WEB_HTTP_CONTROL_PATH" value: {{ .Values.controlplane.endpoints.control.path | quote }} - - name: "WEB_HTTP_IDS_PORT" - value: {{ .Values.controlplane.endpoints.ids.port | quote }} - - name: "WEB_HTTP_IDS_PATH" - value: {{ .Values.controlplane.endpoints.ids.path | quote }} + - name: "WEB_HTTP_PROTOCOL_PORT" + value: {{ .Values.controlplane.endpoints.protocol.port | quote }} + - name: "WEB_HTTP_PROTOCOL_PATH" + value: {{ .Values.controlplane.endpoints.protocol.path | quote }} - name: "WEB_HTTP_OBSERVABILITY_PORT" value: {{ .Values.controlplane.endpoints.observability.port | quote}} - name: "WEB_HTTP_OBSERVABILITY_PATH" @@ -178,9 +157,9 @@ spec: ## IDS ## ######### - name: "IDS_WEBHOOK_ADDRESS" - value: {{ include "txdc.controlplane.url.ids" . | quote }} + value: {{ include "txdc.controlplane.url.protocol" . | quote }} - name: "EDC_IDS_ENDPOINT" - value: {{ printf "%s%s" (include "txdc.controlplane.url.ids" .) .Values.controlplane.endpoints.ids.path | quote }} + value: {{ printf "%s%s" (include "txdc.controlplane.url.protocol" .) .Values.controlplane.endpoints.protocol.path | quote }} - name: "EDC_IDS_ID" value: {{ printf "urn:connector:%s" (lower .Values.controlplane.internationalDataSpaces.id) | quote }} - name: "EDC_IDS_DESCRIPTION" @@ -196,12 +175,10 @@ spec: - name: "EDC_OAUTH_PROVIDER_AUDIENCE" value: "idsc:IDS_CONNECTORS_ALL" - name: "EDC_OAUTH_ENDPOINT_AUDIENCE" - value: {{ printf "%s%s%s" (include "txdc.controlplane.url.ids" . ) .Values.controlplane.endpoints.ids.path "/data" | quote }} + value: {{ printf "%s%s%s" (include "txdc.controlplane.url.protocol" . ) .Values.controlplane.endpoints.protocol.path "/data" | quote }} # this is the old setting name for 'EDC_OAUTH_ENDPOINT_AUDIENCE' and is mandatory for Produce EDC v0.1.2 and older - name: "EDC_IDS_ENDPOINT_AUDIENCE" - value: {{ printf "%s%s%s" (include "txdc.controlplane.url.ids" . ) .Values.controlplane.endpoints.ids.path "/data" | quote }} - - {{- if .Values.postgresql.enabled }} + value: {{ printf "%s%s%s" (include "txdc.controlplane.url.protocol" . ) .Values.controlplane.endpoints.protocol.path "/data" | quote }} ################ ## POSTGRESQL ## @@ -256,13 +233,12 @@ spec: value: {{ .Values.postgresql.password | required ".Values.postgresql.password is required" | quote }} - name: "EDC_DATASOURCE_TRANSFERPROCESS_URL" value: {{ .Values.postgresql.jdbcUrl | required ".Values.postgresql.jdbcUrl is required" | quote }} - {{- end }} ################ ## DATA PLANE ## ################ - # see extension https://github.com/eclipse-tractusx/tractusx-edc/tree/develop/edc-extensions/dataplane-selector-configuration + # see extension https://github.com/eclipse-tractusx/tractusx-edc/tree/main/edc-extensions/dataplane-selector-configuration - name: "EDC_DATAPLANE_SELECTOR_DEFAULTPLANE_URL" value: {{ include "txdc.dataplane.url.control" . }}/transfer - name: "EDC_DATAPLANE_SELECTOR_DEFAULTPLANE_SOURCETYPES" @@ -281,17 +257,16 @@ spec: - name: "EDC_TRANSFER_PROXY_TOKEN_VERIFIER_PUBLICKEY_ALIAS" value: {{ .Values.vault.secretNames.transferProxyTokenSignerPublicKey | quote }} - # see extension https://github.com/eclipse-edc/Connector/tree/main/extensions/control-plane/http-receiver + # see extension https://github.com/eclipse-edc/Connector/tree/main/extensions/control-plane/transfer/transfer-pull-http-dynamic-receiver - - name: "EDC_RECEIVER_HTTP_ENDPOINT" + - name: "EDC_RECEIVER_HTTP_DYNAMIC_ENDPOINT" value: {{ .Values.backendService.httpProxyTokenReceiverUrl | required ".Values.backendService.httpProxyTokenReceiverUrl is required" | quote }} ########### ## VAULT ## ########### - {{- if .Values.vault.hashicorp.enabled }} - # see extension https://github.com/eclipse-tractusx/tractusx-edc/tree/develop/edc-extensions/hashicorp-vault + # see extension https://github.com/eclipse-tractusx/tractusx-edc/tree/main/edc-extensions/hashicorp-vault - name: "EDC_VAULT_HASHICORP_URL" value: {{ .Values.vault.hashicorp.url | required ".Values.vault.hashicorp.url is required" | quote }} - name: "EDC_VAULT_HASHICORP_TOKEN" @@ -306,33 +281,12 @@ spec: value: {{ .Values.vault.hashicorp.paths.secret | quote }} - name: "EDC_VAULT_HASHICORP_API_HEALTH_CHECK_PATH" value: {{ .Values.vault.hashicorp.paths.health | quote }} - {{- end }} - - {{- if .Values.vault.azure.enabled }} - - name: "EDC_VAULT_CLIENTID" - value: {{ .Values.vault.azure.client | required ".Values.vault.azure.client is required" | quote }} - - name: "EDC_VAULT_TENANTID" - value: {{ .Values.vault.azure.tenant | required ".Values.vault.azure.tenant is required" | quote }} - - name: "EDC_VAULT_NAME" - value: {{ .Values.vault.azure.name | required ".Values.vault.azure.name is required" | quote }} - # only set the env var if config value not null - {{- if .Values.vault.azure.secret }} - - name: "EDC_VAULT_CLIENTSECRET" - value: {{ .Values.vault.azure.secret | quote }} - {{- end }} - # only set the env var if config value not null - {{- if .Values.vault.azure.certificate }} - - name: "EDC_VAULT_CERTIFICATE" - value: {{ .Values.vault.azure.certificate | quote }} - {{- end }} - {{- end }} ##################### - ## DATA ENCRYPTION ## ##################### - # see extension https://github.com/eclipse-tractusx/tractusx-edc/tree/develop/edc-extensions/data-encryption + # see extension https://github.com/eclipse-tractusx/tractusx-edc/tree/main/edc-extensions/data-encryption - name: "EDC_DATA_ENCRYPTION_KEYS_ALIAS" value: {{ .Values.vault.secretNames.transferProxyTokenEncryptionAesKey | quote }} - name: "EDC_DATA_ENCRYPTION_ALGORITHM" @@ -346,9 +300,17 @@ 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 ## ###################################### + - name: "EDC_CONNECTOR_NAME" + value: {{ include "txdc.fullname" .}}-controlplane {{- range $key, $value := .Values.controlplane.envValueFrom }} - name: {{ $key | quote }} valueFrom: diff --git a/charts/tractusx-connector/templates/deployment-dataplane.yaml b/charts/tractusx-connector/templates/deployment-dataplane.yaml index ff5f6a5ce..5c6b28a1d 100644 --- a/charts/tractusx-connector/templates/deployment-dataplane.yaml +++ b/charts/tractusx-connector/templates/deployment-dataplane.yaml @@ -1,3 +1,25 @@ +# +# Copyright (c) 2023 ZF Friedrichshafen AG +# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (c) 2021, 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 +# + --- apiVersion: apps/v1 kind: Deployment @@ -39,12 +61,8 @@ spec: {{- toYaml .Values.dataplane.securityContext | nindent 12 }} {{- if .Values.dataplane.image.repository }} image: "{{ .Values.dataplane.image.repository }}:{{ .Values.dataplane.image.tag | default .Chart.AppVersion }}" - {{- else if and .Values.vault.hashicorp }} - image: "ghcr.io/catenax-ng/tx-tractusx-edc/edc-dataplane-hashicorp-vault:{{ .Values.dataplane.image.tag | default .Chart.AppVersion }}" - {{- else if .Values.vault.azure }} - image: "ghcr.io/catenax-ng/tx-tractusx-edc/edc-dataplane-azure-vault:{{ .Values.dataplane.image.tag | default .Chart.AppVersion }}" {{- else }} - {{- fail "cannot choose data-plane image automatically based on configuration" }} + image: "tractusx/edc-dataplane-hashicorp-vault:{{ .Values.dataplane.image.tag | default .Chart.AppVersion }}" {{- end }} imagePullPolicy: {{ .Values.dataplane.image.pullPolicy }} ports: @@ -56,8 +74,8 @@ spec: {{- if .Values.dataplane.livenessProbe.enabled }} livenessProbe: httpGet: - path: {{ .Values.dataplane.endpoints.default.path }}/check/liveness - port: {{ .Values.dataplane.endpoints.default.port }} + path: {{ .Values.dataplane.endpoints.observability.path }}/check/liveness + port: {{ .Values.dataplane.endpoints.observability.port }} initialDelaySeconds: {{ .Values.dataplane.livenessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.dataplane.livenessProbe.periodSeconds }} timeoutSeconds: {{ .Values.dataplane.livenessProbe.timeoutSeconds }} @@ -67,8 +85,8 @@ spec: {{- if .Values.dataplane.readinessProbe.enabled }} readinessProbe: httpGet: - path: {{ .Values.dataplane.endpoints.default.path }}/check/readiness - port: {{ .Values.dataplane.endpoints.default.port }} + path: {{ .Values.dataplane.endpoints.observability.path }}/check/readiness + port: {{ .Values.dataplane.endpoints.observability.port }} initialDelaySeconds: {{ .Values.dataplane.readinessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.dataplane.readinessProbe.periodSeconds }} timeoutSeconds: {{ .Values.dataplane.readinessProbe.timeoutSeconds }} @@ -80,7 +98,7 @@ spec: env: {{- if .Values.dataplane.debug.enabled }} - name: "JAVA_TOOL_OPTIONS" - {{- if and .Values.dataplane.debug.enabled .Values.dataplane.debug.suspendOnStart }} + {{- if .Values.dataplane.debug.suspendOnStart }} value: >- {{ printf "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=%v" .Values.dataplane.debug.port }} {{- else }} @@ -100,16 +118,18 @@ spec: value: {{ .Values.dataplane.endpoints.control.port | quote }} - name: "WEB_HTTP_CONTROL_PATH" value: {{ .Values.dataplane.endpoints.control.path | quote }} - - name: "WEB_HTTP_VALIDATION_PORT" - value: {{ .Values.dataplane.endpoints.validation.port | quote }} - - name: "WEB_HTTP_VALIDATION_PATH" - value: {{ .Values.dataplane.endpoints.validation.path | quote }} - name: "WEB_HTTP_PUBLIC_PORT" value: {{ .Values.dataplane.endpoints.public.port | quote }} - name: "WEB_HTTP_PUBLIC_PATH" value: {{ .Values.dataplane.endpoints.public.path | quote }} - name: "EDC_DATAPLANE_TOKEN_VALIDATION_ENDPOINT" - value: {{ include "txdc.controlplane.url.validation" .}} + value: {{ include "txdc.controlplane.url.validation" .}} + - name: "WEB_HTTP_OBSERVABILITY_PORT" + value: {{ .Values.dataplane.endpoints.observability.port | quote }} + - name: "WEB_HTTP_OBSERVABILITY_PATH" + value: {{ .Values.dataplane.endpoints.observability.path | quote }} + - name: "TRACTUSX_API_OBSERVABILITY_ALLOW-INSECURE" + value: {{ .Values.dataplane.endpoints.observability.insecure | quote }} ####### # AWS # @@ -131,8 +151,7 @@ spec: ## VAULT ## ########### - {{- if .Values.vault.hashicorp.enabled }} - # see extension https://github.com/eclipse-tractusx/tractusx-edc/tree/develop/edc-extensions/hashicorp-vault + # see extension https://github.com/eclipse-tractusx/tractusx-edc/tree/main/edc-extensions/hashicorp-vault - name: "EDC_VAULT_HASHICORP_URL" value: {{ .Values.vault.hashicorp.url | required ".Values.vault.hashicorp.url is required" | quote }} - name: "EDC_VAULT_HASHICORP_TOKEN" @@ -147,24 +166,12 @@ spec: value: {{ .Values.vault.hashicorp.paths.secret | quote }} - name: "EDC_VAULT_HASHICORP_API_HEALTH_CHECK_PATH" value: {{ .Values.vault.hashicorp.paths.health | quote }} - {{- end }} - - {{- if .Values.vault.azure.enabled }} - - name: "EDC_VAULT_CLIENTID" - value: {{ .Values.vault.azure.client | quote }} - - name: "EDC_VAULT_TENANTID" - value: {{ .Values.vault.azure.tenant | quote }} - - name: "EDC_VAULT_NAME" - value: {{ .Values.vault.azure.name | quote }} - - name: "EDC_VAULT_CLIENTSECRET" - value: {{ .Values.vault.azure.secret | quote }} - - name: "EDC_VAULT_CERTIFICATE" - value: {{ .Values.vault.azure.certificate | quote }} - {{- end }} - ###################################### - ## Additional environment variables ## - ###################################### + ###################################### + ## Additional environment variables ## + ###################################### + - name: "EDC_CONNECTOR_NAME" + value: {{ include "txdc.fullname" .}}-dataplane {{- range $key, $value := .Values.dataplane.envValueFrom }} - name: {{ $key | quote }} valueFrom: diff --git a/charts/tractusx-connector/templates/hpa-controlplane.yaml b/charts/tractusx-connector/templates/hpa-controlplane.yaml index 36fe8fae0..c52ed9152 100644 --- a/charts/tractusx-connector/templates/hpa-controlplane.yaml +++ b/charts/tractusx-connector/templates/hpa-controlplane.yaml @@ -1,3 +1,25 @@ +# +# Copyright (c) 2023 ZF Friedrichshafen AG +# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (c) 2021, 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 +# + {{- if .Values.controlplane.autoscaling.enabled }} --- apiVersion: autoscaling/v2beta1 diff --git a/charts/tractusx-connector/templates/hpa-dataplane.yaml b/charts/tractusx-connector/templates/hpa-dataplane.yaml index abad34fcc..519c0e526 100644 --- a/charts/tractusx-connector/templates/hpa-dataplane.yaml +++ b/charts/tractusx-connector/templates/hpa-dataplane.yaml @@ -1,3 +1,25 @@ +# +# Copyright (c) 2023 ZF Friedrichshafen AG +# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (c) 2021, 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 +# + {{- if .Values.controlplane.autoscaling.enabled }} --- apiVersion: autoscaling/v2beta1 diff --git a/charts/tractusx-connector/templates/ingress-controlplane.yaml b/charts/tractusx-connector/templates/ingress-controlplane.yaml index a2325b17c..ee490510f 100644 --- a/charts/tractusx-connector/templates/ingress-controlplane.yaml +++ b/charts/tractusx-connector/templates/ingress-controlplane.yaml @@ -1,3 +1,25 @@ +# +# Copyright (c) 2023 ZF Friedrichshafen AG +# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (c) 2021, 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 +# + {{- $fullName := include "txdc.fullname" . }} {{- $controlLabels := include "txdc.controlplane.labels" . | nindent 4 }} {{- $controlEdcEndpoints := .Values.controlplane.endpoints }} diff --git a/charts/tractusx-connector/templates/ingress-dataplane.yaml b/charts/tractusx-connector/templates/ingress-dataplane.yaml index 1f79d09c1..14e88bff9 100644 --- a/charts/tractusx-connector/templates/ingress-dataplane.yaml +++ b/charts/tractusx-connector/templates/ingress-dataplane.yaml @@ -1,3 +1,25 @@ +# +# Copyright (c) 2023 ZF Friedrichshafen AG +# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (c) 2021, 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 +# + {{- $fullName := include "txdc.fullname" . }} {{- $dataLabels := include "txdc.dataplane.labels" . | nindent 4 }} {{- $dataEdcEndpoints := .Values.dataplane.endpoints }} diff --git a/charts/tractusx-connector/templates/service-controlplane.yaml b/charts/tractusx-connector/templates/service-controlplane.yaml index 94a02fa1e..acab58343 100644 --- a/charts/tractusx-connector/templates/service-controlplane.yaml +++ b/charts/tractusx-connector/templates/service-controlplane.yaml @@ -39,18 +39,14 @@ spec: targetPort: control protocol: TCP name: control - - port: {{ .Values.controlplane.endpoints.data.port }} - targetPort: data + - port: {{ .Values.controlplane.endpoints.management.port }} + targetPort: management protocol: TCP - name: data - - port: {{ .Values.controlplane.endpoints.validation.port }} - targetPort: validation + name: management + - port: {{ .Values.controlplane.endpoints.protocol.port }} + targetPort: protocol protocol: TCP - name: validation - - port: {{ .Values.controlplane.endpoints.ids.port }} - targetPort: ids - protocol: TCP - name: ids + name: protocol - port: {{ .Values.controlplane.endpoints.metrics.port }} targetPort: metrics protocol: TCP diff --git a/charts/tractusx-connector/templates/service-dataplane.yaml b/charts/tractusx-connector/templates/service-dataplane.yaml index 26fa9c203..07ee9cba3 100644 --- a/charts/tractusx-connector/templates/service-dataplane.yaml +++ b/charts/tractusx-connector/templates/service-dataplane.yaml @@ -1,3 +1,25 @@ +# +# Copyright (c) 2023 ZF Friedrichshafen AG +# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (c) 2021, 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 +# + --- apiVersion: v1 kind: Service @@ -21,6 +43,10 @@ spec: targetPort: public protocol: TCP name: public + - port: {{ .Values.dataplane.endpoints.observability.port }} + targetPort: observability + protocol: TCP + name: observability - port: {{ .Values.dataplane.endpoints.metrics.port }} targetPort: metrics protocol: TCP diff --git a/charts/tractusx-connector/templates/serviceaccount.yaml b/charts/tractusx-connector/templates/serviceaccount.yaml index c650bcd68..4a6e1ac07 100644 --- a/charts/tractusx-connector/templates/serviceaccount.yaml +++ b/charts/tractusx-connector/templates/serviceaccount.yaml @@ -1,4 +1,27 @@ +# +# Copyright (c) 2023 ZF Friedrichshafen AG +# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (c) 2021, 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 +# + {{- if .Values.serviceAccount.create -}} +--- apiVersion: v1 kind: ServiceAccount metadata: diff --git a/charts/tractusx-connector/templates/tests/test-controlplane-readiness.yaml b/charts/tractusx-connector/templates/tests/test-controlplane-readiness.yaml new file mode 100644 index 000000000..f43ce52eb --- /dev/null +++ b/charts/tractusx-connector/templates/tests/test-controlplane-readiness.yaml @@ -0,0 +1,35 @@ +# +# 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 +# + +--- +apiVersion: v1 +kind: Pod +metadata: + name: "{{include "txdc.fullname" .}}test-controlplane-readiness" + labels: + {{- include "txdc.controlplane.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: curlimages/curl + command: [ 'curl' ] + args: [ '{{- printf "http://%s-controlplane:%v%s/check/readiness" (include "txdc.fullname" $ ) $.Values.controlplane.endpoints.observability.port $.Values.controlplane.endpoints.observability.path -}}' ] + restartPolicy: Never diff --git a/charts/tractusx-connector/templates/tests/test-dataplane-readiness.yaml b/charts/tractusx-connector/templates/tests/test-dataplane-readiness.yaml new file mode 100644 index 000000000..0d013ea66 --- /dev/null +++ b/charts/tractusx-connector/templates/tests/test-dataplane-readiness.yaml @@ -0,0 +1,35 @@ +# +# 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 +# + +--- +apiVersion: v1 +kind: Pod +metadata: + name: "{{include "txdc.fullname" .}}test-dataplane-readiness" + labels: + {{- include "txdc.dataplane.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: curlimages/curl + command: [ 'curl' ] + args: [ '{{- printf "http://%s-dataplane:%v%s/check/readiness" (include "txdc.fullname" $ ) $.Values.dataplane.endpoints.observability.port $.Values.dataplane.endpoints.observability.path -}}' ] + restartPolicy: Never diff --git a/charts/tractusx-connector/values.yaml b/charts/tractusx-connector/values.yaml index cbc266a94..e2bb1a692 100644 --- a/charts/tractusx-connector/values.yaml +++ b/charts/tractusx-connector/values.yaml @@ -38,7 +38,7 @@ controlplane: image: # -- Which derivate of the control plane to use. when left empty the deployment will select the correct image automatically repository: "" - # -- [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) to use + # -- [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) to use pullPolicy: IfNotPresent # -- Overrides the image tag whose default is the chart appVersion tag: "" @@ -89,19 +89,13 @@ controlplane: # -- path for incoming api calls path: /api # -- data management api, used by internal users, can be added to an ingress and must not be internet facing - data: + management: # -- port for incoming api calls port: 8081 # -- path for incoming api calls - path: /data + path: /management # -- authentication key, must be attached to each 'X-Api-Key' request header authKey: "" - # -- validation api, only used by the data plane and should not be added to any ingress - validation: - # -- port for incoming api calls - port: 8082 - # -- path for incoming api calls - path: /validation # -- control api, used for internal control calls. can be added to the internal ingress, but should probably not control: # -- port for incoming api calls @@ -109,7 +103,7 @@ controlplane: # -- path for incoming api calls path: /control # -- ids api, used for inter connector communication and must be internet facing - ids: + protocol: # -- port for incoming api calls port: 8084 # -- path for incoming api calls @@ -128,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 @@ -198,7 +195,7 @@ controlplane: annotations: {} # -- EDC endpoints exposed by this ingress resource endpoints: - - ids + - protocol # -- Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use className: "" # -- TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource @@ -221,7 +218,7 @@ controlplane: annotations: {} # -- EDC endpoints exposed by this ingress resource endpoints: - - data + - management - control # -- Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use className: "" @@ -294,7 +291,7 @@ dataplane: image: # -- Which derivate of the data plane to use. when left empty the deployment will select the correct image automatically repository: "" - # -- [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) to use + # -- [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) to use pullPolicy: IfNotPresent # -- Overrides the image tag whose default is the chart appVersion tag: "" @@ -340,12 +337,16 @@ dataplane: public: port: 8081 path: /api/public - validation: - port: 8082 - path: /validation control: port: 8083 path: /api/dataplane/control + observability: + # -- port for incoming API calls + port: 8085 + # -- observability api, provides /health /readiness and /liveness endpoints + path: /observability + # -- allow or disallow insecure access, i.e. access without authentication + insecure: true metrics: port: 9090 path: /metrics @@ -494,7 +495,6 @@ postgresql: vault: hashicorp: - enabled: false url: "" token: "" timeout: 30 @@ -504,13 +504,6 @@ vault: paths: secret: /v1/secret health: /v1/sys/health - azure: - enabled: false - name: "" - client: "" - tenant: "" - secret: - certificate: secretNames: transferProxyTokenSignerPrivateKey: transfer-proxy-token-signer-private-key transferProxyTokenSignerPublicKey: transfer-proxy-token-signer-public-key diff --git a/checkov.yaml b/checkov.yaml index 5f5d0d107..7c2360c6b 100644 --- a/checkov.yaml +++ b/checkov.yaml @@ -1,3 +1,22 @@ +# +# Copyright (c) 2021, 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 +# + --- skip-check: - CKV_K8S_10 # CPU requests should be set, https://docs.bridgecrew.io/docs/bc_k8s_9 diff --git a/ct.yaml b/ct.yaml index 1b81ec6ce..77a3b465a 100644 --- a/ct.yaml +++ b/ct.yaml @@ -1,3 +1,22 @@ +# +# Copyright (c) 2021, 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 +# + --- validate-maintainers: false chart-dirs: diff --git a/docs/README.md b/docs/README.md index 259c2560b..6a5605eef 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,8 +1,10 @@ # Tractus-X EDC -The Tractus-X EDC repository creates runnable applications out of EDC extensions from the [Eclipse DataSpace Connector](https://github.com/eclipse-edc/Connector) repository. +The Tractus-X EDC repository creates runnable applications out of EDC extensions from +the [Eclipse DataSpace Connector](https://github.com/eclipse-edc/Connector) repository. -When running a EDC connector from the Tractus-X EDC repository there are three setups to choose from. They only vary by using different extensions for +When running a EDC connector from the Tractus-X EDC repository there are three setups to choose from. They only vary by +using different extensions for - Resolving of Connector-Identities - Persistence of the Control-Plane-State @@ -12,15 +14,15 @@ When running a EDC connector from the Tractus-X EDC repository there are three s The three supported setups are. -- Setup 1: In Memory & Azure Vault - - [Control Plane](../edc-controlplane/edc-controlplane-memory/README.md) +- Setup 1: Pure in Memory **Not intended for production use!** + - [Control Plane](../edc-controlplane/edc-runtime-memory/README.md) - [IDS DAPS Extensions](https://github.com/eclipse-edc/Connector/tree/main/extensions/common/iam/oauth2/daps) - In Memory Persistence done by using no extension - - [Azure Key Vault Extension](https://github.com/eclipse-edc/Connector/tree/main/extensions/common/vault/azure-vault) + - In Memory Keyvault with seedable secrets. - [Data Plane](../edc-dataplane/edc-dataplane-azure-vault/README.md) - [Azure Key Vault Extension](https://github.com/eclipse-edc/Connector/tree/main/extensions/common/vault/azure-vault) - Setup 2: PostgreSQL & Azure Vault - - [Control Plane](../edc-controlplane/edc-controlplane-postgresql/README.md) + - [Control Plane](../edc-controlplane/edc-controlplane-postgresql-azure-vault/README.md) - [IDS DAPS Extensions](https://github.com/eclipse-edc/Connector/tree/main/extensions/common/iam/oauth2/daps) - [PostgreSQL Persistence Extensions](https://github.com/eclipse-edc/Connector/tree/main/extensions/control-plane/store/sql) - [Azure Key Vault Extension](https://github.com/eclipse-edc/Connector/tree/main/extensions/common/vault/azure-vault) diff --git a/docs/development/Release.md b/docs/development/Release.md index 3992c0a1d..5f2fbd74d 100644 --- a/docs/development/Release.md +++ b/docs/development/Release.md @@ -38,5 +38,11 @@ The Eclipse Bot is able to approve dependencies automatically, if the license ca from maven central. 2. Create the Eclipse IP Issues or ask an Eclipse Commiter to do this for you. +## 4. Update OpenAPI docs + +As part of the [kits documentation provided for docusaurus](../kit/development-view/page00_development_view.md) we provide an OpenAPI reference. +This refers to the [EDC API](https://github.com/eclipse-edc/Connector/tree/main/resources/openapi) and needs to be updated to the current release. +The yaml files found there are then converted with the [docusaurus openapi plugin](https://www.npmjs.com/package/docusaurus-plugin-openapi-docs). + [maven-shield]: https://img.shields.io/badge/Apache%20Maven-URL-blue [maven-url]: https://maven.apache.org diff --git a/docs/development/decision-records/2023-02-09-release-process/README.md b/docs/development/decision-records/2023-02-09-release-process/README.md index aee5bac5a..4b2771c0a 100644 --- a/docs/development/decision-records/2023-02-09-release-process/README.md +++ b/docs/development/decision-records/2023-02-09-release-process/README.md @@ -8,7 +8,7 @@ To improve stability, reproducibility and maintainability of releases, tractusx- - use release versions of EDC in releases. Release branches must not change upstream dependency versions, unless there is a clear and concise reason to do so. - slightly update branching model -- if possible, bugs/defects should be fixed on `develop` and be backported to the respective `hotfix/` branch +- if possible, bugs/defects should be fixed on `main` and be backported to the respective `hotfix/` branch - only hotfixes for critical security bugs will be provided as defined by the committers for the currently released version. Nothing else. - feature development happens _in developers' forks only_ to keep the Git reflog of the `origin` clean. @@ -31,15 +31,15 @@ Every release version published by tractusx-edc must be reproducible at any time During feature development we only use `-SNAPSHOT` versions of EDC packages. It is assumed that when the build breaks due to changes in upstream, the fix can be done quickly and easily, much more so than working off technical -debt that would otherwise accumulate over several months. Builds on `develop` are therefore _not repeatable_, but that +debt that would otherwise accumulate over several months. Builds on `main` are therefore _not repeatable_, but that downside is easily offset by the tighter alignment with and smaller technical debt and integration pain with the upstream EDC. ### Use release versions of EDC in releases -First, a new branch `releases/X.Y.Z` based off of `develop` is created. This can either be done +First, a new branch `release/X.Y.Z` based off of `main` is created. This can either be done on `HEAD`, or - if desired - on a particular ref. The latter case is relevant if there are already features -in `develop` that are not scoped for a particular release. +in `main` that are not scoped for a particular release. Second, the dependency onto EDC is updated to the most recent build. For example, if a release is created on March 27th 2023, the most recent nightly would be `0.0.1-20230326`. @@ -79,13 +79,13 @@ Once a release is published, for example `0.3.1` it will receive no further deve hotfix branches are created based off of the release branch, here `releases/0.3.1`, thus, `hotfix/0.3.1`. From this, three scenarios emerge: -1. The actual fix is done on `develop` and can be cherry-picked into the `hotfix/0.3.1` branch. No new commits are +1. The actual fix is done on `main` and can be cherry-picked into the `hotfix/0.3.1` branch. No new commits are made directly in that branch. -2. The actual fix is done on `develop` and must be manually ported into the `hotfix/0.3.1` branch. One or several new +2. The actual fix is done on `main` and must be manually ported into the `hotfix/0.3.1` branch. One or several new commits are made on `hotfix/0.3.1`. This is needed when cherry-picking is not available due to incompatibilities - between `develop` and the hotfix branch due to intermittent changes. -3. The fix is only relevant for the `0.3.1` hotfix, it is not needed in `develop`. This can happen, when the problem is - not present on `develop`, because it was already implicitly fixed, or otherwise doesn't exist. + between `main` and the hotfix branch due to intermittent changes. +3. The fix is only relevant for the `0.3.1` hotfix, it is not needed in `main`. This can happen, when the problem is + not present on `main`, because it was already implicitly fixed, or otherwise doesn't exist. This might produce many branches, and the first `hotfix` makes the release obsolete, but it will greatly help readability and make a release's history readily apparent. diff --git a/docs/development/decision-records/2023-02-27_testing/README.md b/docs/development/decision-records/2023-02-27_testing/README.md index 45844203d..0d12ab353 100644 --- a/docs/development/decision-records/2023-02-27_testing/README.md +++ b/docs/development/decision-records/2023-02-27_testing/README.md @@ -82,5 +82,5 @@ This section explains _at which point in time_ we should execute which test. Thi | Unit test | when running tests locally, without any parameters, on every commit on every branch | | | Integration test | on every commit on every branch | | | System/End-To-End test | on pull request branches except when marked as `draft` | | -| Deployment test | before merging pull requests and on every commit on `develop` | | +| Deployment test | before merging pull requests and on every commit on `main` | | | Performance test | Only on a specific schedule, e.g. once per day or week | | diff --git a/docs/development/decision-records/2023-03-23_remove_lombok/README.md b/docs/development/decision-records/2023-03-23_remove_lombok/README.md index b7014c8b8..e4642abf5 100644 --- a/docs/development/decision-records/2023-03-23_remove_lombok/README.md +++ b/docs/development/decision-records/2023-03-23_remove_lombok/README.md @@ -11,9 +11,9 @@ Lombok uses byte-code modification to achieve its goal. That is dangerous for a First and foremost, to achieve its goal, it relies on internal APIs of the JVM, which are not intended for public consumption, thus they can and will get removed, refactored or made otherwise unavailable. This has been discussed at length in the [project's GitHub page](https://github.com/projectlombok/lombok/issues/2681). -This is especially problematic for an OSS project such as TractusX. +This is especially problematic for an OSS project such as Eclipse Tractus-X. -Second, many of the features that are currently used by TractusX-EDC are experimental (e.g. `@UtilityClass`) and are +Second, many of the features that are currently used by Tractus-X EDC are experimental (e.g. `@UtilityClass`) and are known to break some Java standard features, such as static imports. Third, the value that Lombok offers is questionable at best (e.g. various constructor diff --git a/docs/development/decision-records/2023-04-03_renaming_branches/README.md b/docs/development/decision-records/2023-04-03_renaming_branches/README.md new file mode 100644 index 000000000..8976e4d55 --- /dev/null +++ b/docs/development/decision-records/2023-04-03_renaming_branches/README.md @@ -0,0 +1,61 @@ +# Renaming Git branches to comply with Eclipse Tractus-X standards + +## Decision + +Tractus-X EDC will rename its Git branching structure to comply with TractusX release guidelines, and to be able to +leverage +GitHub convenience features, while continuing to use the Gitflow branching model. + +## Rationale + +The Eclipse Tractus-X organization has established +a [release guideline](https://eclipse-tractusx.github.io/docs/release/trg-2/trg-2-1/) which mandates that all projects' +default branch be called `main`. + +### Selecting default branches + +In GitHub, the default branch has a couple of important features attached to it: + +- cloning or forking the repository will automatically check out the default branch +- when creating pull requests the default branch is targeted by default +- [automatic issue linking and closing](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) + only works with the default branch + +### The problem with GitFlow + +The GitFlow branching model suggests that the day-to-day work be done on a branch called `develop`, while the `main` +branch stores the version history and only receives (merge) commits after a version releases. + +This would call for `develop` being the GitHub default branch, which is forbidden by the aforementioned release +guideline. + +## Approach + +In order to comply with the Tractus-X release guideline, to make use of the GitHub features _and_ also use GitFlow, we +propose renaming a couple of branches. While GitFlow _suggests_ branch names, it does not _require_ it, and most +tools allow for customizing them anyway. Thus, from an abstract perspective, the following changes are necessary: + +- `main` becomes our work/development branch. All pull requests target `main`. +- `develop` gets deleted +- a new branch `releases` is introduced, which tracks the release history and receives post-release merge commits. + +Technically this will involve force-pushing, which is a potentially destructive operation. Therefor the following +section outlines the exact sequence of steps. Note that "upstream" refers to `eclipse-tractusx/tractusx-edc`, while " +fork" refers to `catenax-ng/tx-tractusx-edc`. + +- create a new branch `upstream/releases` +- create a new branch `fork/releases`, set it to track `upstream/releases` +- push the contents of `fork/main` -> `upstream/releases` +- synchronize `upstream/develop` with `fork/develop` +- force-push the contents of `develop` -> `upstream/main` (do **not** update the tracking branch!) +- synchronize `upstream/main` -> `fork/main` +- delete/archive `upstream/develop` and `fork/develop` + +_Note that most of this will likely need to be done manually, since GitHub does not allow for advanced Git operations +like force-pushing. Write access to `upstream` is required!_ + +## Further notes + +The new `releases` branch (note the plural) will serve the same purpose that `main` did up until now, which is to track +all releases (via merge commits and tags) in chronological order. We will continue to have separate `release/x.y.z` +branches for every release. diff --git a/docs/development/decision-records/2023-04-11_refactor_helmcharts/README.md b/docs/development/decision-records/2023-04-11_refactor_helmcharts/README.md new file mode 100644 index 000000000..11294f82b --- /dev/null +++ b/docs/development/decision-records/2023-04-11_refactor_helmcharts/README.md @@ -0,0 +1,112 @@ +# Refactor Tractus-X EDC Helm charts + +## Decision + +The Helm charts provided by Tractusx-EDC will be refactored to be more focused and opinionated. Specifically, there will +be the following charts: + +1. `tractusx-connector-memory`: all backing stores are memory-based and thus ephemeral. The vault will also be + memory-based. _This chart is intended for testing/demo purposes only!_ +2. `tractusx-connector`: this is the "production-ready" chart that uses PostgreSQL and Hashicorp-Vault +3. `tractusx-connector-azure-vault`: this is a variant of `tractusx-connector-azure-vault` that uses Azure KeyVault (" + AZKV") instead + of Hashicorp as some stakeholders still use AZKV. + +These charts and their default configuration will be fully [tested](#testing). + +In addition to that, the Docker images will undergo some [refactoring](#docker-image-refactoring) as well. + +## Rationale + +The current "dynamically composed" helm chart has proven to be a source for issues, and it is difficult to isolate +errors due to the great number of variations. Further, only one particular variant (i.e. postgres+hashicorp) is put to +any semblance of testing (i.e. business tests). + +The official recommendation of Tractus-X EDC is to use PostgreSQL and HashiCorp Vault, and alongside it, we will provide +charts for easy testing and setting up demos as well as an Azure KeyVault variant for legacy use cases. + +> Note: using Azure KeyVault is not officially supported or recommended by Tractus-X EDC! + +This will also reduce the number of Docker images that need to be published. + +## Approach + +### Variant 1: `tractusx-connector-memory` + +This chart is intended for blackbox-testing or for easily setting up demos etc. It is **not** recommended for anything +else. It will have the following properties: + +- all backing stores (Asset Index, Policy Store etc.) are ephemeral in-memory stores +- the vault implementation will either be based also on memory, or on the `FsVault`, which uses local storage to store + secrets +- an embedded data plane will be used +- no scalability or replication is possible +- DAPS will be used as identity provider, so there is an implicit dependency onto a DAPS instance +- the `edc-runtime-memory` Docker image will be used. That image contains both control plane and data plane. + +### Variant 2: `tractusx-connector` + +This is the production-ready chart that is published by Tractus-X EDC, and it will actually consist of two charts. One is +the `tractusx-runtime` sub-chart, that contains all configuration for data plane and control plane, and the other one is +the top-level `tractusx-connector` chart, that pulls in other charts as dependencies that are needed for one TractusX +connector application. This is sometimes referred to +as ["umbrella chart"](https://helm.sh/docs/howto/charts_tips_and_tricks/#complex-charts-with-many-dependencies). + +> Note: this will **not** include sub-charts for DAPS or MinIO. + +```shell +tractusx-connector + |-> tractusx-runtime + |-> postgres + |-> hashicorp-vault +``` + +The `tractusx-runtime` chart has the following properties: + +- PostgreSQL is used as persistence backend +- HashiCorp Vault is used as secret store +- the data plane is a separate runtime, i.e. separate pod +- DAPS is used as identity provider +- the `edc-controlplane-postgresql-hashicorp-vault` and `edc-dataplane-hashicorp-vault` Docker images will be used + +### Variant 3: `tractusx-connector-azure-vault` + +This variant is essentially identical to `tractusx-connector` except for dropping the HashiCorp Vault chart, and +replacing the HashiCorp Vault configuration with Azure KeyVault configuration. + +For this, the `edc-controlplane-postgresql-azure-vault` and `edc-dataplane-azure-vault` Docker images will be used. + +### Testing + +There are several steps to testing our Helm charts: + +1. waiting for all pods to come up: using an exemplary configuration, this relies on the health checks, i.e. liveness + and readiness probe (i.e. the runtime`s observability endpoints) to ensure that (most of) the static + configuration is correct, no values are missing etc. +2. executing a set of HTTP requests against the management API and assert a successful HTTP status code. For that we + use [Helm chart tests](https://helm.sh/docs/topics/chart_tests/) + +> Note: we refer to this kind of testing as "deployment testing" + +### Docker image refactoring + +The following changes need to be made to our Docker images: + +- rename `edc-controlplane-memory` -> `-edc-runtime-memory` +- in `edc-runtime-memory` use `FsVault` instead of `AzureVault` +- `edc-runtime-memory` contains an embedded data plane +- rename `edc-controlplane-postgresql` -> `edc-controlplane-postgresql-azure-vault` +- delete `edc-controlplane-memory-hashicorp-vault` + +thus effectively resulting in the following structure: + +```shell +edc-controlplane +|-> edc-runtime-memory +|-> edc-controlplane-postgresql-hashicorp-vault +|-> edc-controlplane-postgresql-azure-vault + +edc-dataplane +|-> edc-dataplane-hashicorp-vault +|-> edc-dataplane-azure-vaul +``` 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..fca3e201c --- /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, Tractus-X 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. Tractus-X 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 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%0DNtR|7lBA}M3~B_qYm z4;IOAiKP7--|$F-TO_F?Vtl0F(GtH3K~bS5qdl_nv**w?_m+hYjm6G@{V%}F3zp(~sT{~x-R_4t*6jYI zP^o$G6`a)A75b#I(cfWjG1LzV*T71HjLGY;tM9>{kF=pBr_aFoG5SE&dbxF{VSETD zCr#@>=QN({2ZzSt*PoC1P=`708=7*0UkfP?3lqmOPl8e7E$y<;Zji|-Egf7FqRU_g z>u*bn! z>ow}j6wuG`;HnCJvS9A*T1IRe<8z5XSwvDbtfSrGlB^F+*T}b*ejK$R2Cx*mcU-T^ zG;3;%)(~e0H(JDK7z-D2c^a;Tb7oBTq6|);bPbDgYMJ9LzlYJb;OIE;nLex*5vK0l zRUOf|QYWg{tgaWJS2fPA9gJ^FoMxJ_B)VU0?3up{i5nYO?tXAH5YOt;9WPuzqUukos?^}`RsZ#Cd{G{N%e z(rRk}Yj}Hn_6uaO7I2a_S`ot+3IR%AD@)|Pn88h!bi%i=$89Q(+DouZ z3Gw4<(J3NXxdo4ol<6h0LZP)tzx$Q;#t2Q6={lo?JPF;}8DJG7E8XaE*^aDw8?lRa z`Ht-$AgiNu^n_25VcH{J7Y-|84MU&q=7fz(YUtF0nBv$nVGT$ zVbFO(zCk$dMAuZas|T(u59itV`1n*Nn&iIX&oOXoxr@Axj{iWpgsZD639atZXq5%~ zz*lE!dWYMTm)B~~;LWID_VnXJqLrQJ>{;bF*jM6u*5l474Kw>RO@DM!dpB{B_tG}; z4|@t>$tK*?2hHZ$-s?B>r0du#jc^j$7n;8Ib9O8N6oSB)Ka!uY{sCNMD5)go1VHuxX zU6C>|MWqXeTAy#rXt+Pv0SXIN*4Z<*1(-4Wi;eY3`8HXXfn26D*9dWUp;oIMbk;Tq zazciO+oP;9R_Rj<8prN$rrq`#m)xSnBwL$jYpeDs9UzsQ+JEx2wS2<3rtIKGR3B=| zHE?Rd;et!LZExpa4tb>2XXpo^B{rtzga2UWTZFDDk3%Hjx@*T#FP<0DHRn$)=0I(N zDejnV8s&av=)S$wKIv-{Yvyk>giq_rApXO1w%xJ}hR^+t>s@P$UhLD7r4*Zs8-!EH z_s56jWV#D}MVr(rZ1O@axa!wo;kv451rze%P{)Y%PFr1j9iLfbk9FtA`027GNgajp z0g0XEU`R(X)>4yA7!Ox$LRUP($u;|XcBj0jpQg){TCY1|M?AL9JJG%$55o7ATae>>$gALuq^j%;QM z&qc|KG|qcfu%0ccv!9`7j~Xpw0L;OeEtV_jy7E*6;q6;X>1Bh=vi|wwosfh40(^|} z_~@|2HDQ-g3k()#=GH7?N+au5JdbBBzYfsy&7q2l zio_FH!<+9nXx2BFmO_oEiquW#stRY)n5I@Ik7RV(7g%pwVbJ=M`c|3n zhKX$$Q=g{DWSP$z+{Boh)77c-QIm3tGn1z6f6X~%%%m&P0AxbpZ z*hadrXkhrZg=pi89* z;ohW~o^t@j_t386VdBQFBakI$=w;Eyb{7kX;p|n?QB&iBNqteCeOsO$>l*xs$UB_Z zQOYH|6PwP#u1zq^aUY2=BOc#6BzuQ4dlJ97#P%Kj8Ws!3l!<(&u9 z_pRXQ?Ka?;D_@omKiC_)YBlu;`=pA6b;YNJ+%FDw*>-mXnRzT(W_DOf=I-m~+Df?TrCm7mh|rIvwI;btd2jZ9d>&RVqV<8OVx8--+Sv?T_ZvN&!+kI+JKzVK zm?^HMU&P^vy%N$3zvw4kJLxyU#A*tb;o#9LLuwJ#HlXj#RqdrIUtMAOIXwgGXz=YJ ztfr0FJCw@cl1C9`BgEpXkA4d`p$ zwH*V!Icz397-M&?d{D!`+c}`YIRn|t0;5?(<@Y{pD1-hw(GUUKm_WdU*PZZ?!B3{R zYEA{4oF@M+CRMI`e~^=Qdi-8WcXd^SrcUQF3a`LDA6f`($%S1$<@!R0z~XPVHnMyU z?pCA@QYt+)-g-pl$CCopxDTj9q%p2x%l!LQu0%KQ%ZQ9A*^uxenC8&l#!rP1?>1^p z8WJO^nb!O2DlQF?23V9S54S6un_Y1XhMy=YGZc$8f^fOw;jx&98=W1V-BZFb6f7SN4Gq=Q z)r%X`{J$^&dt5|Bg!AeCEatT$0OJCn6=1^_jRmiM&^Qg9Y?TA;Pt9anm53+xEd9RdL zIloXmQn_2Z$CId}vJF30S#1Q#;b(5GTg&%_aH?CswbZ|-aKiw4fjS3p7nYQS*=!At zRGKKdxVmDQCtE(=J1P`w6fR4|X9CVlw*pvv1tlenw6rvklas85hNQVUHA!SZPfyPt zb8`ZqQq9-ez&9>1o6ULyLj_!JV1O^?`2YjnsZXE$PfxAc?RLErR4GVNG9UO@@;( z@y`y=-r|>1Yxl?99nM^PP&J3G?%89P_J85M{rk<%aGF=T*t=S4+rmJ9fAyNj{*m`z zY-Ud!W&SwU%|=VLb!dA2#R8;j2=z#L71<$$SKW}o4_S=VE>4s$?1eFZ>)sM9{s_2; zGb9od6UQOHm^%Xg?gYTu9L{WQWo2axJtYx1Xx9;Wpy^+s!NE;~1GtEHXAA9sA=hRC zY)89NCAY9pkcMnsVYF0N7-)nR11^MGP+&bvK~+`E&W>rx{i!is%n1G2k4{ZZ&C14B zY3et0*-na;TJeg~aITuQmcDv$`M?AgSlV%Ozn2L-JE#eC40kDL{#^aJ`QHl@_~ZY6 zsbrt^$FmMhPe7n!FcI)|l0^H?pgUMWCfE>h8_XXbfNF4RY zF*vCc6HsNXWd0if!GAaR#{>S-*mT_g`_Kk9HXt^lwsL%OQdV7ELR}rl&dx4`+7EDz zt^(V#J7mDU>-gbnNw-qPBGySYkKYf$c6%7ytF^_fdSP?h`p=vM){%F2NC@oZR8{+K z&bDq={P4_U!S;Y?gJs}=-5<9j#uNgv5S==u^U@bp@<1m%_s5U`>J3CO2*(hgosE$VJEDV-=LRP04@AwQ&ffjm zwzW|}^g(#atAAv-=Gqa0`A0HqF)zY5)<}Be=FY*rpFH)Ac7ZM7DDCZ=Mc~_UY znM@wtboRT`yPZ*Qo|`Q+{m(ETm=H#(y)6fPObX$*>xZM22P%eA!^`u9rfTKKDjIH-^kb&!Qtg(lR!ni&t2ULg(CVedmY^jE-M;7= z6JKY7)3ro53uMIN0)y3EJ5C492b#;|IL>1~>nzubWYRj0swT0S-Gf1sC#C+9J=fzr zshK)5#4CRhxcXLH^k@2(rrVG_GLmA2)}RMKZ_78A_z=jfSQKj7vcS^+QQ7QWdrrWP+3ZkbG+T=?zodnB=Ws#obpZBoW;)mR9=Jq2B$gZiJ$vU zaoulTBVPx;xlxZVEZnrkde<3Cr;EX3!`4T7`Rg`iW}gzAJoNZ7NEq`RRuxN%#B{T! z(v5V_ig^h1^Ye4-Qw-DhQhV=J(8KfdxLfDW6i##9e2R*8<(LcAhC(7ksQ>JrSxsU` zEEXBip)m6r8ehNXYs#D_QDgpPD9fp1*41>2^A`9VHo?V1&Oz2X1fyR7041?!E?Bs? zv&rDmw7?wJNb+6ZH$VX41MdQ!I^|B?w zi<}*s>bkM??zoV**oGUCfxYxW+K(x1Rx`Ch-aq4PVP`x0dtPn6@7L3?XUQsv>y1r~ zu5k4pNN@#N&oa6&7ZEw%a5DQ^;2(ZinBuH+1dEbaF2q)04*u!dBp!D~3MFr5u-Nbl z`Sw+*{x3w~=z)*;^*_L+23M?>kQX&;BaItW?m&TWlR?oII*Vo61t}<=&XINslI{&b zHxv%XMr@!S+IVhuhlcI{u) zwm(Fh>v?KIlGsmv`dGL1RCb|7c8G5!9?04|N8w-@kt{&e(AA(#Z5q!Q!7|^CEECmU zg`&{FPeNySLc}-!J&$v8NL8q-HB!FT`K%Ax=1VLpAIn~MKlUT?Mthg+L#SB|4-5K` ze%}q;wdt+9wY3V#k;Lwd`Ide8CvtKEE-oC8*jNl8G()vP99YYB-%Ye0La!$6u`Y<9 zNUs{($&C172~(FX6iF2@Qaq^64%+$Y(JuwQo)vnR^*8Nt7CDsF%WMSE)2-@kx7muH zFn)Hde>7aib~GxMes@c-Mm|-#oA527YeD3*3cYiplkC|5rV}^vP))LdeRFyE!_qi} z`8oE9YM0-F&)Jka@kWyMeZ7L!t~hbjH3|+(GFYfk_fuCtrWh%N@OgKH1BQ)0F;kac zKp=D*$|suX9*!A7`AN@6VNKis9AOV(B&;n1Ih5_LxX6v4s_b;z)pGvZcD@Tn?wQ*` zJ{B5cy3-bG7%nkfDX2z)fSw~OCl?SH*#3=?S=o>|K}1Z9keeGqRf zOdFHCZ?fcOgRCIezLoUT4>D-zpynkn{v(S`*sCuJk47XbP#7rd{3)(qYZ%YJ?X>^= z`P28~{$n$aK8XT(pQMcS?>C5-GS+#>?GINd>uV6y3)>7@U`^UTo?|6lZF?T;X!sHM+ zPodLl;r49?b;H&)pT0w+E;fE3q&}6~vZfi^tc_$g#I=BP4vh<*da(H5{^cgfd8=@9 zWAWH%7JG9@JpC$bX^oi$>QImFTE5zS)w}-@ryJN6w_IlqR)|gEV2cI0>)Y^#}*YrDK~y8j`IVFbq3x)R3^`rozp{ zmyt+Q8=^I=PJ+&ITJNW(zATxMEQfP^7bWKsI`)gL&mmJ1JI#_4m;7Paa5lC7MY;R2 z*bCI*$bxlcOMHtcf{~O)PZK=e58)(v_=;)c-7&$*8oP{KtmLV%qjx8w0cf5Ol^C7= z9+7TacM{7hJDU_x|1vT&nV=V`Uzr2f2=MdEw+J+57s04-O)~xWa;OZs8SB!_CTIX_ z{3$wLE|)`{?`h6`tp+;U!y4?k9qzN2Q$XbWP*?f0)Iu2|kOg-YHvdhd3!J@1gJEW6OjV#-~Rk<>+Oj>jKK2a}iA6P2pe)qeNpCEuUM zaDACOLwvj{C+D^c)f#tw`bcx+esjK_ZMkGqm8^F#z~DX7?ld~a9<)e3$I3zJ`Lul) zvZ7(3V4lBf`d~nfD6ZA!xVos0kXyal7fZnd>mQX4$i~5$o_Eg;YaEU47ww3{!OxR}zQA4Ja_0`Z=n#c7@HE z{=S;i9AWa;2FFKsB3RL!qg!Z9aIhEHsKPEC zNgh~S55ZL%xDM9Y2|l8*wlEk1L!p;)AnLbI=_G@%1eTBBqDg}5v>Qrj3_{QrdQ-P= zQ#fuID8UxVDE)X_#!`}T!3O7N#!&iX#L}4$+b}IJ&r>6}lID#lpb!yZ4Jj=T*JUK# z)J1Mh953$1Wyf~lqHH0yIH13=J|%;aIrxdyCJCj@;e^slMUJ7=(Apm4E68p0=c{9l zOqz828#aPYE?OK~KnCLd1~zRxWUL#lS~L!yKCQ?TpI?B9+MixFk$ZLp)w+D#JYnir zmGh8lL#q-$@3eHmY{o6$gb}_>RW+Gy^3~9ljYXM(_i|=&x;O}o0oTH#Kv~SzYoWK- z7hch26=+K(oq_h?Sl~>}TSwLRK$n)a&bx7u7AP86mh9bHC7Aq3dq>Z^f&ws>=p)fNvi-kir zNxxolnn-GS{U*yq@!k9Pb~9xK1qBpECQ?8gC@~ckdT~A80$lw`N`%tI87pthMY_q~k zWkSAPO%&A3C_-81@-!PAMG`W`3W7AxX(_B-$rKb61R&i60Pcl&*2#cC6n9(18U-J7 zTyAcqP}AiNvM6jv7uu$;uT2+0Z|el<9^0>X3X-Q82&x4(Aux8+v3Ng~3n_>;+hQ#2U-ipYqVyU0Z zbGN>)K^HDlEIX@*-8&>bZPfsxc_ZQiwS$hVm@Phm5A2YP+WWpU7z`w#VNOr;QyL0; z=TtENdv5P+8*?PCA<9Nc8FyfJLTdPIQQN!5rN?hh4WsUp_u*Ai#$8#|e$HWQM;O-L zW%9(EC4-IRLxB>%3NFKwAdpA0FRs9S83T=e#);JzY%Zy+(j5UqK4d=Huqkm0sMPo| zN5{k_8t=oYbkZgG!KJ0Avb0}R>d%qo*5$`<16RV+i^Da(;eSM}p#XCiW3O+<=%~?1 z%Arf>DLmgos$WMRyhhv=+sty9R!8~LpXoTIAN1-r1>dYo0B{L{DX~GOlX7Z#EPihweK3#uOEf1jKZSh;(ABYg#ovx zdCrs`J%GkY0vN-{$cP!Y!rr0AE=PEjdwlD%xn@`+z0c3;EA0ag-=9opZ?C}G9~hL& z_pMx-r+Zet^|9%_87v0Gp%|we(~lziAtZ*kVxZVp6g;1#6 z?+anFH)|dVE}n43+LXO$&=turt%V~1O6Os{8d4i<=rR9ks@A_+o$v2reLTTl;Qs{{ z<1nGCA)t}wk$Qn8Sf2*5z0Qba4L3x8qc>SRk$H9!anKTvpI@zIgB$m)mEU3Wdd#B7 z6(rmpqHxmZGc`WGk86nHN#y~jcUh&^weR3;*V4T8#rGb_$Vw4W5>Ck$NGUfrMijTx zr(8cpc3oyMuCYCqT&&v_YCdc0*u7q1CnK+z&p$Gp_WWE?Vx}TwV~bN99q{hpp1*f7 zf97|C`iDNn;|ngXh3ywGge^M0VbVo7fvhO+9#r`xv(7}V>3jPgMF)F*W4_)cVWL_GqYeXt6+g-}{_cZ2(_9)D41Jt9Du9xOg?Ws8 zGreATcBTLU(I}*nTJ&*63&ZbiOH>M{9At5kYW0)2oG)jw*rc}xlM?PtpPZU?$1=hx z_Z~kyDRK4>nrSX%ZcR?llpF5MR^~5+Y~;LP1tIo;JG;KA2swPuaJIrI=E6FG!+|ubLsJt1 z(AL=>hyvII5R%ev%3N+VmW9XEAkXz|laG%LSQQycJ?B+~rvQ=YD7B8685x#9d=N>W zLXlbk&^iM{WH+vJP)iFhSO_kcD9|Xd7lDyf-o*Yf>&?&KiM?b8++zR_%q4(63koQ? zAI{~#4(pG&fv|YDa~Io}Z`a@D13<+6z9ElE+5XbgBa59K%Tr3r=3pV+i~WC@1|Tjf z{5C*#z=G{QXzfu10IK){qZfky12CAcISLb8&sqa8OaOA|nV7gJ(`T`6EVRl`^!o#- z^b`sJLz)hbj6?yP6}J1+C%!wb69Bvfpn~J3G@PWQqySiFp7R$M*`G#YVPOHFCv5eQ z7eGdh?`T}0qX>9pp7(CMYR)_zVAvtl{4eA+r+Y?C{+9pU?2~`^22kz4aUgae!gh+S z?k%tee@eKjh+myinq8(1Ge=!WLPV=pDu9(fcfOwYUnmoRGfDmMjrs91WPktvFDTM~ zCCL2e5I5JiAv&7eu2CAhg)&#JIL`FtB#s7W$AE=k}e&oLCUkUl!Ii4bae(HjH2!W>mZ}75+xjTL)5COkmf0t`K52V6U&~DM zk{=b01wgiMjLiM_FAwI5YYv@0QB!X|M>g0m07LOgNm;Th`m#RB?0Bj-S*ZGE+Bdk$ z;4f|Fr=NjL)*j$Hn;Gem7u^(KC(kV!EpKgQFPi?Nwf*qO$m@R;^`AF3_fIze`t>VS z6NM4*heMIj{dOz`=o!kiamnrJZi%CY;NO`@2n5y}*ay|OUEq=)d~D;Y}5bG`s@ombCbX2|9nR50|EX538aDn=WCr*tl9l#tPm_M ziRL<*K=`{7CE8#L&k?ZRZV~LZ+q{5V1p5!h3pB_Y%U7)`-W`H-Gc)A2`G0ijHu?=md z9<;f|l6ooz%)b3=SRf;*lFF81srOfH{c%cDa-k{@$ypRWDwS*8SuEdb2Whb2W@wnu0YCTd2dgt> z$nLfNy`l_+g;R4Y#R^m&LiU-pF>~g`^2`&aMm*Cii%WV39p=Nw2^VE~QM=IW*yfMe z7EMIXhXkX6c`cnOv5IJ^`;z}=xSuP*wa`SF*Ve2VO42uf7m9zX0f}0uzfsA?Lq1>U_WZIN8I*_}6@Pv$siK$14xHYY6auY;upVcHX;a;dWmL)-nNMqPHNOJ-w zMdVh(vqF#GEc~!ymW~K$rjeCNP6raJsH9ugXEa)x?fb zwgWNTsS!XILBUZ=IFxAQX4d~Z*R5VstyudU?K%3MiK_4*Y#r(^C5(Yc zLEuJok1-a2ck88$3YmS-*u{C&v|T9kKOf2iD{-rh%oKCbzF=HlXvQ-vFTWYB@=UtR zgEp^XiTm!>dKg=UoduKXLMjHJR-!y@4M6B`vDviY&7eT_~0LuGK68_(cAB2i4;g8p1Ru>jOqlIqP`e(m0s?zT<2c{{dIX&WSZgZ zqzmUrSt|VLl#!$X8iat)-8q?Fv{c*<+S?$Fz3X>Mt{e7G zAt>h58y{}Rn{4Xwwgt0zq_DC~z=inBUy#BncBEOTDvE3e4TPtxMwaZ?X{3`~DUc-gYsZ9#dupt5tyzJRY~1I$%D}H-MF2KLrr$vy2Q9 zAP2BHL1E7`g9iY-P_9c1SafRBE0uq(n7@Wu!0x$znyJ6a$?I!)dMc(e)uu@G{PhKf z80~|~z0ftg0ljfr^(MZ+(LT#ytA&vYuK5LX1cnsb4qW^#O2b$to4&3i35qVU*H6d5-NuA1C?v%Z{#grtI2`5MOl)Yr<3Y|`v z#^W~FO3&*3ebeVFl~SEf_+ncuS}l({fCOhYkM}q1M;FJ((!9JLqN1Ym^70vg%v>5y z1k$mAlGAl5%d!W^Yl-73pfQoWn|VNXW8pdg8oFK6*nup9AHR=}kMT@dCV**QTuf;$ z9XGsG)yh;&x3oIm{LymY^l)I|J_h*rXf!o7A5LSM#{s9^r@&6xy<~|5W->auljw<8 ze`;BB=w_-CTH%B0k8czJ;X3&p@=b;13Llk9#g_08sp4>g zq{X5sM^-xnEkx8`IcD{~Ers12Ksx-Ng3d5)%A8#vSfj2? ziKb8o{_R|8=Q&9ptQwc%f4(8Kg?#y1Eod12<;s$dkLBg6rSt9o?;mn5sJBD`#N1tQ zSc)Qg*m8f&1`hGO8q!Ob((b|3w$4Y5SGL#3+VI${(h(J)6CFWeVUz3Q74ssvm#=bC zgwX4NtYcnSG|I4Ey$B*e-vm}N{NEI+gN1G+t6{&-Wu`n+1q{q%?sGcaLV?2Gd!DsH zdb?fu;15iBty_+3!7TDBDznRHFYT%gZ&$@|-)^VJ^SHYWa4gs#%w`JXxsDHfU1*$4 z>>ZE3_cA`*Ddg3KeJCBXm}o1Ht8-!hjKq!4bXB-V|FyYKgM2VGi|k)4fC#(nZKcz- zor0yDJ5{6m{QGXEgaLM^^Blk_W_x>X7}C4VqQco}XWrYUp6 z*n*&1UP+1cQNvjX(N1o5vT;Rr43HClbA4S?9|Uz~)vKuDY+tS5Cl%qB7Ep!mQk3K) zTygq6;KzWdtik#{#by0$C2W=XLLP7c0SiD$oRfJ7CABJl6_JL7$c6J(V52wx6+89bW zfByXa`S?uaE1En#J^-FmVGs@&O5-C%Lam`r%XxJ8$hMM($h^O zDHC4cC$Fj?9&YpXL}X)g8#m{#Y#X}=hK3YH zL;~t;cL+pwfshdZ0>}e`^83cda}pNd7Z!V-PW(t;hx9^V5Os7rVoGWeUZj zpG1HN<4+d~223SyBBRj^ksl!;3XYDo4ULWJr++z%8e+Ad`5Of6Vb9mF#*$>gKYqyS z=;%n~*ca}7V>N43As^$k%L6pD82~|feFjzs1_q`s_vq#HEDJIIILJhYs1)Owx?%@XLzx@Tx8|v= zSt*FW;^hq&Pw=yF9RgNURCstAfFNwFuW$E+6V5g|SCdoZZviIQ$nC{mR9svx5HLgm ztYpOc+FE)>2GPM>wG{9+p3Nrf$B!T5SrS-WXZ;Mf*K46BZjX;w%_uc5hrHSdH7#Mn zxN&vsRztXbd*Sr+h1rC#)ii?vI86cbpuo^Rf^8(B*x28skBvOQ%n;XzzA~JI2vL~@iMt%vx0Wl3W zektx7y~d*Yu_QMqBYveS)3|1(6dW9U%5Fg&;PvweFARTq(gZba z?%>BTe9M8y3sW_36grXoD;Jg}`L_Q^5-_kDj5MsjtFPm2{&MwC7y)2AzXSaFm4JHoq$t;+mU>0z~}c5$SEST*~F1Jd8ABgX2T~#i+sFNfy_N(as^8dY!s^P+OP;>!O z`vfVCy0W9xC)Vpz=OD9o)YZ5mm;80bG2^KnSCh&c=~8TZGQHy?rn-csjii_AU@TT+ z91Eg$oPBx6J(hyD3}mDfM?V~9&dO{>TVa^h=Rs|VgXLch}R9Ael7~mo4#m}EPKSLV}T->>2xD!m7BRBYs>a&GC zc2Y1Px`3YPebQ)d^zhPu%R~*aEl0Bc*J3LhCeDi6t6yAw70eV;QsagrDn=orh++?S z!ujt0GPQlyL@zo77+C6wrPy|~K729sJW(&t=}JmftdLrLxn?Tg>C$MKk5PoU@sfo& ze-!jfAM@|L1`KTK>i-vUZvmFo+I0(KC$g=LqJjdVfP_d$8wk=!N@D;5N_Q&?iXsXE zN-Nz;NVkfBgrqb`w{*i93w^&h=X~e?>YRUHy5Ifs0MB~Xy6-v19AnIRKW_2)eDfs+ zT4a}#IL9UbYouaQI~d{2{%>dA&fr>cla}jdbaJYbWU-l1=^H(mrc0#q6?RqW3{4Jh zzB2!?t~EWeTz-YwE{1mS>R{tgSEF*F}!Kt|VSaJ>Pw|y$Mwps8JD5$8!q8KK?VTC`3oQcT? zovPpjNJp`tjfq%A_)A4=wo%!KqKd%YlmW$EH`0_+pJie2xuj$`E-6p8+?R>Na^yPy z(nyNMbF$W$9~#79BOtUE@oP~`f=BD7pG%h&%dC{#zB+s-A!Ci(?^#fAaAHQrdD3G% z^1J|tdMu|;SLfJFrJ@~fv?E`0v^lL7wGf@Cn5gJA8JQPAGc+Ko@8F$Gd4hOsCc}x# z?CH5ppx3!JtB`&B_n#CE;4)Iw(h7x3G#o!X8{7RG6=v)tuH*l83Y-~C_okDSH8)Q! zD=+UI9aT3Ni}N5`e}0_|@#dF zrZR}v;^(IwkKEiC`S{e9W=Ct0drpcnseY6E>IwKHglS6-Ybp*krFSwbFl z2yCk}J>Jp`=g!G_p=#w$(9GA!wVP8MZ_k~H(_9hzo%Z}H9MUF!#|E`W)ua{_-y{82 z1GBH}(TxZnx_l6*Zy>UyEzcpFjg9Sjj7r)86hDyRTfTefUbH(Aa4vTgl# z7$3aERTpDbzO6o8!HRU%G;ymFx5%2RZ`9!vCSKv=_qNy$&A;;xnT&O}^;p0dp0c}~ zjXVi;B_$&83xlJ3k(`opkeoaU(C>`|9dPAWE!L}EXmyUYYPS*bs{426x|M%kF;RZ) z4|$RAT;JbqVtZ4ele;`wp4Qx$pc;j&O6(R!#rWtQjnp(W97gSYY5MhI1U9;P^IhIT z-x1(4Vbr>f;Db*pglJ^lF5L%Z2+;Ee>cg7LWC}@T<=?*5!E(ug0Yj+Y5-KO|^B#=y z+lc#7kRUQ(x&L3Kv0HWkuM zudfdDU82lPiYaX(^-@wTz45a9{z=otD`Fp=%FiurIm}Q_tNZBa+jG5l`7fBhDm$m^ zF0V{?St0Z2+{x0xT7@rR(xP6wPll8>O|E?%wD3HtuB2fimC_WCaECoA9wi;3c<0U? z7ygbci{WViv;FJUD2cOb*mkNn`^(%G9wKz?1 zHPNJ(PMIgmuLkkui7O}soG|4}y*B%ptM}!cYtQ$bET zcXC1qW#s42Z6Jyn0*YaiHiV0Mn`1+X$|4%mNe9OhTQ7Kp+V)0yN z=l0m3FFSv~=d{l=rrrU6+qPN^vS4IpmV)9_kC6#tByXITVn9*=Ds*&GoG(L80AwPD~;rf^Ox0A~y ztcS~1=dvQd{!>x%4zWENmq?VKw59)4lz7z+%46>>&W4$A z-DwPK`+A36A2*N3e*Qfky82Ym@2#bFDm@=$Oo~oL)|oSCic@*7bw7Y1i`P0(Bc4uo zjo~m~kG0l^K*~I}A9+a@o^O|VOBt`q?pLrlloY$CTl@V{zy2o^$s(f(b81Lvbx-!6 zIzUOOQQH6!(qg<_{a?gua!vbd2$1zeu>y#E=jW#8XB;o|NCkaRl|0*;qudNmwKt(L!oOX$6@7JRy`Q2f5b{O zOwyG~&@bf*riHyMnM*3CoN(pKlaf~$zG(WUs-v<>ybIK-@9X(~IH}qpJ4(6W3A35DB1)Z|a%t^uSv@jDa`0!QTmi?48l? zN88!=KKFoyDc^h+U!FQ`_sbIP<&wX+FRiIgj)j)l1^e_5K4M$#-f=kp=t+_JpSGU= z?E(Eu{WBxBH?M+%n6lf({8@e@{7wprHZd+jDOCJuV`rBgT`s!UH8*B?1(a`J>0_>oe|(EJrv9+Wj4pr zt#8r-rW@~@?n+tx&59o{|LEP$j7fpG)2-@TNz@1sikcN56#qD91H$|P@#F^PE#gUi zKNIe!4-cLHy&PH>C_amE#nlgu?fd-&5z zB$}*~P@nAW@QSrHHZ?7(&PiCXH!@!Kd6MXrh5NQQzZfr!*@+*S?Jw15rw^~X<1u#d zOt;UM=L<0oehT*)8s1RH$gQ0oyzXoIG+}c5obWBXG#@cf`q6}AWoN~M^SUTlbuzLR ze1^2jj_@2FV4QkG{pR4i!hEWj7jj2lS)S31$Se;p>}F@{3FplHa5*a8OJzm&)|W4* zGxlzM^!wA^SvH&xGE_`ST6`At?r?3$i;lPIWfQxVy1OJj+Mq>h?dZPmi|asLu$sk@r5qiJ zL6&2D;kC&OjH99UZqiDZEyuey%jsALMw7TCpd)dayMmVW8zAG4>`9=arPF5 z$ryTyri$>Km2COxa(c0=SF1BLWM3`_=dY8sBqXh)iJ3JO!Rg}kQ*%mdH+R?mq7SQ= z^rAmM66K>cdhn-#Pm{j9{OwRn2NRW@oQ!d&g&ZzLgvcg^%*V-HozbX#=bg!R#bZJ5 znbwT$^=9K(`ISvc?3NZra>nnQ#k`C{?N>&3vWu}TmMJN6zlsZ#(|r|?FQM#3>lUAP zf=!bx$?1LmD{bFck;Tm9_gV*|a%0#HQmM0MWYdqdOYd60r_R|y;vIuJyFKMQ-AXKY zO5%UM*JPO1IhC&wY5ckBP(t(Yu-I^uMQWrcZ$e-oOR7^nrS@m(?_XJG{WuAJWC*=H z%^4=QTq>u&95K4{>G9slsqS8f_21pukkK_7p>%t<((O38=Ghy0mniq-4kfJKIKfa= zR>Pf5YZsiH_o9fmP2%*vcfShMMyq1?G{tl4sbrrTkLBm`k08$vFqq#Fi_i4T+K5g-zjM$ybY7N2WfH2c|;D>Z~4iF~Ya zWfvsfmHVZjaMJ;*3Ey@V`>T82)a6O%2&em)7c2XTzMOEem`sqb=s0;;@&pAd-E}3^ z!N2z%e0%K3-$NCrbncihDT;NxzrJ+LOXbSxRjv`1+=8V{`pKj759M#vE|3Rb^Ij?n zr<3ey;!V;*cCF&}P~3;~!}w2}Y87Tf-p zI{uE#K?B8DQDe8EYDw8%!9C@9W&zKNe`-4!)oQNejKa)Ny*QD(p|Meuk&&IGr5)%g^;Q9Mg<2Y3Bl?v- zuKe$@3USjIV0i}ym9o;(?~vP=McqLRwix9n*o|dIZOA(jI*~#!3;itX0lQPtbittB zSohrKE#=C;Txd%_73NYU)c~?0Pk{kRsx8a@5+} z+nMbw*Vl{lBDHz3g-LLQ_gl$O+R}58YozyB+A__eqyo>s{q^gY|3^TmSi89~La6fc z_D0FWkPG+BP)m9eF`6~)&i6|wKI;sUaw8!PEi5e=Kx86dD`p730v%crz}5Inm-P>n zIZn?1o6ESck~w>POKTIu-Q?WWwbJ=d-dr5t{tbE+46M;M_5WPS`G+U(R|VXA^lzc$hXPNJl@m)*o-1#-Q`4 zFXAcSVdx{E`D2kJIB13L?L59^JzC6cZ!_HZ_K0)KhTlwRtLI>W3WTHI9 z^J-2B>KH6tc6#wg^m-^aOo(1Xo%JdK)cf??oVwWLDC zj=p{@=iMKn?(REDDfzCmsGQ7_p62#Z3iR9 z>ZE)zU)o1+nhh!)q&upQLKRTSv(JQHL@TPNr{~Y6?G71^R+P8}#N9!{NU1zaNEo8t zboCGQKo)ve=y(G~6LC;la77-N{(CUL9lLR7!I9Yg>mRt&mfgL26d}GAI{A>rc$lX^ z?5Pm@dXwzo&WB63xs@NNPf@pD;sKnbGxO+wMchMJH!x z6+;9ZUcHdXJvIYUXXwX=TNMt+crE3jq>WhG1>M35XdJ4A-~_Tr%?odTVYDel1SNFz z>{weIMDteTir=??rgye_6e)!$Ptiei+`H7g-Y!8guCFR2$*?Vp{>+(?zZI%NrUdj^)m2 zh{|FuREVX+R;UI7@tUHd?*kW?r1PiNuf#bnb0x?Sx0!<9cA5i>1#Hmx&r}S*G`y(& z?~$d7PMYmG!YP4C;e=ocQ(Y45lKMa1-)J03fhBWgsbkIe^Ud$C=;dC$e0f3?8k{VV zW$i{eKKia>7-!Wl35;#v9&fX~sY7PAof~XO2$&u3NCKyH>6L1y)kV)i{8evoeAi#b zvvgayGQVp<*n=X1%5nZ8SOT9m!$xFqc9d5Jv!x&r)YJk;EVErnk3=FF)~ivSHxB)q zSuOg?)4$t{{|J zlJPVH_qY*ecIDF2(lO>u={vwiVcJ9Kpm{^H;qPy%u5NX-rLUI3-~ zjA(h3@Xl{_O7@)2x9Vqy&lxKJ`49yjcMkkR(K0@D;-B0R-~Q*%1^}?wO|>@g(>>Ai zx9Xv4K;mrh;a{pAiF184|L_7BE14J!SY{(Y8}lAt|FzDlp8u#G{{xEIznf}Q=P)m~ zG4s{QeM4iH&>(wE;!VsWJ)1_smQqQv_7`ULsv|Z_shl=>#Qs<)HArKra;#taT`4<% z>GQ`^FxxYfG4sy#-I-&R7VI+IHqB{WyB(2WM-c|ck^MWf@ASN@ zY8*=Feo1kTQ+AajLFdh}yq&XLVqVV*rSFsp*(KPRe)Ebl%GmnsaH64O1kdYaG44FN zgT34IJB{fboH8OkN9n>w%9`F^4-t`QG%9-fDsAD|Xjz(AaKZtOWv(GAouvA?Cx?rB zbBz)>sTh1O(>>a~nk#HIWmf8E&#U^M6{@VNFu)fZ76FbhGr&Tj0NYl)l{!+*tB{WR zwUtz(4h<|z84zki{LH^Y3Zt4?gsPT&co~^)jJ(&YsNxcbg9itfY8B#=Imc!7LY@%x z_$Q4kV&c4X!zV|(-?^s>4)@RKM{oyBH*`t4Q<<4`(usBrHCD;l`7+f8=q;I_Ns?!y zi0c>SJ>Ah&>+|A8X#bLG8sq2>=@yNOKLs;&5p{jcX>o2#(OI=)UX{`9wERGLNhW(roAaIWX|a(R-~k|{ z7YIS{-^D@uFJFYOTOB-U{GhkLf2;_ljS%n(tkA3BWo2SMK0bPYcN`7`ry3RFsFFYb znT6mkk0JW5%XZN0!wJy`EN1-k-$f5sum9VCv#hdYjN-~yyrih)rQ%LM()`+r0{Y!w zGmCmxUTX@mJnHE`JsveHAego!vbIiT&~i^&>eahaub4_ad*I<$)yTQX<$t!9enx-1 zdZHpmOp2F2&eX?j=-D2l>yc2~BEtL2^f}7a__KH(sa>Bq5c&0m{V_ppzsclRM-Igp zhpA_lNHJKgslczoXjAXlt17N=H#a=#LoUx7)6{!rS$p~@4t{5xcFn8iE%l|^qiMgc#i&VwdH8q!S+N_6Y*L6G{ok${+d(btlo?k-~9;@yY=PpZcZXC0FuUPfb z{SDidF76U@K7$n5xhwQH?30c-R`M2dt3GMAQ%JfZvEb-7>wDBYx9n}|7|Sp`9*!#t z$+Yy}>NL#8>=s@7PSewWpX@C^xPL#5D5jItB6%M8>#yUYwxArRr)#AR`o6ufc>dvD z3#X(%yXd_Z#ckQYQBa&k)k_+Y1BVVpqc{_l?Z|VmSX-GJrdpeKqqP5Zkvj;bgoPMn zSGk)vf0FY5CQ=HvG^!2Ki8{8u>$S6XshPTTpnmY@>oY=KtC>83at<|@^eX$ycI{UB zB|W<2so-`qLixyaU*XVbMC6OBGly4R6`66K9_=}K%3H-#uQ)k<+UA-?Hvjl5(_Ti+ zTaSx2nWYKF+431k`8SNkbWhj4Kb=<95pnF4Dm`24>C)&IQo_eNLTC&u&Nlz*yQu!r zCY4=e(9X1%oHAjxcNt1ZX>d_<@%uG8(X5iVvis)^?}2J7fyAkM8a%f4_}7_*Bja^d#FOajUx0{_hdiIYh$;UP^Hh$ z^80=$c0(AA&tij4uf*X5J1Y6WhgXuJ9g3{C{lw*;DbggzR$X(1fL^u-xOV z&vrEU;2qz(>ubhx#cw*WDkm_|F-$+xvi#huKdSW3w8H$avwk_^QeOX);lUxln784N zsY#_)+ntY;QUz&XMTNh|>w^`ng3uYtn>TIRGy`uV{h4@M$?p8s85W|x7w$edORZUV zii=BWuH7z)HYPrPB-zK1Aiw41UjZ?*8XIE&HJ{6rUJy11gvBF}*rNB2)Mff z7~GIOo)>4;;h+TW32*-$gJ$XpS|1Kgm#w5Vh_(7$pKV_?(LX}PY_!uS9T-xfOIgf#WxU7PLPoPx4 zLJ4T5j1eoBLYd*_h&dR8<9@?L<4mMQ1x3Z!*jO?6#nT>eC_QsX>tN}FSOpCx8hd*3 z*RSKxHj%Wl{Z;ZC_)yJmHL#`2=%=>%(^#wQV5Dpj^ku z&ci>=d3ue{%PT9>LoymOO>B8oZ{>A6rHNJ)^csHki|=>hn__p86jXF3nLCG^Ke`8g ziuN|`I#bwRN87R+*I|D&E{`)n^6#ft@Ta%8H(k3^k=(8L+LS#!)5%SDJ@h|Ql61~q z9&lA%(iSRBV)kESC!thb%>6_&nD%f;FFeoi&KwElBChP#(LaOM0!=bEEb*3hJu;`-cL} z01kOfem=AuZ)b|CjR82E$q?(S~F-QobI%m1A2 zb1yG1!cpv3p%kLaO5p+jXaiUcbxsz1h&1H*R*Q?O>VZLU&!Zl@a0Eh6NEfEF*rMWBJut=r4c( zVvD8+GzLcqt_(`Y#>dyG8s$bnkm zSfAt=0sAB#-IXE8<_UK2kzj!Bhuho}#>!c_X@yL^sAE7nu zUdzY&*2WZFX$P2vl~yRRp$vVCyJXFHK~6o8-;fa8d&vP zK8|YNIx6U>*45Re6jXweb}1r@fN!g_>G|{!0C+7&s);~eztsQj4UKncI414hL0v_t z;nQHyM7@Ce-VsI`0}W`P!`kUB2A9Hl2anw&t>`i)CXXSD*c*{JGg6yhzSp1sk}sfK zYbiZYqcDw`J9q5mU0nr>sx<|l@nSX|QlEZ7*HW3;d@eq96fM3inu*Z4MP*e~{39ja zymNLY;V@{BAebZ&9Em(%fISJGJPiV6xXl@$prKJ2`!S#sfl_h1hMwD2lFqT-!Lg0i=l`R| zgxti1t^bOxdU0Hoa2fExxo4({rbY!>S=0J_{%=)PN%c|4Z6KL5Y4th_?l*(mq;#0R zhDfKhpH6Lo-wdZ^feuP*CV)7hmBUU}SXgM=>!Wmlf+7|j;06Mk7>AvpJC6x-_{gvg z;1{uE``RQDX?}Em9AWQgg$}n@(h}$yPWuAV`CG8jWou*jigiO zq2UW!1y?C0g_aC)ihFF!^O=E6?;^yW^5r?H!(G!eVeu|jh0H6Zp2pPH>cvrQCCX_A zG(0FUzRo(%$|}?0Fc$<4oL2PTWWi#&f3|gWXcD~(fGS~VW#6)Go2H4B=;X{y?iq=v zPoLI8?J;f2C45>3H#U#Xmai(FV_}g_P)YNm?d|J}#S0bLTaEqD$GecL;EsVZ`yp>s1K!^IflDYnbmH|fvvFt$cvBTHVm?dESOKYXX zY1s|ihuwDi4h*K#vE3DK&WSk97gJ3Sgn5>=w5Y;_o}fHx)$?^PBCvW29iq!q#NRUg zw8^}W!UZ}h^*Icmii?ZedJ$Xq5_;mW)1xTKfJhwvQD-+CSg)@DyOHl1<(dK?b&MyC znW1E6mkP0;Tn{V&m~*jnxiHJyj(jMA%WF+lUU}u&F(>Xy*o?Qa22>0+CWR20bTrZey?^yk4~;Hr&orV%g}@yIpm7#X1mfyrrEjIg_5^-l zw&P+Y>8$LUwaQZerk_TbOR+$Z$HKGA3-47nHY%a(#$vsxi_~3C)JT)ONLGq_i!-lW zn93j&$g>bgMcv4#66-N{JS01Y8bC1Agpe;@^wzG{Wo6S=WXJM{kb1Vu%bjRnWveb1@A2hEHlBrGh< z$JaL!dx>y$A|=U#!Xo^5T#8uyBpKu^?fJaKUG6fDR!-4@CPD-%V;2g^0m~TTt#Mlr z>|fE&4Az!Z93(Dy%%tnlQih${4@08qghQ|94(I6&B#D>9NZX)}z#E)^o(Fsy;@5k$ z87d)@XO}w%yz=oW#bOl>``#9TI4Kvl!sv}e#lO(w4Xzm9Y7cTaC@-;a zl09DbNIpIxP2;WB{l9a_9~!W@E2cU-Z!zk1+x0-@)9V8UO%FEQ-Wz{ydj|FKtrFY! zo@g1#7p#%kq5N8u?#;uT5YKI%+qnDf^ZU=4TzWpfz>FqVCg+V@6K)SMmDX4K_pp+y zFX)ZEU1fgJQP(8@8G^BcdW&Jrp`Gm*>A;MJ-L)Qb9z0OV_vLpKEwh6FTRDE9+4pg( zGD7&y*6{DGhFn1Mg~U`4W*ouluWo9}hK?RuIy%#GrgT=s$Go-GCGn3f874F+MK@^y zHpZ)EvV~dz)*C}8BSCwNHpu0R7zL4QJ9864?A_Lkv!Qba(J%$R1Zdh|42mE8UR|eK z{EHWV&5kq`!#c4^`5mrf9;2W)x)tvoVe4V*GR?$T#GyD&QR_CB9j_3nK5;=FK3C>De(D8tKF{Sb1reI$qr+8 zF133QKR4A?YSDgR0#-yLO zpJ#=m`z#)Q_e&AVE^s6pa&<4~AndhQCxBJqo31A2r?6+_gLz6uPQKtn^qmWU0@&F~ z+lg)?;`#iI$t@(ELp(=BLP*aWs(3DxXOg@olZ@qaoa!){E-kXZdqO_2!2ITxu{~iX zt6ImNYs8)H&EHOEpuHq;fvquw!b=`x~18y=Ffuf4I42)7IU`F+~Vaj>50}Gto2+1tsU+h$Szy7-(p91LFZk zd2cf^UJoZ`Cw}4fF8!2{-d>i!wv?ZO4k-7hPemmqd!L*N?rcys>P@K-`3kYzfpU=d$DPF&-O@P${Op zVj%TwkTh}}h?1xk-_iS@iYJa}(EZvIaM8tTfZg+^GB0 zUAUt`ixr~+R%|(q+9fcUmz$eAkk7{Jq_}Tgi)CAu1x^R>KmY}MGLOiujDu={S8kzG zyxpxC{UgNOaspFKzx4A%%HSW$^(^V0Xi%1sDIn!)EdaNj+2wkKS=mZhcq_a=I_P@9 zxNlZ8LWZ)@LN;8{glhK{%-lk{(-Ie0_2GNzMjZl#gC_t5i%q^DP)N?imm_>QN49<_ zrDb#(C3TB~u+q;C3Sd3f7&mJt-@}OS*AjFRbo+Hu znwIAJ(5&oU(yiBE=l=MrMW6HR-;T$Ef|g2|#-#Px3%E_^f=+d14LE6Z9(P(|%=t(Y zVk&+<$S9=X;p&d-dG;=knxyufrd{LRb}e19Qz>T0_2))EAFbrR`9b6N&E-)bZ{7h% zwwDw9l`pebEjS%)_J~-YSt(h0P^ex+Z*6xXiJb0&$=Td5{8yd&TXNnDcurlAPcm87 zm**S3&wY2ocWKVPB8?4TUZc0v8w{PG61%t*xm%n*2wb z#p@lq26((ng>Ygq%w2CoL!D8VreF(>+hE2wAm=uDL-Wo!obiY5F-)VMpiF7HYme3_ zHIvT=&!}a*Am$Yzy`6>m0A9N*SR5O+laV#DZ^rtji!2zO)M2NCnvLwn|*lvm_5J1)lleVpju^c+m`U>?k+#oOoJS*KCkHAeqohPV=4OFew{V< z&675EpKUGGxPl5VEG(Qh3=z1$S)e!VO2B;TTGGHul}5|Vn}TB@eP+Kt4n7;-__;;% zI4_^apr`qX9BZy}P3wSRN($-o#>{+g+$e{H&-eexUEa=5SE*w>%`MR8`0(a7+UrBH zx%bR0-aO$o9J#XX-l4n>wzN+)KxsL~*W@aQ&Jn1jnU|p2BC2EcAMF8hL zY5{?~FB33B!^(Cs-LQ>hesMTS4-$ySi{YP~K+;jreJ}KGN~qAG;oraK7eni zj|EQ|EOr%5Q><_ofI3FPV}Xg)g>>bSwP%9^a@Pe8x%U>= z^VC8bI!+=gbW#5M`Rrx#hw4v<{kO4FsXF%-U%M_KRnm0pTEW;GU*9jt2Ip~F8xYf( z>+dlTH&y(j^2fV}QL`|?$RiOV5ae-$YvW&3RMPhLeCQ~U2$B=u0K--pm@@zPF}!Gs zoEIo{%4xZqDiaJ-=1^?hA#PJzbQgDfOA8bZntNe59=gGYbCs;E&!hSh^^g|9xG`?W zE%CS{M@aGsOg$5-1n^3{-TAHQOHmoqz`PuN=H zfCHWItxB?GQkKB&*X?|D-j*M$edD!84;XKwJ!t4Ey|yKnBR_SkQh!IiBlWa?AXl#P zl(jYaQ3uYtrjW4l%B_un43)4P96qPBdykt7!n9(`{nSKGTpa4gzuk&2s;E#54i2_Y z(Fi}&!YLDcFwtAgeveaPA%^t9rsLw;sTn&)pa{(RfX_-uR(2l%94!e~tMMjC4y+L4 zI{_NZhZ{VgIES~}RlGjy0-9%nYHflFV{h}PDam&1=vrGHUwh4EL}}Ld?F1d2E4^GK zFfeqYfJWzcCDJf&JjB&-W4WV9E(6i*SjZIJpoj7B|>-VFV zBoAjO(6g{K#&6yG^7wZ90i6%kVMZIB%GJm|6u+U|vv%~Muz7l={Ovy37lC3&7kV5w z^N;Co>vy29dy-PL;rf7m(3@j8Sa|7}`uhX@$>a9At|FSP?_Rg2lkMZ+^H~k!DW2%( z*4Ul9Uw@XVf*7V?5q$2@?qxV|ySVZfBt6VostZku? z%<%&1!{HnwoH}yZb@vT4M((EMdyN4K>6Hd30doV+&C7CKz{bY;sfjbKXyrT1V}8e@ zn>??57mi+ehAB8U`FZbsQSp61JXIN&Q*wa&uKlFQehj<2T723#n{SJ$xe6XliUdD1 z3ro@mS0a!m6u22WN0i)CoBQ(g)!gVchwcXtyPgft>`Xe-?BZ?A(=PCQhspebEDNcz z&m9`71w+@!CZ4W#XWUyEKBFSDr;kyTa&A}F_0@x83ht4&Mocu6Q%O$!h47IfeP1;| z4rkZ{y5!R244nggN$@yZ$tYWQ`=sqgNduyBvPPU`OZEZ8{PO1&I>o)FTe5fW0RDVW zx1$we05TcuqU@!KVNy~1A%-*xt0RY>-x^R6=@5P-Iz0R#j%3mDJ7IAiB|8wb7KYsc zk+9z(JA`ouL>z5JGtkA%d}N><-@kLXZo+=qZ<^iKoG{G>I&%}JO8mzHQU)t7CPv0x zU-|WG>kPg=dHgm1w7CIsB18=Z4u{yQ=UBm^E#u$#DBv20u=;&fAHWp47JFjo`5n?* zd-qO+2>e_hytLzZc3Wb>DBJB1*Zof#Z}K;Hb!{2_VwldQyoTdlSpLi++W4t^0>-BY z>aOhfFKoV%x7kpJn<9Ze%6W*Nfx3q8&q$`tSHT6n7k@#g#d?V1*nA#9o?ZBo#5hw8>^km&Wax(tuk-J#9v`!z2iKB4?EaYj&s0n54Kf-Ulq37X zpRnsNacOSkimEL|Z4f89#QsKtG%qTNy#hzO=>4ff^=RFS@0WD%usmENM8T6**SObE zVLYg>y|VyGxQRK2ni|z60+dkr_f&y~-h>lx0|e#7nErk`H(tTtD>tsb_hn?*pYPbq zer0@8>0t0e>~pU!=sB)FG$h^8%6zn~;q^>6h1mEF{5Ua% zlX!#s?3beN{nE|NbB!NWnjt1ew8z9(@K?x>Ywvwo@MmOW%>{}Rb)4seq(YKB9zXuI z+SX+}pQ@vxP+-{=ecoy8@OE34r;j)P;E^7|3O!-gf~Heihc6WNb{G%tlm%KwEmgc9;ry zwNp<>rlrT|P{WJP3f$f$x}D@TcB(?O#s{)QQ^cjD=rDi>;+tV+@83F=fu%$ZS89nq z)!XP|Cixb`>jyvm*3?8)H^|gq^M?YIB`KN7+iKi#UTGiiE#=i03gJ zn%1f!2+)f_OpLsBb-l^;CwD{#JDw;a>~za11pukyv=f7e)oX)qlF)rqR-zS{Is+6m~i)UAqud$Ab`<>L_HM*ZjM(ud(QDs%g&QrO z!+z-BN^gOx7(KzK1RU_IGa)C3?{vVaI$`YWt6Hbcy@$@6?TjU9p=W*6rROBxYa&OR zXmIKV!?#bjo)lW^>^fg{zjtTg6GjS$mRM=B#w5)uxb{lR%BWABaswxwj4o2!mPFO` z=cu0X5H3nd?Fn&Od@aE8Pa2#}oW*I3RB89|_33Gfn(&)C1PD6_bND7etCvi3d zs$mRcBM#1BW`2$jM9i@ZH5D5MN25O)79IT+oUa zuzOdoY+=b?W;=1>g!yQ5y#tAyrGJ6sFav`?F$y`4sKuf<&fyuV?A@|A!Lv~7 zI5qWy>S~#gM62vJoY8D6lCF@jq&U-GM5cU?^9TU4qlZi!leGaoO)GP_v$9b5#DT0G zBt#+V#J*>WoUA+N#@^uSINXRBOOLldH$VKcNw~F9s_12)%kbhUR;u8hxfPsUCE^LvKJMpVDa9kDuRK9@~%EOq6 zo&A7IVdypr#4*L9QOoqN#zI3nIHc{_vv`Y&kInL13>7Y2zMMOpr?B$m*)v*N+D~>K z<-PAxQ(qyNgPZKHr-gp2l8X7H|8Cwf@d8H6&?%* z2zC$5P)D-#{mj#+4QAZdxg-1Uz*Uy{EDXfL=nI3Ro*o{(s0;vhNu87OSN$Q$Hu91l z=j8N-oNOoZ1`PkrlqQ@Wr-R``_=N7@LSP%x&pmZPUj6{4CRwz1yB;y@j1pX-2f4NT z==r~#PX${)Aj+7$hU~Gdr)cQLFhCZ0UJW(1XQj7e_s$a0tUUahVS@Pe>#wEdMB~(s zX4E(4>DRSB>UvY&ThvQ*rQH91=aUwzb#Y!SnWk#X?rp$WQ`;}@-5VoH(-toEuWGIT zNPX4mRj7!_fKAYiCNtQs9-^37pDv8s@x;@!A8pz(X8QCO;2z;7hA;HzLr6ooQHKQF z8rsO=v}%uYVQ2J9;5t>l#}iBJ*td7g(kU96 zP0nDD&)Ux$kHP1;H8&AjI-5bm8Dea(ou|XE!fmvtPH{yGV$YW3a7#l;@eAWX0dG{4iQ6P9_Dh>+J(jzULa-i4gz=7_?=Kq0D+G znft#>__+Xz>yWjE&Ln(q_{(-2cQ{Zui{pWCR@z;F1YN-G-G^A6F+g%?KaXAtdyS+} zxCzKNRMgNVhP2XDR)SX#JuXOK%4LBJ|3Ll02U)hHt>x z7=X2+&dcv!g>O9%Ug$kB&h(|C6pe!lUD)@lLPDFjx)df^-iNXX`ES{u9QVrM7WS@)_YIXRgV3-$W}xxIVW(jI#d1TRut?A{C%x?#;0 z;I{bYJ6(_taK2=Ft2U~%$5)Tk{$1?jdj_HC&i#Ov(a7-dPfNiVhu~<`UhO}qdr|0< z6BD&Lu7)?g)l@tP0s7U87Z*@0Hsv|kos(amwS7-n^+!;0+MTa^BL2!y7h@R-V^}s2 zANTw?t#Um$GmyY=?kJx^0s zj60apG-8!~VY4A*t{-(TB)ZD4zfm9-8Z{8}Wq_Lp~s!(O6v)KAM$OXTEkOJ>T` z!6RhF`~AH(p^nKfX&1)Q8a2Ah7?W;87P}a~6TJXP9J}>Nfo-u(FPWR06YEpY0v%Op zFQsbi9~{m=7`H7x8TS1#&Sf-_L_1T!ZtcsLFXK_8n4+dm5PtgOYL_hY?p6%X;*L7r0h#n4 z1~lM=#fA$8cxU!+K;oBGq(Bz^)K2iukWiuJ+^X_&6w^ZB{zIu?Tey~^Uluq_}oND*}vm&l(q1~K2ULK zGwg?9ixN+|>lUf}$)w-SuC{sPkob@}D$SsuJ`tR0x|VA|e81 zY(YBl+-{_|9FZUHT|caK8~`k`yGpC6g&Y7icH_!X2+7!()^?81|u~6dq!JhTd zE}z=uU~g*=HxVHWMY~T@LN!Ek$12DW<21CE;Mf>zL(zK#SjyflW&XW z-ot3H5|{=NnW-!JqCUCwHsY*T(r4IO=ULIk^ZvR8zJU ziwt*!1>$zvEJ{Hk9B-U<3#9`p-Ab&9zb)Y-Ac>!*;~VTrF#SXPhOCER z13qO9KM*0Vq;X>8xMxW*=b)O*nfY{KWsAn#mSWbzQ#ukJe1?C<7bq9AN<=>i>KvC&CAS)*XX0MFIa@G^I&@ zOUn4UlaRA<`i8Wa*iP{7F(2OuSzBjNP9s|NB=3DzkO1RHKtc$Ug>#8Ncv&6Jk{4TLll*$9zDsJY zF$)l2Kj!ah0lN{ZAVBqozm6PHz1Nodk2-0|`M{AQ9HWiYuf`IYq@UxE;CuglY|jD}|CseGTC!48l0*i~(2Y9G&%84>4z6)>EGYU#<5n za#x#sigK1YFZq{4&szmkj-R7CecW#FQhQ}m+yr-CtHTTb@-}fZ)Ao5?36YSZ$wHYk zRL{RO_2wTRQBiB;b6)td5nm{pG~ubFe7e!H3Z(}ed__pgrx_Vg)ps6! z^I#`#K#PYzXRk9_rtqO{`rhac1*+_<#wq5gsTKMcV8`>tWA=jsgKNN|B1A9*1XtVA z4Eo25V24=%YJ$XiijA!%n=%mS64<_3USYjNQK?OscqU9k=e^^X;5U7VPE>F1wS&_Q zpcw|`I=42*U`=lRYgVZTdBHCJnn({ z(>fh{KacgY{Cv$CBtLbr-Fm|4DE+p?9FLH-Si%0akB=KILyC{KxOC+&ZgHsCR%+jC zbo7j+)|0V{V}kiEl%tg&>=vf%DGo}R+&B{!Ij7a}PI^$#jIZsCOz_f1@%*EoMkdt# zdwMrO0jbM+pO<V4K5c?oKt5Qi%qSNvQuM?MH#(RNGb|x2U>Yo9jNrS%m zxAuQdm()dWkQgER+|M+f(cM4&WVG;Dt<%Mh0aMdPqo1v#a>U^ZuWRyOfBWY0YLhXg z?OLE4WO6cbvPE+~sWB60Q-KPmND)&%tvq(U4cJZBvn>;>gp?i78bfG70dHfo;If5P zyX@hP{SQ=#6SJ;ft~(g5o)M{{E+=_}A_G;O{wSE4m7T3fAUL(@@m23THG>h)mnF}H zj?OnRKcsaRJwnOa^dK!MIdx$C)s|HnUXtQ}O4E{9{Uv#!rFhO+X z>WJ4COCm0Bmr}=e&gk9kL;m{BuM0+tf`nI;mq-~lag83r@$QllqL;bEi;Nu=ZrSS1 z7!=t_X!U)%WxqHmGGZ+d&}ve9xFg~I-0*k5hxe#VdTPxA$IrfgD6GJepbl;av2Wcr z2V9UV4nj(QS3CUSvG=q(4U8>KwEBkkL|CN3^RpwhQ<|kMsC0F5v+Z2IOUU#C?**S> z-PiO{tvjmUP<}6ITGQ)&SYsA4MkDx9Wc0p&FVEs9N725>@r*=IYT|%3kokQEor=9b zVYqzLyQ^ z+;#Cl#zGeTWp$7(U}=(wLqbTun`irN6`dIC%~2G1zHM6x$3kwZUCiUutUP(?RqySp?^8L4bM{_)?X~6{bBr-PU>?EA z9{+yD`aS#(%eid4*v$=VmT+2tls zSX}dr1>16?+hOF*S+p~MTgg|XL8Ju%*fA=Bx2dTos^2C(&ix3I?Q*=ULXXrKaiqEy z-B@k8$}h3ra$lyPHS7-GZSsXfU%u%I2Z19{q8 zT-#1-18bqTTY6aDt4J{K!@#(@E|aKsJ+LY|GABl_{eGnQxU^g%u5NJ>&?-{heBT3! zphA7(Iu4ZMB9bl~W{mfZq;rju)y&5H%XP*ESbHtgk;_vY9h6tRbg9j7=k18@H@LKa z+NG7e(sQZUPji*vZ_k(PYpeAY`di}T^%I^|;>UYSZBf2ryPWxdE()q3MX zctefoSo{vGv47jsRP2IqKtNsp_cIl>I|9p%N>28sB3V82_{!47RGrvq%e434!EfoN zCp2pd;}v6?k8(BpT)wR|TCV7VdsBY)&pcPW%-PAk5Vg92>Rl3}F{|f_h{~;HvuB$& zoO+B;W4m(pf48v4Z-HVbd&L9Y%QCsVr^&O!XuP^Hz|V0HFp)4PzBj59S4uCL6aMs2{$$Xd^)W+bC07U@^3FKWkvGTsDl8wjH&9j>7lPVQcG= zqP_Ex`g2pmDE0pstlf$4`gveE-GcttoS5^t#oiO#7L}zSe>jcBg)7Vch>KW1{JB@* zYWD5+L1q(&xg8Xpw{u;moZ{9EFRJeD%VyKP*z!NsjyBweBE#Fu>ja`J8>q8@r0~@n z{&;`8Dc#Dhf#3&})HF|mg1%MChy(fl>2p<8Bj0UnGe1xq$m;>wGhL3t*e7rXobL+h zT#25A0)Y0UZ5iGL{uzTZY5UFV&i{^!X#4SmaVR#*@&qiH#bA8B2k|-lnE?iX#V*-n zm=4=;@6`d6)r?20Imtj zFy754d%|Bxo<21f5wUXk@s6XEGUd7yK@IWy0?pWN$_?bg5%BKJ|Ea@;$+x)=9*nNZMPM$imS2n@eydeC25x=#34vikr$wxqul6x0f`B0$E*PjXWVs4w`?+aYdJ zgX>Rup%U>S1(3}Tbp}WpjzHuBOVHCsMqEIp5}V~P&t7veHOVE1)05i^bj(^bcu%^^ z)CF-`JX4j~L1yQYx)$#l&g*Y6*r<@-;@-VN{0pu&s3BRFe^{TwO(pI6RxY zpe=MX)I`8ldOASZ#9VM&z=irdm*=qa8W*Zq0NWNbz?d?)-7HaQFX!$?yL0#&y~0B}z&8AB?!- zZIqMlSAyGIQdNz;bf5F0d34iWb~B-hGn>q=A$Pt`xazE8!uU-D6bIg1I_eW}*cuNc z*5N(fN=!21)Yu!t#L9ksB$_I4?(Y?}zvFL0+U@1M#wJ;qHX$)|dOJ z1dAe=(k)EEu(8&e+>JF=~&!b@&&F8C_dDm`n~Bl0ooED4mSKcD~IQn>C7M2hbsea zuSD4JN7k5)g*heNw}5`LKFNALeI>Ky^r_cv|9p%mSJzWp6}ck`|z*;tnD=Dg}uYp01IktbX{z0SaI z)b#>zir0S#CSMr*0B$bfq$q}&UW+rY-K6{MPZ4`*PaeKobR+E`|79Ji*3MyTd`M3B zEt-`5tN6pp->%t!oQO1fB}WKOMsfVsS72PYA$}!i#owI^LkL0j&vl0!+$0&briqCu zr4Eukv-PXqT_C&LGLOF-B+bfIt8^_yEckbs$o;6X)LA-(8u`0^TcNZ@&?~mQM>+*kZ5bs6(t^J)ct>N!u3@bQLmB^_~;$61^i6K9-uN^=o z!6qF+op9Of79_o+MSDY*y^f;=S=1GKQjN_~C=kFl0T-mZHjWKUiS2k`$KM%4k$^mT zs*S;^5J|F-?HC8>cdyV(yx9^wflbgCEo(&+h77pUH=AcokiKp1l}r*es5>D91c(dt z9D{eUwT33~!Q@BRv-${-WaF;v6&TfOGm7ryxz5=?oog{!(Y)<=X{LAg#=1JYCP^`; zVs7zkR5>fUvRZvDo6fC#s8uw+UVm&F3WpCcu?K133~C=jW*sXD+Y4Y0=z+YE&8UT% zG#DnAgUT*3ZvFxJ;wV@xcabbr*+_%$JUl!cBy8~rrFqAR_z(~bJ^?INF>V2-PLu*z zJ#p`ES9W*2bYaDnylS?D3K->i`nPmV@^Pvqpw-b5gxjHq2uhgZ-gsJKo$}k3*D-88 zrlA5iFNAf{CrJf$@P*iy*g+ zvFgCT^w*FT7z%?<7>E+`x-yWavEEuww7Njsik^r`vk?9xi}-2!8S8cNT_R#Cgy*l>$Cwc~FH&;;jHfKD7@Ip_u%zs_HUm zBK|_=d*Bdo>{o8*qm2i)@TgM&KoOlEt3-jd*`|vn*tTaM`4e>y(9OmjTL3@!i`pC} znK*hIMb}sXDFQ-4egyz_*3?g0NB9Tf?VmC5n$X%|58p?c=}~~X)RCM$&zU{jG?NH~BQ zdL-FFlQXb#C+RCX&gMdiMBqjO!UFk6kPlpaMGpM>eEZQ9hg z4oke!YRt^#N%6Pn+-cOO1)MbPo2eGsTiGb4^5wsXxok2keR==eY|oXD*@IkM;n2^I ztBwMKIs!=4C}UUxqlbCd3F56iL_x=Xo$P;M)x7E$rT_})=&?~%*8-63mqV-6`SaId z6N2%C`1%-12AMkxP!@eIY_Z?e)D!{)UoMsNP`sDM^|d6EP@$sVr7S z1Tx~!FtD=k>2eXD+kvokMeAPi=~Zg>!}rxJmd(t@Bat*9X|9F-jKN0y^n|r2;;`8&8DOfD zgPR^N2Wjvs%k2d+$nY{ZAGr?`mtuaa3+h9hZ8*b>f6q;~41nst% z@uZ)hZXXsfwcTIKqhw)rc1uOE^p^o_FVPU<+RpL^NFzsej9F3^56Em~!B@S;%WSZqc_hSUDWEO)oVOzz)6 z1t)|Nw9dvenu|xI6k<3+-b2|IR*A!%fyQRwdt*VwIWL}(l5(?pbkhNIVG43L2>L8k z+lQ@9(wnx4Sh}G^mun}=$>~lA6>vNxs}k;V9je&LEnvMp!rr0w@xk1jeXzsCHNrs| z$X;}$knm(J0WpgJmk2;ahrg_^u6dzUNb(r_YB;!0cLkIQy1r3qp|bT=e(qhHWSH{bLttN@KDbwO(R2$H(XffwqZ z50V6FiW~BeW(ErC5 z2L+zM#K|w}b5~|=o>R?|1Yg`1q`!@WgK)TJ#sCez(Q0uoXfWG1ki{1ANBGn zFflUwn{9hJ_gn~CnS+PM1{+sq7gt~V=Uxo?>tDc*JWX>dyjAZ2N15YlXCQ-*{w^tvqARlwKHAqmk2-Q&2omism`>YfH0s%NaZ?quK0 z!g~I>*2#==eg2)-jkG47`cb6tkL_>%&DStnptpK-A=A$K4E~i~Luw`QAOw)Ll1MMF zTva)Z;*3Iu^&v>x0NGOewACS6T2e?selCPajsi7wD9t`XPmFcv8yshUfWbsTG==z` zpA1e@)CeeO)ZxP+ik@xcty(2a$o!lTM3Nv8SqY*qWQz1yS~Or|Pe^9~sEErlHeR%H zrI!&K-$4FE)CfwCZMHFje5qhj|F&Eg!aI`h6@mEr*|QDgS0Lg-0I{4D3i0-~0`Y}I z@csz}@3Vl_j?NiUuO)z@Wq!a4=R%}Vs6JAXQX)1_;k`1!H$amn0%Lq0#HngAo{_zg zRpXg4gQ}Q+aPK{Iqj0U`SQy(dcB7e5`E__yX+Xh&T8{U}&9k(O>(;#@PlgYOE*7?< zV@6t*Y3sdTju;-EiOiaVX`n!4DRp0tNHz)>+AelIej>4xI zxVOz&@$vECwEaZ<*M{6e6Ysw@*!%+flRKv02z~+b&K#zBFt)vsn!U$qAWL zWJ-WdF#5|+jmn211P$5tQeX}sa7Umar|kr$$7Y0N&M*sv`_4(wwlQq*j%^kR0P_D2 z<6wL--fB_fiqG`IVhHm@r@)Twf=@za%`RMgzOKz}>~saq8LNjL!WA9Q3q1Y#wO;OJ z2^4noxL_j@|2ARnqPI}bFV9mq&+nAfdd*(Bb>9#(6T91XgG+lI;|2Ok**aLy9lQJ} zp|(9O<9eJ$+@wR?Y0L7*vsr!-dlAryVrTCk$#jrAc8o&JVSFP6nmeJOzud{m$tb0b z23-iH;oIfcL()KiLR36px?*Lt&C^V)itm5Ref8?q$fzh>@u%YAC9yzjYwH147i?>{ zUI6LnNLp_;KZ4$0a|aKwv)69w@7}}G(_a7B+x^NEQ>OLn!7bs-J#Tyym!sd>rzox*3r}Jc^?e_qW0d% z$oQ;W4`9~ajiJl!)hwpiZ_p;K?{oCV39y@gdTXJum=vt;(1De5#oEW>wqn-B>b8;i7>QTLUj#Rh~VsQW8%n!_&oT3!h2u zm}v&OK-f}qdkfKqo;C-Wtmno`GcTGtg>Kq!}VlL>z zttWFhyTv-x!lt{UG{j;~SEt)LyzYKbP!QB_8eo3h(AJi_a)m&@m;iHQ5#o78MR553 zMtDM{r4eu5+=uB{>)_y^!o-iQXeUDQcqV3M5d+9QfWZ^*2|z1?9-b^BzG&Kc8w!R? za&oyy+v?D6ILruvadmaI-!{TRf}o-OF2hlEa26;?@F==CH>z-OQ%_HiLfS3eK!>ha z--w94(B-47mr>c`sMZ}nKN_<54P6Q#PPewRYwziaWTQfmVe1+dr5{+=ZRTKVWmUUi zh>Ksv$?bDNYzwc~in|i3w=&dq3T~ID-}SqtXbw*Rq#h5MTLa|VeqSu zAHA>#XeKHWo;ZF$wSC8q+b=|{%xYDvz`Lp(_*lufQ-Y$kz5S$t!9iwz1Cu^AKNMC# z``aR5v~7HRywRrX#W!>WkesaA2;WTTKS6Z;hSn_g2X|za`5bM)l0#Gu*v3^=Rbk?i zpiyPbB-Bx&$JR40- zEPMC%&+KG36O;X04s3nMZu|N3PSK55 zFJDrE6k3`4)Djz2V`F226~2+d`ERJ&>-tGOr>W^hXQvjv^g~19{n5!!(}R!Wm1Kt6g1TX*U|d(=d0wP35rag%cnYAr2j;& z_?oDw1GuEsEm_p78$cBQi-*pIa@fyKa2Tv-d`kQGQ>H0}*|M(1$Yb!vKi@xagJ+A0 zg+<U2H#SPi$*t!PjEQi$B`vtvIL0r_$k^@r=c*V# z)zxjqiUbh?c+1$XB_$ve|PVIh}GiQpKEbdP8yZuYHDo68ul_ZwQjhy zv$HdpRW)J>i7#hN*mnctV|@4THKp#qaN)vc&Ro&#ZHYMJ;fTEx_suOW^HHce6ew+s zx%YJN(ckxWTXy+{IeIr4WplXd8+)dF*D7{6A}F#rZU4STl^J2rV8GvZPJh?QN3nkH z;_oY>6+1!UBa14CRzDG(w1H{e>r1-Ha{-p?_Ep^cwY0PgC~NPK{U6~J<{4OF#XCJk zKjSe>znqRe19c@H#93rT^l)$IyFIYQ`m(?CskNPyhcHlz`^tLh@q9girC5h_BpKBIZ^<}RA zG_f&%(TR(<_PUO4&ZdbCmT@bkY`iIXf{OE+4{S=;S2JLY;_fj`}wox zMkGP$0+WRrP;^$L9f_4lv`9owf@%YQ?4s}A^K)~l155H@HaR>r^m{A~HWFB3sS_R;!G}N-00G6a6)Qya zlz%|%v2Et!gsf- z8rp^k?4+<`z|s0lzm;3Ro@x zLM<>#Cb9ZDR0MI`L23=l%3{@NQtLppMudOB1mB}mM?hep4GOF_fJMlk;IO85G^Akc zp7ZIGlgNMrOf7-IkV$49KSMu)&b9tT+&@Jv@ zC|OgcK>yw72TZKQ2Kr8$7fJ;kIPyMl&NT8XRl`;Fp8K3Xe{uPVqj&-vUwEx13bC@X zen+5;rS~UTorXV`7orb%9F(B*OOFl-gR-;5;j6QokIx6G1?3Q_ouWoVIM+SZ zi}Z7#NLd3Rwg#n@pkGq}{_D~#4zM|lG0I2zpvpu2-ey#**=f;%WWZ3KodG~ZCJWpX z2-^-EyLhL?1+GEy!!Rg>%?L{(u;*f&mo-Lr8aG5C*@y)vbpw=eOfJ0H5#kT7$Wh?< z?ntJ+0hWW2X#*uxeGmgZy^-9`J$u4)52^IjsYY^>)E`GT69N`_6~--Mhj@cO57=r3 z%30QCl_^|6YrF*O7LV@1NexlnO(**|t8h8Nn+y)7!iL`d{zX&6?GWdEDJpu1G(_Wr z0>R5DxVX67m<8yaaZjS<55PZ|r6v&d2+$Xu-@fHQUgL>DLqIw;H6^HJ-VZY+*hQ8_ z$gsg}*#_JL?BWJ~gZ+W{mZ_eoI0_PRAtsudoi)#0BS4&;fK#Vk)`ot1Noxlf6;5d7 z2UsiL1RGjS5GEmxk(1$KP0J{{es079LUH#<(xSrcraI=6?cxmyhj^de(O=toO0rN& z@r2g(x6|7+KS@O&noX<#Mu+^U-Vm!PuFfah%r@&_O=FI^C0m9K%ZBac@ zk&(!Pe_@#z>u>PG4hqD_xn}Dv05~2=m`m2SwwB#l^>Y93A@i!JYNP=@2%4?b{+vI% z_>I*~%bYtGMs>Q6EHJNNJ~Y?6zVE2j3G+4I7wwn##>BPTkDXJr^~@g-p-Wd<`9m+g zu@oM&?_y$h!$6{BT_x;&2(kfE2cltUNrk_I+J{=-N;?gwnS$C{KNL2w)1X2R zP9nwHEn5mfRUkDcip0c35~2ugn+*q?%*f14oABLMDa&36NJwgrT?*3+u?7T;tlJ37 zi)_l{r;8=uoJ=abYI1bpV7ScSr(3^O4dxe_8&BvAh)`<|A9+sie~2acUcdO&TdCpR z-s^Es32hsg3Y-KBOG-dqxgWAH79l7kq`@CZz!cQ{AfkK^ya6ueEXAxM?QLzJP%=8M zsj2Bg+}AicIX7(H9DlQTQ^7Fks-$>og2sA!dV0@Athk_LvaI6VzrRP8$6}=89L##5 zh+uer)Ipen=&DJ^2Gh&5G+~rF>a|)@qkeUFqxW+J%IAY)rx~t}8Z5zRz@U1OkLV0c zTF>rPcDmO;VYr#iYS7bSD3-qW+Tfg0#5_<5dMKGupW{GLMkeGa1csz!MPZaRtwjnc zsK4N=P>{lcTJy%v=KnjvZMCS{v#3e%X#ckhscMn$;nV6S+GseV9p z524fJxGGT$!oc4n&Hrxzflqj96#ggXB(J>={2S0Mtu9)B<1F&Z|9^hP_7ubo;J~oq z;U`8V858yTO|tkw3whX^WP49wdS+lEu>^Yp-hpl#Y)4ph+JJ&92+@w8o`8TrJnRjK zI#iN7^9zEbR8$d=@b-?Sp$t5Vk`tu~+#N_y^McgankAbOjHI#4hlmy1Ta2Zc09#lz`=XeP5eJAb411>j@>PnB{4RhlQ$$V%;Hpj|5Is`XZP;+i#zqL0 zQDX7a0ig8f8S$A*5s5M={G!eyV`<5cUGawjKh_mbB)$=~Xp>Qal{=L07LZ>S=7U9!#WTT$tg_o4e7Uq^~L64{n7_wL<>Dwq@MnlGFWA*C#GC=1;;GP_21T;RI6A!Uwlre9iJ<=a59HTwBj zUjGlrBp&~Zm}DSI4B!B9WuYLTDF9|{OT>NhxtJY4FkqF)BglDZ=nYizMgBs2*V=y% z{Oex&AtLGc_@h|n`pT8da<+)SDp@cr37K#As-2ylM)V|XCC^Wj(-zEv-!eO*PH-iNf(~IfEKsNwwe3-F7p}Lpwf))M zZBt~|a~2Z-e!L!bq_v5rZ%~s_0X*9!EgAtQs0B*xiI(rZ=^mE;Z{v)dusbbWf=TUM zuEm7rRpslTx*!_&^7E_JdZ>%&6ct&L1m)x$Lq^Tgg~taQ_c9t9)#@Qih)IUW$9Kc7 z2_`K7-|z|rUNS@523Vm`95XcJgrl$ueL}`fA(AlMb-U& zRZ~5Ws}@4kWCi9Or8M(@ioWF&{CG3_Ow&oZw{|+K*Axe5{Z^=C*5{7=)zNW^NTRyF z?*`Hi5R-WFojcb?Tp5rvL*h>uu8xjJ5i@}4K?6{>U|sQdJVHZY$&{h$uE7lK5K9dT zA_jFMgEtXSc#tX=aY93G!ecd{OpIOLDnYeoVKKhDz~sLeRAm1qLB&58*zr{-Jn+`n zn==QM!%NeCr;HjVw&quLO0oJoP8?1OPD5#*p3r$XmK+88`NpkMo zCm3+Tz#I!^8gDcDCO<(G{lw3&Am5#tTUZI7ii$!J zR^RknGAfK~*Ah<;cry{}j}o(rD4XCv3m3cDO*-7`iD1?*iIL&qG>A!qSs%EbjX-b~ zYwZs;<#dbaB?21L&I){(!2cyb0!f9t>Gg|h1*Y1u(9Rg&FK6M}veg^3v=iy<5O^s+7%gIgnzSP%X-yy4Q>Qr!W5W^fm zBaRFg36e;O9mn>wKeJCI>58LkM7jGhZ-!!{fJdI?ca@F?=f$YbZ|h&URjes$ePbg< zh@-_b5u+`I?p%)1tDSzo3P3GX8#ES_>CC|R8wOAS^PqK1OiWlWEhE~6a8}r8djFuA zBP#kjIy$35kPr-y-@I>BbADuUKx6O|`@uE*jrJ#9x9~r@^x$>oV`L*_1BYXc8yGh<%$?9rFw1-Za6tT zT`D0!b>h{zAnWP&WL8J_2!U%``1L6}%N`e8wlon)kEa!MOri`^qS$fw=b^py>}9uX z=9PUuI%K@(_Y3Vf z0RoZJqEv`SP3GUn2Eqoj(%}CSMi4duLI-i2Om8W#s_GEThSBsu)~wM$N;^H71>dxe zVC`tB3B&XCwDoA`v(q3w|p<-*5cfy*)M@T60$(2bGaETkN-y^jhwyvn8C?_aVpw9e`T12ZM#QCtzTVW&Kf1bD|9$H6wTvOBWR*GA%&KnZ zETB^OF*YXGAvVM-$KD+}G5zVqFYC_Y=n~F1pE&RRcqqr?VwXHCnz@3Goxf(Y>+Ww! zUI{^>tM|LC|E=;RE5^TLgQ5-Jf&%vFZ9bWWEV?Wjc{!uJC*Lj_2GbAKY)(I`Ag6W9}9Y z^BIOs>kjYtU$yhj+waA#C1Sf3bYD1(YbD)R9bW%Dhuo6Kd| zHIk?nMzr?a_o&k=C^~R!Z;ino{!q8fz{4Ex?`s4fzW>^UW2#uf($Bdzbe9)8ovi6R_n+^jT&sSWcN>Cesc!?Q%J+<<6RyfkBp}Jw4Xuad7iW85R+~~G!a`+^Bb^WIgxhr4Ze)U;a zs_o=TbN$e_t=7E<^FE5M)_1!{_b4a*Xh=+)_CEed6+$Jqyy?eH`}2zO_uIRxidINfUY;9!Bb-Kpn4&_Sq=XFvB+v!ZXU$u^(_{y1c-|j`~nZp`2 zH!>z~mEc9oM3kq*vq(CufzHRYgZ$?wfa{McImauKPAsbZn`gIvq9sS zf;6M9(58dcYiqvW5Kxj9tFD$XsJ`F#opVJT$7d?3douQ3dGZH-=m{#Z-itfrIWl5? zjM`?pYx}N;H_Q(*J^7}TG-e)smuoo|Y3ysus?8j5pz&B9~0y8U`-%a85s z8Ao@-g|6%ky||k8>kge|jJI=^)A9+&h=>LaQT$N4d}=<__Pid}tup6d)~m+bHhsm^ zI(2VI#pn!VKA8wGSY~W;WTslyH=NDB$t7|3NcYV_v*f|hs7N7LS?THP6R)NCFA3Nj zV1eTeSHpiX2Q+xLoRyIoG)dLI+CW1?Bde(R6)`y7F^~)pBBM-8r0_7i*k%|Q5P-IR zX_2$UF_$fzlA1<&Lkf$i_0UH>>(unyF#$bH_4m|kdi0oh9L1Dqb6k@Lr*C{`Rx=jh zhztKzZ)x+H zghQ!z;1tc&^^TuqB^|KYE_fFk>q4t$dP%KGF}Ijy{hrS*_uAx5f-*kFkL~9u*y45S zp1|d+wd%PiCVCQqJn~R_zxI^DbJZ{K9%6yI%;l%;X|w38phGTB zF6FIXD3aNRv#A$pbyQS^r$@C}3>P;H3mRSE@{8{Nw^R@rGFt8VAd@4S6v!;@d&~7c z(*Bd^fs9S))u_@@p!7h<#L!tFUA3&AIDy}|iP#mDptga5??*~SbHP)h+`^}~0o6h+ zlp6@k4X!Pk@fLG;Nmcsn8DwHy!j`E=nyJ`74}BH-vR7#L7vYIpBE>I*PB6LcICU=| z>q4SIP-4(>s@w6GgAFMJj2oqzt?h0rnXHTdrV&%C6r&(LU~N}hE0CtwIlnI4oatFHgd}{9e45L6SCGfz`M6 zZx3m|T=p&cnh<1ZyPU_7WqZ=q^stg@VN_(PI*&JRY06l+a&>;gG6mYY7sIQxbN39L zI?tOGej;gZz+yFplo6iR+`9AM*LiT8)R-mBD{$`$;z>Tap>x}7v$ExOX}>xzK4NBU z+d=XCv&-6nq~KSL4kCHLI%c)0uQU5tiFM zer|_LJTph)a>m7yH*cJ+v@LFj4)HXaJu3X3cwA?lhKBYky{UVBjd})@ALw+P>_m~6 zNI?uC$3WxW@hSA_W)|)(X=%Co%`6$OD+5sO%Dr*;13Xs$E!%A{O z_)x(1;^Py(C*m|NEn|FSdU0*4P+gyx!LF~#0e3R(3N^RH%Sx123rP)7X7N57+%YIw5qBGE+_tfMo4m$>$@k`&f)O>%uGixE+gGN&5 z&FM|&Cs^{S#)O^fi&s7~HyJ!`?}8T7vm+-3l0$hu=Q=d7@+Q(3$s4|mPH^?QGs$7{ z`swjgrzhC=YI8Fu2fD7|*eOszWv`jZ?L50(;!D@XlrHXQ$2P%B8d*Q~)9w@0`y{yi zA>&gund&3=*KZuipOHCA*-qibt|-SjIi&NsiQjZ8Q)cBBehN)p`t8f1BjK;de4lggaOW zJlU&r>nF|s;R1l&F}5JeR63I)OW7dxyJRPD`UP-lZ>hc?WBv2Hi@HvlSfQK5Wj? zs4C3)p<@uzr&W4wrR(|YC$9H?OKH6%_uQc2b8ufw2j8){Cmdd05^LRd#-%6Zv`Gb4 z9LpcL+i{ca&8OcIaiKeQ?e>3oYsKepQh{_^(;2){s~;#`T&5ZReASV}$}=XS?GJoD z%(u#`meOu#KYAxLEMzV{>h~qO13xYpS?;OZaY^c7U&_QU&YJ$Sf;+t*s26?HsO(*_ zifNs+b)4*KN*=}l!TYa!V)~9gI~Q)eEbPI3xvYrjrt_b2BMueYE#;=qwNEVMH|L1C4Xx!6WIZ-TVVn~N0VXnv6d_(Jj>6toRt7>Wm1`*fLOLC5D}j zpxGe;%as;r`a;W{5eg>mpCk4;N=@ou>=Bw4sZ0GiYFGV5WBl5Dm5ka@1-NQ&n7ooN zzLlYW&=WLC_7;Zv%KrU2G6IaLo5=s4#&-7v8?lcV*H?n&t9jus=CXvl^t%7wu4hqb zYj^!8P?L$NX?OPyS7o^|D~VID&Hv(36yOV`r>AQTx+>oRfrwO}-SdN4AGsDz1}q#{ zst}CuLF>BxIL0XLz7{O}{vHIR8hP>f;X~qmXXxPo$Qp04kD3n#iU-Y2`=feRamOGS zFbAfkr5Wp6{~b;}1AYh}WiZ|`CAZQ|X?7PcbUUi7h2=8RDNe7A3b z^-$q{ilg&A99E4yPncEa6gDngOt-iSW*>?rsC>5+IULh}0v(<(R$bdVLqPieaJoV9 zsAYoZFTmR&9O@66=n&Sb6|&zyK^v**@R?1;^*GwVH3z$T+DP*6+V zbQk~nOb^V9Fw4=s1ICW&UEn(Aqj`}GsvTg4uyb>ln#Q8p4GL}MMC1>^mWU%^rbTtk z!h#Q+6;k1WK@&I#49EUGv;y0WjH#cGQBzvKetj+khHvBIjFSzq5kT~5>9zw5V=y~j zg@yT}fDF|U1-RZz9~P@p3_oZh9Rv&O>bS!{WjuXQPrA$ai(S6avbe;*99Q$Uvui1O z+{R!f*^g=(8~Cp%i&&MXhKKKAg>_oKCtE4XKZGcQ7$ZC@E10~1?k^C53PD?e3X>Mv zVtMFq=2j)fs4yiUYHRKsKhkI~h!wB?a?}Mva z5yVXjsb;(3a6|A8yk2FR_3`66q`wmbO+<(KcoS!julADd2qs4@J)yKd$~Rw@K0`TF zA-?$eOKC`3R|qF{YlwS<&Gto4(I<;%WFPb2k`Gsb8$C>CN5I{HAy{K)Big2&S6AN- zY=u;+fPI1IOD-tHab_Rrxl%!bjC=R)MNR7AqeoPsFJIn91@FC1IXnC&R?^dVqWk9_ zbgynYgkbCgtfo+OA$o~|0vAY|mCgDf#c)I4IW#s0Bae(8D(wj0D8WYjj9yk0u8$wX z8y=0rE$WuGa+GAI)Yy{V{(Ij_` zhdJ2tq^nl{gc@kQ7*Me1UK+P#@d^k)>uFhJdiC;UV&FApz_aBygkFRYGd+!9)8mu6 zNsyJP>C1QTy1gv^d01x9b>Ux(=% zj%hwNHukt4Q_LKoFMk0r4T_=3$%ydq2R%J{pj}gfsIZ5J=Y3OF<$tW%_-!-C{X%%` zQ3|jTC>#AOSs!scWn%@smp*U&(XLMdj}VC$JkNb+f62~kdOn{zrdV>X(?WE+%*kS( z(hkLsH&#JGVrK90@F4+HGDg3P;NrbQLS&#IBDf3A5=ygzRPfAAQHQ1gj-LYaFhW0* zmX=0AbQlh=Po6zn=KL=k^QXmF3w`j$E=eLu)@B6^~SKO$sDeVv6~rrswDwGh3Vq z-8|7w69+u39mpYmz4ZwQ__{b(y2u01Mp04G=Z7?DP+~dOOP7XB@H&6swM4y*{~2Fd zCEbu)`GW1g=Q}?K{9Bx+{janAkK|!DHY`Y;rXVy6X(c>7tAwrDqfC*^WMEFrsFc+{ z!^mKvC@Lw*15*zrtsy)&1a}`FEBo`lQ>V5iWL%Ej2MRA~w@B30(DBX9%_+QBrxa+q zB`3r1&u;tWc(G*M9bdmkg^9=h;d^A+;cNcx6aT&X#($$E@_*o0#0>Bh&h@G-_H=6q zqFh9jv7{2UbQJP|rEK4I{rYvT)fQPZJtShts&*`WAyR;c-}JO0I$Hk2UQPDEvf~Qk z1{%>5+sUcY#W@+gA0M$ET$k{+Y>XL3Ge167df*r@Y;5Klkb&dR$L1TV{ZE*?K_Jtl%5)vP2D>m7KG^=CauEd?lzEh)X=wp?5E$35Klz82x*`X&PNSu2d3iZ$D~4VI6c87Y4@00moVW}1m zbYiRu>grOP{I#)8lD-vW{{sn>2PXGKWrQjod10h*CwOS)&|(vg2J8t~YPnQ1wxDGk zdUg=62kaM`aGxT)QPXw_5{ZKJBF#kGGznQ66w_D`JrojeG z|D$d$`Pbjh|NNMKXbT-_eF^Jd9}sl-yN=F}$x%^L`}1f}feHWvci3`&huO;1Sfvd3 z3OB(i2I=tW^XF$_`bU9IZs1M3fK3DO#0e7<9?}sFq^N$69sBn`8t<)b9~>llQNVvl zi+*}(mZB9WjKUzW`q|O(1>^!C3#hl!=`^i3gyx0fOI{v)o;~f5)c6cZo2@@ICK<$^I4!8f}Bc4^-*#0uS^oxjzv8bu3frt!`KD6Am>i$Llmt}jjVTolc+!wL4 zxw#p|)})dT=;%V)^;kAI9m$yO47?0NUH8-sz5@?JJG$OETG8bH)aGG1tSKzIs=+BX zCDM_2)q*avEkyJBbxhG^M0|^d2j=@=Eq#Hk3iPU%=wD!JgvTF!9@bKTs|boXE9^WZ zAgMumlYw4+5HC{397!cs2e4>wUrvc=(ARep5H}WX&EIePZM5S3Nf#i5bf|3;=bDz5 z5Tr1wH4ZJ-?d~8RUQ|kvm=IFUxE=q(~V3CLcYA0(8U z#l=Lc!An=JPA?Xyx#Uuw-ayHHU!X7QrOvb8!(LmiztMUwq4&}ueQwT{GybVt#fPC2 zb<4bu_$s>d3%_;u%jjHFoZfvbVDquPa$lAmM~fPCY@eS0)FVar#LvTP+CDl@(P5%F zB(o)?)NH&!zu)1gh*cz^I?cGyZG8nY{aZ;tE?BFTBLEtHJi3O7_beW1n220Ns~QBx zd^%;@&&i%Y|A>j-fWz2$u-B1`v~j1{zV+ay;zTD_#$Rx^S@=0D*2sjKYEMl}pyru| z{u_u&z!!@ep^d1%3C16cM3|1+TU+>1?d3Rl@YXhF8uA&9OntX<4UtprJA7CctDH}h zQ7veOiFT7l;N3b@tJJSq#lTRzFh2_)w&@KZpb*@OR`pBA|MfRk8(`ps(Y?gN&s380+Q(CV+-k7{hnK!Q zMR#SBNwI&47N=suTjd7ea~rm7v6idlh8lX2%_6hHQ>n z>bp1U7w^$^dGBg?^Ko`^(x<_fhWuu2)uk)3TiMv++}zxhhHfohx_B`Z&t_wpg1ESN zCb-rkLqjHd(tsgk0W-4HN(b^^(bJ0rIEX${q4<|B2Ihjws07rQzO+qXy+r`!XAT}j z3|1+=I4x6Wz_0ym1sSLuWBQlOyUrzb8W+|Zxkob`^zQxe&}&!iGsc_!{k6sQMn1}w z?XO?S9S#n7Q!(Rk#cXliwFa&y!(-ZBALQ%i`)dkS4;-tp)W5t=F#YQ|`_7$wra!;z zw=zXL4k@Vh<&j^IhXSR(vU0zGfJpGb#n4QgNyIW5zRIuh_@DPe;02F04%{pdH23e= zaop>fzyCX67B{uD&_wnW2z?(L`e%l688$L8?T@dE#+y}A&E8ae@Z(ac00V3rA1*W! zTcln~Rwk+&ksy1WlXY~=f`V0S*qc(1S^UzuhF==BN#?E8wNfOKGnWnQ1< zkiKi5sHieb2X2Y_;lTpv^BU&*7JWZ1!QnyP)bztHUBHDi@aG7{x8pn`vGbq5a3QN_ zcK+Q0%48M;jo08gSDm0+nSg*X6B#`dU*y5FFAixwAQJty7veL&!xR(sd^BHxnED&1 zC66A;yr{9Vh&w8KU5--~gPyk=vhXWef1V&C?R!QXr5y+A9_^C!y%D`5{i@4H%diLo zX^Fz;@v@fQampj>-gwb^y^B3hrL8h~7~}0*t`mK$>l)4%%yl@*tVHyn3wsJCG24V# z<Xc{B~ zmK+R5Y7iGD!0;5?lN4G^xJS!JVK_pUjZWXXT8*6KrudB>-c+0@ao$idQ00ao=JSzp(Um3WyV_5uz2WaW&s&I9Y_LfV_ z$I{Rbj+hoD`qKFx`Is)SzxJk;@xf~!FV|bYJ+Cjmy>el1M_OD6ou7y50FJ64I3#s#2 zq#RWkoQ}@UJ$T05w+YFD{T7|@0KwkwUAqcvYp()8MMv@q1ZHuHv+yGab?qf6@`d;y zB&-$CvthO#I_DJ_7)U_ibFx^yaD(tW`@eYy21cP4e9i-Q0IILI$q<*UJ+ZZLgC2l z);(-iq_}IV+x}IqeoYEWG{v63^0z#?aBjd#((ID1H{*W8!zUhm&NY)}*%4*aYY|uX z_R>!Gtw$t~o(3Gz_6QT(yY~z%XykF7VI|i;{Dzz#Tk|Ez5WbdFrkHA;gYQZ(iq}^n zF0h;lA(Jipp|77`1m*7r4#}ow=8h+KoX=K}u>i`e<@bqF@#x*L7$Av+z@6n6N8yOmUOg`$0 z*Jaqtn3;pV4!?(ZfQ*qN^XJX(+13J!m$@3NT)w1}gjseu>Z*IrA#jwlx6eQ)mMGlS z-{ZAvR&$$z5OJyTEGNS@XT+Fx-0Q}ur>t(S>!Mylp~-^UM>~sW2KNsyOnrzp6!#vU zeYiiS_Kf9V?8jOib|2-mHj5CO&aIG!=pobhNP@XVu>rq6{1i6k8#g4C3@cq1hc3kT za*}!E`}Y@toL|O0-tE)BNgu)28+`yhR}rP3my&w+NxclC8`s@VPv$jYbktg=t03>Y z5-aC}`5|&AHiXx({xUGlwD4J{$S@>`USFw1xVgE{=uEVG{yJF+Zw`z8`YUMD6FV~B z+0~_1djulKx3Kgj&$+V7frY^@EiGzTfuEIVRbq?Y$imY9!`%-z?7T6-4$rvnLuArlO%&vF9Fl~{AkAD{#e2e=H z0DT6+)aq;}r*k(8_L!TS`}p|$n$inzGP{x zIX;bxi&IQ8uu&;(!o&rHE#xSPM)O$2fPSK_aWI!!Ry0yxzlM!g&;Y=rP8oJ%WF0^r z5Qla)3qNOL9wFw2Ze&0DX~kxu3-9Vxt1bayM|Xl}c%-pl>0xR6s2I~z7aswS9rmzt z=&<9=L@1!MivcdtQWj55#%)OH%x{{yfccj{xdhIcWlQd6IOvyn@>O&v=f~RKD>bT> z*?p34KIz{y%)Vqf=<>Dc|LN|$!>T;9weOjUW5$|fBnpaTj$i?mSP%t~h#~?OP>ND4 z5h6uF5Fw&~#;7w!QMV$BA|jnlv49AOn4$ z`}XattEma6yw#Z#ZuscYCDb2=(>O}E?f9SneCpn(=3JbDqGBB1HU}q5^1L7G(N1GH zFqN_&Er=f~6Jl|VDPgMKYUj8$Ac>80^!RZZ+63WvnA+5-uIQSjosT4SwADZv%C59F zOwPHynEI+Ihr zU`$IC9YDejs!1?|*jse169+#hngzY_I0NT#I*1QQkBkyVn6Rto8B5ra#O$#XC+=JN z!)URdcils&G59fg@yHMW)=1B2cL)?@WNA9mUTHsU+s?c)G8%yq*o0DTi1nmhM=niX|)q42c%4pAQHJ=z! z{u!V7&fwpv2L|^KpQt%>r|#21*k9?(SMr|?xp?gHsKI~joMWIp_^-RUgJ}X^O`Yr| z-*H3!%YX55NaFfy2F$_8Zfqu(ait1f+fP}3L7y}nc7b-(_)>q|EIYH!o1d<-Pa`T* zo|7GZjVV}WjGFf9e(4xf;??`cL%a;q6e-v`oY3S6u@vwKJV!5^J1yRss?H=-f&J9T z#8Z0*Lws;+5}8*s&36;8E+@*Lxp;8~B!M5=1=XycO>^ZJvH$41RUg&nj&Ue2GLP=_ zb2J%#y5^hy>!Tkkru%Du678;XdV2R3MNjRsslj$9n`_3^`))qD@@?43ntc+7ObhRU z{yUx(g`e=3^&vX(d0H^*L8nf6jJVXMPg~EF&0l{pL8%#pM$@WQh{vlFomqgUs?4-n zrng3$tXd^f0vgJMQ=;sQB`B~T4rQ8_mL(LC@Ve>60!@Gxl7iXkPNfKP#ctDpyNywYrX4ZQKU+#A<7`F3nS^ zt-n!R6EH>5HK?lE?RZP-Zya`{Emu2mLu#i<(h3*s)$&u1JIq}XBSB@1eiRw>urZUo zI-;VZ5l{@qEc$u>{Xk_YRJSt|O(c=%%N(61pWWDibk6oZsVb5JUW~vjf+1LwJ0vIu zFnQkg`E)Ed;c$)fY9Y0l$Cfu51=YG=#Z6&EfAo?{gy|V6A;AL9ALAI-U|N(puhgvGb^2K1dbHy z`O3E6RCkr<*+a4?g-^(v+ii05U#T%wbbZh!3s0zOm~MOe{W;GP&Q#TD1m9j3SNDZO zyi{^`Luu9{R>N_gl19}YD?7UcAgxIdD^K?yNLG#A@n?Ig5f4)^1a5la6l!V7D zf&TJN3~xm&GKX?$%j(*Q=p@Ewy?p(;1cjp>z`ZfcclxwxDomRyIq)-PCr~H8)F1K1AMyOl+O#$2tl&2OJ}iI~`jEcGK~0Ij4$vxN6Pf?nu?? zWTe15{zl;$-?Rk9=cPJ2I85U$mn>N#oqtNse>c|w2bO3 zm0GVsAkS+0%c@LxZc{b%Y1-H4CTRvI7)rj5mF)k#?0Bl<)!y}P$HE)V)cL2WjtiQv z7+yGKvct4RGfIo!zaEmdu`A@bl2#!K>sV#wM8x<7bcDgx-f$Rathn z40TAQ@kovi%_7Z#ve-e?jOkZ+h_28xnP&~Mat36x7U>xoE)=9F>3GHJFhqGvl>pi< zsT(^N%88V_$0vIoRLy;+5>kpPT$tPN# z*gGZ{EK^VS`nGv7q6tv7m?vQ34p0^YcS^vl8po<{?zkWl@T8f#y0#K=#}QIU9?jXf zXDz&!-C&+WP?8=r=bpWLHD=E?Cz}J3WJRCQo#*uU6CQ3)J7{a}utoZ6OTa`TKea5Y zzPe@U@kx5jmN5@}OalM|K7M9XB}K4Ga~%FeaJ2~6)YdM2;V+M+ck{n~vDP9kGCya| zrO($y*BLpMms+d!*G4I=a&TR=GO4l1v3%UDq616RofQV8CFgZ^$1fh}-IbHdry%1? z#o7U9Bfupo6-|ctbXLokAC3^hQ4cMy)r_@ViXW3v#M2qjh}x#SM~{kgR00y915G6^ zviK+}k2(DpE>@+MiYa38g#MhI@$pJ;Ps1e%@JY)F^_eqeICsPvk=Jc3-KFhPyu#4X zFoXOss*loUn=IemLD}8jS~O5ErS_wvJz$`sfSGIY^ncQFf6{yS*&u=_J^Xmob*;Bc z@7cRa>imOECas_4dSuy#d|QX)QfWd~bU<0K$-&aRa{q{yCu4##^UFPQb|k3vSSC`}PFEkMT~^NZUz2 zZYOO!n-_S1zB7IBJKibc(f;^jGblm9*V3_RW20ND>y~*+S~LtI^cvazjqjZh@(20t zwwu5hhile8@282)w#V1k%16-$l4}~RD)X;Jw0d91oJ$gF*?nl;whPE0$CgJhdEVc& z9)~ZZv@-9I$fuDB+bS-_HwN=5dG}SCc!*=hn z;og_u-gAR*e6a;|q9H$YOEZ;-7LE<1%83*tCPEo4cqVu>$S2uwSH>HNV<&t4k$ayOYzy;y zR=iD)d!LGx`X6S2Q`ubt5s*xJb&MJR@h6On)Y_%Vw^Fc!25$I}sT`OQI)UJcy=JR$ZR-I8zk3Kfd)jfM-iMO;aWYScH zP|1tp89^oOQpwxk2j$)ej>YJoK9-u-=AO3vwavd=ziAsGxm#wLG>!}qvR%j9qc06D z#hGCbf~VyqkA$(*>4CNuTo!G?KjFtR+~+AYJTxwZdL(OBgYN~ZL4vK2zy^wR5_XpZ zZrb$e#ykuR$qa|)1>^9`Q6MEi4MpG~a~zLG92Tr0bRZC+l)|&1^XI1<6FjYH*C}Lg zJ=_7lbRRkj45QRqQ zD@?V%fJj-Qt@fyi3(VM6!Kzd~tRbDy!Ll2aI*58)jT#QWTzN;5xPlqC_m$vnrnzeS z=9M^nd2FGEqjqhp69q4e-r6#jJYD?;)jL2V$J+T8y98h)k}rdC0; zGqqp^tw0LMa62g3p&7OdNHr+1FP-&VBIwhwf!=M9$8i|YeCva(C-zbM{IU}ocavWf z3>uIzywK+)IGTe6AYok|cIi?Q>kY}1n%Cu&R^dqdREGJcl~5oOk@mz_T8Q`bMCsKn z-wLoiN^#DzP5#|c5t_u-BT)99Gv!bF&enIU%%0TON?d0u&e-PQ<8G1kuke@# zFU*2U{K<#bJlMTSUvZXlR+}Tk0O=~ziHGQn?xg9)JdZAqFCUH z<=C-fBjHChD4i&*&Y(oUJkuwcywYk*OeI?hYAMQ6YGrxrhpH-j($vQUH^Dr?dfqR1 z-rQ{K94cR=r#cBj6ldt4ov?M9j#_&4%v{4LbOM(r>J?en zpin(1ALcM;n2tFE;M1!=c$k}8K+{)+4?g(mjjmV2NdXnbA^xjSQA1ZZv#|5k%a`-$ z-_hApwvKep!q>M)t1o&3^l8Za9Zi3wPq&5w=za|&?#9lD9AqY_e9{TLNXmTzC5q*l zI57dVlc3(yRY-;PaIsW?QQJp;x+NvyHE3Jx@m}=i{6FZcx zOUt<*3aqmI{dhp~Lv_=>c_Z|qcP0MAGxz!VS*~9nONtc_g(S%fbeiw<_;O(?r-cR^ z3yu|{*a>R2w8P^Fk*xuGDbO{uDhUNoy)@2*hXO(&UHvUoP%MwINK;{9B#~?;FkMwiM5vcd<7gANLA>(tFKhN#M)+y5|lDyrwK*8X<_%7D&X|F5~A zPT-Jk64R~N%W=g1hiJ)aYKB;Atho`7KD!}!Z9ke7$snjSjN{EC4l{k+Tu51j8>`Pg zix!?~!JJAgq*gOYercHT@LEK^TbdNk6MsnHiP;ufucy}3)Y#nFmrl=G3!ov1iAW^L ztZyehcPoxP1$*g514fg#v?xwRmQW9P)#?KLNdj0mR?|LMHPa*S=YK<*q%!esS|Y zPfTdl&TH#4ORq>!_OUDNArJPHdHxQ zzQ8F~%<+^Hc(WoGl^Y4$s(##R_aJ6#S>dHGsqTNB=92hZZMrM|f8$PRY9Q8i(xD6+ z*TD$Y_2vI2r@`PO2eLbC>%yiH-hj=UFO$nhA(QGRLALu9m8O+#KN=WlMP{-XrT4;} zN%FtCH5c#4S6LZf-LhBs@WZ^Kq8k|x-^h+l)jw%k@@Z~XxW!tMef@_UV?rGkPA*Le zcD!mYX}PEVBEX(~94Q&$sW->!y1gVkWrbSTN=emRv*?3AtdNWxy4Nwe!q~g(zWVPk z+Sg}L4|bF>lt~!AY&&C-G#)c3Fp94KHUvogBx&0zTE%;kmy;8d@lagYeqFpC{|OlH z)mH~>9U+>K=Hve+gIg|HazKp5+TB=a$S{u(y|?1YR95kR_%Z zuuM(Dr~nh?m{!4oG((_0SQjx>!D8*&e>DyvJw|AY#``yo`RTuRSClVx<#e#arCjUl z3dUz=N@+NcfR*5e~F?hL}R+Tj(zXF z%cN$x4s)DIsBIYhOyi{_%pf^$_@B{RI>U_-js}TP2mVPrj%Pg2-+?Pohci&BhYF7r z7nwQ$Xtl`Kz8E@U;;VL$8eaILCrVN;{foBI-NQs-^HJ0zzQ!cbwt1Pxdf8l{0gtr_kx2=##_ zEh5xeD>H-NoD)lx+_po7Ud3Kjy!Y(e%iq5l~qhJZJF}y zRXE$}h@1pTU7WCCu6>4=G3r~vCUjZPE}I!28dLakkyBp_#zo*LHfaX!Ow|*DLqiiG zv-4k6xOr!0**$<^dUoddAff8>p@H@aiTqf9vq@#%)eQ~{1~kjEbWCmuwRP+%1=0y`KsNR{z(y&-L{y1GiSjXK^a!@RxE(8$P@ zlStvdEP2x=vAsYbEyaVTrn{lXYolV<8ecfU5T#2ib5G=@Bqy8n=9p@QJSh46oE@P# zMbj?&w+Af>o1XPQ0W+*a5WMMLofH`vDGf~fCs9sQapYl>9QL$z0Cjv`3@oz0vu^{o zf%?S{|~(OsDqFYmrQ+N#y(xFan(ubabbLEIeYmvyH^AYp{tV!t^q}W1pH(y)dBD4Rl zRcOETVwKtdZb4q0}04$ zJ&M9=r=pbmHpRtspz1$ljss&v&nAwt2xhf$-&y#`!`DtDkHog${9KWG<0He&?_-?v z``mM#b{2$G1~eX5=}#*Amk8N+S(nb<^oyu`6PC2MEzBak-3HfcGue*?{+CotoB3h- zn+Q+EOzDasUOjM{=K`h_X^Pz5Bn(*w24BpwyJeQtE>c%20ZEGEGTSmt#c03n^Dl^z zD}N2ZKt@^CUZ4C+dbU{2By^zp?hi`ZZWrzB?X{q76-WNA^+}d3YiqS{UvKiBzvL#P zzk%s=?k=l3>e3T=?C|w~DSpkni;r{#-i~wqY}+=Ck3RZ@%+vgnPxf?` z7Ko8H0TXMB_+RV5n;d;F;b$oX4hf~0zJp6?uUkNYUL2Xa%irV;E;kwhp@CULg>?8# zp|Vn0&(~tahck9Q%0?@JI(YBaAZBd-2GBgcV(mxzUDqc&tMt8zy7}QmNovY6jf4HM zDl^l4=c}!j6kmxB`?O85<&2gqyeXpDf}~+%W?e`_fr0dow@phD_?e40mHZ{sT-Zv^*>ka0U z^~Nk#k-z_~x$k9!o2-!qn6-!5C8a0k8EI1_;ZbA zz`2^|!thF=2gJ14cce2Sbw1=lW>HG7;;8^=-PA#==+14m+Er#zCo^`8eWo0lGiGH; z?XXY1Itrb$N)lR3BlK-NJ0I0_O$;!Mx~H8u8nTmdCA7o0M+37~c2BjD-PpE9d7^IZ_^(__V9q`W{KX^Ba(m+=aC%J%*ZVmDzv!7-B#!mw)m5rTzYunjz9#VAL` z*R~oRA3ysW=T14IC!OV-*-V z!g$#di~nK=?qS_LQjnl?=hl-+%*LXW>Qr#0Y1ZIpw+(zeD(xad1W=gtMHaVis_+)V znti8LWy{`#kgoo@TxH+S2UwU+vK?I)r>cxpU~~Pgjp?5Cqw=Yryh7G(UB)omaCFaj zv}$ou!N^?3X>DIr8Rqx;Jn?Gh(%pS{Za?Uw%E(fP_mBd@rBu!q*Q&~-6Jr*h{Okqy zQR##;k>9;&KIpwp8)cx~dnNt+h2rO#<3}x8WUr|ln)pgBSj{-G>saC4Z;O-zg0~w6 z4!GUf8~eLSmy)ap`iz5K`NMRfq02ZrWjA-&3c8B2%ybt$#wyuR^g2q;SZ(DNIJ9JG^$lL0| z!kg}~Y3_ToUVK}vX1uDB(I^$#69ZC@X-yocx*i?InXzQkiizh8YqAx(76kZE~gvB94;Ecgqi(2ma3d**TQ$`4Z z*zV%s0vhS8T!s3yG%0kJ zeMTgsFmVA0w_DSbtV~fi?nlpj%f+=UzLrSohYrn!i2x4DXkMI1#j3GmW|=UP_R)yJ z3|d%_(!%h2t{GS8thDcu&t#cC?#62eHU7Zi?eeF3fAw5(3x4U4o9-&KdEw7j6`vS% zCI}U%W4SvlDk>K@Ei}?u)M)Rv%IUD%6h%05ev7RBPWQ73jCjGz9$BabeNv2TNLm75 z{$*3ZG;M8b#MS~TC*p~R%}JSUg)Es2TQA5~%KGzs#2j0`HGQFLacnrAG8byG1goN& zTTx-l!xm)m;r=Y@2zj)NgrtP6%L&7SB$A{ij+<$d;T4BJ#9P+G6-q~po8^RqpojKu zaacYreQ&?QD{9X3SO?jV)OX5gQUN5dz@f&4%wd?vTm~q#RYG(VfOSg}R~KASpT=AV zqlRZVERla3IN+@l<-VQ$)gxF|`T6XlVq9pX?;m&INKA9w()JY+|CG9>vNH?xE_@Jk zt~R^>$V+UO?F$w!ger$%fqldHwuT%o(aR@Dl^gvJ?5%asR<2(|MI}sHBKcJp0dshS z&m1eW19_(){|_CqFPe)(LfGs=MZabt8HC{I-fI0s01;Lwjt|J7O<{8-=wLbUTD5C{ zR>ESw7ZkX#b?bE&ud{(xJmdkzENtqByctG>+T4slEr|q*iU1<%z-QQu#VvW~8mtX> z-nQuqm3nR2iyFpe8uNFT;F|YV{UAT!?zZNoW{kM&ZoRZCLuW_s=`?A~Mwi}O8$0$c zI}tE$vaw6c#x=j+v2j%W9@ur%q($r5K&7bN#RyL7?H6=?k|_P>sEZY~M#PW3L0g;2 zk0Bp<0tZRq*2?sv7r~fhxMt(EgbOSr`2DAdOpdW71(wej?Rs{MYpcKIh7Q3%&?&+=7Yt~pZn_m4<5y4oT0puYwO7HNb zRMB9+bgXK>UGm1P6dpTIBhvs}-cn-9rqX?K@K*Uf%*QXns~HAtpu%mVv!B1wP@j1x zZ+np7QK@Q)gO7D;cpM(+{cmt9tFrx+aCfJtry~)2w)g`Qu>@FR^r4ZsO}!0Ab#}C7 zWJvyJp^NscW&MIldQrbAcT6VsB52ci#Q;ux9Wp)qQSGf|q;Rax^5J5$}9z*1I{CIT< z_8XW>E7I+)378uhu`H}UI2vZ!-MU&iY=VCww5uL<{37!it+F@)lEjb6sUeH@T!#)# z>bcZ!OVH||Taqb+{Iqcy3}#oy9Uwkn+n@>Mmc7UAvHt)dU$v;WmleA;Sw8n8BB&XI zM8$TbaeO4!YE#LSHJ1|bR;?qa_hp8KI8<}|1Q@0^HJKeFo(XD5il`k6M{RkC+Zj_V z*wr)2)w|&_gWo)?6RAWLR$Fsn&_kA@bV8U+1T+2Og$T5A0PeVHRNwT=aJ)rGSwedh zVlR2F=c5g2ZuWjuCyQkz37kxNB`mSxxmTS|=H-5;J6hNssQUuS8PFNVvr^x!a>2dt zzH5k!xm1YncHW+s4t(i^2qTgfm*Uu(@wS=XJBGgVPAr_f@Q84tJK(PLD`7UK6Wu>V z@DmguEE?YB>K6x&G}kmX#!eS$Ml>h+ap<%lzuTWu)&hHYFVY$ zJ4-B~7%1J}KG45L^eBj^;#N?^kbFN+{`;|$CTjL6EZ8{wsrTI%YsI&2@e0G`Kl|^j jdK-Mhzw=|14Gb&4?R#O*IEO^>Gh@Tm%aRN>fAN0+x40W4 diff --git a/docs/kit/adoption-view/images/domain-model.png b/docs/kit/adoption-view/images/domain-model.png new file mode 100644 index 0000000000000000000000000000000000000000..de9562a11cbf896d48e88f70a00254d94e931906 GIT binary patch literal 142498 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úO>|I#cA^or? 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 literal 0 HcmV?d00001 diff --git a/docs/kit/adoption-view/images/edc_architecture.png b/docs/kit/adoption-view/images/edc_architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..99b5525eae8358c94213239d15610c004bcadb2b GIT binary patch literal 7768 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 The shown picture illustrates only a generic view of the Domain Model and is not intended to show all aspects of the project. + +## Asset + +An asset represents data (databases, files, cache information, etc.) which should be published and shared between +organizations. For each asset, a [`DataAddress`](#data-address) needs to be resolvable. + +## Data address + +A data address is a pointer into the physical storage location where an asset will be stored. + +## Contract + +A contract always contains one or more [`Assets`](#asset) and a single [`Policy`](#policy). The contract construct is +used to define the arrangement between two parties ("consumer" and "provider"). Regarding this arrangement, the contract +passes several stages which are explained below: + +### Contract definition + + Contract definitions associate a policy with assets. A `ContractDefinition` object contains an access policy, a contract + policy, and an asset selector which links the contract to one or more assets. + +### Contract offer + + The contract offer is a dynamic representation of the [`ContractDefinition`](#contract-definition) + for a specific consumer and serves as protocol's data transfer object (DTO) for a particular contract negotiation. + Contract offers are not persisted and will be regenerated on every request. The connector acting as data provider will + generate contract offers only for contract definitions dedicated to the organization or data space participant + operating the requesting connector acting as data consumer. A contract offer is always related to a single asset of + the `ContractDefinition` object (e.g. for a `ContractDefinition` containing three `Asset` objects, the connector will + generate three `ContractOffer` objects). + +### Contract negotiation + + A `ContractNegotiation` captures the current state of the negotiation of a contract (`ContractOffer` -> + `ContractAgreement`) between two parties. This process is inherently asynchronous. + +### Contract agreement + + A contract agreement represents the agreed-upon terms of access and usage of an asset's data between two data space + participants, including a start and an end date and further relevant information. + +## Policy + +Contract policies represent permitted and prohibited actions over a certain asset. These actions can be limited further +by constraints (temporal or spatial) and duties ("e.g. deletion of the data after 30 days"). + +## Transfer process + +After a successful contract negotiation, a `DataRequest` is sent from a consumer connector to a provider connector to +initiate the data transfer. It references the requested [`Asset`](#asset) and [`ContractAgreement`](#contract-agreement) +as well as information about the [data destination](#data-address). + +Similar to the `ContractNegotiation`, this object captures the current state of a data transfer. This process is +inherently asynchronous, so the `TransferProcess` objects are stored in a backing data store (`TransferProcessStore`). diff --git a/docs/kit/development-view/page00_development_view.md b/docs/kit/development-view/page00_development_view.md new file mode 100644 index 000000000..03806581d --- /dev/null +++ b/docs/kit/development-view/page00_development_view.md @@ -0,0 +1,24 @@ +# Development View + +## Project Overview + +Eclipse Tractus-X is an initiative of companies under the umbrella of the Eclipse Foundation. +It is a pilot for the larger initiative of CatenaX. +A broader overview of the project can be found on the initiative's [Github page][tractusx-edc-link] +or the homepage of the [Eclipse Foundation](https://projects.eclipse.org/projects/automotive.tractusx). + +## The EDC + +The Eclipse Dataspace Connector is one of the core components facilitating Tractus-X. + +:::note Tractus-X EDC or Core EDC? + +This documentation is for Tractus-X EDC. +It includes the Core EDC with all of its functionality. +However, this core is supplemented by extensions that allow for the use of additional backends and connection types. +Furthermore, the provided Helm charts, build configuration and tests allow for a smoother deployment. +::: + +You can find the repository for the Tractus-X EDC [here][tractusx-edc-link]. + +[tractusx-edc-link]: https://github.com/eclipse-tractusx/tractusx-edc diff --git a/docs/kit/development-view/page01_eclipse_foundation.md b/docs/kit/development-view/page01_eclipse_foundation.md new file mode 100644 index 000000000..bf71bd5b9 --- /dev/null +++ b/docs/kit/development-view/page01_eclipse_foundation.md @@ -0,0 +1,35 @@ +# The Eclipse Foundation + +## Eclipse Development Process + +This Eclipse Foundation open project is governed by the Eclipse Foundation +Development Process and operates under the terms of the Eclipse IP Policy. + +* +* + +## Eclipse Contributor Agreement + +In order to be able to contribute to Eclipse Foundation projects you must +electronically sign the Eclipse Contributor Agreement (ECA). + +* + +The ECA provides the Eclipse Foundation with a permanent record that you agree +that each of your contributions will comply with the commitments documented in +the Developer Certificate of Origin (DCO). Having an ECA on file associated with +the email address matching the "Author" field of your contribution's Git commits +fulfills the DCO's requirement that you sign-off on your contributions. + +For more information, please see the Eclipse Committer Handbook: + + +## License + +Code in Tractus-X EDC is published under the [Apache License](https://github.com/eclipse-tractusx/tractusx-edc/blob/main/LICENSE). + +## Contact + +Contact the project developers via the project's "dev" list. + +* diff --git a/docs/kit/development-view/page02_repository_structure.md b/docs/kit/development-view/page02_repository_structure.md new file mode 100644 index 000000000..11fb7c7c2 --- /dev/null +++ b/docs/kit/development-view/page02_repository_structure.md @@ -0,0 +1,26 @@ +# Repository Structure + +The repository for Tractus-X EDC can be found [here](https://github.com/eclipse-tractusx/tractusx-edc). +It contains the following components: + +## EDC Extensions + +The core EDC is extensible by design. +Tractus-X EDC provides such extensions. +These extensions and their documentation are available +[here](https://github.com/eclipse-tractusx/tractusx-edc/blob/main/edc-extensions/README.md). + +## Gradle Files for EDC Builds + +Builds of Tractus-X EDC are performed via Gradle. +To allow for different configurations, different builds are provided. +For example separate secrets backends are supported, but require separate builds of EDC. +Therefor, different builds are available for both +[data plane](https://github.com/eclipse-tractusx/tractusx-edc/blob/main/edc-dataplane/README.md) +and [control plane](https://github.com/eclipse-tractusx/tractusx-edc/blob/main/edc-controlplane/README.md), + +## Helm Charts for EDC Deployment + +To facilitate deployment of these different builds and their prerequisites, +Helm charts are provided. The charts and their documentation can be found +[here](https://github.com/eclipse-tractusx/tractusx-edc/blob/main/charts/README.md). diff --git a/docs/kit/development-view/page03_project_structure.md b/docs/kit/development-view/page03_project_structure.md new file mode 100644 index 000000000..9f20c5a3d --- /dev/null +++ b/docs/kit/development-view/page03_project_structure.md @@ -0,0 +1,21 @@ +# Project Structure + +## Issue Tracking + +Issues are maintained in [GitHub Issues](https://github.com/eclipse-tractusx/tractusx-edc/issues). + +## Reporting Vulnerabilities + +Vulnerabilities in the Eclipse Tractus-X code base are best reported directly to the +[Eclipse Foundation](https://www.eclipse.org/security/). + +## Git Flow + +The Tractus-X EDC repository uses a Git Flow, with `main` as the development branch and `releases` as the release branch. +Other branches should follow the naming conventions of `feature/x` or `hotfix/x`, though this is not strictly enforced. + +## Tooling + +We use Java 11 with Gradle for dependencies and builds. +We use [Spotless](https://github.com/diffplug/spotless) for code formatting. +Releases are in the form of Docker containers and Helm charts. diff --git a/docs/kit/operation-view/page00_operation_view.md b/docs/kit/operation-view/page00_operation_view.md new file mode 100644 index 000000000..87ceaac6b --- /dev/null +++ b/docs/kit/operation-view/page00_operation_view.md @@ -0,0 +1,24 @@ +# Software Operation View + +## Introduction + +The following documentation will guide you through the Tractus-X EDC deployment. +You will be setting up multiple controllers and enabling communication between them. + +:::note Tractus-X EDC or Core EDC? + +The following guide assumes the use of the Tractus-X EDC. +It includes the Core EDC with all of its functionality. +However, this core is supplemented by extensions that allow for the use of additional backends and connection types. +Furthermore, the provided Helm charts, build configuration and tests allow for a smoother deployment. +::: + +## Connector Components + +In a usual EDC environment, each participant would operate at least one connector. +Each of these connectors consists of a control plane and a data plane. +The control plane functions as administration layer and is responsible for resource management, contract negotiation and administering data transfer. +The data plane does the heavy lifting of transferring and receiving data streams. + +Each of these planes comes in several variants, allowing for example secrets to be stored in Azure Vault or a Hashicorp Vault. +The setup on the following pages assumes the use of Hashicorp Vault for secrets and PostgreSQL for data storage. diff --git a/docs/kit/operation-view/page02_technical_prerequisites.md b/docs/kit/operation-view/page02_technical_prerequisites.md new file mode 100644 index 000000000..682da831b --- /dev/null +++ b/docs/kit/operation-view/page02_technical_prerequisites.md @@ -0,0 +1,43 @@ +# Technical Prerequisites + +## Obtaining Releases + +The most recent release of Tractus-X EDC can be obtained under `https://github.com/eclipse-tractusx/tractusx-edc/releases`. +To create your own build, you can clone the repository at `https://github.com/eclipse-tractusx/tractusx-edc` and consult the provided README.md. +This can be useful if you want to use non-standard extensions or configuration. + +## Container Environment + +Tractus-X releases come in the form of Docker containers and corresponding Helm charts. +As such, recent versions of the following are required. + +- Docker +- Kubernetes +- Helm + +Seeing as these are standard tools, Tractus-X EDC will run on any cloud environment that can accept Helm charts. + +## Backend Dependencies + +The EDC requires backend services for persistence of data and secrets. The following backends are currently supported. + +Data Storage: + +- PostgreSQL database +- In memory database + +Secret Storage: + +- Hashicorp Vault +- Azure Vault + +The default setup assumes data storage via PostgreSQL database. +In memory storage is only recommended for running tests. +Hashicorp Vault is the default secret provider, because it is platform-agnostic. + +Helm charts are provided to set up these services locally. +**These are not suited for production environments.** + +## All-in-one deployment + +An all-in-one deployment is no longer in scope and will not be provided. diff --git a/docs/kit/operation-view/page03_local_setup_controlplane.md b/docs/kit/operation-view/page03_local_setup_controlplane.md new file mode 100644 index 000000000..aef8c9fe2 --- /dev/null +++ b/docs/kit/operation-view/page03_local_setup_controlplane.md @@ -0,0 +1,141 @@ +# Setting up a local EDC Control Plane + +## Basics + +The EDC is split into control and data plane. +The data plane handles the actual data transfer between parties. +The control plane manages the following: + +- Resource Management (e.g. Assets, Policies & Contract Definitions CRUD) +- Contract Offering & Contract Negotiation +- Data Transfer Coordination / Management + +The EDC control plane can run as a single container on your local machine. +The following is a short overview of the necessary steps to start up the default configuration. + +## Building + +Tractus-X EDC is build with Gradle. The following command creates the default control plane as a docker container: + +```shell +./gardlew :edc-controlplane:edc-controlplane-postgresql-hashicorp-vault:dockerize +``` + +## Example Configuration + +The following commands can be used to create the necessary configuration files for the EDC container. +They assume sane - but unsafe - defaults. An explanation of the respective parameters can be found [here](https://github.com/eclipse-tractusx/tractusx-edc/blob/main/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/README.md). + +:::danger +The following configuration is for testing purposes only. Do not use it in production. +::: + +### Example configuration.properties + +```shell +# Create configuration.properties +export CONFIGURATION_PROPERTIES_FILE=$(mktemp /tmp/configuration.properties.XXXXXX) +cat << 'EOF' > ${CONFIGURATION_PROPERTIES_FILE} + +web.http.default.port=8080 +web.http.default.path=/api +web.http.management.port=8181 +web.http.management.path=/data +web.http.control.port=9999 +web.http.control.path=/api/controlplane/control +web.http.protocol.port=8282 +web.http.protocol.path=/api/v1/ids + +edc.receiver.http.dynamic.endpoint=http://backend-service + +edc.ids.title=Eclipse Dataspace Connector +edc.ids.description=Eclipse Dataspace Connector +edc.ids.id=urn:connector:edc +edc.ids.security.profile=base +edc.ids.endpoint=http://localhost:8282/api/v1/ids +edc.ids.maintainer=http://localhost +edc.ids.curator=http://localhost +edc.ids.catalog.id=urn:catalog:default +ids.webhook.address=http://localhost:8282/api/v1/ids + +edc.hostname=localhost + +edc.api.auth.key=password + +# OAuth / DAPS related configuration +edc.oauth.token.url=https://daps.catena-x.net +edc.oauth.certificate.alias=key-to-daps-certificate-in-keyvault +edc.oauth.private.key.alias=key-to-private-key-in-keyvault +edc.oauth.client.id=daps-oauth-client-id + +# HashiCorp vault related configuration +edc.vault.hashicorp.url=http://vault +edc.vault.hashicorp.token=55555555-6666-7777-8888-999999999999 +edc.vault.hashicorp.timeout.seconds=30 + +# Control- / Data- Plane configuration +edc.transfer.proxy.endpoint=http://dataplane-public-endpoint/public +edc.transfer.proxy.token.signer.privatekey.alias=token-signer-private-key + +# Postgresql related configuration +edc.datasource.asset.name=asset +edc.datasource.asset.url=jdbc:postgresql://postgres.svc.cluster.local:5432/edc_asset +edc.datasource.asset.user=user +edc.datasource.asset.password=pass +edc.datasource.contractdefinition.name=contractdefinition +edc.datasource.contractdefinition.url=jdbc:postgresql://postgres.svc.cluster.local:5432/edc_contractdefinition +edc.datasource.contractdefinition.user=user +edc.datasource.contractdefinition.password=pass +edc.datasource.contractnegotiation.name=contractnegotiation +edc.datasource.contractnegotiation.url=jdbc:postgresql://postgres.svc.cluster.local:5432/edc_contractnegotiation +edc.datasource.contractnegotiation.user=user +edc.datasource.contractnegotiation.password=pass +edc.datasource.policy.name=policy +edc.datasource.policy.url=jdbc:postgresql://postgres.svc.cluster.local:5432/edc_policy +edc.datasource.policy.user=user +edc.datasource.policy.password=pass +edc.datasource.transferprocess.name=transferprocess +edc.datasource.transferprocess.url=jdbc:postgresql://postgres.svc.cluster.local:5432/edc_transferprocess +edc.datasource.transferprocess.user=user +edc.datasource.transferprocess.password=pass +EOF +``` + +### Example logging.properties + +```shell +# Create logging.properties +export LOGGING_PROPERTIES_FILE=$(mktemp /tmp/logging.properties.XXXXXX) +cat << 'EOF' > ${LOGGING_PROPERTIES_FILE} +.level=INFO +org.eclipse.edc.level=ALL +handlers=java.util.logging.ConsoleHandler +java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter +java.util.logging.ConsoleHandler.level=ALL +java.util.logging.SimpleFormatter.format=[%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS] [%4$-7s] %5$s%6$s%n +EOF +``` + +### Example opentelemetry.properties + +```shell +# Create opentelemetry.properties +export OPENTELEMETRY_PROPERTIES_FILE=$(mktemp /tmp/opentelemetry.properties.XXXXXX) +cat << 'EOF' > ${OPENTELEMETRY_PROPERTIES_FILE} +otel.javaagent.enabled=false +otel.javaagent.debug=false +EOF +``` + +## Running the Control Plane + +Once the configuration is created, the container can be run directly via docker. + +```shell +docker run \ + -p 8080:8080 -p 8181:8181 -p 8182:8182 -p 8282:8282 -p 9090:9090 -p 9999:9999 \ + -v ${CONFIGURATION_PROPERTIES_FILE:-/dev/null}:/app/configuration.properties \ + -v ${LOGGING_PROPERTIES_FILE:-/dev/null}:/app/logging.properties \ + -v ${OPENTELEMETRY_PROPERTIES_FILE:-/dev/null}:/app/opentelemetry.properties \ + -i edc-controlplane-postgresql-hashicorp-vault:latest +``` diff --git a/docs/kit/operation-view/page04_local_setup_dataplane.md b/docs/kit/operation-view/page04_local_setup_dataplane.md new file mode 100644 index 000000000..d84a6c9f1 --- /dev/null +++ b/docs/kit/operation-view/page04_local_setup_dataplane.md @@ -0,0 +1,98 @@ +# Setting up a local EDC Data Plane + +## Basics + +The EDC is split into control and data plane. +The data plane handles the actual data transfer between parties. +The control plane manages the following: + +- Resource Management (e.g. Assets, Policies & Contract Definitions CRUD) +- Contract Offering & Contract Negotiation +- Data Transfer Coordination / Management + +The EDC data plane can run as a single container on your local machine. +The following is a short overview of the necessary steps to start up the default configuration. + +## Building + +Tractus-X EDC is build with Gradle. The following command creates the default data plane as a docker container: + +```shell +./gardlew :edc-dataplane:edc-dataplane-hashicorp-vault:dockerize +``` + +## Example Configuration + +The following commands can be used to create the necessary configuration files for the EDC container. +They assume sane - but unsafe - defaults. An explanation of the respective parameters can be found [here](https://github.com/eclipse-tractusx/tractusx-edc/blob/main/edc-dataplane/edc-dataplane-hashicorp-vault/README.md). + +:::danger + +The following configuration is for testing purposes only. Do not use it in production. +::: + +### Example configuration.properties + +```shell +# Create configuration.properties +export CONFIGURATION_PROPERTIES_FILE=$(mktemp /tmp/configuration.properties.XXXXXX) +cat << 'EOF' > ${CONFIGURATION_PROPERTIES_FILE} + +web.http.default.port=8080 +web.http.default.path=/api +web.http.public.port=8185 +web.http.public.path=/public +web.http.control.port=9999 +web.http.control.path=/api/dataplane/control + +# Validation endpoint of controlplane +edc.dataplane.token.validation.endpoint=http://controlplane:9999/api/controlplane/control/token + +# EDC hostname +edc.hostname=localhost + +# HashiCorp vault related configuration +edc.vault.hashicorp.url=http://vault +edc.vault.hashicorp.token=55555555-6666-7777-8888-999999999999 +edc.vault.hashicorp.timeout.seconds=30 +EOF +``` + +### Example logging.properties + +```shell +# Create logging.properties +export LOGGING_PROPERTIES_FILE=$(mktemp /tmp/logging.properties.XXXXXX) +cat << 'EOF' > ${LOGGING_PROPERTIES_FILE} +.level=INFO +org.eclipse.edc.level=ALL +handlers=java.util.logging.ConsoleHandler +java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter +java.util.logging.ConsoleHandler.level=ALL +java.util.logging.SimpleFormatter.format=[%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS] [%4$-7s] %5$s%6$s%n +EOF +``` + +### Example opentelemetry.properties + +```shell +# Create opentelemetry.properties +export OPENTELEMETRY_PROPERTIES_FILE=$(mktemp /tmp/opentelemetry.properties.XXXXXX) +cat << 'EOF' > ${OPENTELEMETRY_PROPERTIES_FILE} +otel.javaagent.enabled=true +otel.javaagent.debug=false +EOF +``` + +## Running + +Once the configuration is created, the container can be run directly via docker. + +```shell +docker run \ + -p 8080:8080 -p 8185:8185 -p 9999:9999 -p 9090:9090 \ + -v ${CONFIGURATION_PROPERTIES_FILE:-/dev/null}:/app/configuration.properties \ + -v ${LOGGING_PROPERTIES_FILE:-/dev/null}:/app/logging.properties \ + -v ${OPENTELEMETRY_PROPERTIES_FILE:-/dev/null}:/app/opentelemetry.properties \ + -i edc-dataplane-hashicorp-vault:latest +``` diff --git a/docs/kit/operation-view/page06_kubernetes_setup.md b/docs/kit/operation-view/page06_kubernetes_setup.md new file mode 100644 index 000000000..67930be5e --- /dev/null +++ b/docs/kit/operation-view/page06_kubernetes_setup.md @@ -0,0 +1,22 @@ +# Setup in Kubernetes via Helm + +## Introduction + +While the local setup described earlier is sufficient to test basic EDC functionality, it is not appropriate for any actual environments. +For a more complete setup, Helm charts are provided. + +## Setup + +To set up an example environment, you can use the following Helm commands: + +```shell +helm repo add tractusx-edc https://eclipse-tractusx.github.io/charts/dev +helm install my-release tractusx-edc/tractusx-connector-memory --version 0.3.3 +``` + +## Configuration + +The Helm chart used above can be found [here](https://github.com/eclipse-tractusx/tractusx-edc/tree/main/charts). +Configuring that deployment requires the same parameters as the local setup described previously. +Helm expects these parameters in the relevant `values.yaml`. +Similar example configurations can be found with the respective charts under the above link. diff --git a/docs/kit/operation-view/page08_api.md b/docs/kit/operation-view/page08_api.md new file mode 100644 index 000000000..9f45abe14 --- /dev/null +++ b/docs/kit/operation-view/page08_api.md @@ -0,0 +1,64 @@ +# EDC API Examples + +## API Spec + +The API spec of the EDC is constantly evolving. +The full API documentation for each release can be viewed on [management-api](../development-view/openAPI/management-api/management-api.info.mdx). +The following are some example API calls for common use cases. +They assume the default parameters from the previous local setup. + +## Create an Asset + +All objects in EDC are created by posting their JSON-serialized representation to the respective API input. +Since most EDC objects are rather openly defined, most of the properties provided depend on the need of the individual user. +Assets are no exception here. + +URL + +```http request +POST http://localhost:8080/api/v1/assets/ +``` + +Body + +```json +{ + "asset": { + "id": "asset1", + "properties": { + "exampleProperty": "exampleValue" + } + }, + "dataAddress": { + "properties": { + "baseUrl": "https://path.to/the_asset", + "type": "HttpData" + } + } +} +``` + +## Request an Asset Catalog + +To inspect the assets available to an EDC connector, we request its catalog. + +URL + +```http request +POST http://localhost:8080/api/v1/catalog/request +``` + +Body + +```json +{ + "providerUrl": "www.example.provider", + "querySpec": { + "filter": "AvailableWithPolicyXYZ", + "limit": 0, + "offset": 0, + "sortField": "id", + "sortOrder": "ASC" + } +} +``` diff --git a/docs/kit/operation-view/page09_upgrading.md b/docs/kit/operation-view/page09_upgrading.md new file mode 100644 index 000000000..414d0ab46 --- /dev/null +++ b/docs/kit/operation-view/page09_upgrading.md @@ -0,0 +1,20 @@ +# Upgrading Tractus-X EDC + +Among the goals of Tractus-X EDC is making EDC upgrades as painless as possible. +The changes in each release are documented [here](https://github.com/eclipse-tractusx/tractusx-edc/tree/main/docs/migration). +Usually there are only three steps to each upgrade. + +## Database Migration + +Database migration is simple to accomplish with a PostgreSQL backend. +The [PostgreSQL Migration Extension](https://github.com/eclipse-tractusx/tractusx-edc/tree/main/edc-extensions/postgresql-migration) is the preferred approach. +Alternatively, the `.sql` files therein can be used to manually update the database schema. + +## Updating EDC + +The easy part of the upgrade process is to simply switch the outdated EDC containers with their newer counterparts. + +## Updating Settings + +Check the newest [Migration Documents](https://github.com/eclipse-tractusx/tractusx-edc/tree/develop/docs/migration) +for any changes to the settings structure and apply them to your settings. diff --git a/docs/kit/operation-view/page10_extensions.md b/docs/kit/operation-view/page10_extensions.md new file mode 100644 index 000000000..798c86195 --- /dev/null +++ b/docs/kit/operation-view/page10_extensions.md @@ -0,0 +1,44 @@ +# EDC Extensions + +The following extensions provide additional functionality to the core EDC. +They are currently only available in Tractus-X EDC. + +## Business Partner Validation + +This extension allows for validation of business partners within the access policy. + +## Control Plane Adapter + +The goal of this extension is to simplify the process of retrieving data out of EDC. +It returns `EndpointDataReference` object, hiding all the communication details for contract offers, +contract negotiation, transfer process and retrieving the underlying data through the data-planes. + +## CX OAuth2 + +This extension enables OAuth2 authentication between EDC connectors, +instead of the more complex authentication flow used by core EDC. + +## Data Encryption + +The EDC encrypts sensitive information inside a token it sends to other applications (potentially cross-company). +This extension implements the encryption of this data and should be used with secure keys and algorithms at all times. + +## Data Plane Selector + +This control plane extension makes it possible to configure one or more data plane instances. +During a transfer the control plane will look for an instance with matching capabilities to transfer data. + +## Hashicorp Vault + +This extension allows for usage of Hashicorp Vault for secret storage. +It is the default used in Tractus-X EDC. + +## PostrgreSQL Migration + +While the core EDC is able to interact with PostgreSQL databases, +it does not automate migrations between schema versions. +This extension adds that functionality. + +## Transfer Process SFTP + +This extension allows for the use of SFTP backends for the data plane (but is not included in the provided control- and data plane). diff --git a/docs/migration/Version_0.3.1_0.3.2.md b/docs/migration/Version_0.3.1_0.3.2.md index d6f49da29..4099e8c4b 100644 --- a/docs/migration/Version_0.3.1_0.3.2.md +++ b/docs/migration/Version_0.3.1_0.3.2.md @@ -2,7 +2,7 @@ ## Configuration of Azure KeyVault -When using Helm Charts that use the Azure KeyVault (`edc-controlplane-memory`, `edc-controlplane-postgres`) +When using Helm Charts that use the Azure KeyVault (`edc-runtime-memory`, `edc-controlplane-postgres`) it is now possible to select _either_ authentication via Client Secret (`azure.vault.secret`) or via certificate (`azure.vault.certificate`). diff --git a/docs/migration/Version_0.3.3_0.3.4.md b/docs/migration/Version_0.3.3_0.3.4.md new file mode 100644 index 000000000..a93de9600 --- /dev/null +++ b/docs/migration/Version_0.3.3_0.3.4.md @@ -0,0 +1,21 @@ +# Migration from 0.3.3 to 0.3.4 + +## Refactoring of Helm Charts + +In issue [#136](https://github.com/eclipse-tractusx/tractusx-edc/issues/136) work has begun to split the Helm charts up +into several technology-focused charts: + +- In-memory: for testing and development +- PostgreSQL+Hashicorp: this is the **recommended** distribution of Tractus-X EDC +- (Azure KeyVault: uses Azure KeyVault instead of Hashicorp Vault.) - Work in Progress + +Unfortunately, due to time constraints, we had to release 0.3.4 **without** the Azure KeyVault chart, it will be +included in one of the subsequent releases in the future. + +**Please note that the Azure KeyVault variant is not included in the 0.3.4 Release! If you rely on AZKV please do NOT +upgrade to 0.3.4 yet!** + +## Change in Docker image publishing + +Starting with the 0.3.3 release we switched over to publish our Docker images +to [Docker Hub](https://hub.docker.com/search?q=tractusx) instead of GHCR. diff --git a/edc-controlplane/build.gradle.kts b/edc-controlplane/build.gradle.kts index d27dbdf36..b7a1fee31 100644 --- a/edc-controlplane/build.gradle.kts +++ b/edc-controlplane/build.gradle.kts @@ -1,3 +1,21 @@ +/* + * Copyright (c) 2021,2022 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 + */ plugins { `java-library` @@ -5,8 +23,8 @@ plugins { dependencies { implementation(project(":edc-controlplane:edc-controlplane-base")) - implementation(project(":edc-controlplane:edc-controlplane-memory")) + implementation(project(":edc-controlplane:edc-runtime-memory")) implementation(project(":edc-controlplane:edc-controlplane-memory-hashicorp-vault")) - implementation(project(":edc-controlplane:edc-controlplane-postgresql")) + implementation(project(":edc-controlplane:edc-controlplane-postgresql-azure-vault")) implementation(project(":edc-controlplane:edc-controlplane-postgresql-hashicorp-vault")) } diff --git a/edc-controlplane/edc-controlplane-base/build.gradle.kts b/edc-controlplane/edc-controlplane-base/build.gradle.kts index 5a6ed21c1..87dca3970 100644 --- a/edc-controlplane/edc-controlplane-base/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-base/build.gradle.kts @@ -1,3 +1,22 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * Copyright (c) 2021,2022 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 + */ plugins { `java-library` diff --git a/edc-controlplane/edc-controlplane-memory-hashicorp-vault/build.gradle.kts b/edc-controlplane/edc-controlplane-memory-hashicorp-vault/build.gradle.kts index eb7aef8fe..0f69332a4 100644 --- a/edc-controlplane/edc-controlplane-memory-hashicorp-vault/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-memory-hashicorp-vault/build.gradle.kts @@ -1,9 +1,27 @@ -import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * Copyright (c) 2021,2022 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 + */ plugins { `java-library` id("application") - id("com.github.johnrengelman.shadow") version "8.0.0" + id("com.github.johnrengelman.shadow") version "8.1.1" } dependencies { diff --git a/edc-controlplane/edc-controlplane-memory-hashicorp-vault/notice.md b/edc-controlplane/edc-controlplane-memory-hashicorp-vault/notice.md new file mode 100644 index 000000000..b709f09c2 --- /dev/null +++ b/edc-controlplane/edc-controlplane-memory-hashicorp-vault/notice.md @@ -0,0 +1,28 @@ +# Notice for Docker image + +An EDC Control Plane using memory-based storage, and HashiCorp Vault as secret store. + +DockerHub: https://hub.docker.com/r/tractusx/edc-controlplane-memory-hashicorp-vault + +Eclipse Tractus-X product(s) installed within the image: + +## Tractus-X-EDC Control Plane + +- GitHub: https://github.com/eclipse-tractusx/tractusx-edc +- Project home: https://projects.eclipse.org/projects/automotive.tractusx +- Dockerfile: https://github.com/eclipse-tractusx/tractusx-edc/blob/main/edc-controlplane/edc-controlplane-memory-hashicorp-vault/src/main/docker/Dockerfile +- Project license: [Apache License, Version 2.0](https://github.com/eclipse-tractusx/tractusx-edc/blob/main/LICENSE) + +## Used base image + +- [eclipse-temurin:17.0.6_10-jre-alpine](https://github.com/adoptium/containers) +- Official Eclipse Temurin DockerHub page: https://hub.docker.com/_/eclipse-temurin +- Eclipse Temurin Project: https://projects.eclipse.org/projects/adoptium.temurin +- Additional information about the Eclipse Temurin + images: https://github.com/docker-library/repo-info/tree/master/repos/eclipse-temurin + +As with all Docker images, these likely also contain other software which may be under other licenses (such as Bash, etc +from the base distribution, along with any direct or indirect dependencies of the primary software being contained). + +As for any pre-built image usage, it is the image user's responsibility to ensure that any use of this image complies +with any relevant licenses for all software contained within. diff --git a/edc-controlplane/edc-controlplane-memory-hashicorp-vault/src/main/docker/Dockerfile b/edc-controlplane/edc-controlplane-memory-hashicorp-vault/src/main/docker/Dockerfile index 229c44868..2848f38e0 100644 --- a/edc-controlplane/edc-controlplane-memory-hashicorp-vault/src/main/docker/Dockerfile +++ b/edc-controlplane/edc-controlplane-memory-hashicorp-vault/src/main/docker/Dockerfile @@ -18,15 +18,17 @@ # # SPDX-License-Identifier: Apache-2.0 # -FROM alpine:3.17.1 as otel + +FROM alpine:3.18.0 AS otel ENV OTEL_AGENT_LOCATION "https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v1.12.1/opentelemetry-javaagent.jar" HEALTHCHECK NONE -RUN wget ${OTEL_AGENT_LOCATION} -O /tmp/opentelemetry-javaagent.jar +RUN apk update && apk add curl=8.1.0-r0 --no-cache +RUN curl -L --proto "=https" -sSf ${OTEL_AGENT_LOCATION} --output /tmp/opentelemetry-javaagent.jar -FROM eclipse-temurin:11.0.18_10-jre-alpine +FROM eclipse-temurin:17.0.6_10-jre-alpine ARG JAR ARG APP_USER=docker diff --git a/edc-controlplane/edc-controlplane-memory/build.gradle.kts b/edc-controlplane/edc-controlplane-memory/build.gradle.kts deleted file mode 100644 index 1254a3634..000000000 --- a/edc-controlplane/edc-controlplane-memory/build.gradle.kts +++ /dev/null @@ -1,25 +0,0 @@ -import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage - -plugins { - `java-library` - id("application") - id("com.github.johnrengelman.shadow") version "8.0.0" -} - -dependencies { - runtimeOnly(project(":edc-controlplane:edc-controlplane-base")) - runtimeOnly(edc.core.controlplane) - runtimeOnly(edc.azure.vault) - runtimeOnly(edc.azure.identity) - -} - -tasks.withType { - exclude("**/pom.properties", "**/pom.xm") - mergeServiceFiles() - archiveFileName.set("${project.name}.jar") -} - -application { - mainClass.set("org.eclipse.edc.boot.system.runtime.BaseRuntime") -} diff --git a/edc-controlplane/edc-controlplane-postgresql/README.md b/edc-controlplane/edc-controlplane-postgresql-azure-vault/README.md similarity index 98% rename from edc-controlplane/edc-controlplane-postgresql/README.md rename to edc-controlplane/edc-controlplane-postgresql-azure-vault/README.md index b9ec0afd0..94792735e 100644 --- a/edc-controlplane/edc-controlplane-postgresql/README.md +++ b/edc-controlplane/edc-controlplane-postgresql-azure-vault/README.md @@ -3,12 +3,12 @@ ## Building ```shell -./gardlew :edc-controlplane:edc-controlplane-postgresql:dockerize +./gradlew :edc-controlplane:edc-controlplane-postgresql-azure-vault:dockerize ``` ## Configuration -Listed below are configuration keys needed to get the `edc-controlplane-postgresql` up and running. +Listed below are configuration keys needed to get the `edc-controlplane-postgresql-azure-vault` up and running. Details regarding each configuration property can be found at the [documentary section of the EDC](https://github.com/eclipse-edc/Connector/tree/main/docs). | Key | Required | Example | Description | @@ -177,5 +177,5 @@ docker run \ -v ${CONFIGURATION_PROPERTIES_FILE:-/dev/null}:/app/configuration.properties \ -v ${LOGGING_PROPERTIES_FILE:-/dev/null}:/app/logging.properties \ -v ${OPENTELEMETRY_PROPERTIES_FILE:-/dev/null}:/app/opentelemetry.properties \ - -i edc-controlplane-postgresql:latest + -i edc-controlplane-postgresql-azure-vault:latest ``` diff --git a/edc-controlplane/edc-controlplane-postgresql-azure-vault/build.gradle.kts b/edc-controlplane/edc-controlplane-postgresql-azure-vault/build.gradle.kts new file mode 100644 index 000000000..3367ab2c8 --- /dev/null +++ b/edc-controlplane/edc-controlplane-postgresql-azure-vault/build.gradle.kts @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * Copyright (c) 2021,2022 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 + */ + +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + +plugins { + `java-library` + id("application") + id("com.github.johnrengelman.shadow") version "8.1.1" +} + +dependencies { + runtimeOnly(project(":edc-controlplane:edc-controlplane-base")) + runtimeOnly(project(":edc-extensions:postgresql-migration")) + runtimeOnly(edc.azure.vault) + runtimeOnly(edc.bundles.sqlstores) + runtimeOnly(edc.transaction.local) + runtimeOnly(edc.sql.pool) + runtimeOnly(edc.core.controlplane) + runtimeOnly(edc.dpf.transfer) +} + + +tasks.withType { + exclude("**/pom.properties", "**/pom.xm") + mergeServiceFiles() + archiveFileName.set("${project.name}.jar") +} + + +application { + mainClass.set("org.eclipse.edc.boot.system.runtime.BaseRuntime") +} diff --git a/edc-controlplane/edc-controlplane-postgresql-azure-vault/notice.md b/edc-controlplane/edc-controlplane-postgresql-azure-vault/notice.md new file mode 100644 index 000000000..bd2b9b276 --- /dev/null +++ b/edc-controlplane/edc-controlplane-postgresql-azure-vault/notice.md @@ -0,0 +1,28 @@ +# Notice for Docker image + +An EDC Control Plane using PostgreSQL as persistence backend, and Azure KeyVault as secret store. + +DockerHub: https://hub.docker.com/r/tractusx/edc-controlplane-postgresql-azure-vault + +Eclipse Tractus-X product(s) installed within the image: + +## Tractus-X EDC Control Plane + +- GitHub: https://github.com/eclipse-tractusx/tractusx-edc +- Project home: https://projects.eclipse.org/projects/automotive.tractusx +- Dockerfile: https://github.com/eclipse-tractusx/tractusx-edc/blob/main/edc-controlplane/edc-controlplane-postgresql-azure-vault/src/main/docker/Dockerfile +- Project license: [Apache License, Version 2.0](https://github.com/eclipse-tractusx/tractusx-edc/blob/main/LICENSE) + +## Used base image + +- [eclipse-temurin:17.0.6_10-jre-alpine](https://github.com/adoptium/containers) +- Official Eclipse Temurin DockerHub page: https://hub.docker.com/_/eclipse-temurin +- Eclipse Temurin Project: https://projects.eclipse.org/projects/adoptium.temurin +- Additional information about the Eclipse Temurin + images: https://github.com/docker-library/repo-info/tree/master/repos/eclipse-temurin + +As with all Docker images, these likely also contain other software which may be under other licenses (such as Bash, etc +from the base distribution, along with any direct or indirect dependencies of the primary software being contained). + +As for any pre-built image usage, it is the image user's responsibility to ensure that any use of this image complies +with any relevant licenses for all software contained within. diff --git a/edc-controlplane/edc-controlplane-memory/src/main/docker/Dockerfile b/edc-controlplane/edc-controlplane-postgresql-azure-vault/src/main/docker/Dockerfile similarity index 89% rename from edc-controlplane/edc-controlplane-memory/src/main/docker/Dockerfile rename to edc-controlplane/edc-controlplane-postgresql-azure-vault/src/main/docker/Dockerfile index b3e04fac7..c2a217e50 100644 --- a/edc-controlplane/edc-controlplane-memory/src/main/docker/Dockerfile +++ b/edc-controlplane/edc-controlplane-postgresql-azure-vault/src/main/docker/Dockerfile @@ -18,15 +18,16 @@ # # SPDX-License-Identifier: Apache-2.0 # -FROM alpine:3.17.2 as otel +FROM alpine:3.18.0 AS otel ENV OTEL_AGENT_LOCATION "https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v1.12.1/opentelemetry-javaagent.jar" HEALTHCHECK NONE -RUN wget ${OTEL_AGENT_LOCATION} -O /tmp/opentelemetry-javaagent.jar +RUN apk update && apk add curl=8.1.0-r0 --no-cache +RUN curl -L --proto "=https" -sSf ${OTEL_AGENT_LOCATION} --output /tmp/opentelemetry-javaagent.jar -FROM eclipse-temurin:11.0.18_10-jre-alpine +FROM eclipse-temurin:17.0.6_10-jre-alpine ARG JAR ARG APP_USER=docker diff --git a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts index 7c8a46f54..e760bc0c8 100644 --- a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts @@ -1,10 +1,29 @@ -import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * Copyright (c) 2021,2022 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 + */ + import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar plugins { `java-library` id("application") - id("com.github.johnrengelman.shadow") version "8.0.0" + id("com.github.johnrengelman.shadow") version "8.1.1" } dependencies { diff --git a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/notice.md b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/notice.md new file mode 100644 index 000000000..381253ec9 --- /dev/null +++ b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/notice.md @@ -0,0 +1,28 @@ +# Notice for Docker image + +An EDC Control Plane using PostgreSQL as persistence backend, and HashiCorp Vault as secret store. + +DockerHub: https://hub.docker.com/r/tractusx/edc-controlplane-postgresql-hashicorp-vault + +Eclipse Tractus-X product(s) installed within the image: + +## Tractus-X EDC Control Plane + +- GitHub: https://github.com/eclipse-tractusx/tractusx-edc +- Project home: https://projects.eclipse.org/projects/automotive.tractusx +- Dockerfile: https://github.com/eclipse-tractusx/tractusx-edc/blob/main/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/src/main/docker/Dockerfile +- Project license: [Apache License, Version 2.0](https://github.com/eclipse-tractusx/tractusx-edc/blob/main/LICENSE) + +## Used base image + +- [eclipse-temurin:17.0.6_10-jre-alpine](https://github.com/adoptium/containers) +- Official Eclipse Temurin DockerHub page: https://hub.docker.com/_/eclipse-temurin +- Eclipse Temurin Project: https://projects.eclipse.org/projects/adoptium.temurin +- Additional information about the Eclipse Temurin + images: https://github.com/docker-library/repo-info/tree/master/repos/eclipse-temurin + +As with all Docker images, these likely also contain other software which may be under other licenses (such as Bash, etc +from the base distribution, along with any direct or indirect dependencies of the primary software being contained). + +As for any pre-built image usage, it is the image user's responsibility to ensure that any use of this image complies +with any relevant licenses for all software contained within. diff --git a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/src/main/docker/Dockerfile b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/src/main/docker/Dockerfile index b3e04fac7..c2a217e50 100644 --- a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/src/main/docker/Dockerfile +++ b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/src/main/docker/Dockerfile @@ -18,15 +18,16 @@ # # SPDX-License-Identifier: Apache-2.0 # -FROM alpine:3.17.2 as otel +FROM alpine:3.18.0 AS otel ENV OTEL_AGENT_LOCATION "https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v1.12.1/opentelemetry-javaagent.jar" HEALTHCHECK NONE -RUN wget ${OTEL_AGENT_LOCATION} -O /tmp/opentelemetry-javaagent.jar +RUN apk update && apk add curl=8.1.0-r0 --no-cache +RUN curl -L --proto "=https" -sSf ${OTEL_AGENT_LOCATION} --output /tmp/opentelemetry-javaagent.jar -FROM eclipse-temurin:11.0.18_10-jre-alpine +FROM eclipse-temurin:17.0.6_10-jre-alpine ARG JAR ARG APP_USER=docker diff --git a/edc-controlplane/edc-controlplane-postgresql/build.gradle.kts b/edc-controlplane/edc-controlplane-postgresql/build.gradle.kts deleted file mode 100644 index 659aa6891..000000000 --- a/edc-controlplane/edc-controlplane-postgresql/build.gradle.kts +++ /dev/null @@ -1,30 +0,0 @@ -import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar - -plugins { - `java-library` - id("application") - id("com.github.johnrengelman.shadow") version "8.0.0" -} - -dependencies { - runtimeOnly(project(":edc-controlplane:edc-controlplane-base")) - runtimeOnly(project(":edc-extensions:postgresql-migration")) - runtimeOnly(edc.azure.vault) - runtimeOnly(edc.bundles.sqlstores) - runtimeOnly(edc.core.controlplane) - runtimeOnly(edc.dpf.transfer) - -} - - -tasks.withType { - exclude("**/pom.properties", "**/pom.xm") - mergeServiceFiles() - archiveFileName.set("${project.name}.jar") -} - - -application { - mainClass.set("org.eclipse.edc.boot.system.runtime.BaseRuntime") -} diff --git a/edc-controlplane/edc-controlplane-memory/README.md b/edc-controlplane/edc-runtime-memory/README.md similarity index 54% rename from edc-controlplane/edc-controlplane-memory/README.md rename to edc-controlplane/edc-runtime-memory/README.md index ca1f0bef7..caf7fe24e 100644 --- a/edc-controlplane/edc-controlplane-memory/README.md +++ b/edc-controlplane/edc-runtime-memory/README.md @@ -1,54 +1,64 @@ # EDC Control-Plane backed by In-Memory Stores +## Security + +### In-memory Vault implementation + +The goal of this extension is to provide an ephemeral, memory-based vault implementation that can be used in testing or +demo scenarios. + +Please not that this vault does not encrypt the secrets, they are held in memory in plain text at runtime! In addition, +its ephemeral nature makes it unsuitable for replicated/multi-instance scenarios, i.e. Kubernetes. + +> It is not a secure secret store, please do NOT use it in production workloads! + ## Building ```shell -./gradlew :edc-controlplane:edc-controlplane-memory:dockerize +./gradlew :edc-controlplane:edc-runtime-memory:dockerize ``` ## Configuration (configuration.properties) -Listed below are configuration keys needed to get the `edc-controlplane-memory` up and running. -Details regarding each configuration property can be found at the [documentary section of the EDC](https://github.com/eclipse-edc/Connector/tree/main/docs). - -| Key | Required | Example | Description | -|--------------------------------------------------|----------|--------------------------------------|----------------------------| -| edc.api.auth.key | | password | default value: random UUID | -| web.http.default.port | X | 8080 | | -| web.http.default.path | X | /api | | -| web.http.data.port | X | 8181 | | -| web.http.data.path | X | /data | | -| web.http.validation.port | X | 8182 | | -| web.http.validation.path | X | /validation | | -| web.http.control.port | X | 9999 | | -| web.http.control.path | X | /api/controlplane/control | | -| web.http.ids.port | X | 8282 | | -| web.http.ids.path | X | /api/v1/ids | | -| edc.receiver.http.endpoint | X | | | -| edc.ids.title | | Eclipse Dataspace Connector | | -| edc.ids.description | | Eclipse Dataspace Connector | | -| edc.ids.id | | urn:connector:edc | | -| edc.ids.security.profile | | base | | -| edc.ids.endpoint | | | | -| edc.ids.maintainer | | | | -| edc.ids.curator | | | | -| edc.ids.catalog.id | | urn:catalog:default | | -| ids.webhook.address | | | | -| edc.hostname | | localhost | | -| edc.oauth.token.url | X | | | -| edc.oauth.public.key.alias | X | key-to-daps-certificate-in-keyvault | | -| edc.oauth.private.key.alias | X | key-to-private-key-in-keyvault | | -| edc.oauth.client.id | X | daps-oauth-client-id | | -| edc.vault.clientid | X | 00000000-1111-2222-3333-444444444444 | | -| edc.vault.tenantid | X | 55555555-6666-7777-8888-999999999999 | | -| edc.vault.name | X | my-vault-name | | -| edc.vault.clientsecret | X | 34-chars-secret | | -| edc.transfer.proxy.endpoint | X | | | -| edc.transfer.proxy.token.signer.privatekey.alias | X | | | +Listed below are configuration keys needed to get the `edc-runtime-memory` up and running. +Details regarding each configuration property can be found at +the [documentary section of the EDC](https://github.com/eclipse-edc/Connector/tree/main/docs). + +| Key | Required | Example | Description | +|--------------------------------------------------|----------|-------------------------------------|----------------------------| +| edc.api.auth.key | | password | default value: random UUID | +| web.http.default.port | X | 8080 | | +| web.http.default.path | X | /api | | +| web.http.data.port | X | 8181 | | +| web.http.data.path | X | /data | | +| web.http.validation.port | X | 8182 | | +| web.http.validation.path | X | /validation | | +| web.http.control.port | X | 9999 | | +| web.http.control.path | X | /api/controlplane/control | | +| web.http.ids.port | X | 8282 | | +| web.http.ids.path | X | /api/v1/ids | | +| edc.receiver.http.endpoint | X | | | +| edc.ids.title | | Eclipse Dataspace Connector | | +| edc.ids.description | | Eclipse Dataspace Connector | | +| edc.ids.id | | urn:connector:edc | | +| edc.ids.security.profile | | base | | +| edc.ids.endpoint | | | | +| edc.ids.maintainer | | | | +| edc.ids.curator | | | | +| edc.ids.catalog.id | | urn:catalog:default | | +| ids.webhook.address | | | | +| edc.hostname | | localhost | | +| edc.oauth.token.url | X | | | +| edc.oauth.public.key.alias | X | key-to-daps-certificate-in-keyvault | | +| edc.oauth.private.key.alias | X | key-to-private-key-in-keyvault | | +| edc.oauth.client.id | X | daps-oauth-client-id | | +| edc.transfer.proxy.endpoint | X | | | +| edc.transfer.proxy.token.signer.privatekey.alias | X | | | ### Example configuration.properties -JDK properties-style configuration of the EDC Control-Plane is expected to be mounted to `/app/configuration.properties` within the container. +JDK properties-style configuration of the EDC Control-Plane is expected to be mounted to `/app/configuration.properties` +within the container. ```shell # Create configuration.properties @@ -88,12 +98,6 @@ edc.oauth.public.key.alias=key-to-daps-certificate-in-keyvault edc.oauth.private.key.alias=key-to-private-key-in-keyvault edc.oauth.client.id=daps-oauth-client-id -# Azure vault related configuration -edc.vault.clientid=00000000-1111-2222-3333-444444444444 -edc.vault.tenantid=55555555-6666-7777-8888-999999999999 -edc.vault.name=my-vault-name -edc.vault.clientsecret=34-chars-secret - # Control- / Data- Plane configuration edc.transfer.proxy.endpoint=http://dataplane-public-endpoint/public edc.transfer.proxy.token.signer.privatekey.alias=azure-vault-token-signer-private-key @@ -115,24 +119,13 @@ java.util.logging.SimpleFormatter.format=[%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS] [ EOF ``` -### Example opentelemetry.properties - -```shell -# Create opentelemetry.properties -export OPENTELEMETRY_PROPERTIES_FILE=$(mktemp /tmp/opentelemetry.properties.XXXXXX) -cat << 'EOF' > ${OPENTELEMETRY_PROPERTIES_FILE} -otel.javaagent.enabled=true -otel.javaagent.debug=false -EOF -``` - ## Running ```shell docker run \ + -e SECRETS="key1:secret1,key2:secret2" \ -p 8080:8080 -p 8181:8181 -p 8182:8182 -p 8282:8282 -p 9090:9090 -p 9999:9999 \ -v ${CONFIGURATION_PROPERTIES_FILE:-/dev/null}:/app/configuration.properties \ -v ${LOGGING_PROPERTIES_FILE:-/dev/null}:/app/logging.properties \ - -v ${OPENTELEMETRY_PROPERTIES_FILE:-/dev/null}:/app/opentelemetry.properties \ - -i edc-controlplane-memory:latest + -i edc-runtime-memory:latest ``` diff --git a/edc-controlplane/edc-runtime-memory/build.gradle.kts b/edc-controlplane/edc-runtime-memory/build.gradle.kts new file mode 100644 index 000000000..b834ed12f --- /dev/null +++ b/edc-controlplane/edc-runtime-memory/build.gradle.kts @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021,2022 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 + */ + +plugins { + `java-library` + id("application") + id("com.github.johnrengelman.shadow") version "8.1.1" +} + +dependencies { + implementation(edc.spi.core) + runtimeOnly(project(":edc-controlplane:edc-controlplane-base")) { + exclude(module = "data-encryption") + } + runtimeOnly(project(":edc-dataplane:edc-dataplane-base")) + runtimeOnly(edc.core.controlplane) +} + +tasks.withType { + exclude("**/pom.properties", "**/pom.xm") + mergeServiceFiles() + archiveFileName.set("${project.name}.jar") +} + +application { + mainClass.set("org.eclipse.edc.boot.system.runtime.BaseRuntime") +} diff --git a/edc-controlplane/edc-runtime-memory/notice.md b/edc-controlplane/edc-runtime-memory/notice.md new file mode 100644 index 000000000..621cb44b5 --- /dev/null +++ b/edc-controlplane/edc-runtime-memory/notice.md @@ -0,0 +1,28 @@ +# Notice for Docker image + +An EDC Control Plane using memory-based storage, and Azure KeyVault as secret store. + +DockerHub: https://hub.docker.com/r/tractusx/edc-runtime-memory + +Eclipse Tractus-X product(s) installed within the image: + +## Tractus-X EDC Control Plane + +- GitHub: https://github.com/eclipse-tractusx/tractusx-edc +- Project home: https://projects.eclipse.org/projects/automotive.tractusx +- Dockerfile: https://github.com/eclipse-tractusx/tractusx-edc/blob/main/edc-controlplane/edc-runtime-memory/src/main/docker/Dockerfile +- Project license: [Apache License, Version 2.0](https://github.com/eclipse-tractusx/tractusx-edc/blob/main/LICENSE) + +## Used base image + +- [eclipse-temurin:17.0.6_10-jre-alpine](https://github.com/adoptium/containers) +- Official Eclipse Temurin DockerHub page: https://hub.docker.com/_/eclipse-temurin +- Eclipse Temurin Project: https://projects.eclipse.org/projects/adoptium.temurin +- Additional information about the Eclipse Temurin + images: https://github.com/docker-library/repo-info/tree/master/repos/eclipse-temurin + +As with all Docker images, these likely also contain other software which may be under other licenses (such as Bash, etc +from the base distribution, along with any direct or indirect dependencies of the primary software being contained). + +As for any pre-built image usage, it is the image user's responsibility to ensure that any use of this image complies +with any relevant licenses for all software contained within. diff --git a/edc-controlplane/edc-controlplane-postgresql/src/main/docker/Dockerfile b/edc-controlplane/edc-runtime-memory/src/main/docker/Dockerfile similarity index 59% rename from edc-controlplane/edc-controlplane-postgresql/src/main/docker/Dockerfile rename to edc-controlplane/edc-runtime-memory/src/main/docker/Dockerfile index b3e04fac7..de17857c4 100644 --- a/edc-controlplane/edc-controlplane-postgresql/src/main/docker/Dockerfile +++ b/edc-controlplane/edc-runtime-memory/src/main/docker/Dockerfile @@ -1,4 +1,5 @@ # +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) # Copyright (c) 2023 ZF Friedrichshafen AG # Copyright (c) 2022,2023 Mercedes-Benz Tech Innovation GmbH # Copyright (c) 2021,2023 Contributors to the Eclipse Foundation @@ -18,15 +19,8 @@ # # SPDX-License-Identifier: Apache-2.0 # -FROM alpine:3.17.2 as otel -ENV OTEL_AGENT_LOCATION "https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v1.12.1/opentelemetry-javaagent.jar" - -HEALTHCHECK NONE - -RUN wget ${OTEL_AGENT_LOCATION} -O /tmp/opentelemetry-javaagent.jar - -FROM eclipse-temurin:11.0.18_10-jre-alpine +FROM eclipse-temurin:17.0.6_10-jre-alpine ARG JAR ARG APP_USER=docker @@ -46,18 +40,10 @@ RUN adduser \ USER "$APP_USER" WORKDIR /app -COPY --from=otel /tmp/opentelemetry-javaagent.jar . COPY ${JAR} edc-controlplane.jar HEALTHCHECK NONE -CMD ["java", \ - "-javaagent:/app/opentelemetry-javaagent.jar", \ - "-Dedc.fs.config=/app/configuration.properties", \ - "-Djava.util.logging.config.file=/app/logging.properties", \ - "-Dotel.javaagent.configuration-file=/app/opentelemetry.properties", \ - "-Dotel.metrics.exporter=prometheus", \ - "-Dotel.exporter.prometheus.port=9090", \ - "-Djava.security.egd=file:/dev/urandom", \ - "-jar", \ - "edc-controlplane.jar"] +# need the sh -c syntax so that the SECRETS variable gets expanded +# use the "exec" syntax so that SIGINT reaches the JVM -> graceful termination +CMD ["sh", "-c", "exec java -Dedc.fs.config=/app/configuration.properties -Dedc.vault.secrets=\"${SECRETS}\" -Djava.util.logging.config.file=/app/logging.properties -Djava.security.egd=file:/dev/urandom -jar edc-controlplane.jar"] diff --git a/edc-controlplane/edc-runtime-memory/src/main/java/org/eclipse/tractusx/edc/vault/memory/InMemoryVault.java b/edc-controlplane/edc-runtime-memory/src/main/java/org/eclipse/tractusx/edc/vault/memory/InMemoryVault.java new file mode 100644 index 000000000..9b92a83c0 --- /dev/null +++ b/edc-controlplane/edc-runtime-memory/src/main/java/org/eclipse/tractusx/edc/vault/memory/InMemoryVault.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.vault.memory; + +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.security.Vault; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class InMemoryVault implements Vault { + private final Map secrets = new ConcurrentHashMap<>(); + private final Monitor monitor; + + public InMemoryVault(Monitor monitor) { + this.monitor = monitor; + } + + @Override + public @Nullable String resolveSecret(String s) { + monitor.debug("resolving secret " + s); + return secrets.getOrDefault(s, null); + } + + @Override + public Result storeSecret(String s, String s1) { + monitor.debug("storing secret " + s); + secrets.put(s, s1); + return Result.success(); + } + + @Override + public Result deleteSecret(String s) { + monitor.debug("deleting secret " + s); + return secrets.remove(s) == null ? + Result.failure("Secret with key " + s + " does not exist") : + Result.success(); + } +} diff --git a/edc-controlplane/edc-runtime-memory/src/main/java/org/eclipse/tractusx/edc/vault/memory/VaultMemoryExtension.java b/edc-controlplane/edc-runtime-memory/src/main/java/org/eclipse/tractusx/edc/vault/memory/VaultMemoryExtension.java new file mode 100644 index 000000000..c8d2cc7d2 --- /dev/null +++ b/edc-controlplane/edc-runtime-memory/src/main/java/org/eclipse/tractusx/edc/vault/memory/VaultMemoryExtension.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ +package org.eclipse.tractusx.edc.vault.memory; + +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Provider; +import org.eclipse.edc.runtime.metamodel.annotation.Provides; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.spi.security.*; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; + +import java.util.stream.Stream; + +@Provides({PrivateKeyResolver.class, CertificateResolver.class}) +@Extension(value = "In-memory vault extension", categories = {"vault", "security"}) +public class VaultMemoryExtension implements ServiceExtension { + + @Setting(value = "Secrets with which the vault gets initially populated. Specify as comma-separated list of key:secret pairs.") + public static final String VAULT_MEMORY_SECRETS_PROPERTY = "edc.vault.secrets"; + public static final String NAME = "In-Memory Vault Extension"; + + @Override + public String name() { + return NAME; + } + + @Provider + public Vault createInMemVault(ServiceExtensionContext context) { + var seedSecrets = context.getSetting(VAULT_MEMORY_SECRETS_PROPERTY, null); + var vault = new InMemoryVault(context.getMonitor()); + context.registerService(PrivateKeyResolver.class, new VaultPrivateKeyResolver(vault)); + context.registerService(CertificateResolver.class, new VaultCertificateResolver(vault)); + if (seedSecrets != null) { + Stream.of(seedSecrets.split(";")) + .filter(pair -> pair.contains(":")) + .map(kvp -> kvp.split(":", 2)) + .filter(kvp -> kvp.length >= 2) + .forEach(pair -> vault.storeSecret(pair[0], pair[1])); + } + return vault; + } +} diff --git a/edc-controlplane/edc-runtime-memory/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-controlplane/edc-runtime-memory/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 000000000..b105388ea --- /dev/null +++ b/edc-controlplane/edc-runtime-memory/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,21 @@ +# +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (c) 2021,2022 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 +# + +org.eclipse.tractusx.edc.vault.memory.VaultMemoryExtension diff --git a/edc-controlplane/edc-runtime-memory/src/test/java/org/eclipse/tractusx/edc/vault/memory/InMemoryVaultTest.java b/edc-controlplane/edc-runtime-memory/src/test/java/org/eclipse/tractusx/edc/vault/memory/InMemoryVaultTest.java new file mode 100644 index 000000000..c00ae8180 --- /dev/null +++ b/edc-controlplane/edc-runtime-memory/src/test/java/org/eclipse/tractusx/edc/vault/memory/InMemoryVaultTest.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.vault.memory; + +import org.eclipse.edc.spi.monitor.Monitor; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +class InMemoryVaultTest { + + private InMemoryVault vault; + + @BeforeEach + void setUp() { + vault = new InMemoryVault(mock(Monitor.class)); + } + + @Test + void resolveSecret() { + assertThat(vault.resolveSecret("key")).isNull(); + vault.storeSecret("key", "secret"); + assertThat(vault.resolveSecret("key")).isEqualTo("secret"); + } + + @Test + void storeSecret() { + assertThat(vault.storeSecret("key", "value1").succeeded()).isTrue(); + assertThat(vault.resolveSecret("key")).isEqualTo("value1"); + assertThat(vault.storeSecret("key", "value2").succeeded()).isTrue(); + assertThat(vault.resolveSecret("key")).isEqualTo("value2"); + } + + @Test + void deleteSecret() { + assertThat(vault.deleteSecret("key").succeeded()).isFalse(); + assertThat(vault.storeSecret("key", "value1").succeeded()).isTrue(); + assertThat(vault.deleteSecret("key").succeeded()).isTrue(); + assertThat(vault.resolveSecret("key")).isNull(); + + } +} diff --git a/edc-controlplane/edc-runtime-memory/src/test/java/org/eclipse/tractusx/edc/vault/memory/VaultMemoryExtensionTest.java b/edc-controlplane/edc-runtime-memory/src/test/java/org/eclipse/tractusx/edc/vault/memory/VaultMemoryExtensionTest.java new file mode 100644 index 000000000..bec589d79 --- /dev/null +++ b/edc-controlplane/edc-runtime-memory/src/test/java/org/eclipse/tractusx/edc/vault/memory/VaultMemoryExtensionTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.vault.memory; + +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +class VaultMemoryExtensionTest { + private VaultMemoryExtension extension; + private ServiceExtensionContext context; + private Monitor monitor; + + @BeforeEach + void setup() { + extension = new VaultMemoryExtension(); + context = mock(ServiceExtensionContext.class); + monitor = mock(Monitor.class); + when(context.getMonitor()).thenReturn(monitor); + } + + @Test + void name() { + assertThat(extension.name()).isEqualTo("In-Memory Vault Extension"); + } + + @ParameterizedTest + @ValueSource(strings = {"key1:", "key1:value1", "key1:value1;", ";key1:value1", ";sdf;key1:value1"}) + void createInMemVault_validString(String secret) { + when(context.getSetting(eq(VaultMemoryExtension.VAULT_MEMORY_SECRETS_PROPERTY), eq(null))).thenReturn(secret); + extension.createInMemVault(context); + verify(monitor, times(1)).debug(anyString()); + } +} diff --git a/edc-dataplane/build.gradle.kts b/edc-dataplane/build.gradle.kts index 063edd76c..d60f4a7fe 100644 --- a/edc-dataplane/build.gradle.kts +++ b/edc-dataplane/build.gradle.kts @@ -1,3 +1,21 @@ +/* + * Copyright (c) 2021,2022 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 + */ plugins { `java-library` diff --git a/edc-dataplane/edc-dataplane-azure-vault/build.gradle.kts b/edc-dataplane/edc-dataplane-azure-vault/build.gradle.kts index bde51831c..1ae42a29a 100644 --- a/edc-dataplane/edc-dataplane-azure-vault/build.gradle.kts +++ b/edc-dataplane/edc-dataplane-azure-vault/build.gradle.kts @@ -1,15 +1,33 @@ +/* + * Copyright (c) 2021,2022 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 + */ plugins { `java-library` id("application") - id("com.github.johnrengelman.shadow") version "8.0.0" + id("com.github.johnrengelman.shadow") version "8.1.1" } dependencies { implementation(project(":edc-dataplane:edc-dataplane-base")) implementation(edc.azure.vault) implementation(edc.azure.identity) - implementation("com.azure:azure-security-keyvault-secrets:4.5.4") + implementation("com.azure:azure-security-keyvault-secrets:4.6.1") } tasks.withType { diff --git a/edc-dataplane/edc-dataplane-azure-vault/notice.md b/edc-dataplane/edc-dataplane-azure-vault/notice.md new file mode 100644 index 000000000..e3a83858c --- /dev/null +++ b/edc-dataplane/edc-dataplane-azure-vault/notice.md @@ -0,0 +1,28 @@ +# Notice for Docker image + +An EDC Data Plane using the Azure KeyVault. + +DockerHub: https://hub.docker.com/r/tractusx/edc-dataplane-azure-vault + +Eclipse Tractus-X product(s) installed within the image: + +## Tractus-X EDC Data Plane + +- GitHub: https://github.com/eclipse-tractusx/tractusx-edc +- Project home: https://projects.eclipse.org/projects/automotive.tractusx +- Dockerfile: https://github.com/eclipse-tractusx/tractusx-edc/blob/main/edc-dataplane/edc-dataplane-azure-vault/src/main/docker/Dockerfile +- Project license: [Apache License, Version 2.0](https://github.com/eclipse-tractusx/tractusx-edc/blob/main/LICENSE) + +## Used base image + +- [eclipse-temurin:17.0.6_10-jre-alpine](https://github.com/adoptium/containers) +- Official Eclipse Temurin DockerHub page: https://hub.docker.com/_/eclipse-temurin +- Eclipse Temurin Project: https://projects.eclipse.org/projects/adoptium.temurin +- Additional information about the Eclipse Temurin + images: https://github.com/docker-library/repo-info/tree/master/repos/eclipse-temurin + +As with all Docker images, these likely also contain other software which may be under other licenses (such as Bash, etc +from the base distribution, along with any direct or indirect dependencies of the primary software being contained). + +As for any pre-built image usage, it is the image user's responsibility to ensure that any use of this image complies +with any relevant licenses for all software contained within. diff --git a/edc-dataplane/edc-dataplane-azure-vault/src/main/docker/Dockerfile b/edc-dataplane/edc-dataplane-azure-vault/src/main/docker/Dockerfile index 5c3b12f11..25864f70a 100644 --- a/edc-dataplane/edc-dataplane-azure-vault/src/main/docker/Dockerfile +++ b/edc-dataplane/edc-dataplane-azure-vault/src/main/docker/Dockerfile @@ -18,15 +18,16 @@ # # SPDX-License-Identifier: Apache-2.0 # -FROM alpine:3.17.2 as otel +FROM alpine:3.18.0 AS otel ENV OTEL_AGENT_LOCATION "https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v1.12.1/opentelemetry-javaagent.jar" HEALTHCHECK NONE -RUN wget ${OTEL_AGENT_LOCATION} -O /tmp/opentelemetry-javaagent.jar +RUN apk update && apk add curl=8.1.0-r0 --no-cache +RUN curl -L --proto "=https" -sSf ${OTEL_AGENT_LOCATION} --output /tmp/opentelemetry-javaagent.jar -FROM eclipse-temurin:11.0.18_10-jre-alpine +FROM eclipse-temurin:17.0.6_10-jre-alpine ARG JAR ARG APP_USER=docker diff --git a/edc-dataplane/edc-dataplane-base/build.gradle.kts b/edc-dataplane/edc-dataplane-base/build.gradle.kts index 686e5fd06..af49bf16d 100644 --- a/edc-dataplane/edc-dataplane-base/build.gradle.kts +++ b/edc-dataplane/edc-dataplane-base/build.gradle.kts @@ -1,21 +1,40 @@ +/* + * Copyright (c) 2021,2022 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 + */ + plugins { `java-library` } dependencies { - implementation(edc.config.filesystem) - implementation(edc.dpf.awss3) - implementation(edc.dpf.oauth2) - implementation(edc.dpf.http) + runtimeOnly(project(":edc-extensions:observability-api-customization")) - implementation(edc.dpf.framework) - implementation(edc.dpf.api) - implementation(edc.api.observability) - implementation(edc.core.connector) - implementation(edc.boot) + runtimeOnly(edc.config.filesystem) + runtimeOnly(edc.dpf.awss3) + runtimeOnly(edc.dpf.oauth2) + runtimeOnly(edc.dpf.http) + runtimeOnly(edc.dpf.framework) + runtimeOnly(edc.dpf.api) + runtimeOnly(edc.core.connector) + runtimeOnly(edc.boot) - implementation(edc.bundles.monitoring) - implementation(edc.ext.http) + runtimeOnly(edc.bundles.monitoring) + runtimeOnly(edc.ext.http) } \ No newline at end of file diff --git a/edc-dataplane/edc-dataplane-hashicorp-vault/build.gradle.kts b/edc-dataplane/edc-dataplane-hashicorp-vault/build.gradle.kts index 7f53f8c5c..42bbd4d57 100644 --- a/edc-dataplane/edc-dataplane-hashicorp-vault/build.gradle.kts +++ b/edc-dataplane/edc-dataplane-hashicorp-vault/build.gradle.kts @@ -1,8 +1,26 @@ +/* + * Copyright (c) 2021,2022 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 + */ plugins { `java-library` id("application") - id("com.github.johnrengelman.shadow") version "8.0.0" + id("com.github.johnrengelman.shadow") version "8.1.1" } dependencies { diff --git a/edc-dataplane/edc-dataplane-hashicorp-vault/notice.md b/edc-dataplane/edc-dataplane-hashicorp-vault/notice.md new file mode 100644 index 000000000..8e1108d7c --- /dev/null +++ b/edc-dataplane/edc-dataplane-hashicorp-vault/notice.md @@ -0,0 +1,28 @@ +# Notice for Docker image + +An EDC Data Plane using the HashiCorp Vault + +DockerHub: https://hub.docker.com/r/tractusx/edc-dataplane-hashicorp-vault + +Eclipse Tractus-X product(s) installed within the image: + +## Tractus-X-EDC Data Plane + +- GitHub: https://github.com/eclipse-tractusx/tractusx-edc +- Project home: https://projects.eclipse.org/projects/automotive.tractusx +- Dockerfile: https://github.com/eclipse-tractusx/tractusx-edc/blob/main/edc-dataplane/edc-dataplane-hashicorp-vault/src/main/docker/Dockerfile +- Project license: [Apache License, Version 2.0](https://github.com/eclipse-tractusx/tractusx-edc/blob/main/LICENSE) + +## Used base image + +- [eclipse-temurin:17.0.6_10-jre-alpine](https://github.com/adoptium/containers) +- Official Eclipse Temurin DockerHub page: https://hub.docker.com/_/eclipse-temurin +- Eclipse Temurin Project: https://projects.eclipse.org/projects/adoptium.temurin +- Additional information about the Eclipse Temurin + images: https://github.com/docker-library/repo-info/tree/master/repos/eclipse-temurin + +As with all Docker images, these likely also contain other software which may be under other licenses (such as Bash, etc +from the base distribution, along with any direct or indirect dependencies of the primary software being contained). + +As for any pre-built image usage, it is the image user's responsibility to ensure that any use of this image complies +with any relevant licenses for all software contained within. diff --git a/edc-dataplane/edc-dataplane-hashicorp-vault/src/main/docker/Dockerfile b/edc-dataplane/edc-dataplane-hashicorp-vault/src/main/docker/Dockerfile index 5c3b12f11..25864f70a 100644 --- a/edc-dataplane/edc-dataplane-hashicorp-vault/src/main/docker/Dockerfile +++ b/edc-dataplane/edc-dataplane-hashicorp-vault/src/main/docker/Dockerfile @@ -18,15 +18,16 @@ # # SPDX-License-Identifier: Apache-2.0 # -FROM alpine:3.17.2 as otel +FROM alpine:3.18.0 AS otel ENV OTEL_AGENT_LOCATION "https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v1.12.1/opentelemetry-javaagent.jar" HEALTHCHECK NONE -RUN wget ${OTEL_AGENT_LOCATION} -O /tmp/opentelemetry-javaagent.jar +RUN apk update && apk add curl=8.1.0-r0 --no-cache +RUN curl -L --proto "=https" -sSf ${OTEL_AGENT_LOCATION} --output /tmp/opentelemetry-javaagent.jar -FROM eclipse-temurin:11.0.18_10-jre-alpine +FROM eclipse-temurin:17.0.6_10-jre-alpine ARG JAR ARG APP_USER=docker diff --git a/edc-extensions/build.gradle.kts b/edc-extensions/build.gradle.kts index f7740ef67..646417ebf 100644 --- a/edc-extensions/build.gradle.kts +++ b/edc-extensions/build.gradle.kts @@ -1,3 +1,21 @@ +/* + * 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 + */ plugins { `java-library` diff --git a/edc-extensions/business-partner-validation/build.gradle.kts b/edc-extensions/business-partner-validation/build.gradle.kts index 53cb11e31..87311b589 100644 --- a/edc-extensions/business-partner-validation/build.gradle.kts +++ b/edc-extensions/business-partner-validation/build.gradle.kts @@ -1,3 +1,21 @@ +/* + * Copyright (c) 2021,2022 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 + */ plugins { `java-library` @@ -7,5 +25,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/README.md b/edc-extensions/control-plane-adapter/README.md index fe9d4787c..5f2d0d890 100644 --- a/edc-extensions/control-plane-adapter/README.md +++ b/edc-extensions/control-plane-adapter/README.md @@ -39,9 +39,17 @@ To run CP-Adapter in "PERSISTENT" mode, You need to create a proper tables with 1. Client sends a GET request with two parameters: assetId and the url of the provider controlplane: ```plain - /adapter/asset/sync/{assetId}?providerUrl={providerUrl} + {controlplaneUrl}:{web.http.management.port}/{web.http.management.path}/adapter/asset/sync/{assetId}?providerUrl={providerUrl} ``` + | Name | Description | + |----------------------------|----------------------------------------------------------------------------------| + | `controlplaneUrl` | The URL where the control plane of the consumer connector is available | + | `web.http.management.port` | Port of the management API provided by the control plane | + | `web.http.management.path` | Path of the management API provided by the control plane | + | `assetId` | ID of the wanted asset | + | `providerUrl` | URL pointing to the `data` endpoint of the IDS context of the provider connector | + The example ULR could be: ```plain diff --git a/edc-extensions/control-plane-adapter/build.gradle.kts b/edc-extensions/control-plane-adapter/build.gradle.kts index fe34a0866..f22dd2e5f 100644 --- a/edc-extensions/control-plane-adapter/build.gradle.kts +++ b/edc-extensions/control-plane-adapter/build.gradle.kts @@ -1,3 +1,21 @@ +/* + * 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 + */ plugins { `java-library` diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/objectstore/ObjectStoreServiceInMemory.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/objectstore/ObjectStoreServiceInMemory.java index 3e1a8190a..ccfd6e44d 100644 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/objectstore/ObjectStoreServiceInMemory.java +++ b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/objectstore/ObjectStoreServiceInMemory.java @@ -22,6 +22,7 @@ import java.util.Objects; import java.util.stream.Collectors; import lombok.AllArgsConstructor; +import org.eclipse.edc.spi.EdcException; @AllArgsConstructor public class ObjectStoreServiceInMemory implements ObjectStoreService { @@ -33,7 +34,7 @@ public void put(String key, ObjectType objectType, Object object) { try { map.put(getKey(key, objectType), mapper.writeValueAsString(object)); } catch (JsonProcessingException e) { - e.printStackTrace(); + throw new IllegalArgumentException(); } } @@ -67,7 +68,7 @@ private T map(Class type, String json) { try { object = mapper.readValue(json, type); } catch (JsonProcessingException e) { - e.printStackTrace(); + throw new EdcException(e); } return object; } diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/objectstore/ObjectStoreServiceSql.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/objectstore/ObjectStoreServiceSql.java index b16daea50..1fb528e2c 100644 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/objectstore/ObjectStoreServiceSql.java +++ b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/objectstore/ObjectStoreServiceSql.java @@ -70,7 +70,7 @@ private String objectToJson(Object object, String type) { try { return mapper.writeValueAsString(object); } catch (JsonProcessingException e) { - e.printStackTrace(); + throw new IllegalArgumentException(String.format("Can not parse object of type %s", type)); } } @@ -79,7 +79,7 @@ private T jsonToObject(ObjectEntity entity, Class type) { try { return mapper.readValue(entity.getObject(), type); } catch (JsonProcessingException e) { - e.printStackTrace(); + throw new IllegalArgumentException(String.format("Can not parse object of type %s", type)); } } diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/SqlObjectStore.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/SqlObjectStore.java index 4a5ec94a9..5540428f3 100644 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/SqlObjectStore.java +++ b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/SqlObjectStore.java @@ -58,7 +58,7 @@ public void saveMessage(ObjectEntity objectEntity) { objectEntity.getType(), objectEntity.getObject()); } catch (SQLException e) { - e.printStackTrace(); + throw new EdcPersistenceException(e); } }); @@ -71,7 +71,7 @@ public ObjectEntity find(String id, String type) { var sql = statements.getFindByIdAndTypeTemplate(); return executeQuerySingle(connection, false, this::mapObjectEntity, sql, id, type); } catch (SQLException e) { - e.printStackTrace(); + throw new EdcPersistenceException(e); } }); @@ -88,7 +88,7 @@ public List find(String type) { stream.close(); return result; } catch (SQLException e) { - e.printStackTrace(); + throw new EdcPersistenceException(e); } }); @@ -101,7 +101,7 @@ public void deleteMessage(String id, String type) { var stmt = statements.getDeleteTemplate(); executeQuery(connection, stmt, id, type); } catch (SQLException | IllegalStateException e) { - e.printStackTrace(); + throw new EdcPersistenceException(e); } }); diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/SqlQueueStore.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/SqlQueueStore.java index 0d808fae1..60336eb5b 100644 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/SqlQueueStore.java +++ b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/SqlQueueStore.java @@ -69,7 +69,7 @@ public void saveMessage(QueueMessage queueMessage) { toJson(queueMessage.getMessage()), queueMessage.getInvokeAfter()); } catch (SQLException e) { - e.printStackTrace(); + throw new EdcPersistenceException(e); } }); @@ -82,7 +82,7 @@ public QueueMessage findById(String id) { var sql = statements.getFindByIdTemplate(); return executeQuerySingle(connection, false, this::mapQueueMessage, sql, id); } catch (SQLException e) { - e.printStackTrace(); + throw new EdcPersistenceException(e); } }); @@ -99,7 +99,7 @@ public void deleteMessage(String id) { var stmt = statements.getDeleteTemplate(); executeQuery(connection, stmt, id); } catch (SQLException | IllegalStateException e) { - e.printStackTrace(); + throw new EdcPersistenceException(e); } } @@ -123,7 +123,7 @@ public void updateMessage(QueueMessage queueMessage) { queueMessage.getInvokeAfter(), queueMessage.getId()); } catch (SQLException | IllegalStateException e) { - e.printStackTrace(); + throw new EdcPersistenceException(e); } } @@ -146,7 +146,7 @@ public List findMessagesToSend(int max) { stream.close(); return result; } catch (SQLException e) { - e.printStackTrace(); + throw new EdcPersistenceException(e); } }); diff --git a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/service/ResultServiceTest.java b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/service/ResultServiceTest.java index 2b7bff25f..0c4c649de 100644 --- a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/service/ResultServiceTest.java +++ b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/service/ResultServiceTest.java @@ -119,7 +119,7 @@ private void sleep(long milisec) { try { Thread.sleep(milisec); } catch (InterruptedException e) { - e.printStackTrace(); + throw new RuntimeException(e); } } diff --git a/edc-extensions/cx-oauth2/build.gradle.kts b/edc-extensions/cx-oauth2/build.gradle.kts index 8e7dd8111..5d25556f0 100644 --- a/edc-extensions/cx-oauth2/build.gradle.kts +++ b/edc-extensions/cx-oauth2/build.gradle.kts @@ -1,3 +1,22 @@ +/* + * 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 + */ + plugins { `java-library` diff --git a/edc-extensions/data-encryption/build.gradle.kts b/edc-extensions/data-encryption/build.gradle.kts index f619da546..8f5a67de0 100644 --- a/edc-extensions/data-encryption/build.gradle.kts +++ b/edc-extensions/data-encryption/build.gradle.kts @@ -1,3 +1,21 @@ +/* + * 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 + */ plugins { `maven-publish` diff --git a/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/algorithms/aes/AesAlgorithm.java b/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/algorithms/aes/AesAlgorithm.java index 69c54a173..6f463fc82 100644 --- a/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/algorithms/aes/AesAlgorithm.java +++ b/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/algorithms/aes/AesAlgorithm.java @@ -20,93 +20,91 @@ */ package org.eclipse.tractusx.edc.data.encryption.algorithms.aes; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.eclipse.tractusx.edc.data.encryption.algorithms.CryptoAlgorithm; +import org.eclipse.tractusx.edc.data.encryption.data.CryptoDataFactory; +import org.eclipse.tractusx.edc.data.encryption.data.DecryptedData; +import org.eclipse.tractusx.edc.data.encryption.data.EncryptedData; +import org.eclipse.tractusx.edc.data.encryption.key.AesKey; +import org.eclipse.tractusx.edc.data.encryption.util.ArrayUtil; + import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.util.Objects; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; -import lombok.NonNull; -import lombok.SneakyThrows; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.eclipse.tractusx.edc.data.encryption.algorithms.CryptoAlgorithm; -import org.eclipse.tractusx.edc.data.encryption.data.CryptoDataFactory; -import org.eclipse.tractusx.edc.data.encryption.data.DecryptedData; -import org.eclipse.tractusx.edc.data.encryption.data.EncryptedData; -import org.eclipse.tractusx.edc.data.encryption.key.AesKey; -import org.eclipse.tractusx.edc.data.encryption.util.ArrayUtil; -import org.jetbrains.annotations.NotNull; public class AesAlgorithm implements CryptoAlgorithm { - private static final String AES_GCM = "AES/GCM/NoPadding"; - private static final String AES = "AES"; - private static final Object MONITOR = new Object(); + private static final String AES_GCM = "AES/GCM/NoPadding"; + private static final String AES = "AES"; + private static final Object MONITOR = new Object(); + + private final SecureRandom secureRandom; - private final SecureRandom secureRandom; + private final CryptoDataFactory cryptoDataFactory; + private AesInitializationVectorIterator initializationVectorIterator; - @NonNull private final CryptoDataFactory cryptoDataFactory; - private AesInitializationVectorIterator initializationVectorIterator; + public AesAlgorithm(CryptoDataFactory cryptoDataFactory) { + this.cryptoDataFactory = Objects.requireNonNull(cryptoDataFactory); - @SneakyThrows - public AesAlgorithm(@NotNull CryptoDataFactory cryptoDataFactory) { - this.cryptoDataFactory = cryptoDataFactory; + // We use new SecureRandom() and not SecureRandom.getInstanceStrong(), as the second one + // would use a blocking algorithm, which leads to an increased encryption time of up to 3 + // minutes. Since we have already used /dev/urandom, which only provides pseudo-randomness and + // is also non-blocking, switching to a non-blocking algorithm should not matter here either. + this.secureRandom = new SecureRandom(); + this.initializationVectorIterator = new AesInitializationVectorIterator(this.secureRandom); + } - // We use new SecureRandom() and not SecureRandom.getInstanceStrong(), as the second one - // would use a blocking algorithm, which leads to an increased encryption time of up to 3 - // minutes. Since we have already used /dev/urandom, which only provides pseudo-randomness and - // is also non-blocking, switching to a non-blocking algorithm should not matter here either. - this.secureRandom = new SecureRandom(); - this.initializationVectorIterator = new AesInitializationVectorIterator(this.secureRandom); - } + @Override + public synchronized EncryptedData encrypt(DecryptedData data, AesKey key) + throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException, + NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException { - @Override - public synchronized EncryptedData encrypt(DecryptedData data, AesKey key) - throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException, - NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException { + final byte[] initializationVector; + synchronized (MONITOR) { + if (!initializationVectorIterator.hasNext()) { + initializationVectorIterator = new AesInitializationVectorIterator(this.secureRandom); + } - final byte[] initializationVector; - synchronized (MONITOR) { - if (!initializationVectorIterator.hasNext()) { - initializationVectorIterator = new AesInitializationVectorIterator(this.secureRandom); - } + initializationVector = initializationVectorIterator.next(); + } - initializationVector = initializationVectorIterator.next(); + Cipher cipher = Cipher.getInstance(AES_GCM, new BouncyCastleProvider()); + final SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), AES); + final GCMParameterSpec gcmParameterSpec = + new GCMParameterSpec(16 * 8 /* =128 */, initializationVector); + cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec); + byte[] encrypted = cipher.doFinal(data.getBytes()); + byte[] encryptedWithVector = ArrayUtil.concat(initializationVector, encrypted); + + return cryptoDataFactory.encryptedFromBytes(encryptedWithVector); } - Cipher cipher = Cipher.getInstance(AES_GCM, new BouncyCastleProvider()); - final SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), AES); - final GCMParameterSpec gcmParameterSpec = - new GCMParameterSpec(16 * 8 /* =128 */, initializationVector); - cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec); - byte[] encrypted = cipher.doFinal(data.getBytes()); - byte[] encryptedWithVector = ArrayUtil.concat(initializationVector, encrypted); - - return cryptoDataFactory.encryptedFromBytes(encryptedWithVector); - } - - @Override - public DecryptedData decrypt(EncryptedData data, AesKey key) - throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException, - NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException { - byte[] encryptedWithVector = data.getBytes(); - byte[] initializationVector = ArrayUtil.subArray(encryptedWithVector, 0, 16); - byte[] encrypted = ArrayUtil.subArray(encryptedWithVector, 16, encryptedWithVector.length - 16); - - Cipher cipher = Cipher.getInstance(AES_GCM, new BouncyCastleProvider()); - final SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), AES); - final GCMParameterSpec gcmParameterSpec = - new GCMParameterSpec(16 * 8 /* =128 */, initializationVector); - cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec); - byte[] decryptedData = cipher.doFinal(encrypted); - return cryptoDataFactory.decryptedFromBytes(decryptedData); - } - - public String getAlgorithm() { - return this.secureRandom.getAlgorithm(); - } + @Override + public DecryptedData decrypt(EncryptedData data, AesKey key) + throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException, + NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException { + byte[] encryptedWithVector = data.getBytes(); + byte[] initializationVector = ArrayUtil.subArray(encryptedWithVector, 0, 16); + byte[] encrypted = ArrayUtil.subArray(encryptedWithVector, 16, encryptedWithVector.length - 16); + + Cipher cipher = Cipher.getInstance(AES_GCM, new BouncyCastleProvider()); + final SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), AES); + final GCMParameterSpec gcmParameterSpec = + new GCMParameterSpec(16 * 8 /* =128 */, initializationVector); + cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec); + byte[] decryptedData = cipher.doFinal(encrypted); + return cryptoDataFactory.decryptedFromBytes(decryptedData); + } + + public String getAlgorithm() { + return this.secureRandom.getAlgorithm(); + } } diff --git a/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/algorithms/aes/AesInitializationVectorIterator.java b/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/algorithms/aes/AesInitializationVectorIterator.java index cd0a6b1ec..73d02c3d5 100644 --- a/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/algorithms/aes/AesInitializationVectorIterator.java +++ b/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/algorithms/aes/AesInitializationVectorIterator.java @@ -20,51 +20,50 @@ */ package org.eclipse.tractusx.edc.data.encryption.algorithms.aes; +import org.eclipse.tractusx.edc.data.encryption.util.ArrayUtil; + import java.security.SecureRandom; import java.util.Iterator; import java.util.NoSuchElementException; -import lombok.SneakyThrows; -import org.eclipse.tractusx.edc.data.encryption.util.ArrayUtil; public class AesInitializationVectorIterator implements Iterator { - public static final int RANDOM_SIZE = 12; - public static final int COUNTER_SIZE = 4; - - private final ByteCounter counter; + public static final int RANDOM_SIZE = 12; + public static final int COUNTER_SIZE = 4; - private SecureRandom secureRandom; + private final ByteCounter counter; - public AesInitializationVectorIterator(SecureRandom secureRandom) { - this.counter = new ByteCounter(COUNTER_SIZE); - this.secureRandom = secureRandom; - } + private SecureRandom secureRandom; - public AesInitializationVectorIterator(ByteCounter byteCounter) { - this.counter = byteCounter; - } + public AesInitializationVectorIterator(SecureRandom secureRandom) { + this.counter = new ByteCounter(COUNTER_SIZE); + this.secureRandom = secureRandom; + } - @Override - public boolean hasNext() { - return !counter.isMaxed(); - } + public AesInitializationVectorIterator(ByteCounter byteCounter) { + this.counter = byteCounter; + } - @Override - public byte[] next() { - if (counter.isMaxed()) { - throw new NoSuchElementException(getClass().getSimpleName() + " has no more elements"); + @Override + public boolean hasNext() { + return !counter.isMaxed(); } - byte[] random = getNextRandom(); - counter.increment(); + @Override + public byte[] next() { + if (counter.isMaxed()) { + throw new NoSuchElementException(getClass().getSimpleName() + " has no more elements"); + } - return ArrayUtil.concat(random, counter.getBytes()); - } + byte[] random = getNextRandom(); + counter.increment(); - @SneakyThrows - public byte[] getNextRandom() { - byte[] newVector = new byte[RANDOM_SIZE]; - secureRandom.nextBytes(newVector); - return newVector; - } + return ArrayUtil.concat(random, counter.getBytes()); + } + + public byte[] getNextRandom() { + byte[] newVector = new byte[RANDOM_SIZE]; + secureRandom.nextBytes(newVector); + return newVector; + } } diff --git a/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/algorithms/aes/ByteCounter.java b/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/algorithms/aes/ByteCounter.java index 55eec8184..e7874c158 100644 --- a/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/algorithms/aes/ByteCounter.java +++ b/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/algorithms/aes/ByteCounter.java @@ -19,63 +19,69 @@ */ package org.eclipse.tractusx.edc.data.encryption.algorithms.aes; -/** Big Endian Byte Counter */ +/** + * Big Endian Byte Counter + */ public class ByteCounter { - private final byte[] counter; - - /** - * Constructs a new ByteCounter with the given number of bytes. E.g. a ByteCounter with 4 bytes - * will have a counter value of [0, 0, 0, 0]. - * - * @param size number of bytes used by the counter - */ - public ByteCounter(int size) { - this.counter = new byte[size]; - } + private final byte[] counter; - /** - * Constructs a new ByteCounter with the given counter value. Counter cannot grow bigger than the - * size of the array. - * - * @param counter initial counter value - */ - public ByteCounter(byte[] counter) { - this.counter = counter; - } + /** + * Constructs a new ByteCounter with the given number of bytes. E.g. a ByteCounter with 4 bytes + * will have a counter value of [0, 0, 0, 0]. + * + * @param size number of bytes used by the counter + */ + public ByteCounter(int size) { + this.counter = new byte[size]; + } - /** Returns the counter value as a byte array. */ - public byte[] getBytes() { - return counter; - } + /** + * Constructs a new ByteCounter with the given counter value. Counter cannot grow bigger than the + * size of the array. + * + * @param counter initial counter value + */ + public ByteCounter(byte[] counter) { + this.counter = counter; + } - /** Returns true if counter is maxed */ - public boolean isMaxed() { - for (byte b : counter) { - if (b != (byte) 0xff) return false; + /** + * Returns the counter value as a byte array. + */ + public byte[] getBytes() { + return counter; } - return true; - } - /** - * Increments the counter by one. - * - * @throws IllegalStateException if the counter is already maxed - */ - public void increment() { - incrementByte(counter.length - 1); - } + /** + * Returns true if counter is maxed + */ + public boolean isMaxed() { + for (byte b : counter) { + if (b != (byte) 0xff) return false; + } + return true; + } - private void incrementByte(int index) { - if (isMaxed()) { - throw new IllegalStateException("Counter is already maxed"); + /** + * Increments the counter by one. + * + * @throws IllegalStateException if the counter is already maxed + */ + public void increment() { + incrementByte(counter.length - 1); } - if (counter[index] == (byte) 0xff) { - incrementByte(index - 1); - counter[index] = (byte) 0x00; - } else { - counter[index]++; + private void incrementByte(int index) { + if (isMaxed()) { + throw new IllegalStateException("Counter is already maxed"); + } + + if (counter[index] == (byte) 0xff) { + incrementByte(index - 1); + counter[index] = (byte) 0x00; + } else { + counter[index]++; + } } - } } diff --git a/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/data/CryptoDataFactoryImpl.java b/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/data/CryptoDataFactoryImpl.java index b23966170..a01331275 100644 --- a/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/data/CryptoDataFactoryImpl.java +++ b/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/data/CryptoDataFactoryImpl.java @@ -19,58 +19,89 @@ */ package org.eclipse.tractusx.edc.data.encryption.data; -import lombok.Value; import org.bouncycastle.util.encoders.Base64; public class CryptoDataFactoryImpl implements CryptoDataFactory { - public DecryptedData decryptedFromText(String text) { - final byte[] bytes = text.getBytes(); - final String base64 = Base64.toBase64String(bytes); - return new DecryptedDataImpl(bytes, base64, text); - } - - public DecryptedData decryptedFromBase64(String base64) { - final byte[] bytes = Base64.decode(base64); - final String text = new String(bytes); - return new DecryptedDataImpl(bytes, base64, text); - } - - public DecryptedData decryptedFromBytes(byte[] bytes) { - final String base64 = Base64.toBase64String(bytes); - final String text = new String(bytes); - return new DecryptedDataImpl(bytes, base64, text); - } - - public EncryptedData encryptedFromText(String text) { - final byte[] bytes = text.getBytes(); - final String base64 = Base64.toBase64String(bytes); - return new EncryptedDataImpl(bytes, base64, text); - } - - public EncryptedData encryptedFromBase64(String base64) { - final byte[] bytes = Base64.decode(base64); - final String text = new String(bytes); - return new EncryptedDataImpl(bytes, base64, text); - } - - public EncryptedData encryptedFromBytes(byte[] bytes) { - final String base64 = Base64.toBase64String(bytes); - final String text = new String(bytes); - return new EncryptedDataImpl(bytes, base64, text); - } - - @Value - private static class DecryptedDataImpl implements DecryptedData { - byte[] bytes; - String base64; - String text; - } - - @Value - private static class EncryptedDataImpl implements EncryptedData { - byte[] bytes; - String base64; - String text; - } + public DecryptedData decryptedFromText(String text) { + final byte[] bytes = text.getBytes(); + final String base64 = Base64.toBase64String(bytes); + return new DecryptedDataImpl(bytes, base64, text); + } + + public DecryptedData decryptedFromBase64(String base64) { + final byte[] bytes = Base64.decode(base64); + final String text = new String(bytes); + return new DecryptedDataImpl(bytes, base64, text); + } + + public DecryptedData decryptedFromBytes(byte[] bytes) { + final String base64 = Base64.toBase64String(bytes); + final String text = new String(bytes); + return new DecryptedDataImpl(bytes, base64, text); + } + + public EncryptedData encryptedFromText(String text) { + final byte[] bytes = text.getBytes(); + final String base64 = Base64.toBase64String(bytes); + return new EncryptedDataImpl(bytes, base64, text); + } + + public EncryptedData encryptedFromBase64(String base64) { + final byte[] bytes = Base64.decode(base64); + final String text = new String(bytes); + return new EncryptedDataImpl(bytes, base64, text); + } + + public EncryptedData encryptedFromBytes(byte[] bytes) { + final String base64 = Base64.toBase64String(bytes); + final String text = new String(bytes); + return new EncryptedDataImpl(bytes, base64, text); + } + + + private static class DecryptedDataImpl implements DecryptedData { + private final byte[] bytes; + private final String base64; + private final String text; + + private DecryptedDataImpl(byte[] bytes, String base64, String text) { + this.bytes = bytes; + this.base64 = base64; + this.text = text; + } + + @Override + public byte[] getBytes() { + return bytes; + } + + @Override + public String getBase64() { + return base64; + } + } + + + private static class EncryptedDataImpl implements EncryptedData { + private final byte[] bytes; + private final String base64; + private final String text; + + private EncryptedDataImpl(byte[] bytes, String base64, String text) { + this.bytes = bytes; + this.base64 = base64; + this.text = text; + } + + @Override + public byte[] getBytes() { + return bytes; + } + + @Override + public String getBase64() { + return base64; + } + } } diff --git a/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/encrypter/AesDataEncrypterConfiguration.java b/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/encrypter/AesDataEncrypterConfiguration.java index 725828acc..0723306e4 100644 --- a/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/encrypter/AesDataEncrypterConfiguration.java +++ b/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/encrypter/AesDataEncrypterConfiguration.java @@ -21,12 +21,28 @@ package org.eclipse.tractusx.edc.data.encryption.encrypter; import java.time.Duration; -import lombok.NonNull; -import lombok.Value; -@Value + public class AesDataEncrypterConfiguration { - @NonNull String keySetAlias; - boolean cachingEnabled; - @NonNull Duration cachingDuration; + private final String keySetAlias; + private final boolean cachingEnabled; + private final Duration cachingDuration; + + public AesDataEncrypterConfiguration(String keySetAlias, boolean cachingEnabled, Duration cachingDuration) { + this.keySetAlias = keySetAlias; + this.cachingEnabled = cachingEnabled; + this.cachingDuration = cachingDuration; + } + + public Duration getCachingDuration() { + return cachingDuration; + } + + public boolean isCachingEnabled() { + return cachingEnabled; + } + + public String getKeySetAlias() { + return keySetAlias; + } } diff --git a/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/encrypter/AesDataEncrypterImpl.java b/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/encrypter/AesDataEncrypterImpl.java index 160f57df0..d8b4add87 100644 --- a/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/encrypter/AesDataEncrypterImpl.java +++ b/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/encrypter/AesDataEncrypterImpl.java @@ -20,15 +20,6 @@ package org.eclipse.tractusx.edc.data.encryption.encrypter; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.Optional; -import javax.crypto.AEADBadTagException; -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import lombok.RequiredArgsConstructor; import org.eclipse.edc.connector.transfer.dataplane.spi.security.DataEncrypter; import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.monitor.Monitor; @@ -40,69 +31,85 @@ import org.eclipse.tractusx.edc.data.encryption.key.AesKey; import org.eclipse.tractusx.edc.data.encryption.provider.KeyProvider; -@RequiredArgsConstructor +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Optional; +import javax.crypto.AEADBadTagException; +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; + public class AesDataEncrypterImpl implements DataEncrypter { - private final CryptoAlgorithm encryptionStrategy; - private final Monitor monitor; - private final KeyProvider keyProvider; - private final CryptoAlgorithm algorithm; - private final CryptoDataFactory cryptoDataFactory; + private final CryptoAlgorithm encryptionStrategy; + private final Monitor monitor; + private final KeyProvider keyProvider; + private final CryptoAlgorithm algorithm; + private final CryptoDataFactory cryptoDataFactory; - @Override - public String encrypt(String value) { - DecryptedData decryptedData = cryptoDataFactory.decryptedFromText(value); - AesKey key = keyProvider.getEncryptionKey(); + public AesDataEncrypterImpl(CryptoAlgorithm encryptionStrategy, Monitor monitor, KeyProvider keyProvider, CryptoAlgorithm algorithm, CryptoDataFactory cryptoDataFactory) { + this.encryptionStrategy = encryptionStrategy; + this.monitor = monitor; + this.keyProvider = keyProvider; + this.algorithm = algorithm; + this.cryptoDataFactory = cryptoDataFactory; + } - try { - EncryptedData encryptedData = algorithm.encrypt(decryptedData, key); - return encryptedData.getBase64(); - } catch (IllegalBlockSizeException - | BadPaddingException - | InvalidKeyException - | InvalidAlgorithmParameterException - | NoSuchPaddingException - | NoSuchAlgorithmException e) { - throw new EdcException(e); + @Override + public String encrypt(String value) { + DecryptedData decryptedData = cryptoDataFactory.decryptedFromText(value); + AesKey key = keyProvider.getEncryptionKey(); + + try { + EncryptedData encryptedData = algorithm.encrypt(decryptedData, key); + return encryptedData.getBase64(); + } catch (IllegalBlockSizeException + | BadPaddingException + | InvalidKeyException + | InvalidAlgorithmParameterException + | NoSuchPaddingException + | NoSuchAlgorithmException e) { + throw new EdcException(e); + } } - } - @Override - public String decrypt(String value) { - EncryptedData encryptedData = cryptoDataFactory.encryptedFromBase64(value); + @Override + public String decrypt(String value) { + EncryptedData encryptedData = cryptoDataFactory.encryptedFromBase64(value); - return keyProvider - .getDecryptionKeySet() - .map(key -> decrypt(encryptedData, key)) - .filter(Optional::isPresent) - .map(Optional::get) - .map(DecryptedData::getBytes) - .map(String::new) - .findFirst() - .orElseThrow( - () -> - new EdcException( - DataEncryptionExtension.EXTENSION_NAME - + ": Failed to decrypt data. This can happen if the key set is empty, contains invalid keys, the decryption key rotated out of the key set or because the data was encrypted by a different algorithm.")); - } + return keyProvider + .getDecryptionKeySet() + .map(key -> decrypt(encryptedData, key)) + .filter(Optional::isPresent) + .map(Optional::get) + .map(DecryptedData::getBytes) + .map(String::new) + .findFirst() + .orElseThrow( + () -> + new EdcException( + DataEncryptionExtension.EXTENSION_NAME + + ": Failed to decrypt data. This can happen if the key set is empty, contains invalid keys, the decryption key rotated out of the key set or because the data was encrypted by a different algorithm.")); + } - private Optional decrypt(EncryptedData data, AesKey key) { - try { - return Optional.of(encryptionStrategy.decrypt(data, key)); - } catch (AEADBadTagException e) { // thrown when wrong key is used for decryption - return Optional.empty(); - } catch (IllegalBlockSizeException - | BadPaddingException - | InvalidKeyException - | NoSuchPaddingException - | NoSuchAlgorithmException - | InvalidAlgorithmParameterException e) { - monitor.warning( - String.format( - DataEncryptionExtension.EXTENSION_NAME - + ": Exception decrypting data using key from rotating key set. %s", - e.getMessage())); - throw new EdcException(e); + private Optional decrypt(EncryptedData data, AesKey key) { + try { + return Optional.of(encryptionStrategy.decrypt(data, key)); + } catch (AEADBadTagException e) { // thrown when wrong key is used for decryption + return Optional.empty(); + } catch (IllegalBlockSizeException + | BadPaddingException + | InvalidKeyException + | NoSuchPaddingException + | NoSuchAlgorithmException + | InvalidAlgorithmParameterException e) { + monitor.warning( + String.format( + DataEncryptionExtension.EXTENSION_NAME + + ": Exception decrypting data using key from rotating key set. %s", + e.getMessage())); + throw new EdcException(e); + } } - } } diff --git a/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/encrypter/DataEncrypterFactory.java b/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/encrypter/DataEncrypterFactory.java index 916ab245f..c40e20b08 100644 --- a/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/encrypter/DataEncrypterFactory.java +++ b/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/encrypter/DataEncrypterFactory.java @@ -21,9 +21,6 @@ package org.eclipse.tractusx.edc.data.encryption.encrypter; -import static java.lang.String.format; - -import lombok.RequiredArgsConstructor; import org.eclipse.edc.connector.transfer.dataplane.spi.security.DataEncrypter; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.security.Vault; @@ -36,45 +33,52 @@ import org.eclipse.tractusx.edc.data.encryption.provider.CachingKeyProvider; import org.eclipse.tractusx.edc.data.encryption.provider.KeyProvider; -@RequiredArgsConstructor -public class DataEncrypterFactory { +import static java.lang.String.format; - public static final String AES_ALGORITHM = "AES"; - public static final String NONE = "NONE"; +public class DataEncrypterFactory { - private final Vault vault; - private final Monitor monitor; - private final CryptoKeyFactory keyFactory; + public static final String AES_ALGORITHM = "AES"; + public static final String NONE = "NONE"; - public DataEncrypter createNoneEncrypter() { - return new DataEncrypter() { - @Override - public String encrypt(String data) { - return data; - } + private final Vault vault; + private final Monitor monitor; + private final CryptoKeyFactory keyFactory; - @Override - public String decrypt(String data) { - return data; - } - }; - } + public DataEncrypterFactory(Vault vault, Monitor monitor, CryptoKeyFactory keyFactory) { + this.vault = vault; + this.monitor = monitor; + this.keyFactory = keyFactory; + } - public DataEncrypter createAesEncrypter(AesDataEncrypterConfiguration configuration) { - KeyProvider keyProvider = - new AesKeyProvider(vault, configuration.getKeySetAlias(), keyFactory); + public DataEncrypter createNoneEncrypter() { + return new DataEncrypter() { + @Override + public String encrypt(String data) { + return data; + } - if (configuration.isCachingEnabled()) { - keyProvider = new CachingKeyProvider<>(keyProvider, configuration.getCachingDuration()); + @Override + public String decrypt(String data) { + return data; + } + }; } + + public DataEncrypter createAesEncrypter(AesDataEncrypterConfiguration configuration) { + KeyProvider keyProvider = + new AesKeyProvider(vault, configuration.getKeySetAlias(), keyFactory); - final CryptoDataFactory cryptoDataFactory = new CryptoDataFactoryImpl(); - final AesAlgorithm algorithm = new AesAlgorithm(cryptoDataFactory); + if (configuration.isCachingEnabled()) { + keyProvider = new CachingKeyProvider<>(keyProvider, configuration.getCachingDuration()); + } - monitor.debug( - format( - "AES algorithm was initialised with SecureRandom algorithm '%s'", - algorithm.getAlgorithm())); - return new AesDataEncrypterImpl(algorithm, monitor, keyProvider, algorithm, cryptoDataFactory); - } + final CryptoDataFactory cryptoDataFactory = new CryptoDataFactoryImpl(); + final AesAlgorithm algorithm = new AesAlgorithm(cryptoDataFactory); + + monitor.debug( + format( + "AES algorithm was initialised with SecureRandom algorithm '%s'", + algorithm.getAlgorithm())); + return new AesDataEncrypterImpl(algorithm, monitor, keyProvider, algorithm, cryptoDataFactory); + } } diff --git a/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/key/CryptoKeyFactoryImpl.java b/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/key/CryptoKeyFactoryImpl.java index f3fa102a4..7a5b0fc15 100644 --- a/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/key/CryptoKeyFactoryImpl.java +++ b/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/key/CryptoKeyFactoryImpl.java @@ -19,7 +19,6 @@ */ package org.eclipse.tractusx.edc.data.encryption.key; -import lombok.Value; import org.bouncycastle.util.encoders.Base64; public class CryptoKeyFactoryImpl implements CryptoKeyFactory { @@ -37,9 +36,24 @@ public AesKey fromBytes(byte[] key) { return new AesKeyImpl(key, Base64.toBase64String(key)); } - @Value + private static class AesKeyImpl implements AesKey { - byte[] bytes; - String base64; + private final byte[] bytes; + private final String base64; + + private AesKeyImpl(byte[] bytes, String base64) { + this.bytes = bytes; + this.base64 = base64; + } + + @Override + public byte[] getBytes() { + return bytes; + } + + @Override + public String getBase64() { + return base64; + } } } diff --git a/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/provider/AesKeyProvider.java b/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/provider/AesKeyProvider.java index 82b8eccdd..e740a6f43 100644 --- a/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/provider/AesKeyProvider.java +++ b/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/provider/AesKeyProvider.java @@ -19,51 +19,56 @@ */ package org.eclipse.tractusx.edc.data.encryption.provider; -import java.util.Arrays; -import java.util.function.Predicate; -import java.util.stream.Stream; -import lombok.RequiredArgsConstructor; import org.bouncycastle.util.encoders.Base64; import org.eclipse.edc.spi.security.Vault; import org.eclipse.tractusx.edc.data.encryption.DataEncryptionExtension; import org.eclipse.tractusx.edc.data.encryption.key.AesKey; import org.eclipse.tractusx.edc.data.encryption.key.CryptoKeyFactory; -@RequiredArgsConstructor +import java.util.Arrays; +import java.util.function.Predicate; +import java.util.stream.Stream; + public class AesKeyProvider implements KeyProvider { - private static final String KEY_SEPARATOR = ","; + private static final String KEY_SEPARATOR = ","; + + private final Vault vault; + private final String vaultKeyAlias; + private final CryptoKeyFactory cryptoKeyFactory; - private final Vault vault; - private final String vaultKeyAlias; - private final CryptoKeyFactory cryptoKeyFactory; + public AesKeyProvider(Vault vault, String vaultKeyAlias, CryptoKeyFactory cryptoKeyFactory) { + this.vault = vault; + this.vaultKeyAlias = vaultKeyAlias; + this.cryptoKeyFactory = cryptoKeyFactory; + } - @Override - public Stream getDecryptionKeySet() { - return getKeysStream(); - } + @Override + public AesKey getEncryptionKey() { + return getKeysStream() + .findFirst() + .orElseThrow( + () -> + new RuntimeException( + DataEncryptionExtension.EXTENSION_NAME + + ": Vault must contain at least one key.")); + } - @Override - public AesKey getEncryptionKey() { - return getKeysStream() - .findFirst() - .orElseThrow( - () -> - new RuntimeException( - DataEncryptionExtension.EXTENSION_NAME - + ": Vault must contain at least one key.")); - } + @Override + public Stream getDecryptionKeySet() { + return getKeysStream(); + } - private Stream getKeysStream() { - return Arrays.stream(getKeys().split(KEY_SEPARATOR)) - .map(String::trim) - .filter(Predicate.not(String::isEmpty)) - .map(Base64::decode) - .map(cryptoKeyFactory::fromBytes); - } + private Stream getKeysStream() { + return Arrays.stream(getKeys().split(KEY_SEPARATOR)) + .map(String::trim) + .filter(Predicate.not(String::isEmpty)) + .map(Base64::decode) + .map(cryptoKeyFactory::fromBytes); + } - private String getKeys() { - String keys = vault.resolveSecret(vaultKeyAlias); - return keys == null ? "" : keys; - } + private String getKeys() { + String keys = vault.resolveSecret(vaultKeyAlias); + return keys == null ? "" : keys; + } } diff --git a/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/provider/CachingKeyProvider.java b/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/provider/CachingKeyProvider.java index b4b490918..4819b6386 100644 --- a/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/provider/CachingKeyProvider.java +++ b/edc-extensions/data-encryption/src/main/java/org/eclipse/tractusx/edc/data/encryption/provider/CachingKeyProvider.java @@ -20,60 +20,73 @@ package org.eclipse.tractusx.edc.data.encryption.provider; +import org.eclipse.tractusx.edc.data.encryption.key.CryptoKey; + import java.time.Clock; import java.time.Duration; import java.time.Instant; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; -import lombok.NonNull; -import lombok.Value; -import org.eclipse.tractusx.edc.data.encryption.key.CryptoKey; public class CachingKeyProvider implements KeyProvider { - @NonNull private final KeyProvider decoratedProvider; - @NonNull private final Clock clock; - @NonNull private final Duration cacheExpiration; - - private CachedKeys cachedKeys; - - public CachingKeyProvider(KeyProvider keyProvider, Duration cacheExpiration) { - this(keyProvider, cacheExpiration, Clock.systemUTC()); - } - - public CachingKeyProvider(KeyProvider keyProvider, Duration cacheExpiration, Clock clock) { - - this.decoratedProvider = keyProvider; - this.cacheExpiration = cacheExpiration; - this.clock = clock; - } - - @Override - public T getEncryptionKey() { - checkCache(); - return cachedKeys.getEncryptionKey(); - } - - @Override - public Stream getDecryptionKeySet() { - checkCache(); - return cachedKeys.getDecryptionKeys().stream(); - } - - private void checkCache() { - if (cachedKeys == null || cachedKeys.expiration.isBefore(clock.instant())) { - T encryptionKey = decoratedProvider.getEncryptionKey(); - List decryptionKeys = decoratedProvider.getDecryptionKeySet().collect(Collectors.toList()); - cachedKeys = - new CachedKeys<>(encryptionKey, decryptionKeys, clock.instant().plus(cacheExpiration)); + private final KeyProvider decoratedProvider; + private final Clock clock; + private final Duration cacheExpiration; + + private CachedKeys cachedKeys; + + public CachingKeyProvider(KeyProvider keyProvider, Duration cacheExpiration) { + this(keyProvider, cacheExpiration, Clock.systemUTC()); + } + + public CachingKeyProvider(KeyProvider keyProvider, Duration cacheExpiration, Clock clock) { + this.decoratedProvider = Objects.requireNonNull(keyProvider); + this.cacheExpiration = Objects.requireNonNull(cacheExpiration); + this.clock = Objects.requireNonNull(clock); + } + + @Override + public T getEncryptionKey() { + checkCache(); + return cachedKeys.getEncryptionKey(); + } + + @Override + public Stream getDecryptionKeySet() { + checkCache(); + return cachedKeys.getDecryptionKeys().stream(); + } + + private void checkCache() { + if (cachedKeys == null || cachedKeys.expiration.isBefore(clock.instant())) { + T encryptionKey = decoratedProvider.getEncryptionKey(); + List decryptionKeys = decoratedProvider.getDecryptionKeySet().collect(Collectors.toList()); + cachedKeys = + new CachedKeys<>(encryptionKey, decryptionKeys, clock.instant().plus(cacheExpiration)); + } + } + + + private static class CachedKeys { + private final T encryptionKey; + private final List decryptionKeys; + private final Instant expiration; + + private CachedKeys(T encryptionKey, List decryptionKeys, Instant expiration) { + this.encryptionKey = encryptionKey; + this.decryptionKeys = decryptionKeys; + this.expiration = Objects.requireNonNull(expiration); + } + + public List getDecryptionKeys() { + return decryptionKeys; + } + + public T getEncryptionKey() { + return encryptionKey; + } } - } - - @Value - private static class CachedKeys { - T encryptionKey; - List decryptionKeys; - @NonNull Instant expiration; - } } diff --git a/edc-extensions/data-encryption/src/test/java/org/eclipse/tractusx/edc/data/encryption/algorithms/aes/AesAlgorithmTest.java b/edc-extensions/data-encryption/src/test/java/org/eclipse/tractusx/edc/data/encryption/algorithms/aes/AesAlgorithmTest.java index 4d19927fb..d141887cf 100644 --- a/edc-extensions/data-encryption/src/test/java/org/eclipse/tractusx/edc/data/encryption/algorithms/aes/AesAlgorithmTest.java +++ b/edc-extensions/data-encryption/src/test/java/org/eclipse/tractusx/edc/data/encryption/algorithms/aes/AesAlgorithmTest.java @@ -19,7 +19,6 @@ */ package org.eclipse.tractusx.edc.data.encryption.algorithms.aes; -import lombok.SneakyThrows; import org.bouncycastle.util.encoders.Base64; import org.eclipse.tractusx.edc.data.encryption.data.CryptoDataFactory; import org.eclipse.tractusx.edc.data.encryption.data.CryptoDataFactoryImpl; @@ -31,62 +30,69 @@ class AesAlgorithmTest { - private static final byte[] KEY_128_BIT = Base64.decode("dVUjmYJzbwVcntkFZU+lNQ=="); - private static final byte[] KEY_196_BIT = Base64.decode("NcgHzzRTUC+z396tWG9hqIbeihujz0m8"); - private static final byte[] KEY_256_BIT = - Base64.decode("OSD+3NcZAmS/6UXbq6NL8UL+aQIAJDLL7BE2rBX5MtA="); + private static final byte[] KEY_128_BIT = Base64.decode("dVUjmYJzbwVcntkFZU+lNQ=="); + private static final byte[] KEY_196_BIT = Base64.decode("NcgHzzRTUC+z396tWG9hqIbeihujz0m8"); + private static final byte[] KEY_256_BIT = + Base64.decode("OSD+3NcZAmS/6UXbq6NL8UL+aQIAJDLL7BE2rBX5MtA="); - private final AesAlgorithm strategy = new AesAlgorithm(new CryptoDataFactoryImpl()); - private final CryptoDataFactory cryptoDataFactory = new CryptoDataFactoryImpl(); + private final AesAlgorithm strategy = new AesAlgorithm(new CryptoDataFactoryImpl()); + private final CryptoDataFactory cryptoDataFactory = new CryptoDataFactoryImpl(); - @Test - void test128BitKey() { - testKey(KEY_128_BIT); - } + @Test + void test128BitKey() { + testKey(KEY_128_BIT); + } - @Test - void test196BitKey() { - testKey(KEY_196_BIT); - } + @Test + void test196BitKey() { + testKey(KEY_196_BIT); + } - @Test - void test256BitKey() { - testKey(KEY_256_BIT); - } + @Test + void test256BitKey() { + testKey(KEY_256_BIT); + } - @Test - @SneakyThrows - void testSameDataEncryptedDifferently() { - final AesKey aesKey = createKey(KEY_128_BIT); - final DecryptedData expected = cryptoDataFactory.decryptedFromText("same data"); - final EncryptedData result1 = strategy.encrypt(expected, aesKey); - final EncryptedData result2 = strategy.encrypt(expected, aesKey); + @Test + void testSameDataEncryptedDifferently() { + final AesKey aesKey = createKey(KEY_128_BIT); + final DecryptedData expected = cryptoDataFactory.decryptedFromText("same data"); - Assertions.assertNotEquals(result1.getBase64(), result2.getBase64()); - } + try { + final EncryptedData result1 = strategy.encrypt(expected, aesKey); + final EncryptedData result2 = strategy.encrypt(expected, aesKey); - @SneakyThrows - void testKey(byte[] key) { - final AesKey aesKey = createKey(key); - final DecryptedData expected = cryptoDataFactory.decryptedFromText("I will be encrypted"); - final EncryptedData encryptedResult = strategy.encrypt(expected, aesKey); - final DecryptedData result = strategy.decrypt(encryptedResult, aesKey); + Assertions.assertNotEquals(result1.getBase64(), result2.getBase64()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } - Assertions.assertEquals(expected.getBase64(), result.getBase64()); - } - AesKey createKey(byte[] key) { - return new AesKey() { + void testKey(byte[] key) { + final AesKey aesKey = createKey(key); + final DecryptedData expected = cryptoDataFactory.decryptedFromText("I will be encrypted"); + try { + final EncryptedData encryptedResult = strategy.encrypt(expected, aesKey); + final DecryptedData result = strategy.decrypt(encryptedResult, aesKey); + Assertions.assertEquals(expected.getBase64(), result.getBase64()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } - @Override - public byte[] getBytes() { - return key; - } + AesKey createKey(byte[] key) { + return new AesKey() { - @Override - public String getBase64() { - return Base64.toBase64String(key); - } - }; - } + @Override + public byte[] getBytes() { + return key; + } + + @Override + public String getBase64() { + return Base64.toBase64String(key); + } + }; + } } diff --git a/edc-extensions/data-encryption/src/test/java/org/eclipse/tractusx/edc/data/encryption/algorithms/aes/AesInitializationVectorIteratorTest.java b/edc-extensions/data-encryption/src/test/java/org/eclipse/tractusx/edc/data/encryption/algorithms/aes/AesInitializationVectorIteratorTest.java index ceebf50d6..f70a3bf70 100644 --- a/edc-extensions/data-encryption/src/test/java/org/eclipse/tractusx/edc/data/encryption/algorithms/aes/AesInitializationVectorIteratorTest.java +++ b/edc-extensions/data-encryption/src/test/java/org/eclipse/tractusx/edc/data/encryption/algorithms/aes/AesInitializationVectorIteratorTest.java @@ -20,61 +20,57 @@ */ package org.eclipse.tractusx.edc.data.encryption.algorithms.aes; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.List; -import java.util.NoSuchElementException; -import lombok.SneakyThrows; import org.eclipse.tractusx.edc.data.encryption.util.ArrayUtil; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; + class AesInitializationVectorIteratorTest { - @Test - @SneakyThrows - void testDistinctVectors() { - final int vectorCount = 100; - final SecureRandom secureRandom = new SecureRandom(); - AesInitializationVectorIterator iterator = new AesInitializationVectorIterator(secureRandom); + @Test + void testDistinctVectors() { + final int vectorCount = 100; + final SecureRandom secureRandom = new SecureRandom(); + AesInitializationVectorIterator iterator = new AesInitializationVectorIterator(secureRandom); - List vectors = new ArrayList<>(); - for (var i = 0; i < vectorCount; i++) { - vectors.add(iterator.next()); - } + List vectors = new ArrayList<>(); + for (var i = 0; i < vectorCount; i++) { + vectors.add(iterator.next()); + } - long distinctVectors = vectors.stream().map(ArrayUtil::byteArrayToHex).distinct().count(); - Assertions.assertEquals(vectorCount, distinctVectors); - } + long distinctVectors = vectors.stream().map(ArrayUtil::byteArrayToHex).distinct().count(); + Assertions.assertEquals(vectorCount, distinctVectors); + } - @Test - @SneakyThrows - void testHasNextTrueOnCounterContinuing() { - ByteCounter counter = Mockito.mock(ByteCounter.class); - AesInitializationVectorIterator iterator = new AesInitializationVectorIterator(counter); + @Test + void testHasNextTrueOnCounterContinuing() { + ByteCounter counter = Mockito.mock(ByteCounter.class); + AesInitializationVectorIterator iterator = new AesInitializationVectorIterator(counter); - Mockito.when(counter.isMaxed()).thenReturn(false); - Assertions.assertTrue(iterator.hasNext()); - } + Mockito.when(counter.isMaxed()).thenReturn(false); + Assertions.assertTrue(iterator.hasNext()); + } - @Test - @SneakyThrows - void testHasNextFalseOnCounterEnd() { - ByteCounter counter = Mockito.mock(ByteCounter.class); - AesInitializationVectorIterator iterator = new AesInitializationVectorIterator(counter); + @Test + void testHasNextFalseOnCounterEnd() { + ByteCounter counter = Mockito.mock(ByteCounter.class); + AesInitializationVectorIterator iterator = new AesInitializationVectorIterator(counter); - Mockito.when(counter.isMaxed()).thenReturn(true); - Assertions.assertFalse(iterator.hasNext()); - } + Mockito.when(counter.isMaxed()).thenReturn(true); + Assertions.assertFalse(iterator.hasNext()); + } - @Test - @SneakyThrows - void testNoSuchElementExceptionOnCounterEnd() { - ByteCounter counter = Mockito.mock(ByteCounter.class); - AesInitializationVectorIterator iterator = new AesInitializationVectorIterator(counter); + @Test + void testNoSuchElementExceptionOnCounterEnd() { + ByteCounter counter = Mockito.mock(ByteCounter.class); + AesInitializationVectorIterator iterator = new AesInitializationVectorIterator(counter); - Mockito.when(counter.isMaxed()).thenReturn(true); - Assertions.assertThrows(NoSuchElementException.class, iterator::next); - } + Mockito.when(counter.isMaxed()).thenReturn(true); + Assertions.assertThrows(NoSuchElementException.class, iterator::next); + } } diff --git a/edc-extensions/data-encryption/src/test/java/org/eclipse/tractusx/edc/data/encryption/encrypter/DataEncrypterAesComponentTest.java b/edc-extensions/data-encryption/src/test/java/org/eclipse/tractusx/edc/data/encryption/encrypter/DataEncrypterAesComponentTest.java index aa9140629..6dcd103cb 100644 --- a/edc-extensions/data-encryption/src/test/java/org/eclipse/tractusx/edc/data/encryption/encrypter/DataEncrypterAesComponentTest.java +++ b/edc-extensions/data-encryption/src/test/java/org/eclipse/tractusx/edc/data/encryption/encrypter/DataEncrypterAesComponentTest.java @@ -19,7 +19,6 @@ */ package org.eclipse.tractusx.edc.data.encryption.encrypter; -import lombok.SneakyThrows; import org.eclipse.edc.connector.transfer.dataplane.spi.security.DataEncrypter; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.security.Vault; @@ -42,64 +41,68 @@ @SuppressWarnings("FieldCanBeLocal") class DataEncrypterAesComponentTest { - private static final String KEY_128_BIT_BASE_64 = "7h6sh6t6tchCmNnHjK2kFA=="; - private static final String KEY_256_BIT_BASE_64 = "OSD+3NcZAmS/6UXbq6NL8UL+aQIAJDLL7BE2rBX5MtA="; - - private DataEncrypter dataEncrypter; - private CryptoAlgorithm algorithm; - private KeyProvider keyProvider; - private CryptoKeyFactory cryptoKeyFactory; - private CryptoDataFactory cryptoDataFactory; - - // mocks - private Monitor monitor; - private Vault vault; - - @BeforeEach - void setup() { - monitor = Mockito.mock(Monitor.class); - vault = Mockito.mock(Vault.class); - - cryptoKeyFactory = new CryptoKeyFactoryImpl(); - cryptoDataFactory = new CryptoDataFactoryImpl(); - algorithm = new AesAlgorithm(cryptoDataFactory); - keyProvider = new AesKeyProvider(vault, "foo", cryptoKeyFactory); - - dataEncrypter = - new AesDataEncrypterImpl(algorithm, monitor, keyProvider, algorithm, cryptoDataFactory); - } - - @Test - @SneakyThrows - void testKeyRotation() { - Mockito.when(vault.resolveSecret(Mockito.anyString())) - .thenReturn( - String.format( - "%s, %s, %s, %s", - KEY_128_BIT_BASE_64, - KEY_128_BIT_BASE_64, - KEY_128_BIT_BASE_64, - KEY_256_BIT_BASE_64)); - - final AesKey key256Bit = cryptoKeyFactory.fromBase64(KEY_256_BIT_BASE_64); - final String expectedResult = "hello"; - final DecryptedData decryptedResult = cryptoDataFactory.decryptedFromText(expectedResult); - final EncryptedData encryptedResult = algorithm.encrypt(decryptedResult, key256Bit); - - var result = dataEncrypter.decrypt(encryptedResult.getBase64()); - - Assertions.assertEquals(expectedResult, result); - } - - @Test - void testEncryption() { - Mockito.when(vault.resolveSecret(Mockito.anyString())).thenReturn(KEY_128_BIT_BASE_64); - - final String expectedResult = "hello world!"; - - var encryptedResult = dataEncrypter.encrypt(expectedResult); - var result = dataEncrypter.decrypt(encryptedResult); - - Assertions.assertEquals(expectedResult, result); - } + private static final String KEY_128_BIT_BASE_64 = "7h6sh6t6tchCmNnHjK2kFA=="; + private static final String KEY_256_BIT_BASE_64 = "OSD+3NcZAmS/6UXbq6NL8UL+aQIAJDLL7BE2rBX5MtA="; + + private DataEncrypter dataEncrypter; + private CryptoAlgorithm algorithm; + private KeyProvider keyProvider; + private CryptoKeyFactory cryptoKeyFactory; + private CryptoDataFactory cryptoDataFactory; + + // mocks + private Monitor monitor; + private Vault vault; + + @BeforeEach + void setup() { + monitor = Mockito.mock(Monitor.class); + vault = Mockito.mock(Vault.class); + + cryptoKeyFactory = new CryptoKeyFactoryImpl(); + cryptoDataFactory = new CryptoDataFactoryImpl(); + algorithm = new AesAlgorithm(cryptoDataFactory); + keyProvider = new AesKeyProvider(vault, "foo", cryptoKeyFactory); + + dataEncrypter = + new AesDataEncrypterImpl(algorithm, monitor, keyProvider, algorithm, cryptoDataFactory); + } + + @Test + void testKeyRotation() { + Mockito.when(vault.resolveSecret(Mockito.anyString())) + .thenReturn( + String.format( + "%s, %s, %s, %s", + KEY_128_BIT_BASE_64, + KEY_128_BIT_BASE_64, + KEY_128_BIT_BASE_64, + KEY_256_BIT_BASE_64)); + + final AesKey key256Bit = cryptoKeyFactory.fromBase64(KEY_256_BIT_BASE_64); + final String expectedResult = "hello"; + final DecryptedData decryptedResult = cryptoDataFactory.decryptedFromText(expectedResult); + + try { + final EncryptedData encryptedResult = algorithm.encrypt(decryptedResult, key256Bit); + + var result = dataEncrypter.decrypt(encryptedResult.getBase64()); + + Assertions.assertEquals(expectedResult, result); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + void testEncryption() { + Mockito.when(vault.resolveSecret(Mockito.anyString())).thenReturn(KEY_128_BIT_BASE_64); + + final String expectedResult = "hello world!"; + + var encryptedResult = dataEncrypter.encrypt(expectedResult); + var result = dataEncrypter.decrypt(encryptedResult); + + Assertions.assertEquals(expectedResult, result); + } } diff --git a/edc-extensions/dataplane-selector-configuration/build.gradle.kts b/edc-extensions/dataplane-selector-configuration/build.gradle.kts index 70714bd52..ba7eff87d 100644 --- a/edc-extensions/dataplane-selector-configuration/build.gradle.kts +++ b/edc-extensions/dataplane-selector-configuration/build.gradle.kts @@ -1,3 +1,21 @@ +/* + * 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 + */ plugins { `maven-publish` diff --git a/edc-extensions/hashicorp-vault/build.gradle.kts b/edc-extensions/hashicorp-vault/build.gradle.kts index 90758edd0..20d5c0aa4 100644 --- a/edc-extensions/hashicorp-vault/build.gradle.kts +++ b/edc-extensions/hashicorp-vault/build.gradle.kts @@ -1,3 +1,21 @@ +/* + * 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 + */ plugins { `maven-publish` @@ -9,7 +27,7 @@ dependencies { implementation(edc.junit) implementation(libs.bouncyCastle.bcpkix) implementation(libs.okhttp) - implementation("org.testcontainers:junit-jupiter:1.17.6") - implementation("org.testcontainers:vault:1.17.6") + implementation("org.testcontainers:vault:1.18.1") + implementation("org.testcontainers:junit-jupiter:1.18.1") testImplementation(libs.mockito.inline) } diff --git a/edc-extensions/observability-api-customization/build.gradle.kts b/edc-extensions/observability-api-customization/build.gradle.kts index 28558e336..33f1920d1 100644 --- a/edc-extensions/observability-api-customization/build.gradle.kts +++ b/edc-extensions/observability-api-customization/build.gradle.kts @@ -1,3 +1,22 @@ +/* + * 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 + */ + plugins { `maven-publish` `java-library` diff --git a/edc-extensions/postgresql-migration/build.gradle.kts b/edc-extensions/postgresql-migration/build.gradle.kts index 8d7b1fa05..5b90d57df 100644 --- a/edc-extensions/postgresql-migration/build.gradle.kts +++ b/edc-extensions/postgresql-migration/build.gradle.kts @@ -1,3 +1,21 @@ +/* + * 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 + */ plugins { `maven-publish` @@ -11,5 +29,5 @@ dependencies { implementation(edc.sql.assetindex) implementation(edc.sql.core) - implementation("org.flywaydb:flyway-core:9.15.2") + implementation("org.flywaydb:flyway-core:9.18.0") } diff --git a/edc-extensions/postgresql-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/CpAdapterPostgresqlMigrationExtension.java b/edc-extensions/postgresql-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/CpAdapterPostgresqlMigrationExtension.java index 63e43c1e7..15c3e710d 100644 --- a/edc-extensions/postgresql-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/CpAdapterPostgresqlMigrationExtension.java +++ b/edc-extensions/postgresql-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/CpAdapterPostgresqlMigrationExtension.java @@ -1,16 +1,35 @@ +/* + * 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 + */ + package org.eclipse.tractusx.edc.postgresql.migration; public class CpAdapterPostgresqlMigrationExtension extends AbstractPostgresqlMigrationExtension { - private static final String NAME_SUBSYSTEM = "cpadapter"; - private static final String DATASOURCE_SETTING_NAME = "edc.datasource.cpadapter.name"; + private static final String NAME_SUBSYSTEM = "cpadapter"; + private static final String DATASOURCE_SETTING_NAME = "edc.datasource.cpadapter.name"; - @Override - protected String getDataSourceNameConfigurationKey() { - return DATASOURCE_SETTING_NAME; - } + @Override + protected String getDataSourceNameConfigurationKey() { + return DATASOURCE_SETTING_NAME; + } - @Override - protected String getSubsystemName() { - return NAME_SUBSYSTEM; - } + @Override + protected String getSubsystemName() { + return NAME_SUBSYSTEM; + } } diff --git a/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/transferprocess/V0_0_7__Default_Value_For_Properties.sql b/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/transferprocess/V0_0_7__Default_Value_For_Properties.sql new file mode 100644 index 000000000..44bcc7e59 --- /dev/null +++ b/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/transferprocess/V0_0_7__Default_Value_For_Properties.sql @@ -0,0 +1,15 @@ +-- +-- Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH +-- +-- 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 +-- +-- SPDX-License-Identifier: Apache-2.0 +-- +-- Contributors: +-- Mercedes-Benz Tech Innovation GmbH - Rewrite to be SQL Init Schema +-- + +-- set default value +ALTER TABLE edc_transfer_process ALTER COLUMN transferprocess_properties SET DEFAULT '{}'; diff --git a/edc-extensions/provision-additional-headers/build.gradle.kts b/edc-extensions/provision-additional-headers/build.gradle.kts index 191ada758..1ac640a88 100644 --- a/edc-extensions/provision-additional-headers/build.gradle.kts +++ b/edc-extensions/provision-additional-headers/build.gradle.kts @@ -1,3 +1,21 @@ +/* + * 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 + */ plugins { `maven-publish` diff --git a/edc-extensions/transferprocess-sftp-client/build.gradle.kts b/edc-extensions/transferprocess-sftp-client/build.gradle.kts index 550ed105a..67ae01bfb 100644 --- a/edc-extensions/transferprocess-sftp-client/build.gradle.kts +++ b/edc-extensions/transferprocess-sftp-client/build.gradle.kts @@ -1,3 +1,21 @@ +/* + * 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 + */ plugins { `maven-publish` diff --git a/edc-extensions/transferprocess-sftp-client/src/test/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/AbstractSftpClientWrapperIT.java b/edc-extensions/transferprocess-sftp-client/src/test/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/AbstractSftpClientWrapperIT.java index 757319606..d1d1c1ce5 100644 --- a/edc-extensions/transferprocess-sftp-client/src/test/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/AbstractSftpClientWrapperIT.java +++ b/edc-extensions/transferprocess-sftp-client/src/test/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/AbstractSftpClientWrapperIT.java @@ -1,7 +1,42 @@ +/* + * 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 + */ + + package org.eclipse.tractusx.edc.transferprocess.sftp.client; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.testcontainers.shaded.org.awaitility.Awaitility.await; +import lombok.NoArgsConstructor; +import lombok.SneakyThrows; +import org.bouncycastle.crypto.params.RSAKeyParameters; +import org.bouncycastle.crypto.util.OpenSSHPublicKeyUtil; +import org.eclipse.tractusx.edc.transferprocess.sftp.common.SftpLocation; +import org.eclipse.tractusx.edc.transferprocess.sftp.common.SftpUser; +import org.junit.ClassRule; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.shaded.org.apache.commons.io.IOUtils; +import org.testcontainers.shaded.org.apache.commons.lang3.RandomUtils; +import org.testcontainers.utility.DockerImageName; import java.io.File; import java.io.FileOutputStream; @@ -20,211 +55,199 @@ import java.util.Map; import java.util.Set; import java.util.stream.Stream; -import lombok.NoArgsConstructor; -import lombok.SneakyThrows; -import org.bouncycastle.crypto.params.RSAKeyParameters; -import org.bouncycastle.crypto.util.OpenSSHPublicKeyUtil; -import org.eclipse.tractusx.edc.transferprocess.sftp.common.SftpLocation; -import org.eclipse.tractusx.edc.transferprocess.sftp.common.SftpUser; -import org.junit.ClassRule; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.ArgumentsProvider; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.wait.strategy.Wait; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.shaded.org.apache.commons.io.IOUtils; -import org.testcontainers.shaded.org.apache.commons.lang3.RandomUtils; -import org.testcontainers.utility.DockerImageName; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.testcontainers.shaded.org.awaitility.Awaitility.await; abstract class AbstractSftpClientWrapperIT { - static final String DOCKER_IMAGE_NAME = "atmoz/sftp:alpine-3.6"; - static final String sftpPathPrefix = "transfer"; - static final Map DOCKER_ENV = - Map.of("SFTP_USERS", String.format("user:password:::%s", sftpPathPrefix)); - static final Path dockerVolumeDirectory; - static final Path remotePasswordUploadDirectory; - static final Path remotePasswordDownloadDirectory; - static final Path remoteKeypairUploadDirectory; - static final Path remoteKeypairDownloadDirectory; - static final Path localUploadAndGeneratorDirectory; - static final Path keyDirectory; - static final Path publicKeyPath; - static final KeyPair keyPair; - @Container @ClassRule private static final GenericContainer sftpContainer; - - static { - keyPair = generateKeyPair(); - - try { - Set fullPermission = new HashSet(); - fullPermission.add(PosixFilePermission.OWNER_EXECUTE); - fullPermission.add(PosixFilePermission.OWNER_READ); - fullPermission.add(PosixFilePermission.OWNER_WRITE); - fullPermission.add(PosixFilePermission.GROUP_EXECUTE); - fullPermission.add(PosixFilePermission.GROUP_READ); - fullPermission.add(PosixFilePermission.GROUP_WRITE); - fullPermission.add(PosixFilePermission.OTHERS_EXECUTE); - fullPermission.add(PosixFilePermission.OTHERS_READ); - fullPermission.add(PosixFilePermission.OTHERS_WRITE); - - dockerVolumeDirectory = Files.createTempDirectory(SftpClientWrapperIT.class.getName()); - localUploadAndGeneratorDirectory = - Files.createTempDirectory(SftpClientWrapperIT.class.getName()); - remotePasswordUploadDirectory = - Files.createDirectory(dockerVolumeDirectory.resolve("passwordUpload")); - remotePasswordDownloadDirectory = - Files.createDirectory(dockerVolumeDirectory.resolve("passwordDownload")); - remoteKeypairUploadDirectory = - Files.createDirectory(dockerVolumeDirectory.resolve("keypairUpload")); - remoteKeypairDownloadDirectory = - Files.createDirectory(dockerVolumeDirectory.resolve("keypairDownload")); - keyDirectory = Files.createTempDirectory(SftpClientWrapperIT.class.getName()); - publicKeyPath = keyDirectory.resolve("public"); - - try (final OutputStreamWriter fileWriter = - new OutputStreamWriter(new FileOutputStream(publicKeyPath.toString()))) { - final RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); - final RSAKeyParameters publicKeyParameters = - new RSAKeyParameters(false, publicKey.getModulus(), publicKey.getPublicExponent()); - byte[] encodedKey = OpenSSHPublicKeyUtil.encodePublicKey(publicKeyParameters); - String keyString = Base64.getEncoder().encodeToString(encodedKey); - String authKeysEntry = String.format("ssh-rsa %s", keyString); - fileWriter.write(authKeysEntry); - } - - Files.setPosixFilePermissions(dockerVolumeDirectory, fullPermission); - Files.setPosixFilePermissions(remotePasswordUploadDirectory, fullPermission); - Files.setPosixFilePermissions(remotePasswordDownloadDirectory, fullPermission); - Files.setPosixFilePermissions(remoteKeypairUploadDirectory, fullPermission); - Files.setPosixFilePermissions(remoteKeypairDownloadDirectory, fullPermission); - Files.setPosixFilePermissions(keyDirectory, fullPermission); - } catch (IOException e) { - throw new RuntimeException(); - } + static final String DOCKER_IMAGE_NAME = "atmoz/sftp:alpine-3.6"; + static final String sftpPathPrefix = "transfer"; + static final Map DOCKER_ENV = + Map.of("SFTP_USERS", String.format("user:password:::%s", sftpPathPrefix)); + static final Path dockerVolumeDirectory; + static final Path remotePasswordUploadDirectory; + static final Path remotePasswordDownloadDirectory; + static final Path remoteKeypairUploadDirectory; + static final Path remoteKeypairDownloadDirectory; + static final Path localUploadAndGeneratorDirectory; + static final Path keyDirectory; + static final Path publicKeyPath; + static final KeyPair keyPair; + @Container + @ClassRule + private static final GenericContainer sftpContainer; + + static { + keyPair = generateKeyPair(); + + try { + Set fullPermission = new HashSet(); + fullPermission.add(PosixFilePermission.OWNER_EXECUTE); + fullPermission.add(PosixFilePermission.OWNER_READ); + fullPermission.add(PosixFilePermission.OWNER_WRITE); + fullPermission.add(PosixFilePermission.GROUP_EXECUTE); + fullPermission.add(PosixFilePermission.GROUP_READ); + fullPermission.add(PosixFilePermission.GROUP_WRITE); + fullPermission.add(PosixFilePermission.OTHERS_EXECUTE); + fullPermission.add(PosixFilePermission.OTHERS_READ); + fullPermission.add(PosixFilePermission.OTHERS_WRITE); + + dockerVolumeDirectory = Files.createTempDirectory(SftpClientWrapperIT.class.getName()); + localUploadAndGeneratorDirectory = + Files.createTempDirectory(SftpClientWrapperIT.class.getName()); + remotePasswordUploadDirectory = + Files.createDirectory(dockerVolumeDirectory.resolve("passwordUpload")); + remotePasswordDownloadDirectory = + Files.createDirectory(dockerVolumeDirectory.resolve("passwordDownload")); + remoteKeypairUploadDirectory = + Files.createDirectory(dockerVolumeDirectory.resolve("keypairUpload")); + remoteKeypairDownloadDirectory = + Files.createDirectory(dockerVolumeDirectory.resolve("keypairDownload")); + keyDirectory = Files.createTempDirectory(SftpClientWrapperIT.class.getName()); + publicKeyPath = keyDirectory.resolve("public"); + + try (final OutputStreamWriter fileWriter = + new OutputStreamWriter(new FileOutputStream(publicKeyPath.toString()))) { + final RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + final RSAKeyParameters publicKeyParameters = + new RSAKeyParameters(false, publicKey.getModulus(), publicKey.getPublicExponent()); + byte[] encodedKey = OpenSSHPublicKeyUtil.encodePublicKey(publicKeyParameters); + String keyString = Base64.getEncoder().encodeToString(encodedKey); + String authKeysEntry = String.format("ssh-rsa %s", keyString); + fileWriter.write(authKeysEntry); + } + + Files.setPosixFilePermissions(dockerVolumeDirectory, fullPermission); + Files.setPosixFilePermissions(remotePasswordUploadDirectory, fullPermission); + Files.setPosixFilePermissions(remotePasswordDownloadDirectory, fullPermission); + Files.setPosixFilePermissions(remoteKeypairUploadDirectory, fullPermission); + Files.setPosixFilePermissions(remoteKeypairDownloadDirectory, fullPermission); + Files.setPosixFilePermissions(keyDirectory, fullPermission); + } catch (IOException e) { + throw new RuntimeException(); + } - sftpContainer = - new GenericContainer<>(DockerImageName.parse(DOCKER_IMAGE_NAME)) - .withEnv(DOCKER_ENV) - .withExposedPorts(22) - .waitingFor(Wait.forListeningPort()) - .withFileSystemBind( - dockerVolumeDirectory.toAbsolutePath().toString(), - String.format("/home/user/%s", sftpPathPrefix)) - .withFileSystemBind(keyDirectory.toAbsolutePath().toString(), "/home/user/keys"); - sftpContainer.start(); - - await().atMost(10, SECONDS).until(sftpContainer::isRunning); - - try { - sftpContainer.execInContainer("mkdir", "-p", "/home/user/.ssh"); - sftpContainer.execInContainer("chmod", "700", "/home/user/.ssh"); - sftpContainer.execInContainer("chown", "user", "/home/user/.ssh/"); - sftpContainer.execInContainer( - "cp", "-f", "/home/user/keys/public", "/home/user/.ssh/authorized_keys"); - sftpContainer.execInContainer("chown", "user", "/home/user/.ssh/authorized_keys"); - sftpContainer.execInContainer("chmod", "600", "/home/user/.ssh/authorized_keys"); - } catch (IOException | InterruptedException e) { - throw new RuntimeException(); - } - } - - @AfterAll - @SneakyThrows - static void tearDown() { - if (Files.exists(dockerVolumeDirectory)) { - Files.walk(dockerVolumeDirectory) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); - } - if (Files.exists(localUploadAndGeneratorDirectory)) { - Files.walk(localUploadAndGeneratorDirectory) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); + sftpContainer = + new GenericContainer<>(DockerImageName.parse(DOCKER_IMAGE_NAME)) + .withEnv(DOCKER_ENV) + .withExposedPorts(22) + .waitingFor(Wait.forListeningPort()) + .withFileSystemBind( + dockerVolumeDirectory.toAbsolutePath().toString(), + String.format("/home/user/%s", sftpPathPrefix)) + .withFileSystemBind(keyDirectory.toAbsolutePath().toString(), "/home/user/keys"); + sftpContainer.start(); + + await().atMost(10, SECONDS).until(sftpContainer::isRunning); + + try { + sftpContainer.execInContainer("mkdir", "-p", "/home/user/.ssh"); + sftpContainer.execInContainer("chmod", "700", "/home/user/.ssh"); + sftpContainer.execInContainer("chown", "user", "/home/user/.ssh/"); + sftpContainer.execInContainer( + "cp", "-f", "/home/user/keys/public", "/home/user/.ssh/authorized_keys"); + sftpContainer.execInContainer("chown", "user", "/home/user/.ssh/authorized_keys"); + sftpContainer.execInContainer("chmod", "600", "/home/user/.ssh/authorized_keys"); + } catch (IOException | InterruptedException e) { + throw new RuntimeException(); + } } - if (Files.exists(keyDirectory)) { - Files.walk(keyDirectory) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); + + @AfterAll + @SneakyThrows + static void tearDown() { + if (Files.exists(dockerVolumeDirectory)) { + Files.walk(dockerVolumeDirectory) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } + if (Files.exists(localUploadAndGeneratorDirectory)) { + Files.walk(localUploadAndGeneratorDirectory) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } + if (Files.exists(keyDirectory)) { + Files.walk(keyDirectory) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } } - } - - protected SftpUser getPasswordUser() { - return SftpUser.builder().name("user").password("password").build(); - } - - protected SftpUser getKeyPairUser() { - return SftpUser.builder().name("user").keyPair(keyPair).build(); - } - - protected SftpLocation getSftpLocation(String path) { - return SftpLocation.builder() - .host("127.0.0.1") - .port(sftpContainer.getFirstMappedPort()) - .path(path) - .build(); - } - - protected SftpClientWrapper getSftpClient(SftpLocation location, SftpUser sftpUser) { - SftpClientConfig config = - SftpClientConfig.builder() - .sftpLocation(location) - .sftpUser(sftpUser) - .hostVerification(false) - .build(); - return new SftpClientWrapperImpl(config); - } - - @NoArgsConstructor - protected static class FilesProvider implements ArgumentsProvider { - @Override - public Stream provideArguments(ExtensionContext context) { - return Stream.of( - Arguments.of(get1KBFile()), Arguments.of(get1MBFile()), Arguments.of(get2MBFile())); + + @SneakyThrows + private static KeyPair generateKeyPair() { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + return keyPairGenerator.generateKeyPair(); } - private static final int BUFFER_SIZE = 4 * 1024 * 1024; + protected SftpUser getPasswordUser() { + return SftpUser.builder().name("user").password("password").build(); + } - public File get1KBFile() { - return generateFile(1 * 1024); + protected SftpUser getKeyPairUser() { + return SftpUser.builder().name("user").keyPair(keyPair).build(); } - public File get1MBFile() { - return generateFile(1 * 1024 * 1024); + protected SftpLocation getSftpLocation(String path) { + return SftpLocation.builder() + .host("127.0.0.1") + .port(sftpContainer.getFirstMappedPort()) + .path(path) + .build(); } - public File get2MBFile() { - return generateFile(2 * 1024 * 1024); + protected SftpClientWrapper getSftpClient(SftpLocation location, SftpUser sftpUser) { + SftpClientConfig config = + SftpClientConfig.builder() + .sftpLocation(location) + .sftpUser(sftpUser) + .hostVerification(false) + .build(); + return new SftpClientWrapperImpl(config); } - @SneakyThrows - private File generateFile(final int byteSize) { - Path path = localUploadAndGeneratorDirectory.resolve(String.format("%s.bin", byteSize)); - if (!Files.exists(path)) { - Files.createFile(path); - try (final OutputStream outputStream = Files.newOutputStream(path)) { - int bufferSize; - int remaining = byteSize; - do { - bufferSize = Math.min(remaining, BUFFER_SIZE); - byte[] chunk = RandomUtils.nextBytes(bufferSize); - IOUtils.write(chunk, outputStream); - remaining = remaining - bufferSize; - } while (remaining > 0); + @NoArgsConstructor + protected static class FilesProvider implements ArgumentsProvider { + private static final int BUFFER_SIZE = 4 * 1024 * 1024; + + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of( + Arguments.of(get1KBFile()), Arguments.of(get1MBFile()), Arguments.of(get2MBFile())); + } + + public File get1KBFile() { + return generateFile(1024); + } + + public File get1MBFile() { + return generateFile(1024 * 1024); + } + + public File get2MBFile() { + return generateFile(2 * 1024 * 1024); + } + + @SneakyThrows + private File generateFile(final int byteSize) { + Path path = localUploadAndGeneratorDirectory.resolve(String.format("%s.bin", byteSize)); + if (!Files.exists(path)) { + Files.createFile(path); + try (final OutputStream outputStream = Files.newOutputStream(path)) { + int bufferSize; + int remaining = byteSize; + do { + bufferSize = Math.min(remaining, BUFFER_SIZE); + byte[] chunk = RandomUtils.nextBytes(bufferSize); + IOUtils.write(chunk, outputStream); + remaining = remaining - bufferSize; + } while (remaining > 0); + } + } + return path.toFile(); } - } - return path.toFile(); } - } - - @SneakyThrows - private static KeyPair generateKeyPair() { - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); - keyPairGenerator.initialize(2048); - return keyPairGenerator.generateKeyPair(); - } } diff --git a/edc-extensions/transferprocess-sftp-common/build.gradle.kts b/edc-extensions/transferprocess-sftp-common/build.gradle.kts index 1226ccce1..b102c23c1 100644 --- a/edc-extensions/transferprocess-sftp-common/build.gradle.kts +++ b/edc-extensions/transferprocess-sftp-common/build.gradle.kts @@ -1,3 +1,21 @@ +/* + * 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 + */ plugins { `maven-publish` @@ -9,4 +27,5 @@ dependencies { testImplementation(edc.junit) testImplementation(libs.mockito.inline) - testImplementation(libs.testcontainers.junit)} + testImplementation(libs.testcontainers.junit) +} diff --git a/edc-extensions/transferprocess-sftp-provisioner/build.gradle.kts b/edc-extensions/transferprocess-sftp-provisioner/build.gradle.kts index 243af81d9..bf239f371 100644 --- a/edc-extensions/transferprocess-sftp-provisioner/build.gradle.kts +++ b/edc-extensions/transferprocess-sftp-provisioner/build.gradle.kts @@ -1,3 +1,21 @@ +/* + * 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 + */ plugins { `maven-publish` diff --git a/edc-tests/cucumber/build.gradle.kts b/edc-tests/cucumber/build.gradle.kts index 1000005de..d364320b3 100644 --- a/edc-tests/cucumber/build.gradle.kts +++ b/edc-tests/cucumber/build.gradle.kts @@ -1,3 +1,22 @@ +/* + * 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 + */ + plugins { `java-library` } @@ -16,12 +35,12 @@ 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.1") - testImplementation("io.cucumber:cucumber-junit-platform-engine:7.11.1") - testImplementation("org.slf4j:slf4j-api:2.0.3") + testImplementation("org.junit.platform:junit-platform-suite:1.9.3") + testImplementation("io.cucumber:cucumber-java:7.12.0") + testImplementation("io.cucumber:cucumber-junit-platform-engine:7.12.0") + testImplementation("org.slf4j:slf4j-api:2.0.7") testImplementation(libs.restAssured) testImplementation(libs.postgres) testImplementation(libs.awaitility) @@ -38,4 +57,4 @@ tasks.withType(Test::class) { // do not publish edcBuild { publish.set(false) -} \ No newline at end of file +} diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/Chart.yaml b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/Chart.yaml index 613b1f45c..0fd769a8f 100644 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/Chart.yaml +++ b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/Chart.yaml @@ -1,3 +1,17 @@ +# +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# +# 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 +# +# SPDX-License-Identifier: Apache-2.0 +# +# Contributors: +# Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation +# +# + --- apiVersion: v2 name: ids-daps diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/_helpers.tpl b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/_helpers.tpl index 95b115eee..ec025d4f9 100644 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/_helpers.tpl +++ b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/_helpers.tpl @@ -1,3 +1,5 @@ + + {{/* Expand the name of the chart. */}} diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/configmap.yaml b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/configmap.yaml index 3d3e17c1f..dc57c6055 100644 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/configmap.yaml +++ b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/configmap.yaml @@ -1,3 +1,23 @@ +# + # 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 + # + +--- apiVersion: v1 kind: ConfigMap metadata: @@ -8,9 +28,9 @@ data: scope_mapping.yml: |- --- idsc:IDS_CONNECTOR_ATTRIBUTES_ALL: - - referringConnector + - referringConnector - omejdn.yml: |- + omejdn.yml: |- --- host: http://ids-daps:4567/ path_prefix: '' @@ -39,23 +59,23 @@ data: token_user_attributes: clients.yml: |- - --- - - client_id: data-plane-oauth2 - client_secret: supersecret - name: provision oauth2 - grant_types: - - client_credentials - token_endpoint_auth_method: client_secret_post - scope: openid + --- + - client_id: data-plane-oauth2 + client_secret: supersecret + name: provision oauth2 + grant_types: + - client_credentials + token_endpoint_auth_method: client_secret_post + scope: openid {{- range $i, $val := .Values.connectors }} - - client_id: {{ quote $val.id }} - name: {{ quote $val.name }} - token_endpoint_auth_method: private_key_jwt - grant_types: + - client_id: {{ quote $val.id }} + name: {{ quote $val.name }} + token_endpoint_auth_method: private_key_jwt + grant_types: - client_credentials - scope: + scope: - idsc:IDS_CONNECTOR_ATTRIBUTES_ALL - attributes: + attributes: - key: idsc value: IDS_CONNECTOR_ATTRIBUTES_ALL - key: securityProfile @@ -64,7 +84,7 @@ data: - key: {{ $key }} value: {{ $value }} {{- end }} - redirect_uri: http://localhost:4200 + redirect_uri: http://localhost:4200 {{ end -}} diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/deployment.yaml b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/deployment.yaml index 289476122..5353b8035 100644 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/deployment.yaml +++ b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/deployment.yaml @@ -1,3 +1,18 @@ +# + # Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + # + # 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 + # + # SPDX-License-Identifier: Apache-2.0 + # + # Contributors: + # Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + # + # + +--- apiVersion: apps/v1 kind: Deployment metadata: @@ -59,7 +74,7 @@ spec: cp /opt/config/scope_mapping.yml /etc/daps/scope_mapping.yml apk add --update openssl openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout /etc/keys/omejdn/omejdn.key \ - -subj "/C=DE/ST=Berlin/L=Berlin/O=TractusX-EDC-Test, Inc./OU=DE" + -subj "/C=DE/ST=Berlin/L=Berlin/O=Tractus-X-EDC-Test, Inc./OU=DE" volumeMounts: - mountPath: /etc/daps name: config-dir @@ -106,44 +121,44 @@ spec: resources: {{- toYaml .Values.resources | nindent 12 }} env: - - name: OMEJDN_JWT_AUD_OVERRIDE - value: "idsc:IDS_CONNECTORS_ALL" - - name: OMEJDN_PLUGINS - value: "config/plugins.yml" + - name: OMEJDN_JWT_AUD_OVERRIDE + value: "idsc:IDS_CONNECTORS_ALL" + - name: OMEJDN_PLUGINS + value: "config/plugins.yml" volumes: - - name: config-dir - emptyDir: {} - - name: omejdn-key-dir - emptyDir: {} - - name: omejdn-config - configMap: - name: {{ include "omejdn.fullname" . }} - items: - - key: omejdn.yml - path: omejdn.yml - - name: scope-mapping - configMap: - name: {{ include "omejdn.fullname" . }} - items: - - key: scope_mapping.yml - path: scope_mapping.yml - - name: clients-config - configMap: - name: {{ include "omejdn.fullname" . }} - items: - - key: clients.yml - path: clients.yml - - name: plugins-config - configMap: - name: {{ include "omejdn.fullname" . }} - items: - - key: plugins.yml - path: plugins.yml - - name: client-certificates - configMap: - name: {{ include "omejdn.fullname" . }} - items: + - name: config-dir + emptyDir: { } + - name: omejdn-key-dir + emptyDir: { } + - name: omejdn-config + configMap: + name: {{ include "omejdn.fullname" . }} + items: + - key: omejdn.yml + path: omejdn.yml + - name: scope-mapping + configMap: + name: {{ include "omejdn.fullname" . }} + items: + - key: scope_mapping.yml + path: scope_mapping.yml + - name: clients-config + configMap: + name: {{ include "omejdn.fullname" . }} + items: + - key: clients.yml + path: clients.yml + - name: plugins-config + configMap: + name: {{ include "omejdn.fullname" . }} + items: + - key: plugins.yml + path: plugins.yml + - name: client-certificates + configMap: + name: {{ include "omejdn.fullname" . }} + items: {{- range $i, $val := .Values.connectors }} - - key: {{ $val.name }} - path: {{ $val.id }}.cert + - key: {{ $val.name }} + path: {{ $val.id }}.cert {{- end }} diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/hpa.yaml b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/hpa.yaml index ce2a70957..cf5eb97d0 100644 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/hpa.yaml +++ b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/hpa.yaml @@ -1,4 +1,23 @@ +# 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 +# + {{- if .Values.autoscaling.enabled }} +--- apiVersion: autoscaling/v2beta1 kind: HorizontalPodAutoscaler metadata: diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/imagepullsecret.yaml b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/imagepullsecret.yaml index d7c1d31d7..44f573e0f 100644 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/imagepullsecret.yaml +++ b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/imagepullsecret.yaml @@ -1,3 +1,21 @@ +# 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 + # + {{- if .Values.imagePullSecret.dockerconfigjson }} --- apiVersion: v1 diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/service.yaml b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/service.yaml index 57dfe3921..947e69742 100644 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/service.yaml +++ b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/service.yaml @@ -1,3 +1,22 @@ +# 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 +# + +--- apiVersion: v1 kind: Service metadata: diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/serviceaccount.yaml b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/serviceaccount.yaml index 17baf8239..536f31871 100644 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/serviceaccount.yaml +++ b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/serviceaccount.yaml @@ -1,4 +1,23 @@ +# 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 +# + {{- if .Values.serviceAccount.create -}} +--- apiVersion: v1 kind: ServiceAccount metadata: diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/values.yaml b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/values.yaml index ae82cd1d6..f411b8774 100644 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/values.yaml +++ b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/values.yaml @@ -1,3 +1,21 @@ +# 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 +# + --- # Default values for omejdn. # This is a YAML-formatted file. diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/Chart.yaml b/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/Chart.yaml index d3248326b..c445d4a36 100644 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/Chart.yaml +++ b/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/Chart.yaml @@ -1,3 +1,17 @@ +# +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# +# 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 +# +# SPDX-License-Identifier: Apache-2.0 +# +# Contributors: +# Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation +# +# + --- apiVersion: v2 name: all-in-one @@ -52,12 +66,6 @@ dependencies: repository: https://charts.bitnami.com/bitnami condition: install.postgresql - - name: backend-service - version: 0.0.6 - repository: https://denisneuling.github.io/cx-backend-service - alias: backend - condition: install.backendservice - # MinIo - name: minio alias: minio diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/values.yaml b/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/values.yaml index 57779134b..287fc9975 100644 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/values.yaml +++ b/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/values.yaml @@ -1,3 +1,17 @@ +# +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# +# 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 +# +# SPDX-License-Identifier: Apache-2.0 +# +# Contributors: +# Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation +# +# + --- ########### @@ -8,19 +22,6 @@ install: postgresql: true vault: true minio: true - backendservice: true - -################### -# Backend Service # -################### -backend: - fullnameOverride: "backend" - service: - type: NodePort - frontend: - port: 8080 - backend: - port: 8081 ######## @@ -36,28 +37,28 @@ idsdaps: # Must be the same certificate that is stores in section 'sokrates-vault' certificate: |- -----BEGIN CERTIFICATE----- - MIIEAzCCAuugAwIBAgIUXFgjbN7jxGRUDkoUvEwcN3zcew8wDQYJKoZIhvcNAQEL + MIIEAzCCAuugAwIBAgIULy0aTdGiGkyvVp7l5Ccoq7DQREgwDQYJKoZIhvcNAQEL BQAwgZAxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJl cmxpbjEMMAoGA1UECgwDQk1XMSAwHgYDVQQLDBdlZGMtcGxheWdyb3VuZC1wYXJ0 bmVyMTEvMC0GA1UEAwwmc29rcmF0ZXMtZWRjLmRlbW8uY2F0ZW5hLXgubmV0L0JQ - TjEyMzQwHhcNMjIwNTEwMDc1NzMzWhcNMjMwNTEwMDc1NzMzWjCBkDELMAkGA1UE + TjEyMzQwHhcNMjMwNDI2MTI1OTE5WhcNMzMwNDIzMTI1OTE5WjCBkDELMAkGA1UE BhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVybGluMQwwCgYDVQQK DANCTVcxIDAeBgNVBAsMF2VkYy1wbGF5Z3JvdW5kLXBhcnRuZXIxMS8wLQYDVQQD DCZzb2tyYXRlcy1lZGMuZGVtby5jYXRlbmEteC5uZXQvQlBOMTIzNDCCASIwDQYJ - KoZIhvcNAQEBBQADggEPADCCAQoCggEBAK/41S8rumkk+IzBk9pBDETvjlPmlXfw - 78yRrLmbzaed3kGgygJ2GFFPLcR/Lv0WG8F8au4UEssbOxAU4RRjncCVt66ajaCa - llIqMlH8zaJ8rgxNpGeJU5YvmYRxlIo+Gwi0qnF0tqJh8Hry7OqSo0gK2YBBFJyV - grMsEz3EcsS3ENYJufNgUIeg4QsaL49M0gWxSexPdC4pon96Nvju90D8RlvAJB21 - PInqLniMaFlSnRYzCrUaja6HMmzKA+ZPZ1r9lllzsE00RASxRIxlKkwfzTtMb9O6 - ey2i2vM7hKGGlXjNsnYVX9WXEfvK4JrCadHzgX8qdez19RxFKtB+5gECAwEAAaNT - MFEwHQYDVR0OBBYEFOcHLXRWZjHwexDqtgMGTCN/7aZlMB8GA1UdIwQYMBaAFOcH - LXRWZjHwexDqtgMGTCN/7aZlMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL - BQADggEBAD2a5kuIdICNXfYLpSe7AIONwZVucaArYtpXBxHEy5lMJsTEJgjZzypd - iIMU7onEQGVbii6yVNpWfIpJYM4e8ytVdJuk5evclVKZs/lZ2IshLyWFVj+ITh2E - 28X4C/Hnmt4MPBCNowQf71nMp4LEziBgXp54qFV9C+qSTEVdrherRE0PU/zKyX10 - S/P5o42weTHnAO/pBN/8AmL3AymynKVgcPaW46IjjRAuc6kfZWCrYQ0M4+/7Ws5r - uM55Zae/L+C82OTNNaaK324ogsCkORPeQ23OCrRD8rZJmQ9bpoOGglPminfwEOhB - UHtyKgmvqCyOV3G/4G93W/xsLV0kxLA= + KoZIhvcNAQEBBQADggEPADCCAQoCggEBALCr0h3vT5kNnwWhAmGRvEEo38nKyiXB + Gx0GlepYToKklMgtGIX25OkOrXJqq4BzybxN27DoWvU9DEixylkCbhwmwmpI3IhF + 8w6cV/odaYdQ3tEeZ6zWYXqKx+MVWTHQ8A4Njy64PWNDWBZmaGvxeE48i7EJnnrM + M5CGDAKbA/Jd1nlFxaq9hGiaCHa2kCNKdfrJ6ZUda5rPlLJk5at3VPxvRIpT50Gp + 3P4PtdwpwIHwa7y1xTBc43bEfcD1lmR9VkkxCX8lg4V1OBLx1GVwoUZBkN8P4POT + t+gQq7FbDbBEeOSmKELC3Tc8D8JCGv94sEg6o+4yzgpvyIvMyV8uGcECAwEAAaNT + MFEwHQYDVR0OBBYEFIxEsuJTl+5V8vTCUhMGhWsmdQShMB8GA1UdIwQYMBaAFIxE + suJTl+5V8vTCUhMGhWsmdQShMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL + BQADggEBAJHOKmFNZDk5xebzBARgcIrYrmRb5pIU4gNCWh/q1TF0+CFnnK8RTFTZ + 12pTbid6v5knn/f9bsilnudhxBzBQ4bukiI8Be0nzYfZU2dTU+w1cl/JnJfkGirt + 8Nwqv3fiUXfFBl8nE0RduAk9XF/UBIZXPapE6u1zR29jvuV+ppmhQrFFeJufeBGd + Wwn6XGK4fzENGDyjdk4QB/dg3/heM5h330vIGO4hVvlQBfJhNbC7Iikkr5ulytfd + deuZIfa7hG6WgIgGhg3YL1p/TTpJamBDS860PWyI7RH3o53VPphu/y2Rpud5AECV + xcrqaSGUTZPVyTUB8BxE32LqFDbpZb4= -----END CERTIFICATE----- - id: 99:83:A7:17:86:FF:98:93:CE:A0:DD:A1:F1:36:FA:F6:0F:75:0A:23:keyid:99:83:A7:17:86:FF:98:93:CE:A0:DD:A1:F1:36:FA:F6:0F:75:0A:23 name: plato @@ -66,28 +67,28 @@ idsdaps: # Must be the same certificate that is stores in section 'plato-vault' certificate: |- -----BEGIN CERTIFICATE----- - MIID7TCCAtWgAwIBAgIUJ0bwxUc7n3YK89mOyGXrLx2KO0YwDQYJKoZIhvcNAQEL + MIID7TCCAtWgAwIBAgIUJv9K1yHIGf/crrkLHtKlYUd06OwwDQYJKoZIhvcNAQEL BQAwgYUxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJl cmxpbjEMMAoGA1UECgwDQk1XMSAwHgYDVQQLDBdlZGMtcGxheWdyb3VuZC1wYXJ0 - bmVyMjEkMCIGA1UEAwwbcGxhdG8tZWRjLmRlbW8uY2F0ZW5hLXgubmV0MB4XDTIy - MDQyNTEwMjgwMloXDTIzMDQyNTEwMjgwMlowgYUxCzAJBgNVBAYTAkRFMQ8wDQYD + bmVyMjEkMCIGA1UEAwwbcGxhdG8tZWRjLmRlbW8uY2F0ZW5hLXgubmV0MB4XDTIz + MDQyNjEyNTQxNFoXDTMzMDQyMzEyNTQxNFowgYUxCzAJBgNVBAYTAkRFMQ8wDQYD VQQIDAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEMMAoGA1UECgwDQk1XMSAwHgYD VQQLDBdlZGMtcGxheWdyb3VuZC1wYXJ0bmVyMjEkMCIGA1UEAwwbcGxhdG8tZWRj LmRlbW8uY2F0ZW5hLXgubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC - AQEAm1/UvHXuRU1peEGHULZBP8j8gorXAQvUz8Znb1iVNtldI29GCSXkTHxph66x - TcegdF8aeaoU1mPf3LzMQUU1koZHUq6sRC+50uFcZJ2AjF5IXKQlDPNWR5tPXP56 - RZyqXxjPFHeuTA+YsYyrzEVhzEieOiNaxJDM3uV5pv+FTRHz+xMOgNBonR1QyMh6 - tcwB+EQagoeFl0DjEXAel9WG4hOG/rDiXArTMaVjnTG/ycmF0HeSnbRC+3/+fh/C - hzQJyEbviX67ymyYRJTyynt/Mtrqg5/ssdISexjw3ZmiFNemZIOhIdepoSwnJHFM - 4Jj8B5lq0a1jY5Rc9lDj710RXQIDAQABo1MwUTAdBgNVHQ4EFgQUmYOnF4b/mJPO - oN2h8Tb69g91CiMwHwYDVR0jBBgwFoAUmYOnF4b/mJPOoN2h8Tb69g91CiMwDwYD - VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAKxv/MTIEKNkzReOqrzpt - LM00X6JsDdfxa3rZ0Uq17PjO0R63IPsqzexhfZUML0e/Dwpe97xpvftCOEuICMBA - wOHhQc77MgwyF4dqgRgfJysxw37ACZxU6GI/K2JpKXQLgEhP14oHUIWOzCAbgDhR - jwOx3ZP176Vjxx90pW3hOphRVnq/BRqqEDtFwRzKtGnGvP8ecmC2iY4dXEA3QEp1 - gzg03eglvZSoedEPY5o5y/4n6TplaDmaeoo0QrvAiWik1gY85Lg21aBWVsP45wVS - tFn3m1FCCV8XYIj/EEUAh8VEhphLVEViE6m9Mm4deFDavXcGBb63BCiOQtnjd3eY - zQ== + AQEAwajlhy7Uf6V5P7UNmb5fSL0uJLt1EIeqEuLMTc8J6VhAgv7WGtRJiZySIFfQ + VBcwizO0C+pzyHXY957HJKFWhuiiACmO1NBOQuF5TCe/X9MHUOfK1l52mDu6zXhV + pPRn8ZY7CBul/94Wb/SS0+SJ6ogkdB8nwI++3ET166Wuv1aSdGKAc2daseQ1Ynau + fPL2ziVQDfPh51+9VUG8tuwGrwagFrawDk5FkZB7Z+nPZ0WpmLN2Q+oVRFaSgDRu + cTx6ejMs2tMSKzJIJjIVzgHRIULyPSMS3AlwHub3FwZp2TrXolczDJWL+XIbfHfw + 6KKNWGR80/RM457AhNzApd0GYQIDAQABo1MwUTAdBgNVHQ4EFgQU4TU1BUk881qd + H+g1I2jAuL+jAyAwHwYDVR0jBBgwFoAU4TU1BUk881qdH+g1I2jAuL+jAyAwDwYD + VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAEOkReVM7644reZoPIWEP + 4XVyXKwoIk/zxVPedtcCoXWdG2zVXULvjIc0vf65Ih1e9b65rz+v6yn0isZ5zfSm + mFOsVMg0vL9I4QQtOSx9tL5MXq+zPeRLVpeRvvUD67+wKkf9n+e1DByqCVfaF68U + DDq0L0VQgp3fRNEzLjcXlOOIQ4W/qc1lnxoxVCzQuLJwkZejokV9cj5JDBojmAuK + g+IDL1aArzzKMD5iAAqm0rDbDnMhn0Km+AshDEWgAqtnsVEBRlt+GDAc+d0nplLY + BR8UsaLdtAVHaivXCaZGpjiOsvdOpTwWOaU9HOkuf/1uX5DTaxtClt2BXAnxF3Ug + iQ== -----END CERTIFICATE----- ############## @@ -185,59 +186,59 @@ vault: cat << EOF | /bin/vault kv put secret/plato/daps/my-plato-daps-key content=- -----BEGIN PRIVATE KEY----- - MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCbX9S8de5FTWl4 - QYdQtkE/yPyCitcBC9TPxmdvWJU22V0jb0YJJeRMfGmHrrFNx6B0Xxp5qhTWY9/c - vMxBRTWShkdSrqxEL7nS4VxknYCMXkhcpCUM81ZHm09c/npFnKpfGM8Ud65MD5ix - jKvMRWHMSJ46I1rEkMze5Xmm/4VNEfP7Ew6A0GidHVDIyHq1zAH4RBqCh4WXQOMR - cB6X1YbiE4b+sOJcCtMxpWOdMb/JyYXQd5KdtEL7f/5+H8KHNAnIRu+JfrvKbJhE - lPLKe38y2uqDn+yx0hJ7GPDdmaIU16Zkg6Eh16mhLCckcUzgmPwHmWrRrWNjlFz2 - UOPvXRFdAgMBAAECggEAN2yd5IRk9I/CucUWUfJRoEE/4glI3PSte1iY+R0uTRyI - nuVIpGbB447VzjLAyLAXSqvKM/A58qg56PHoIrhffd8sfhAVH1WvAcymOrX8bxYK - 1hEvrkj3VB/Q1alpUH+sPrQI2pI+uJ8vptY5SmrNkiOtXavS6x+EFVbiaHHpyS26 - ASaCoRpdBoNTm0SAiDBTK6MqTs4vRpqKseGdC76F+jKimYrTJY19ZctSIAMjrnqd - qzRL+jfob5vMqKC22AjInkZ8BZWll1ZoTnv37bq2NAb9lvdY73REm42Wpm5S7PET - Eixe69gvi/IwaSe27S36+kcrQoYHnxbb31+Xt+0pQQKBgQDJfA2ZnYmcA3yvVQhi - e76I3rq6AEfcG4EDhf+JRO2QHKMMXLwfFAdSR8QflxNUWy1y6q/783EpgLJ1Kv8h - uNkTH6JyV7kFhwfvxWreAWx2jRQRACqnuaLnJ/28vd8Il0kc3/BQsWzbg6YTERrq - 0Au2RW/c9blrKS0MyurtOtZsiQKBgQDFaezSCWUspeNci5lrdvMiHBLOUgR2guQm - Gtf9RdBmzvtBqpdkP8AEMhRW7oSGcKpDldd0Klyml7s/CDYTL7sflHtKRiTQmWuJ - +p3uvyylAxr/Swfw56hj5Y4/Oj2CLIuUlglewo40JnvvM5icT7RGvbyaIIhYzIsR - HTv3t8eRNQKBgA4l8eaJk3IrJIRDWlVgDx8ZVM9e2azxGXwf2rPO7UejWyexE1yz - UVhLxc/aEfdod6aMKFNu4tFhQibMICJEEqovHH8e/dUPiFUj7b8tJmqkuXYAJv6k - IHZO7phkVNcLmIy4hO2Fp/k6I11PZC588XWZJqPDdYO63nj5fsmtygTRAoGBAJ72 - YH/wmMuO+Ll4n51tNvJscKg6WuWjGFumme2T3fArEx8ZYraSruex+7bUcVpgNnod - mlQsGFb9LwXecsyYTrFrOqvgN5zRLUr5x1qMDkMBcSfJHyfZIjruidBX8Vd0zyBi - gEERoLhVlM5UWbrkY2HjPo9NSv1WF1U8mSErl0NRAoGAYC3RxEfGxD9+Qi08nQgg - s/48hLdD2k2q4t3FrDsIGPAIEs52CGp9JWil9RyIQxBXWejETwDz+PgmD6U86Mhh - Qf5css6pcP/w1XF8vsyXfPnecgPSyOE4CgLtnQLxNriMiy5pfALELLyxoBQ+nquz - fMNLPC4K85ps/Uu9uzSatl0= + MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDBqOWHLtR/pXk/ + tQ2Zvl9IvS4ku3UQh6oS4sxNzwnpWECC/tYa1EmJnJIgV9BUFzCLM7QL6nPIddj3 + nsckoVaG6KIAKY7U0E5C4XlMJ79f0wdQ58rWXnaYO7rNeFWk9GfxljsIG6X/3hZv + 9JLT5InqiCR0HyfAj77cRPXrpa6/VpJ0YoBzZ1qx5DVidq588vbOJVAN8+HnX71V + Qby27AavBqAWtrAOTkWRkHtn6c9nRamYs3ZD6hVEVpKANG5xPHp6Myza0xIrMkgm + MhXOAdEhQvI9IxLcCXAe5vcXBmnZOteiVzMMlYv5cht8d/Dooo1YZHzT9EzjnsCE + 3MCl3QZhAgMBAAECggEBAKR/RphRWwciE5/dtrPFVUKAD1X8NS/ZTMnGBCyDlLO0 + 1vduZ4dakyxk5mq6rKcBG6biQClu+PJpx+Zt5FJlCQ6HRDRHGKAEYLXGuDXL/W7z + 3d8HRPBaRPqCoeYuNPFs+W3oYjQ86AAzMXPfl2iNU+j3w58vZ6DVeRW5LfsAPTMg + Z8Sooa1jD2a/7uDN4lC0FGkTWif//Dio5tbijqeG8xqBnS8iKi4hgxcQA9azd0KB + 6uwvbX/izq4sVR2ZjxtT9WPX1cpOcXjUZBM9px9eAwLPmsM/AUAOHkKkd1DPYLjX + yyB0qvz+LmUQdJv11yGagsW7lrrvsBsro4ZMp0Ot1wECgYEA5j2XFCKNUcX/8OFm + 8E9q6DXyrd9T3rMxPYWR9nRwV0upN9Zd9mnvOKnl5MYQSgP0XJgwwyHawmG3wIcx + 0puf3uWi2lSpt6aafMCW6JEJbK/49XSPAjrptwkZUcCT3XJv1tMZuXzhv/p4t24o + hw9/EtzVxK7thGGZD6sDsQtbOlkCgYEA11OUofuD1VWN5YwFciPv0RhyfDyYYK7e + nPMXEoiBMQJnGkp3eaUzUgej/V93VtJcg9h0Tqn6NpI4rWUfUdi5ihZ6+hcvUIO4 + Roh+Oxpmu0yBfuBo7Uwf5XMpoQu74Z+cr24Pv32YtEUshUZidMuvOMaBXNJGlKiG + DjbCUV0CG0kCgYAVHvlJA5JrOfqsokDLMr3f53MHuED9YPrXZfVp4myb1XkEgknE + XRtw20UXo4PDBnHYPK3ceLKUuloc80oCw/v6ep5h4PpguovZfeFaHFP9AHeaLMMh + tT3TaKZF9aCa4/CWiG8HsQkUj2mbiiN1oFpL5K5HiLSJPFrKMSn5h80qoQKBgQC2 + obt1UEDXFwONaJ/N2dE0RkoEOdj8WBWUhVJSc9kv2lvcnsCLOqU2tChRZUFxMGcr + pNGxTtZcptTPrO9NmkZ0avDPYg7NeYs4t9hpBNGRlyhWlrwoWOLM2Eq8v5kRmzFo + Ui+lOT/l1q4WNEaZzZDG1Qcv1WHsAKwDLkrOe9ankQKBgQDM1fdqraKN2lCkDSPU + /Uw5nmFA7gNJQ9ta6CVITlDMWFb+e2OcDK7pKT1iEhJAfGndtQ0lwK6I5VDDxhXY + DGcU2UIWMAiJOZILDVjkny9brrIQ/fTwZps2qWNJ0bmYsmwPCe9QskNWz8sYAY6p + eBB+WUqNNqBb25p2CmcwqoT7Tg== -----END PRIVATE KEY----- EOF cat << EOF | /bin/vault kv put secret/plato/daps/my-plato-daps-crt content=- -----BEGIN CERTIFICATE----- - MIID7TCCAtWgAwIBAgIUJ0bwxUc7n3YK89mOyGXrLx2KO0YwDQYJKoZIhvcNAQEL + MIID7TCCAtWgAwIBAgIUJv9K1yHIGf/crrkLHtKlYUd06OwwDQYJKoZIhvcNAQEL BQAwgYUxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJl cmxpbjEMMAoGA1UECgwDQk1XMSAwHgYDVQQLDBdlZGMtcGxheWdyb3VuZC1wYXJ0 - bmVyMjEkMCIGA1UEAwwbcGxhdG8tZWRjLmRlbW8uY2F0ZW5hLXgubmV0MB4XDTIy - MDQyNTEwMjgwMloXDTIzMDQyNTEwMjgwMlowgYUxCzAJBgNVBAYTAkRFMQ8wDQYD + bmVyMjEkMCIGA1UEAwwbcGxhdG8tZWRjLmRlbW8uY2F0ZW5hLXgubmV0MB4XDTIz + MDQyNjEyNTQxNFoXDTMzMDQyMzEyNTQxNFowgYUxCzAJBgNVBAYTAkRFMQ8wDQYD VQQIDAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEMMAoGA1UECgwDQk1XMSAwHgYD VQQLDBdlZGMtcGxheWdyb3VuZC1wYXJ0bmVyMjEkMCIGA1UEAwwbcGxhdG8tZWRj LmRlbW8uY2F0ZW5hLXgubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC - AQEAm1/UvHXuRU1peEGHULZBP8j8gorXAQvUz8Znb1iVNtldI29GCSXkTHxph66x - TcegdF8aeaoU1mPf3LzMQUU1koZHUq6sRC+50uFcZJ2AjF5IXKQlDPNWR5tPXP56 - RZyqXxjPFHeuTA+YsYyrzEVhzEieOiNaxJDM3uV5pv+FTRHz+xMOgNBonR1QyMh6 - tcwB+EQagoeFl0DjEXAel9WG4hOG/rDiXArTMaVjnTG/ycmF0HeSnbRC+3/+fh/C - hzQJyEbviX67ymyYRJTyynt/Mtrqg5/ssdISexjw3ZmiFNemZIOhIdepoSwnJHFM - 4Jj8B5lq0a1jY5Rc9lDj710RXQIDAQABo1MwUTAdBgNVHQ4EFgQUmYOnF4b/mJPO - oN2h8Tb69g91CiMwHwYDVR0jBBgwFoAUmYOnF4b/mJPOoN2h8Tb69g91CiMwDwYD - VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAKxv/MTIEKNkzReOqrzpt - LM00X6JsDdfxa3rZ0Uq17PjO0R63IPsqzexhfZUML0e/Dwpe97xpvftCOEuICMBA - wOHhQc77MgwyF4dqgRgfJysxw37ACZxU6GI/K2JpKXQLgEhP14oHUIWOzCAbgDhR - jwOx3ZP176Vjxx90pW3hOphRVnq/BRqqEDtFwRzKtGnGvP8ecmC2iY4dXEA3QEp1 - gzg03eglvZSoedEPY5o5y/4n6TplaDmaeoo0QrvAiWik1gY85Lg21aBWVsP45wVS - tFn3m1FCCV8XYIj/EEUAh8VEhphLVEViE6m9Mm4deFDavXcGBb63BCiOQtnjd3eY - zQ== + AQEAwajlhy7Uf6V5P7UNmb5fSL0uJLt1EIeqEuLMTc8J6VhAgv7WGtRJiZySIFfQ + VBcwizO0C+pzyHXY957HJKFWhuiiACmO1NBOQuF5TCe/X9MHUOfK1l52mDu6zXhV + pPRn8ZY7CBul/94Wb/SS0+SJ6ogkdB8nwI++3ET166Wuv1aSdGKAc2daseQ1Ynau + fPL2ziVQDfPh51+9VUG8tuwGrwagFrawDk5FkZB7Z+nPZ0WpmLN2Q+oVRFaSgDRu + cTx6ejMs2tMSKzJIJjIVzgHRIULyPSMS3AlwHub3FwZp2TrXolczDJWL+XIbfHfw + 6KKNWGR80/RM457AhNzApd0GYQIDAQABo1MwUTAdBgNVHQ4EFgQU4TU1BUk881qd + H+g1I2jAuL+jAyAwHwYDVR0jBBgwFoAU4TU1BUk881qdH+g1I2jAuL+jAyAwDwYD + VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAEOkReVM7644reZoPIWEP + 4XVyXKwoIk/zxVPedtcCoXWdG2zVXULvjIc0vf65Ih1e9b65rz+v6yn0isZ5zfSm + mFOsVMg0vL9I4QQtOSx9tL5MXq+zPeRLVpeRvvUD67+wKkf9n+e1DByqCVfaF68U + DDq0L0VQgp3fRNEzLjcXlOOIQ4W/qc1lnxoxVCzQuLJwkZejokV9cj5JDBojmAuK + g+IDL1aArzzKMD5iAAqm0rDbDnMhn0Km+AshDEWgAqtnsVEBRlt+GDAc+d0nplLY + BR8UsaLdtAVHaivXCaZGpjiOsvdOpTwWOaU9HOkuf/1uX5DTaxtClt2BXAnxF3Ug + iQ== -----END CERTIFICATE----- EOF @@ -245,59 +246,59 @@ vault: cat << EOF | /bin/vault kv put secret/sokrates/daps/my-sokrates-daps-key content=- -----BEGIN PRIVATE KEY----- - MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCv+NUvK7ppJPiM - wZPaQQxE745T5pV38O/Mkay5m82nnd5BoMoCdhhRTy3Efy79FhvBfGruFBLLGzsQ - FOEUY53Albeumo2gmpZSKjJR/M2ifK4MTaRniVOWL5mEcZSKPhsItKpxdLaiYfB6 - 8uzqkqNICtmAQRSclYKzLBM9xHLEtxDWCbnzYFCHoOELGi+PTNIFsUnsT3QuKaJ/ - ejb47vdA/EZbwCQdtTyJ6i54jGhZUp0WMwq1Go2uhzJsygPmT2da/ZZZc7BNNEQE - sUSMZSpMH807TG/TunstotrzO4ShhpV4zbJ2FV/VlxH7yuCawmnR84F/KnXs9fUc - RSrQfuYBAgMBAAECggEAO+KjsjTgcG3bhBNQnMLsSP15Y0Yicbn18ZlVvaivGS7Z - d14fwSytY+ZdPfTGaey/L16HCVSdfK9cr0Fbw9OO2P5ajzobnp9dLsMbctlkpbpm - hNtbarzKTF8QkIkSsuUl0BWjt46vpJ1N+Jl5VO7oUFkY4dPEDvG2lAEY3zlekWDm - cQeOC/YgpoW4xfRwPPS6QE0w3Q+H5NfNjfz+mSHeItTlVfTKDRliWQLPWeRZFuXh - FlRFUQnTmEE/9wpIe3Hn7WXJ3fQqcYDzxU7/zwwY9I7bB15SgVHlR0ENDPAD5X8F - MVZ3EcLlqGBy+WvTWALp6pc8YfhW3fiTWyuamXtNrQKBgQDonsIzBKEOOKdKGW0e - uyw79ErmnmzkY5nuMrMxrmTA4WKCfJ/YRRA+4sxiltWsIJ3UkHe3OBCSSCdj79hb - ugb/+UzE70hOdgrct2NUQqbrj3gvsVvU8ZRQgTRMqKpmC0zY7KOMx6NU85z3IvS1 - z5fjszcUv4kLQlldYGSAuqPy+wKBgQDBqIkc8p/wcw7ygo1q/GerNeszfoxiIFp8 - h4RWLVhkwrcXFz30wBlUWuv5/kxU8tmJcmXxe72EmUstd6wvNOAnYwCiile6zQiJ - vsr1axavZnGOtNGUp6DUAsd2iviBl7IZ7kAcqCrQo4ivGhfHmahH3hmg8wuAMjYB - 8f+FSPgaMwKBgQC7W4tMrjDOFIFhJEOIWfcRvvxI7VcFSNelS76aiDzsQVwnfxr7 - hPzFucQmsBgfUBHvMADMWGK4f1cCnh5kGtwidXgIsjVJxLeQ+EAPkLOCzQZfW3l8 - dKshgD9QcxTzpaxal5ZPAEikVqaZQtVYToCmzCTUGETYBbOWitnH+Qut2wKBgQC6 - Y6DcSLUhc0xOotLDxv1sbu/aVxF8nFEbDD+Vxf0Otc4MnmUWPRHj+8KlkVkcZcR0 - IrP1kThd+EDAGS+TG9wmbIY+6tH3S8HM+eJUBWcHGJ1xUZ1p61DC3Y3nDWiTKlLT - 3Fi+fCkBOHSku4Npq/2odh7Kp0JJd4o9oxJg0VNhuwKBgQDSFn7dqFE0Xmwc40Vr - 0wJH8cPWXKGt7KJENpj894buk2DniLD4w2x874dzTjrOFi6fKxEzbBNA9Rq9UPo8 - u9gKvl/IyWmV0c4zFCNMjRwVdnkMEte/lXcJZ67T4FXZByqAZlhrr/v0FD442Z9B - AjWFbUiBCFOo+gpAFcQGrkOQHA== + MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwq9Id70+ZDZ8F + oQJhkbxBKN/JysolwRsdBpXqWE6CpJTILRiF9uTpDq1yaquAc8m8Tduw6Fr1PQxI + scpZAm4cJsJqSNyIRfMOnFf6HWmHUN7RHmes1mF6isfjFVkx0PAODY8uuD1jQ1gW + Zmhr8XhOPIuxCZ56zDOQhgwCmwPyXdZ5RcWqvYRomgh2tpAjSnX6yemVHWuaz5Sy + ZOWrd1T8b0SKU+dBqdz+D7XcKcCB8Gu8tcUwXON2xH3A9ZZkfVZJMQl/JYOFdTgS + 8dRlcKFGQZDfD+Dzk7foEKuxWw2wRHjkpihCwt03PA/CQhr/eLBIOqPuMs4Kb8iL + zMlfLhnBAgMBAAECggEAVYco1mMXRsIoXQJAc9moqGbQSBGLYVGl/ZxFkUik4Wwp + turV92y6DvWTFFP9qNblL+sFUxR5jEW8n6iqjAK4KZq9/dQ+Jx6t90HK+YOppd+J + rvUoPa0fTcLH1/Bq2MoMnNEFoxmAZoCgsV9sZ+1jT4TSH6fHeC1JPUsXn19KPtdO + 0b0XvRCVU4sPpzXeaRypnwTsDMgHUoGvxoHQ7Pif5iTnEdgvc7V3ACWOanp/bEuM + 9hoHquggrO/F8SDC4wjn4BlwsxedQZyVF4a76iGS3D/CFrYd8cUKJyCtGySEl7jS + kIwDoG4oQV5mLFSLaq1BDOo8W5ku9JXAW2DZiEgkPQKBgQDav9QTSOp+gqfCDMhT + c45wxYfLfR5QCS5BLufdMmlocL/DzTHTsVddGnOGLoDr8Dbm9nL/vcPmgRtZ0crD + aGqn7sgmbpN6jMsnXhGuOhPZt7Folfbkhv6EFfyjeTZdY4vacrINp97rdVbRvNLs + pDJiHE7PjDTCJh8q2gcWqgc+vwKBgQDOwaJ8NvnnUwrBGzkUHdM4bGunxlK3VV0s + r1BFkmLXbF0qr8sTaUtBj3rbfvMe9R/5hGcuAyDWo+MyVoHc0nzkU1jQwqObLUZB + kg9ZJj4qmnGKnd39TfhDoEluBnl+iYhbXav/2F5eB2UpR0c+DHPrsreGdI3s7O9O + aLL+x5FHfwKBgQCNDwhdyzZTkENHkeCYV7rxo58WrD8g01q9c9bWv8xTKemvBKHt + 5bz1b7oxO8ms24E73I55tdAe0wBlIjDDY5Dra8IrbkCx1Rqn7zQtiowEaD0BuTq1 + UQvM9zSr4d0ZybiEjFOfFLJeWZM7uqy1JojK1YBIvBvFWrnccy4BAnGblwKBgQC5 + GHLNfy4koJw9GpDz6GuC1NVgAtVkWaCrc1uKnS2tq86Qe4ZzH02HKNsVC8a9jTcN + 2zG/6H8KiPfJxdZGiY3TnqYhZk6vik2eQBNLfUgkPdWuAfyNW7MJX8K9JEC6Pof7 + O5XS2rJIvZgb5zrpWp6ggIN6dHfmhosKiALOwnzWIwKBgBsvToOVMzL/7IL4VtSj + u9P+g2shtg9w1dpnscHxUVi2fbKeRRmv2AT140lVpSznIPUmw6FVFUhqE1OTnu1c + qS53otAdwiHAfmYz8u0dZeO0Hc5g4K9geB/BsSthXo3u10HuIVLxefKq2M+3zfJj + ZvNovy5dPYu82VTfm3gUX+Ca -----END PRIVATE KEY----- EOF cat << EOF | /bin/vault kv put secret/sokrates/daps/my-sokrates-daps-crt content=- -----BEGIN CERTIFICATE----- - MIIEAzCCAuugAwIBAgIUXFgjbN7jxGRUDkoUvEwcN3zcew8wDQYJKoZIhvcNAQEL + MIIEAzCCAuugAwIBAgIULy0aTdGiGkyvVp7l5Ccoq7DQREgwDQYJKoZIhvcNAQEL BQAwgZAxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJl cmxpbjEMMAoGA1UECgwDQk1XMSAwHgYDVQQLDBdlZGMtcGxheWdyb3VuZC1wYXJ0 bmVyMTEvMC0GA1UEAwwmc29rcmF0ZXMtZWRjLmRlbW8uY2F0ZW5hLXgubmV0L0JQ - TjEyMzQwHhcNMjIwNTEwMDc1NzMzWhcNMjMwNTEwMDc1NzMzWjCBkDELMAkGA1UE + TjEyMzQwHhcNMjMwNDI2MTI1OTE5WhcNMzMwNDIzMTI1OTE5WjCBkDELMAkGA1UE BhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVybGluMQwwCgYDVQQK DANCTVcxIDAeBgNVBAsMF2VkYy1wbGF5Z3JvdW5kLXBhcnRuZXIxMS8wLQYDVQQD DCZzb2tyYXRlcy1lZGMuZGVtby5jYXRlbmEteC5uZXQvQlBOMTIzNDCCASIwDQYJ - KoZIhvcNAQEBBQADggEPADCCAQoCggEBAK/41S8rumkk+IzBk9pBDETvjlPmlXfw - 78yRrLmbzaed3kGgygJ2GFFPLcR/Lv0WG8F8au4UEssbOxAU4RRjncCVt66ajaCa - llIqMlH8zaJ8rgxNpGeJU5YvmYRxlIo+Gwi0qnF0tqJh8Hry7OqSo0gK2YBBFJyV - grMsEz3EcsS3ENYJufNgUIeg4QsaL49M0gWxSexPdC4pon96Nvju90D8RlvAJB21 - PInqLniMaFlSnRYzCrUaja6HMmzKA+ZPZ1r9lllzsE00RASxRIxlKkwfzTtMb9O6 - ey2i2vM7hKGGlXjNsnYVX9WXEfvK4JrCadHzgX8qdez19RxFKtB+5gECAwEAAaNT - MFEwHQYDVR0OBBYEFOcHLXRWZjHwexDqtgMGTCN/7aZlMB8GA1UdIwQYMBaAFOcH - LXRWZjHwexDqtgMGTCN/7aZlMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL - BQADggEBAD2a5kuIdICNXfYLpSe7AIONwZVucaArYtpXBxHEy5lMJsTEJgjZzypd - iIMU7onEQGVbii6yVNpWfIpJYM4e8ytVdJuk5evclVKZs/lZ2IshLyWFVj+ITh2E - 28X4C/Hnmt4MPBCNowQf71nMp4LEziBgXp54qFV9C+qSTEVdrherRE0PU/zKyX10 - S/P5o42weTHnAO/pBN/8AmL3AymynKVgcPaW46IjjRAuc6kfZWCrYQ0M4+/7Ws5r - uM55Zae/L+C82OTNNaaK324ogsCkORPeQ23OCrRD8rZJmQ9bpoOGglPminfwEOhB - UHtyKgmvqCyOV3G/4G93W/xsLV0kxLA= + KoZIhvcNAQEBBQADggEPADCCAQoCggEBALCr0h3vT5kNnwWhAmGRvEEo38nKyiXB + Gx0GlepYToKklMgtGIX25OkOrXJqq4BzybxN27DoWvU9DEixylkCbhwmwmpI3IhF + 8w6cV/odaYdQ3tEeZ6zWYXqKx+MVWTHQ8A4Njy64PWNDWBZmaGvxeE48i7EJnnrM + M5CGDAKbA/Jd1nlFxaq9hGiaCHa2kCNKdfrJ6ZUda5rPlLJk5at3VPxvRIpT50Gp + 3P4PtdwpwIHwa7y1xTBc43bEfcD1lmR9VkkxCX8lg4V1OBLx1GVwoUZBkN8P4POT + t+gQq7FbDbBEeOSmKELC3Tc8D8JCGv94sEg6o+4yzgpvyIvMyV8uGcECAwEAAaNT + MFEwHQYDVR0OBBYEFIxEsuJTl+5V8vTCUhMGhWsmdQShMB8GA1UdIwQYMBaAFIxE + suJTl+5V8vTCUhMGhWsmdQShMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL + BQADggEBAJHOKmFNZDk5xebzBARgcIrYrmRb5pIU4gNCWh/q1TF0+CFnnK8RTFTZ + 12pTbid6v5knn/f9bsilnudhxBzBQ4bukiI8Be0nzYfZU2dTU+w1cl/JnJfkGirt + 8Nwqv3fiUXfFBl8nE0RduAk9XF/UBIZXPapE6u1zR29jvuV+ppmhQrFFeJufeBGd + Wwn6XGK4fzENGDyjdk4QB/dg3/heM5h330vIGO4hVvlQBfJhNbC7Iikkr5ulytfd + deuZIfa7hG6WgIgGhg3YL1p/TTpJamBDS860PWyI7RH3o53VPphu/y2Rpud5AECV + xcrqaSGUTZPVyTUB8BxE32LqFDbpZb4= -----END CERTIFICATE----- EOF } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/BackendDataService.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/BackendDataService.java new file mode 100644 index 000000000..21beba150 --- /dev/null +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/BackendDataService.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.tests; + + +import java.io.InputStream; +import java.util.List; + +public interface BackendDataService { + List list(String path); + + boolean exists(String path); + + byte[] get(String path); + + void post(String path, InputStream inputStream, long length); + + void post(String path, InputStream inputStream); + + void post(String path, byte[] content); + + void delete(String path); +} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/BackendServiceBackendAPI.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/BackendServiceBackendAPI.java deleted file mode 100644 index 6b2a5ee2e..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/BackendServiceBackendAPI.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH - * Copyright (c) 2021,2022 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 - */ - -package org.eclipse.tractusx.edc.tests; - -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.URI; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.StatusLine; -import org.apache.http.client.HttpClient; -import org.apache.http.client.HttpResponseException; -import org.apache.http.client.ResponseHandler; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpHead; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.utils.URIBuilder; -import org.apache.http.entity.BasicHttpEntity; -import org.apache.http.entity.ContentType; -import org.apache.http.impl.client.AbstractResponseHandler; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.util.EntityUtils; - -@Slf4j -public class BackendServiceBackendAPI { - private static final String HTTP_HEADER_ACCEPT = "Accept"; - private static final String HTTP_HEADER_CONTENT_TYPE = "Content-Type"; - private static final String PATH_ROOT = "/"; - private final String backendServiceBackendApiUrl; - private final HttpClient httpClient; - - public BackendServiceBackendAPI(@NonNull final String backendServiceBackendApiUrl) { - this.backendServiceBackendApiUrl = backendServiceBackendApiUrl; - this.httpClient = HttpClientBuilder.create().build(); - } - - /** Lists all files and directories associated by a backend-service path. */ - @SneakyThrows - public List list(/* @Nullable */ final String path) { - final URI uri = - new URIBuilder(backendServiceBackendApiUrl) - .setPath(Optional.ofNullable(path).orElse(PATH_ROOT)) - .build(); - final HttpGet get = new HttpGet(uri); - get.setHeader(HTTP_HEADER_ACCEPT, ContentType.APPLICATION_JSON.getMimeType()); - - log.debug(String.format("Send %-6s %s", get.getMethod(), get.getURI())); - - return httpClient.execute(get, ListResponseHandler.INSTANCE); - } - - /** Proves existence of a file or directory associated by a backend-service path. */ - @SneakyThrows - public boolean exists(@NonNull final String path) { - final URI uri = new URIBuilder(backendServiceBackendApiUrl).setPath(path).build(); - final HttpHead head = new HttpHead(uri); - - log.debug(String.format("Send %-6s %s", head.getMethod(), head.getURI())); - - return httpClient.execute(head, ExistsResponseHandler.INSTANCE); - } - - /** Retrieves file content associated by a backend-service path. */ - @SneakyThrows - public byte[] get(@NonNull final String path) { - final URI uri = new URIBuilder(backendServiceBackendApiUrl).setPath(path).build(); - final HttpGet get = new HttpGet(uri); - get.setHeader(HTTP_HEADER_ACCEPT, ContentType.APPLICATION_OCTET_STREAM.getMimeType()); - - log.debug(String.format("Send %-6s %s", get.getMethod(), get.getURI())); - - return httpClient.execute(get, GetResponseHandler.INSTANCE); - } - - /** - * Creates a file associated by a backend-service path. If existing truncates and recreates that - * file - */ - @SneakyThrows - public void post( - @NonNull final String path, @NonNull final InputStream inputStream, long length) { - final URI uri = new URIBuilder(backendServiceBackendApiUrl).setPath(path).build(); - final HttpPost post = new HttpPost(uri); - post.addHeader(HTTP_HEADER_CONTENT_TYPE, ContentType.APPLICATION_OCTET_STREAM.getMimeType()); - final BasicHttpEntity entity = new BasicHttpEntity(); - entity.setContent(inputStream); - entity.setContentLength(length); - - post.setEntity(entity); - - log.debug(String.format("Send %-6s %s", post.getMethod(), post.getURI())); - - httpClient.execute(post, PostResponseHandler.INSTANCE); - } - - @SneakyThrows - public void post(@NonNull final String path, @NonNull final InputStream inputStream) { - post(path, inputStream, -1); - } - - @SneakyThrows - public void post(@NonNull final String path, @NonNull final byte[] content) { - try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(content)) { - post(path, byteArrayInputStream, content.length); - } - } - - /** Deletes files (and directories in a recursive manner) associated by a backend-service path. */ - @SneakyThrows - public void delete(@NonNull final String path) { - final URI uri = new URIBuilder(backendServiceBackendApiUrl).setPath(path).build(); - final HttpDelete delete = new HttpDelete(uri); - - httpClient.execute(delete, DeleteResponseHandler.INSTANCE); - } - - @NoArgsConstructor(access = AccessLevel.PRIVATE) - private static final class PostResponseHandler implements ResponseHandler { - public static final DeleteResponseHandler INSTANCE = new DeleteResponseHandler(); - - private static final List ACCEPTABLE_STATUS_CODES = - Arrays.asList(HttpStatus.SC_OK, HttpStatus.SC_ACCEPTED, HttpStatus.SC_CREATED); - - @Override - public Void handleResponse(@NonNull final HttpResponse response) throws IOException { - final StatusLine statusLine = response.getStatusLine(); - final Integer code = statusLine.getStatusCode(); - final HttpEntity entity = response.getEntity(); - - // not interested into content so throw it away - EntityUtils.consume(entity); - - if (ACCEPTABLE_STATUS_CODES.contains(code)) { - return null; - } - - throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase()); - } - } - - @NoArgsConstructor(access = AccessLevel.PRIVATE) - private static class DeleteResponseHandler implements ResponseHandler { - public static final DeleteResponseHandler INSTANCE = new DeleteResponseHandler(); - - private static final List ACCEPTABLE_STATUS_CODES = - Arrays.asList( - HttpStatus.SC_OK, - HttpStatus.SC_ACCEPTED, - HttpStatus.SC_NO_CONTENT, - HttpStatus.SC_NOT_FOUND); - - @Override - public Void handleResponse(@NonNull final HttpResponse response) throws IOException { - final StatusLine statusLine = response.getStatusLine(); - final Integer code = statusLine.getStatusCode(); - - // not interested into content so throw it away - Optional.ofNullable(response.getEntity()).ifPresent(EntityUtils::consumeQuietly); - - if (ACCEPTABLE_STATUS_CODES.contains(code)) { - return null; - } - - throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase()); - } - } - - @NoArgsConstructor(access = AccessLevel.PRIVATE) - private static class GetResponseHandler extends AbstractResponseHandler { - public static final GetResponseHandler INSTANCE = new GetResponseHandler(); - - private static byte[] readAllBytes(@NonNull final InputStream stream) throws IOException { - final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - final byte[] data = new byte[16384]; - - int i; - while ((i = stream.read(data, 0, data.length)) != -1) { - byteArrayOutputStream.write(data, 0, i); - } - - return byteArrayOutputStream.toByteArray(); - } - - @Override - public byte[] handleEntity(@NonNull final HttpEntity entity) throws IOException { - try (final InputStream inputStream = entity.getContent()) { - return readAllBytes(inputStream); - } - } - } - - @NoArgsConstructor(access = AccessLevel.PRIVATE) - private static class ExistsResponseHandler implements ResponseHandler { - public static final ExistsResponseHandler INSTANCE = new ExistsResponseHandler(); - - @Override - public Boolean handleResponse(@NonNull final HttpResponse response) - throws HttpResponseException { - final StatusLine statusLine = response.getStatusLine(); - final int code = statusLine.getStatusCode(); - - Optional.ofNullable(response.getEntity()).ifPresent(EntityUtils::consumeQuietly); - - switch (code) { - case HttpStatus.SC_OK: - return true; - case HttpStatus.SC_NOT_FOUND: - return false; - default: - throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase()); - } - } - } - - private static class ListResponseHandler extends GsonResponseHandler> { - public static final ListResponseHandler INSTANCE = new ListResponseHandler(); - - private ListResponseHandler() { - super(new TypeToken<>() {}); // JVM type erasure: Keep generic args! - } - } - - @RequiredArgsConstructor(access = AccessLevel.PROTECTED) - private static class GsonResponseHandler extends AbstractResponseHandler { - private static final Gson GSON = new Gson(); - - @NonNull private final TypeToken typeToken; - - @Override - public T handleEntity(@NonNull final HttpEntity entity) throws IOException { - try (final InputStreamReader inputStreamReader = new InputStreamReader(entity.getContent())) { - return GSON.fromJson(inputStreamReader, typeToken.getType()); - } - } - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/BackendServiceSteps.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/BackendServiceSteps.java index 05960ddf7..f6773d7c0 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/BackendServiceSteps.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/BackendServiceSteps.java @@ -1,14 +1,33 @@ +/* + * 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 + */ + + package org.eclipse.tractusx.edc.tests; import io.cucumber.java.en.Given; public class BackendServiceSteps { - @Given("'{connector}' has an empty backend-service") - public void cleanBackendService(Connector connector) { - final BackendServiceBackendAPI backendServiceBackendAPI = - connector.getBackendServiceBackendAPI(); + @Given("'{connector}' has an empty backend-service") + public void cleanBackendService(Connector connector) { + var backendServiceBackendAPI = connector.getBackendServiceBackendAPI(); - backendServiceBackendAPI.list("/").forEach(backendServiceBackendAPI::delete); - } + backendServiceBackendAPI.list("/").forEach(backendServiceBackendAPI::delete); + } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/Connector.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/Connector.java index 9ca1bcb19..d4e2ea7a8 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/Connector.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/Connector.java @@ -20,48 +20,71 @@ package org.eclipse.tractusx.edc.tests; -import lombok.Getter; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; + import org.eclipse.tractusx.edc.tests.util.DatabaseCleaner; import org.eclipse.tractusx.edc.tests.util.S3Client; -@RequiredArgsConstructor +import static org.mockito.Mockito.mock; + public class Connector { - @NonNull @Getter private final String name; + private final String name; + + private final Environment environment; + + private final DataManagementAPI dataManagementAPI; + + private final DatabaseCleaner databaseCleaner; + + + private final S3Client s3Client; + + public Connector(String name, Environment environment) { + this.name = name; + this.environment = environment; + dataManagementAPI = loadDataManagementAPI(); + databaseCleaner = loadDatabaseCleaner(); + s3Client = createS3Client(); + } + + public BackendDataService getBackendServiceBackendAPI() { + return mock(BackendDataService.class); + } - @Getter @NonNull private final Environment environment; + public DatabaseCleaner getDatabaseCleaner() { + return databaseCleaner; + } - @Getter(lazy = true) - private final DataManagementAPI dataManagementAPI = loadDataManagementAPI(); + public DataManagementAPI getDataManagementAPI() { + return dataManagementAPI; + } - @Getter(lazy = true) - private final BackendServiceBackendAPI backendServiceBackendAPI = loadBackendServiceBackendAPI(); + public Environment getEnvironment() { + return environment; + } - @Getter(lazy = true) - private final DatabaseCleaner databaseCleaner = loadDatabaseCleaner(); + public S3Client getS3Client() { + return s3Client; + } - @Getter(lazy = true) - private final S3Client s3Client = createS3Client(); + public String getName() { + return name; + } - private DataManagementAPI loadDataManagementAPI() { - return new DataManagementAPI( - environment.getDataManagementUrl(), environment.getDataManagementAuthKey()); - } + private DataManagementAPI loadDataManagementAPI() { + return new DataManagementAPI( + environment.getDataManagementUrl(), environment.getDataManagementAuthKey()); + } - private DatabaseCleaner loadDatabaseCleaner() { - return new DatabaseCleaner( - environment.getDatabaseUrl(), - environment.getDatabaseUser(), - environment.getDatabasePassword()); - } + private DatabaseCleaner loadDatabaseCleaner() { + return new DatabaseCleaner( + environment.getDatabaseUrl(), + environment.getDatabaseUser(), + environment.getDatabasePassword()); + } - private BackendServiceBackendAPI loadBackendServiceBackendAPI() { - return new BackendServiceBackendAPI(environment.getBackendServiceBackendApiUrl()); - } - private S3Client createS3Client() { - return new S3Client(environment); - } + private S3Client createS3Client() { + return new S3Client(environment); + } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/ConnectorFactory.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/ConnectorFactory.java index 7a8ef81a1..364e266f3 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/ConnectorFactory.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/ConnectorFactory.java @@ -20,24 +20,25 @@ package org.eclipse.tractusx.edc.tests; -import java.util.HashMap; import java.util.Locale; import java.util.Map; -import lombok.NonNull; -import lombok.experimental.UtilityClass; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + -@UtilityClass public class ConnectorFactory { - private static final Map CONNECTOR_CACHE = new HashMap<>(); + private static final Map CONNECTOR_CACHE = new ConcurrentHashMap<>(); - public static Connector byName(@NonNull final String name) { - return CONNECTOR_CACHE.computeIfAbsent( - name.toUpperCase(Locale.ROOT), k -> createConnector(name)); - } + public static Connector byName(String name) { + Objects.requireNonNull(name); + return CONNECTOR_CACHE.computeIfAbsent( + name.toUpperCase(Locale.ROOT), k -> createConnector(name)); + } - private static Connector createConnector(@NonNull final String name) { - final Environment environment = Environment.byName(name); + private static Connector createConnector(String name) { + Objects.requireNonNull(name); + Environment environment = Environment.byName(name); - return new Connector(name, environment); - } + return new Connector(name, environment); + } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/Constants.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/Constants.java index 67b484e38..6a7de2ceb 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/Constants.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/Constants.java @@ -20,19 +20,16 @@ package org.eclipse.tractusx.edc.tests; -import lombok.experimental.UtilityClass; - -@UtilityClass public final class Constants { - public static final String DATA_MANAGEMENT_URL = "DATA_MANAGEMENT_URL"; - public static final String DATA_MANAGEMENT_API_AUTH_KEY = "DATA_MANAGEMENT_API_AUTH_KEY"; - public static final String IDS_URL = "IDS_URL"; - public static final String DATA_PLANE_URL = "DATA_PLANE_URL"; - public static final String BACKEND_SERVICE_BACKEND_API_URL = "BACKEND_SERVICE_BACKEND_API_URL"; - public static final String DATABASE_URL = "DATABASE_URL"; - public static final String DATABASE_USER = "DATABASE_USER"; - public static final String DATABASE_PASSWORD = "DATABASE_PASSWORD"; - public static final String EDC_AWS_ENDPOINT_OVERRIDE = "EDC_AWS_ENDPOINT_OVERRIDE"; - public static final String AWS_ACCESS_KEY_ID = "AWS_ACCESS_KEY_ID"; - public static final String AWS_SECRET_ACCESS_KEY = "AWS_SECRET_ACCESS_KEY"; + public static final String DATA_MANAGEMENT_URL = "DATA_MANAGEMENT_URL"; + public static final String DATA_MANAGEMENT_API_AUTH_KEY = "DATA_MANAGEMENT_API_AUTH_KEY"; + public static final String IDS_URL = "IDS_URL"; + public static final String DATA_PLANE_URL = "DATA_PLANE_URL"; + public static final String BACKEND_SERVICE_BACKEND_API_URL = "BACKEND_SERVICE_BACKEND_API_URL"; + public static final String DATABASE_URL = "DATABASE_URL"; + public static final String DATABASE_USER = "DATABASE_USER"; + public static final String DATABASE_PASSWORD = "DATABASE_PASSWORD"; + public static final String EDC_AWS_ENDPOINT_OVERRIDE = "EDC_AWS_ENDPOINT_OVERRIDE"; + public static final String AWS_ACCESS_KEY_ID = "AWS_ACCESS_KEY_ID"; + public static final String AWS_SECRET_ACCESS_KEY = "AWS_SECRET_ACCESS_KEY"; } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/ControlPlaneAdapterSteps.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/ControlPlaneAdapterSteps.java index d29a13aa4..e786c789a 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/ControlPlaneAdapterSteps.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/ControlPlaneAdapterSteps.java @@ -22,64 +22,66 @@ import com.google.gson.Gson; import io.cucumber.datatable.DataTable; -import java.io.IOException; -import java.util.Map; -import lombok.extern.slf4j.Slf4j; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.HttpClientBuilder; import org.eclipse.edc.spi.system.health.HealthStatus; import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Map; -@Slf4j public class ControlPlaneAdapterSteps { - private EndpointDataReference endpointDataReference; + private static final Logger log = LoggerFactory.getLogger(ControlPlaneAdapterSteps.class); + private EndpointDataReference endpointDataReference; - /* - * TODO: see of EndToEndTransfer.feature - * the current Bussinnes test is not running, because of a possible rare condition in the CI pipeline - * regarding the contract validity: see https://github.com/eclipse-edc/Connector/issues/2514 - */ + /* + * TODO: see of EndToEndTransfer.feature + * the current Bussinnes test is not running, because of a possible rare condition in the CI pipeline + * regarding the contract validity: see https://github.com/eclipse-edc/Connector/issues/2514 + */ - // @When("'{connector}' gets a request endpoint from '{connector}'") - public void getEndPointFromGetRequest(Connector consumer, Connector receiver, DataTable table) - throws IOException { + // @When("'{connector}' gets a request endpoint from '{connector}'") + public void getEndPointFromGetRequest(Connector consumer, Connector receiver, DataTable table) + throws IOException { - final DataManagementAPI dataManagementAPI = consumer.getDataManagementAPI(); - final String receiverIdsUrl = receiver.getEnvironment().getIdsUrl() + "/data"; + DataManagementAPI dataManagementAPI = consumer.getDataManagementAPI(); + String receiverIdsUrl = receiver.getEnvironment().getIdsUrl() + "/data"; - for (Map map : table.asMaps()) { - final String assetId = map.get("asset id"); + for (Map map : table.asMaps()) { + String assetId = map.get("asset id"); - endpointDataReference = dataManagementAPI.getEdcEndpoint(assetId, receiverIdsUrl); + endpointDataReference = dataManagementAPI.getEdcEndpoint(assetId, receiverIdsUrl); - log.debug("endpointDataReference in controlplane" + endpointDataReference.toString()); + log.debug("endpointDataReference in controlplane" + endpointDataReference.toString()); + } } - } - /* - * TODO: see EndToEndTransfer.feature - * the current Bussinnes test is not running, because of a possible rare condition in the CI pipeline - * regarding the contract validity: see https://github.com/eclipse-edc/Connector/issues/2514 - */ + /* + * TODO: see EndToEndTransfer.feature + * the current Bussinnes test is not running, because of a possible rare condition in the CI pipeline + * regarding the contract validity: see https://github.com/eclipse-edc/Connector/issues/2514 + */ - // @Then("'{connector}' asks for the asset from the endpoint") - public void receiveEndpoint(Connector consumer) throws IOException { + // @Then("'{connector}' asks for the asset from the endpoint") + public void receiveEndpoint(Connector consumer) throws IOException { - var requestUrl = endpointDataReference.getEndpoint(); - var key = endpointDataReference.getAuthKey(); - var value = endpointDataReference.getAuthCode(); - var httpClient = HttpClientBuilder.create().build(); - var get = new HttpGet(requestUrl); - get.addHeader(key, value); - final CloseableHttpResponse response = httpClient.execute(get); - var bytes = response.getEntity().getContent().readAllBytes(); - var result = new String(bytes); - var resultTransformed = new Gson().fromJson(result, HealthStatus.class); + var requestUrl = endpointDataReference.getEndpoint(); + var key = endpointDataReference.getAuthKey(); + var value = endpointDataReference.getAuthCode(); + var httpClient = HttpClientBuilder.create().build(); + var get = new HttpGet(requestUrl); + get.addHeader(key, value); + CloseableHttpResponse response = httpClient.execute(get); + var bytes = response.getEntity().getContent().readAllBytes(); + var result = new String(bytes); + var resultTransformed = new Gson().fromJson(result, HealthStatus.class); - Assertions.assertTrue(resultTransformed.isHealthy()); - Assertions.assertFalse(resultTransformed.getComponentResults().isEmpty()); - } + Assertions.assertTrue(resultTransformed.isHealthy()); + Assertions.assertFalse(resultTransformed.getComponentResults().isEmpty()); + } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/DataManagementAPI.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/DataManagementAPI.java index 735cbf175..d67dc77ca 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/DataManagementAPI.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/DataManagementAPI.java @@ -23,17 +23,6 @@ import com.google.gson.Gson; import com.google.gson.annotations.SerializedName; import com.google.gson.reflect.TypeToken; -import java.io.IOException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import lombok.Data; -import lombok.NonNull; -import lombok.extern.slf4j.Slf4j; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.CloseableHttpResponse; @@ -45,628 +34,706 @@ import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicHeader; import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; -import org.eclipse.tractusx.edc.tests.data.*; +import org.eclipse.tractusx.edc.tests.data.Asset; +import org.eclipse.tractusx.edc.tests.data.BusinessPartnerNumberConstraint; +import org.eclipse.tractusx.edc.tests.data.Constraint; +import org.eclipse.tractusx.edc.tests.data.ContractDefinition; +import org.eclipse.tractusx.edc.tests.data.ContractNegotiation; +import org.eclipse.tractusx.edc.tests.data.ContractNegotiationState; +import org.eclipse.tractusx.edc.tests.data.ContractOffer; +import org.eclipse.tractusx.edc.tests.data.DataAddress; +import org.eclipse.tractusx.edc.tests.data.HttpProxySinkDataAddress; +import org.eclipse.tractusx.edc.tests.data.HttpProxySourceDataAddress; +import org.eclipse.tractusx.edc.tests.data.Negotiation; +import org.eclipse.tractusx.edc.tests.data.NullDataAddress; +import org.eclipse.tractusx.edc.tests.data.OrConstraint; +import org.eclipse.tractusx.edc.tests.data.PayMeConstraint; +import org.eclipse.tractusx.edc.tests.data.Permission; +import org.eclipse.tractusx.edc.tests.data.Policy; +import org.eclipse.tractusx.edc.tests.data.S3DataAddress; +import org.eclipse.tractusx.edc.tests.data.Transfer; +import org.eclipse.tractusx.edc.tests.data.TransferProcess; +import org.eclipse.tractusx.edc.tests.data.TransferProcessState; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; -@Slf4j public class DataManagementAPI { - private static final String ASSET_PATH = "/assets"; - private static final String POLICY_PATH = "/policydefinitions"; - private static final String CONTRACT_DEFINITIONS_PATH = "/contractdefinitions"; - private static final String CATALOG_PATH = "/catalog"; - private static final String NEGOTIATIONS_PATH = "/contractnegotiations"; - private static final String TRANSFER_PATH = "/transferprocess"; - private static final String ADAPTER_PATH = "/adapter/asset/sync/"; - - private final String dataMgmtUrl; - private final String dataMgmtAuthKey; - private final CloseableHttpClient httpClient; - - public DataManagementAPI(String dataManagementUrl, String dataMgmtAuthKey) { - this.httpClient = HttpClientBuilder.create().build(); - this.dataMgmtUrl = dataManagementUrl; - this.dataMgmtAuthKey = dataMgmtAuthKey; - } - - public List requestCatalogFrom(String receivingConnectorUrl) throws IOException { - final String encodedUrl = URLEncoder.encode(receivingConnectorUrl, StandardCharsets.UTF_8); - final ManagementApiContractOfferCatalog catalog = - get( - CATALOG_PATH, - "providerUrl=" + encodedUrl, - new TypeToken() {}); - - log.debug("Received " + catalog.contractOffers.size() + " offers"); - - return catalog.contractOffers.stream().map(this::mapOffer).collect(Collectors.toList()); - } - - public Negotiation initiateNegotiation( - String receivingConnectorUrl, String definitionId, String assetId, Policy policy) - throws IOException { - final ManagementApiOffer offer = new ManagementApiOffer(); - offer.offerId = definitionId + ":foo"; - offer.assetId = assetId; - offer.policy = mapPolicy(policy); - offer.policy.permissions.forEach(p -> p.target = assetId); - - final ManagementApiNegotiationPayload negotiationPayload = - new ManagementApiNegotiationPayload(); - negotiationPayload.connectorAddress = receivingConnectorUrl; - negotiationPayload.offer = offer; - - final ManagementApiNegotiationResponse response = - post( - NEGOTIATIONS_PATH, - negotiationPayload, - new TypeToken() {}); - - if (response == null) - throw new RuntimeException( - "Initiated negotiation. Connector did not answer with negotiation ID."); - - log.info(String.format("Initiated negotiation (id=%s)", response.getId())); - - final String negotiationId = response.getId(); - return new Negotiation(negotiationId); - } - - public Transfer initiateTransferProcess( - String receivingConnectorUrl, - String contractAgreementId, - String assetId, - DataAddress dataAddress) - throws IOException { - final ManagementApiTransfer transfer = new ManagementApiTransfer(); - - transfer.connectorAddress = receivingConnectorUrl; - transfer.contractId = contractAgreementId; - transfer.assetId = assetId; - transfer.transferType = new ManagementApiTransferType(); - transfer.managedResources = false; - transfer.dataDestination = mapDataAddress(dataAddress); - transfer.protocol = "ids-multipart"; - - return initiateTransferProcess(transfer); - } - - public Transfer initiateTransferProcess( - String receivingConnectorUrl, - String contractAgreementId, - String assetId, - DataAddress dataAddress, - String receiverEndpoint) - throws IOException { - final ManagementApiTransfer transfer = new ManagementApiTransfer(); - - transfer.connectorAddress = receivingConnectorUrl; - transfer.contractId = contractAgreementId; - transfer.assetId = assetId; - transfer.transferType = new ManagementApiTransferType(); - transfer.managedResources = false; - transfer.dataDestination = mapDataAddress(dataAddress); - transfer.protocol = "ids-multipart"; - transfer.properties = new ManagementApiProperties(receiverEndpoint); - - return initiateTransferProcess(transfer); - } - - private Transfer initiateTransferProcess(ManagementApiTransfer transfer) throws IOException { - final ManagementApiTransferResponse response = - post(TRANSFER_PATH, transfer, new TypeToken() {}); - - if (response == null) - throw new RuntimeException( - "Initiated transfer process. Connector did not answer with transfer process ID."); - - log.info(String.format("Initiated transfer process (id=%s)", response.getId())); - - final String transferId = response.getId(); - return new Transfer(transferId); - } - - public Asset initiateTransferProcess( - String endpointUrl, String endpointAuthKey, String endpointAuthCode) throws IOException { - Header header = new BasicHeader(endpointAuthKey, endpointAuthCode); - return get(endpointUrl, header, new TypeToken() {}); - } - - public TransferProcess getTransferProcess(String id) throws IOException { - final ManagementApiTransferProcess transferProcess = - get(TRANSFER_PATH + "/" + id, new TypeToken() {}); - return mapTransferProcess(transferProcess); - } - - public ContractNegotiation getNegotiation(String id) throws IOException { - final ManagementApiNegotiation negotiation = - get(NEGOTIATIONS_PATH + "/" + id, new TypeToken() {}); - return mapNegotiation(negotiation); - } - - public List getNegotiations() throws IOException { - final List negotiations = - get(NEGOTIATIONS_PATH + "/", new TypeToken>() {}); - return negotiations.stream().map(this::mapNegotiation).collect(Collectors.toList()); - } + private static final Logger log = LoggerFactory.getLogger(DataManagementAPI.class); + private static final String ASSET_PATH = "/assets"; + private static final String POLICY_PATH = "/policydefinitions"; + private static final String CONTRACT_DEFINITIONS_PATH = "/contractdefinitions"; + private static final String CATALOG_PATH = "/catalog"; + private static final String NEGOTIATIONS_PATH = "/contractnegotiations"; + private static final String TRANSFER_PATH = "/transferprocess"; + private static final String ADAPTER_PATH = "/adapter/asset/sync/"; + + private final String dataMgmtUrl; + private final String dataMgmtAuthKey; + private final CloseableHttpClient httpClient; + + public DataManagementAPI(String dataManagementUrl, String dataMgmtAuthKey) { + httpClient = HttpClientBuilder.create().build(); + dataMgmtUrl = dataManagementUrl; + this.dataMgmtAuthKey = dataMgmtAuthKey; + } + + public List requestCatalogFrom(String receivingConnectorUrl) throws IOException { + String encodedUrl = URLEncoder.encode(receivingConnectorUrl, StandardCharsets.UTF_8); + ManagementApiContractOfferCatalog catalog = + get( + CATALOG_PATH, + "providerUrl=" + encodedUrl, + new TypeToken() { + }); + + log.debug("Received " + catalog.contractOffers.size() + " offers"); + + return catalog.contractOffers.stream().map(this::mapOffer).collect(Collectors.toList()); + } + + public Negotiation initiateNegotiation( + String receivingConnectorUrl, String definitionId, String assetId, Policy policy) + throws IOException { + ManagementApiOffer offer = new ManagementApiOffer(); + offer.offerId = definitionId + ":foo"; + offer.assetId = assetId; + offer.policy = mapPolicy(policy); + offer.policy.permissions.forEach(p -> p.target = assetId); + + ManagementApiNegotiationPayload negotiationPayload = + new ManagementApiNegotiationPayload(); + negotiationPayload.connectorAddress = receivingConnectorUrl; + negotiationPayload.offer = offer; + + ManagementApiNegotiationResponse response = + post( + NEGOTIATIONS_PATH, + negotiationPayload, + new TypeToken() { + }); + + if (response == null) { + throw new RuntimeException( + "Initiated negotiation. Connector did not answer with negotiation ID."); + } + + log.info(String.format("Initiated negotiation (id=%s)", response.getId())); + + String negotiationId = response.getId(); + return new Negotiation(negotiationId); + } + + public Transfer initiateTransferProcess( + String receivingConnectorUrl, + String contractAgreementId, + String assetId, + DataAddress dataAddress) + throws IOException { + ManagementApiTransfer transfer = new ManagementApiTransfer(); + + transfer.connectorAddress = receivingConnectorUrl; + transfer.contractId = contractAgreementId; + transfer.assetId = assetId; + transfer.transferType = new ManagementApiTransferType(); + transfer.managedResources = false; + transfer.dataDestination = mapDataAddress(dataAddress); + transfer.protocol = "ids-multipart"; + + return initiateTransferProcess(transfer); + } + + public Transfer initiateTransferProcess( + String receivingConnectorUrl, + String contractAgreementId, + String assetId, + DataAddress dataAddress, + String receiverEndpoint) + throws IOException { + ManagementApiTransfer transfer = new ManagementApiTransfer(); + + transfer.connectorAddress = receivingConnectorUrl; + transfer.contractId = contractAgreementId; + transfer.assetId = assetId; + transfer.transferType = new ManagementApiTransferType(); + transfer.managedResources = false; + transfer.dataDestination = mapDataAddress(dataAddress); + transfer.protocol = "ids-multipart"; + transfer.properties = new ManagementApiProperties(receiverEndpoint); + + return initiateTransferProcess(transfer); + } + + public Asset initiateTransferProcess( + String endpointUrl, String endpointAuthKey, String endpointAuthCode) throws IOException { + Header header = new BasicHeader(endpointAuthKey, endpointAuthCode); + return get(endpointUrl, header, new TypeToken() { + }); + } + + public TransferProcess getTransferProcess(String id) throws IOException { + ManagementApiTransferProcess transferProcess = + get(TRANSFER_PATH + "/" + id, new TypeToken() { + }); + return mapTransferProcess(transferProcess); + } + + public ContractNegotiation getNegotiation(String id) throws IOException { + ManagementApiNegotiation negotiation = + get(NEGOTIATIONS_PATH + "/" + id, new TypeToken() { + }); + return mapNegotiation(negotiation); + } + + public List getNegotiations() throws IOException { + List negotiations = + get(NEGOTIATIONS_PATH + "/", new TypeToken>() { + }); + return negotiations.stream().map(this::mapNegotiation).collect(Collectors.toList()); + } + + public void createAsset(Asset asset) throws IOException { + ManagementApiAssetCreate assetCreate = new ManagementApiAssetCreate(); + + assetCreate.asset = mapAsset(asset); + assetCreate.dataAddress = mapDataAddress(asset.getDataAddress()); + + post(ASSET_PATH, assetCreate); + } + + public void createPolicy(Policy policy) throws IOException { + post(POLICY_PATH, mapPolicyDefinition(policy)); + } + + public void createContractDefinition(ContractDefinition contractDefinition) throws IOException { + post(CONTRACT_DEFINITIONS_PATH, mapContractDefinition(contractDefinition)); + } + + public EndpointDataReference getEdcEndpoint(String assetId, String receivingConnectorUrl) + throws IOException { + String encodedUrl = ADAPTER_PATH + assetId + "?providerUrl=" + receivingConnectorUrl; + + EndpointDataReference endpoint = + get(encodedUrl, new TypeToken() { + }); + + return endpoint; + } + + private Transfer initiateTransferProcess(ManagementApiTransfer transfer) throws IOException { + ManagementApiTransferResponse response = + post(TRANSFER_PATH, transfer, new TypeToken() { + }); + + if (response == null) { + throw new RuntimeException( + "Initiated transfer process. Connector did not answer with transfer process ID."); + } - public void createAsset(Asset asset) throws IOException { - final ManagementApiAssetCreate assetCreate = new ManagementApiAssetCreate(); + log.info(String.format("Initiated transfer process (id=%s)", response.getId())); - assetCreate.asset = mapAsset(asset); - assetCreate.dataAddress = mapDataAddress(asset.getDataAddress()); + String transferId = response.getId(); + return new Transfer(transferId); + } + + private T get(String path, String params, TypeToken typeToken) throws IOException { + return get(path + "?" + params, typeToken); + } + + private T get(String path, TypeToken typeToken) throws IOException { + + HttpGet get = new HttpGet(dataMgmtUrl + path); + HttpResponse response = sendRequest(get); + byte[] json = response.getEntity().getContent().readAllBytes(); + + log.debug("Received response: {}", new String(json, StandardCharsets.UTF_8)); + return new Gson().fromJson(new String(json, StandardCharsets.UTF_8), typeToken.getType()); + } + + private T get(String path, Header header, TypeToken typeToken) throws IOException { + + HttpGet get = new HttpGet(path); + get.addHeader(header); + HttpResponse response = sendRequest(get); + byte[] json = response.getEntity().getContent().readAllBytes(); + + log.debug("Received response: {}", new String(json, StandardCharsets.UTF_8)); + return new Gson().fromJson(new String(json, StandardCharsets.UTF_8), typeToken.getType()); + } + + private void post(String path, Object object) throws IOException { + post(path, object, new TypeToken() { + }); + } + + private T post(String path, Object object, TypeToken typeToken) throws IOException { + String url = String.format("%s%s", dataMgmtUrl, path); + HttpPost post = new HttpPost(url); + post.addHeader("Content-Type", "application/json"); + + var json = new Gson().toJson(object); + + log.debug("POST Payload: " + json); + + post.setEntity(new StringEntity(json)); + CloseableHttpResponse response = sendRequest(post); + + T responseJson = null; + if (!typeToken.equals(new TypeToken() { + })) { + byte[] responseBytes = response.getEntity().getContent().readAllBytes(); + responseJson = + new Gson() + .fromJson(new String(responseBytes, StandardCharsets.UTF_8), typeToken.getType()); + } + + response.close(); + + return responseJson; + } + + private CloseableHttpResponse sendRequest(HttpRequestBase request) throws IOException { + request.addHeader("X-Api-Key", dataMgmtAuthKey); + + log.debug(String.format("Send %-6s %s", request.getMethod(), request.getURI())); + + CloseableHttpResponse response = httpClient.execute(request); + if (200 > response.getStatusLine().getStatusCode() + || response.getStatusLine().getStatusCode() >= 300) { + throw new RuntimeException( + String.format("Unexpected response: %s", response.getStatusLine())); + } + + return response; + } + + private ContractNegotiation mapNegotiation(ManagementApiNegotiation negotiation) { + + ContractNegotiationState state; + + switch (negotiation.state) { + case "ERROR": + state = ContractNegotiationState.ERROR; + break; + case "INITIAL": + state = ContractNegotiationState.INITIAL; + break; + case "DECLINED": + state = ContractNegotiationState.DECLINED; + break; + case "CONFIRMED": + state = ContractNegotiationState.CONFIRMED; + break; + default: + state = ContractNegotiationState.UNKNOWN; + } + + return new ContractNegotiation(negotiation.id, state, negotiation.contractAgreementId); + } + + private TransferProcess mapTransferProcess(ManagementApiTransferProcess transferProcess) { - post(ASSET_PATH, assetCreate); - } - - public void createPolicy(Policy policy) throws IOException { - post(POLICY_PATH, mapPolicyDefinition(policy)); - } - - public void createContractDefinition(ContractDefinition contractDefinition) throws IOException { - post(CONTRACT_DEFINITIONS_PATH, mapContractDefinition(contractDefinition)); - } - - public EndpointDataReference getEdcEndpoint(String assetId, String receivingConnectorUrl) - throws IOException { - final String encodedUrl = ADAPTER_PATH + assetId + "?providerUrl=" + receivingConnectorUrl; - - final EndpointDataReference endpoint = - get(encodedUrl, new TypeToken() {}); + TransferProcessState state; - return endpoint; - } + switch (transferProcess.state) { + case "COMPLETED": + state = TransferProcessState.COMPLETED; + break; + case "ERROR": + state = TransferProcessState.ERROR; + break; + default: + state = TransferProcessState.UNKNOWN; + } - private T get(String path, String params, TypeToken typeToken) throws IOException { - return get(path + "?" + params, typeToken); - } - - private T get(String path, TypeToken typeToken) throws IOException { - - final HttpGet get = new HttpGet(dataMgmtUrl + path); - final HttpResponse response = sendRequest(get); - final byte[] json = response.getEntity().getContent().readAllBytes(); + return new TransferProcess(transferProcess.id, state); + } + + private ManagementApiDataAddress mapDataAddress(DataAddress dataAddress) { + Objects.requireNonNull(dataAddress); + ManagementApiDataAddress apiObject = new ManagementApiDataAddress(); + + if (dataAddress instanceof HttpProxySourceDataAddress) { + var address = (HttpProxySourceDataAddress) dataAddress; + var properties = new HashMap(); + properties.put("type", "HttpData"); + properties.put("baseUrl", address.getBaseUrl()); + var oauth2Provision = address.getOauth2Provision(); + if (oauth2Provision != null) { + properties.put("oauth2:tokenUrl", oauth2Provision.getTokenUrl()); + properties.put("oauth2:clientId", oauth2Provision.getClientId()); + properties.put("oauth2:clientSecret", oauth2Provision.getClientSecret()); + properties.put("oauth2:scope", oauth2Provision.getScope()); + } + apiObject.setProperties(properties); + } else if (dataAddress instanceof HttpProxySinkDataAddress) { + apiObject.setProperties(Map.of("type", "HttpProxy")); + } else if (dataAddress instanceof S3DataAddress) { + S3DataAddress a = (S3DataAddress) dataAddress; + apiObject.setProperties( + Map.of( + "type", + "AmazonS3", + "bucketName", + a.getBucketName(), + "region", + a.getRegion(), + "keyName", + a.getKeyName())); + } else if (dataAddress instanceof NullDataAddress) { + // set something that passes validation + apiObject.setProperties(Map.of("type", "HttpData", "baseUrl", "http://localhost")); + } else { + throw new UnsupportedOperationException( + String.format( + "Cannot map data address of type %s to EDC domain", dataAddress.getClass())); + } + + return apiObject; + } + + private ManagementApiAsset mapAsset(Asset asset) { + Map properties = + Map.of( + ManagementApiAsset.ID, asset.getId(), + ManagementApiAsset.DESCRIPTION, asset.getDescription()); + + ManagementApiAsset apiObject = new ManagementApiAsset(); + apiObject.setProperties(properties); + return apiObject; + } + + private Policy mapPolicy(ManagementApiPolicy managementApiPolicy) { + String id = managementApiPolicy.uid; + List permissions = + managementApiPolicy.permissions.stream() + .map(this::mapPermission) + .collect(Collectors.toList()); + + return new Policy(id, permissions); + } - log.debug("Received response: {}", new String(json, StandardCharsets.UTF_8)); - return new Gson().fromJson(new String(json, StandardCharsets.UTF_8), typeToken.getType()); - } + private ManagementApiPolicy mapPolicy(Policy policy) { + List permissions = + policy.getPermission().stream().map(this::mapPermission).collect(Collectors.toList()); + ManagementApiPolicy managementApiPolicy = new ManagementApiPolicy(); + managementApiPolicy.permissions = permissions; - private T get(String path, Header header, TypeToken typeToken) throws IOException { + return managementApiPolicy; + } + + private ManagementApiPolicyDefinition mapPolicyDefinition(Policy policy) { + ManagementApiPolicyDefinition apiObject = new ManagementApiPolicyDefinition(); + apiObject.id = policy.getId(); + apiObject.policy = mapPolicy(policy); + return apiObject; + } + + private Permission mapPermission(ManagementApiPermission managementApiPermission) { + String target = managementApiPermission.target; + String action = managementApiPermission.action.type; + return new Permission(action, new ArrayList<>(), target); + } + + private ManagementApiPermission mapPermission(Permission permission) { + String target = permission.getTarget(); + String action = permission.getAction(); + + ManagementApiRuleAction apiAction = new ManagementApiRuleAction(); + apiAction.type = action; + + var constraints = + permission.getConstraints().stream().map(this::mapConstraint).collect(Collectors.toList()); + + ManagementApiPermission apiObject = new ManagementApiPermission(); + apiObject.target = target; + apiObject.action = apiAction; + apiObject.constraints = constraints; + return apiObject; + } + + private ManagementConstraint mapConstraint(Constraint constraint) { + if (OrConstraint.class.equals(constraint.getClass())) { + return mapConstraint((OrConstraint) constraint); + } else if (BusinessPartnerNumberConstraint.class.equals(constraint.getClass())) { + return mapConstraint((BusinessPartnerNumberConstraint) constraint); + } else if (PayMeConstraint.class.equals(constraint.getClass())) { + return mapConstraint((PayMeConstraint) constraint); + } else { + throw new UnsupportedOperationException( + "Unsupported constraint type: " + constraint.getClass().getName()); + } + } - final HttpGet get = new HttpGet(path); - get.addHeader(header); - final HttpResponse response = sendRequest(get); - final byte[] json = response.getEntity().getContent().readAllBytes(); + private ManagementAtomicConstraint mapConstraint(PayMeConstraint constraint) { + ManagementApiLiteralExpression leftExpression = new ManagementApiLiteralExpression(); + leftExpression.value = "PayMe"; - log.debug("Received response: {}", new String(json, StandardCharsets.UTF_8)); - return new Gson().fromJson(new String(json, StandardCharsets.UTF_8), typeToken.getType()); - } + ManagementApiLiteralExpression rightExpression = new ManagementApiLiteralExpression(); + rightExpression.value = String.valueOf(constraint.getAmount()); - private void post(String path, Object object) throws IOException { - post(path, object, new TypeToken() {}); - } + ManagementAtomicConstraint dataManagementApiConstraint = new ManagementAtomicConstraint(); + dataManagementApiConstraint.leftExpression = leftExpression; + dataManagementApiConstraint.rightExpression = rightExpression; + dataManagementApiConstraint.operator = "EQ"; - private T post(String path, Object object, TypeToken typeToken) throws IOException { - final String url = String.format("%s%s", dataMgmtUrl, path); - final HttpPost post = new HttpPost(url); - post.addHeader("Content-Type", "application/json"); - - var json = new Gson().toJson(object); - - log.debug("POST Payload: " + json); - - post.setEntity(new StringEntity(json)); - final CloseableHttpResponse response = sendRequest(post); - - T responseJson = null; - if (!typeToken.equals(new TypeToken() {})) { - final byte[] responseBytes = response.getEntity().getContent().readAllBytes(); - responseJson = - new Gson() - .fromJson(new String(responseBytes, StandardCharsets.UTF_8), typeToken.getType()); - } - - response.close(); - - return responseJson; - } - - private CloseableHttpResponse sendRequest(HttpRequestBase request) throws IOException { - request.addHeader("X-Api-Key", dataMgmtAuthKey); - - log.debug(String.format("Send %-6s %s", request.getMethod(), request.getURI())); - - final CloseableHttpResponse response = httpClient.execute(request); - if (200 > response.getStatusLine().getStatusCode() - || response.getStatusLine().getStatusCode() >= 300) { - throw new RuntimeException( - String.format("Unexpected response: %s", response.getStatusLine())); - } - - return response; - } - - private ContractNegotiation mapNegotiation(ManagementApiNegotiation negotiation) { - - ContractNegotiationState state; - - switch (negotiation.state) { - case "ERROR": - state = ContractNegotiationState.ERROR; - break; - case "INITIAL": - state = ContractNegotiationState.INITIAL; - break; - case "DECLINED": - state = ContractNegotiationState.DECLINED; - break; - case "CONFIRMED": - state = ContractNegotiationState.CONFIRMED; - break; - default: - state = ContractNegotiationState.UNKNOWN; - } - - return new ContractNegotiation(negotiation.id, negotiation.contractAgreementId, state); - } - - private TransferProcess mapTransferProcess(ManagementApiTransferProcess transferProcess) { - - TransferProcessState state; - - switch (transferProcess.state) { - case "COMPLETED": - state = TransferProcessState.COMPLETED; - break; - case "ERROR": - state = TransferProcessState.ERROR; - break; - default: - state = TransferProcessState.UNKNOWN; - } - - return new TransferProcess(transferProcess.id, state); - } - - private ManagementApiDataAddress mapDataAddress(@NonNull DataAddress dataAddress) { - final ManagementApiDataAddress apiObject = new ManagementApiDataAddress(); - - if (dataAddress instanceof HttpProxySourceDataAddress) { - final var address = (HttpProxySourceDataAddress) dataAddress; - var properties = new HashMap(); - properties.put("type", "HttpData"); - properties.put("baseUrl", address.getBaseUrl()); - var oauth2Provision = address.getOauth2Provision(); - if (oauth2Provision != null) { - properties.put("oauth2:tokenUrl", oauth2Provision.getTokenUrl()); - properties.put("oauth2:clientId", oauth2Provision.getClientId()); - properties.put("oauth2:clientSecret", oauth2Provision.getClientSecret()); - properties.put("oauth2:scope", oauth2Provision.getScope()); - } - apiObject.setProperties(properties); - } else if (dataAddress instanceof HttpProxySinkDataAddress) { - apiObject.setProperties(Map.of("type", "HttpProxy")); - } else if (dataAddress instanceof S3DataAddress) { - final S3DataAddress a = (S3DataAddress) dataAddress; - apiObject.setProperties( - Map.of( - "type", - "AmazonS3", - "bucketName", - a.getBucketName(), - "region", - a.getRegion(), - "keyName", - a.getKeyName())); - } else if (dataAddress instanceof NullDataAddress) { - // set something that passes validation - apiObject.setProperties(Map.of("type", "HttpData", "baseUrl", "http://localhost")); - } else { - throw new UnsupportedOperationException( - String.format( - "Cannot map data address of type %s to EDC domain", dataAddress.getClass())); - } - - return apiObject; - } - - private ManagementApiAsset mapAsset(Asset asset) { - final Map properties = - Map.of( - ManagementApiAsset.ID, asset.getId(), - ManagementApiAsset.DESCRIPTION, asset.getDescription()); - - final ManagementApiAsset apiObject = new ManagementApiAsset(); - apiObject.setProperties(properties); - return apiObject; - } - - private Policy mapPolicy(ManagementApiPolicy managementApiPolicy) { - final String id = managementApiPolicy.uid; - final List permissions = - managementApiPolicy.permissions.stream() - .map(this::mapPermission) - .collect(Collectors.toList()); - - return new Policy(id, permissions); - } - - private ManagementApiPolicy mapPolicy(Policy policy) { - final List permissions = - policy.getPermission().stream().map(this::mapPermission).collect(Collectors.toList()); - final ManagementApiPolicy managementApiPolicy = new ManagementApiPolicy(); - managementApiPolicy.permissions = permissions; - - return managementApiPolicy; - } - - private ManagementApiPolicyDefinition mapPolicyDefinition(Policy policy) { - final ManagementApiPolicyDefinition apiObject = new ManagementApiPolicyDefinition(); - apiObject.id = policy.getId(); - apiObject.policy = mapPolicy(policy); - return apiObject; - } - - private Permission mapPermission(ManagementApiPermission managementApiPermission) { - final String target = managementApiPermission.target; - final String action = managementApiPermission.action.type; - return new Permission(action, target, new ArrayList<>()); - } - - private ManagementApiPermission mapPermission(Permission permission) { - final String target = permission.getTarget(); - final String action = permission.getAction(); - - final ManagementApiRuleAction apiAction = new ManagementApiRuleAction(); - apiAction.type = action; - - var constraints = - permission.getConstraints().stream().map(this::mapConstraint).collect(Collectors.toList()); - - final ManagementApiPermission apiObject = new ManagementApiPermission(); - apiObject.target = target; - apiObject.action = apiAction; - apiObject.constraints = constraints; - return apiObject; - } - - private ManagementConstraint mapConstraint(Constraint constraint) { - if (OrConstraint.class.equals(constraint.getClass())) { - return mapConstraint((OrConstraint) constraint); - } else if (BusinessPartnerNumberConstraint.class.equals(constraint.getClass())) { - return mapConstraint((BusinessPartnerNumberConstraint) constraint); - } else if (PayMeConstraint.class.equals(constraint.getClass())) { - return mapConstraint((PayMeConstraint) constraint); - } else { - throw new UnsupportedOperationException( - "Unsupported constraint type: " + constraint.getClass().getName()); - } - } - - private ManagementAtomicConstraint mapConstraint(PayMeConstraint constraint) { - final ManagementApiLiteralExpression leftExpression = new ManagementApiLiteralExpression(); - leftExpression.value = "PayMe"; - - final ManagementApiLiteralExpression rightExpression = new ManagementApiLiteralExpression(); - rightExpression.value = String.valueOf(constraint.getAmount()); - - final ManagementAtomicConstraint dataManagementApiConstraint = new ManagementAtomicConstraint(); - dataManagementApiConstraint.leftExpression = leftExpression; - dataManagementApiConstraint.rightExpression = rightExpression; - dataManagementApiConstraint.operator = "EQ"; - - return dataManagementApiConstraint; - } - - private ManagementAtomicConstraint mapConstraint(BusinessPartnerNumberConstraint constraint) { - final ManagementApiLiteralExpression leftExpression = new ManagementApiLiteralExpression(); - leftExpression.value = "BusinessPartnerNumber"; - - final ManagementApiLiteralExpression rightExpression = new ManagementApiLiteralExpression(); - rightExpression.value = constraint.getBusinessPartnerNumber(); - - final ManagementAtomicConstraint dataManagementApiConstraint = new ManagementAtomicConstraint(); - dataManagementApiConstraint.leftExpression = leftExpression; - dataManagementApiConstraint.rightExpression = rightExpression; - dataManagementApiConstraint.operator = "EQ"; - - return dataManagementApiConstraint; - } - - private ManagementOrConstraint mapConstraint(OrConstraint constraint) { - var orConstraint = new ManagementOrConstraint(); - orConstraint.constraints = - constraint.getConstraints().stream().map(this::mapConstraint).collect(Collectors.toList()); - return orConstraint; - } - - private ContractOffer mapOffer(ManagementApiContractOffer managementApiContractOffer) { - final String id = managementApiContractOffer.id; - final String assetId = - managementApiContractOffer.assetId != null - ? managementApiContractOffer.assetId - : (String) managementApiContractOffer.asset.getProperties().get(ManagementApiAsset.ID); - - final Policy policy = mapPolicy(managementApiContractOffer.getPolicy()); - - return new ContractOffer(id, policy, assetId); - } - - private ManagementApiContractDefinition mapContractDefinition( - ContractDefinition contractDefinition) { - - final ManagementApiContractDefinition apiObject = new ManagementApiContractDefinition(); - apiObject.id = contractDefinition.getId(); - apiObject.accessPolicyId = contractDefinition.getAcccessPolicyId(); - apiObject.contractPolicyId = contractDefinition.getContractPolicyId(); - apiObject.criteria = new ArrayList<>(); - - for (final String assetId : contractDefinition.getAssetIds()) { - ManagementApiCriterion criterion = new ManagementApiCriterion(); - criterion.operandLeft = ManagementApiAsset.ID; - criterion.operator = "="; - criterion.operandRight = assetId; - - apiObject.criteria.add(criterion); - } - - return apiObject; - } - - @Data - private static class ManagementApiNegotiationResponse { - private String id; - } - - @Data - private static class ManagementApiNegotiationPayload { - private String connectorId = "foo"; - private String connectorAddress; - private ManagementApiOffer offer; - } - - @Data - private static class ManagementApiNegotiation { - private String id; - private String state; - private String contractAgreementId; - } - - @Data - private static class ManagementApiTransferProcess { - private String id; - private String state; - } - - @Data - private static class ManagementApiOffer { - private String offerId; - private String assetId; - private ManagementApiPolicy policy; - } - - @Data - private static class ManagementApiTransfer { - private String connectorId = "foo"; - private String connectorAddress; - private String contractId; - private String assetId; - private String protocol; - private ManagementApiDataAddress dataDestination; - private boolean managedResources; - private ManagementApiTransferType transferType; - private ManagementApiProperties properties; - } - - @Data - private static class ManagementApiTransferType { - private String contentType = "application/octet-stream"; - private boolean isFinite = true; - } - - @Data - private static class ManagementApiTransferResponse { - private String id; - } - - @Data - private static class ManagementApiAssetCreate { - private ManagementApiAsset asset; - private ManagementApiDataAddress dataAddress; - } - - @Data - private static class ManagementApiAsset { - public static final String ID = "asset:prop:id"; - public static final String DESCRIPTION = "asset:prop:description"; - - private Map properties; - } - - @Data - private static class ManagementApiDataAddress { - public static final String TYPE = "type"; - private Map properties; - } - - @Data - private static class ManagementApiProperties { - @SerializedName(value = "receiver.http.endpoint") - private final String receiverHttpEndpoint; - } - - @Data - private static class ManagementApiPolicyDefinition { - private String id; - private ManagementApiPolicy policy; - } - - @Data - private static class ManagementApiPolicy { - private String uid; - private List permissions = new ArrayList<>(); - } - - @Data - private static class ManagementApiPermission { - private String edctype = "dataspaceconnector:permission"; - private ManagementApiRuleAction action; - private String target; - private List constraints = new ArrayList<>(); - } - - @Data - private static class ManagementAtomicConstraint implements ManagementConstraint { - private String edctype = "AtomicConstraint"; - private ManagementApiLiteralExpression leftExpression; - private ManagementApiLiteralExpression rightExpression; - private String operator; - } - - @Data - private static class ManagementOrConstraint implements ManagementConstraint { - private String edctype = "dataspaceconnector:orconstraint"; - private List constraints; - } - - private interface ManagementConstraint {} - - @Data - private static class ManagementApiLiteralExpression { - private String edctype = "dataspaceconnector:literalexpression"; - private String value; - } - - @Data - private static class ManagementApiRuleAction { - private String type; - } - - @Data - private static class ManagementApiContractDefinition { - private String id; - private String accessPolicyId; - private String contractPolicyId; - private List criteria = new ArrayList<>(); - } - - @Data - private static class ManagementApiCriterion { - private Object operandLeft; - private String operator; - private Object operandRight; - } - - @Data - private static class ManagementApiContractOffer { - private String id; - private ManagementApiPolicy policy; - private ManagementApiAsset asset; - private String assetId; - } - - @Data - private static class ManagementApiContractOfferCatalog { - private String id; - private List contractOffers = new ArrayList<>(); - } + return dataManagementApiConstraint; + } + + private ManagementAtomicConstraint mapConstraint(BusinessPartnerNumberConstraint constraint) { + ManagementApiLiteralExpression leftExpression = new ManagementApiLiteralExpression(); + leftExpression.value = "BusinessPartnerNumber"; + + ManagementApiLiteralExpression rightExpression = new ManagementApiLiteralExpression(); + rightExpression.value = constraint.getBusinessPartnerNumber(); + + ManagementAtomicConstraint dataManagementApiConstraint = new ManagementAtomicConstraint(); + dataManagementApiConstraint.leftExpression = leftExpression; + dataManagementApiConstraint.rightExpression = rightExpression; + dataManagementApiConstraint.operator = "EQ"; + + return dataManagementApiConstraint; + } + + private ManagementOrConstraint mapConstraint(OrConstraint constraint) { + var orConstraint = new ManagementOrConstraint(); + orConstraint.constraints = + constraint.getConstraints().stream().map(this::mapConstraint).collect(Collectors.toList()); + return orConstraint; + } + + private ContractOffer mapOffer(ManagementApiContractOffer managementApiContractOffer) { + String id = managementApiContractOffer.id; + String assetId = + managementApiContractOffer.assetId != null + ? managementApiContractOffer.assetId + : (String) managementApiContractOffer.asset.getProperties().get(ManagementApiAsset.ID); + + Policy policy = mapPolicy(managementApiContractOffer.getPolicy()); + + return new ContractOffer(id, policy, assetId); + } + + private ManagementApiContractDefinition mapContractDefinition( + ContractDefinition contractDefinition) { + + ManagementApiContractDefinition apiObject = new ManagementApiContractDefinition(); + apiObject.id = contractDefinition.getId(); + apiObject.accessPolicyId = contractDefinition.getAcccessPolicyId(); + apiObject.contractPolicyId = contractDefinition.getContractPolicyId(); + apiObject.criteria = new ArrayList<>(); + + for (String assetId : contractDefinition.getAssetIds()) { + ManagementApiCriterion criterion = new ManagementApiCriterion(); + criterion.operandLeft = ManagementApiAsset.ID; + criterion.operator = "="; + criterion.operandRight = assetId; + + apiObject.criteria.add(criterion); + } + + return apiObject; + } + + private interface ManagementConstraint { + } + + + private static class ManagementApiNegotiationResponse { + private String id; + + + public String getId() { + return id; + } + } + + + private static class ManagementApiNegotiationPayload { + private final String connectorId = "foo"; + private String connectorAddress; + private ManagementApiOffer offer; + } + + private static class ManagementApiNegotiation { + private String id; + private String state; + private String contractAgreementId; + } + + private static class ManagementApiTransferProcess { + private String id; + private String state; + } + + + private static class ManagementApiOffer { + private String offerId; + private String assetId; + private ManagementApiPolicy policy; + } + + + private static class ManagementApiTransfer { + private final String connectorId = "foo"; + private String connectorAddress; + private String contractId; + private String assetId; + private String protocol; + private ManagementApiDataAddress dataDestination; + private boolean managedResources; + private ManagementApiTransferType transferType; + private ManagementApiProperties properties; + } + + + private static class ManagementApiTransferType { + private final String contentType = "application/octet-stream"; + private final boolean isFinite = true; + } + + + private static class ManagementApiTransferResponse { + private String id; + + + public String getId() { + return id; + } + } + + private static class ManagementApiAssetCreate { + private ManagementApiAsset asset; + private ManagementApiDataAddress dataAddress; + } + + private static class ManagementApiAsset { + public static final String ID = "asset:prop:id"; + public static final String DESCRIPTION = "asset:prop:description"; + + private Map properties; + + + public Map getProperties() { + return properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + } + + + private static class ManagementApiDataAddress { + public static final String TYPE = "type"; + private Map properties; + + + public Map getProperties() { + return properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + } + + + private static class ManagementApiProperties { + @SerializedName(value = "receiver.http.endpoint") + private final String receiverHttpEndpoint; + + private ManagementApiProperties(String receiverHttpEndpoint) { + this.receiverHttpEndpoint = receiverHttpEndpoint; + } + } + + + private static class ManagementApiPolicyDefinition { + private String id; + private ManagementApiPolicy policy; + } + + + private static class ManagementApiPolicy { + private String uid; + private List permissions = new ArrayList<>(); + } + + + private static class ManagementApiPermission { + private final String edctype = "dataspaceconnector:permission"; + private ManagementApiRuleAction action; + private String target; + private List constraints = new ArrayList<>(); + } + + + private static class ManagementAtomicConstraint implements ManagementConstraint { + private final String edctype = "AtomicConstraint"; + private ManagementApiLiteralExpression leftExpression; + private ManagementApiLiteralExpression rightExpression; + private String operator; + } + + + private static class ManagementOrConstraint implements ManagementConstraint { + private final String edctype = "dataspaceconnector:orconstraint"; + private List constraints; + } + + + private static class ManagementApiLiteralExpression { + private final String edctype = "dataspaceconnector:literalexpression"; + private String value; + } + + + private static class ManagementApiRuleAction { + private String type; + } + + + private static class ManagementApiContractDefinition { + private String id; + private String accessPolicyId; + private String contractPolicyId; + private List criteria = new ArrayList<>(); + } + + + private static class ManagementApiCriterion { + private Object operandLeft; + private String operator; + private Object operandRight; + } + + + private static class ManagementApiContractOffer { + private String id; + private ManagementApiPolicy policy; + private ManagementApiAsset asset; + private String assetId; + + + public ManagementApiPolicy getPolicy() { + return policy; + } + } + + + private static class ManagementApiContractOfferCatalog { + private final List contractOffers = new ArrayList<>(); + private String id; + } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/Environment.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/Environment.java index d1a199fd1..49a2353d1 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/Environment.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/Environment.java @@ -20,6 +20,9 @@ package org.eclipse.tractusx.edc.tests; +import java.util.Locale; +import java.util.Objects; + import static org.eclipse.tractusx.edc.tests.Constants.AWS_ACCESS_KEY_ID; import static org.eclipse.tractusx.edc.tests.Constants.AWS_SECRET_ACCESS_KEY; import static org.eclipse.tractusx.edc.tests.Constants.BACKEND_SERVICE_BACKEND_API_URL; @@ -32,45 +35,165 @@ import static org.eclipse.tractusx.edc.tests.Constants.EDC_AWS_ENDPOINT_OVERRIDE; import static org.eclipse.tractusx.edc.tests.Constants.IDS_URL; -import java.util.Locale; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NonNull; -import lombok.ToString; - -@Builder(access = AccessLevel.PRIVATE) -@Getter -@ToString public class Environment { - @NonNull private final String dataManagementAuthKey; - @NonNull private final String dataManagementUrl; - @NonNull private final String idsUrl; - @NonNull private final String dataPlaneUrl; - @NonNull private final String backendServiceBackendApiUrl; - @NonNull private final String databaseUrl; - @NonNull private final String databaseUser; - @NonNull private final String databasePassword; - @NonNull private final String awsEndpointOverride; - @NonNull private final String awsAccessKey; - @NonNull private final String awsSecretAccessKey; - - public static Environment byName(String name) { - name = name.toUpperCase(Locale.ROOT); - - return Environment.builder() - .dataManagementUrl(System.getenv(String.join("_", name, DATA_MANAGEMENT_URL))) - .dataManagementAuthKey(System.getenv(String.join("_", name, DATA_MANAGEMENT_API_AUTH_KEY))) - .idsUrl(System.getenv(String.join("_", name, IDS_URL))) - .dataPlaneUrl(System.getenv(String.join("_", name, DATA_PLANE_URL))) - .backendServiceBackendApiUrl( - System.getenv(String.join("_", name, BACKEND_SERVICE_BACKEND_API_URL))) - .databaseUrl(System.getenv(String.join("_", name, DATABASE_URL))) - .databaseUser(System.getenv(String.join("_", name, DATABASE_USER))) - .databasePassword(System.getenv(String.join("_", name, DATABASE_PASSWORD))) - .awsEndpointOverride(System.getenv(EDC_AWS_ENDPOINT_OVERRIDE)) - .awsAccessKey(System.getenv(String.join("_", name, AWS_ACCESS_KEY_ID))) - .awsSecretAccessKey(System.getenv(String.join("_", name, AWS_SECRET_ACCESS_KEY))) - .build(); - } + + private String awsEndpointOverride; + private String awsAccessKey; + private String awsSecretAccessKey; + private String dataManagementAuthKey; + private String dataManagementUrl; + private String idsUrl; + private String dataPlaneUrl; + private String backendServiceBackendApiUrl; + private String databaseUrl; + private String databaseUser; + private String databasePassword; + + private Environment() { + + } + + + public static Environment byName(String name) { + var upperName = name.toUpperCase(Locale.ROOT); + + return Environment.Builder.newInstance() + .dataManagementUrl(System.getenv(String.join("_", upperName, DATA_MANAGEMENT_URL))) + .dataManagementAuthKey(System.getenv(String.join("_", upperName, DATA_MANAGEMENT_API_AUTH_KEY))) + .idsUrl(System.getenv(String.join("_", upperName, IDS_URL))) + .dataPlaneUrl(System.getenv(String.join("_", upperName, DATA_PLANE_URL))) + .backendServiceBackendApiUrl( + System.getenv(String.join("_", upperName, BACKEND_SERVICE_BACKEND_API_URL))) + .databaseUrl(System.getenv(String.join("_", upperName, DATABASE_URL))) + .databaseUser(System.getenv(String.join("_", upperName, DATABASE_USER))) + .databasePassword(System.getenv(String.join("_", upperName, DATABASE_PASSWORD))) + .awsEndpointOverride(System.getenv(EDC_AWS_ENDPOINT_OVERRIDE)) + .awsAccessKey(System.getenv(String.join("_", upperName, AWS_ACCESS_KEY_ID))) + .awsSecretAccessKey(System.getenv(String.join("_", upperName, AWS_SECRET_ACCESS_KEY))) + .build(); + } + + public String getIdsUrl() { + return idsUrl; + } + + public String getAwsEndpointOverride() { + return awsEndpointOverride; + } + + public String getAwsSecretAccessKey() { + return awsSecretAccessKey; + } + + public String getAwsAccessKey() { + return awsAccessKey; + } + + public String getBackendServiceBackendApiUrl() { + return backendServiceBackendApiUrl; + } + + public String getDatabasePassword() { + return databasePassword; + } + + public String getDatabaseUrl() { + return databaseUrl; + } + + public String getDatabaseUser() { + return databaseUser; + } + + public String getDataManagementAuthKey() { + return dataManagementAuthKey; + } + + public String getDataManagementUrl() { + return dataManagementUrl; + } + + private static class Builder { + + + private final Environment environment; + + private Builder() { + environment = new Environment(); + } + + public static Builder newInstance() { + return new Builder(); + } + + public Builder awsEndpointOverride(String val) { + environment.awsEndpointOverride = val; + return this; + } + + public Builder awsAccessKey(String val) { + environment.awsAccessKey = val; + return this; + } + + public Builder awsSecretAccessKey(String val) { + environment.awsSecretAccessKey = val; + return this; + } + + public Builder dataManagementAuthKey(String val) { + environment.dataManagementAuthKey = val; + return this; + } + + public Builder dataManagementUrl(String val) { + environment.dataManagementUrl = val; + return this; + } + + public Builder idsUrl(String val) { + environment.idsUrl = val; + return this; + } + + public Builder dataPlaneUrl(String val) { + environment.dataPlaneUrl = val; + return this; + } + + public Builder backendServiceBackendApiUrl(String val) { + environment.backendServiceBackendApiUrl = val; + return this; + } + + public Builder databaseUrl(String val) { + environment.databaseUrl = val; + return this; + } + + public Builder databaseUser(String val) { + environment.databaseUser = val; + return this; + } + + public Builder databasePassword(String val) { + environment.databasePassword = val; + return this; + } + + public Environment build() { + Objects.requireNonNull(environment.awsAccessKey); + Objects.requireNonNull(environment.awsEndpointOverride); + Objects.requireNonNull(environment.awsSecretAccessKey); + Objects.requireNonNull(environment.backendServiceBackendApiUrl); + Objects.requireNonNull(environment.databaseUrl); + Objects.requireNonNull(environment.databasePassword); + Objects.requireNonNull(environment.databaseUser); + Objects.requireNonNull(environment.dataManagementUrl); + Objects.requireNonNull(environment.dataPlaneUrl); + Objects.requireNonNull(environment.dataManagementAuthKey); + Objects.requireNonNull(environment.idsUrl); + return environment; + } + } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/HttpProxyTransferSteps.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/HttpProxyTransferSteps.java index c0ae99a48..eee6e6497 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/HttpProxyTransferSteps.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/HttpProxyTransferSteps.java @@ -1,99 +1,124 @@ +/* + * 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 + */ + package org.eclipse.tractusx.edc.tests; import io.cucumber.datatable.DataTable; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; +import org.eclipse.tractusx.edc.tests.data.Asset; +import org.eclipse.tractusx.edc.tests.data.DataAddress; +import org.eclipse.tractusx.edc.tests.data.HttpProxySinkDataAddress; +import org.eclipse.tractusx.edc.tests.data.HttpProxySourceDataAddress; +import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.IOException; import java.time.Duration; import java.util.Arrays; import java.util.List; -import lombok.extern.slf4j.Slf4j; -import org.awaitility.Awaitility; -import org.eclipse.tractusx.edc.tests.data.*; -import org.junit.jupiter.api.Assertions; import static org.awaitility.Awaitility.await; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; -@Slf4j public class HttpProxyTransferSteps { - private static final String ID = "id"; - private static final String DESCRIPTION = "description"; - private static final String BASE_URL = "baseUrl"; - private static final String ASSET_ID = "asset id"; - private static final String RECEIVER_HTTP_ENDPOINT = "receiverHttpEndpoint"; - - @Given("'{connector}' has a http proxy assets") - public void hasAssets(Connector connector, DataTable table) throws Exception { - final DataManagementAPI api = connector.getDataManagementAPI(); - - for (var map : table.asMaps()) { - final String id = map.get(ID); - final String description = map.get(DESCRIPTION); - final String baseUrl = map.get(BASE_URL); - - var oauth2Provision = - Arrays.stream(Oauth2DataAddressFields.values()) - .map(it -> it.text) - .anyMatch(map::containsKey) - ? new HttpProxySourceDataAddress.Oauth2Provision( - map.get(Oauth2DataAddressFields.TOKEN_URL.text), - map.get(Oauth2DataAddressFields.CLIENT_ID.text), - map.get(Oauth2DataAddressFields.CLIENT_SECRET.text), - map.get(Oauth2DataAddressFields.SCOPE.text)) - : null; - - final DataAddress address = new HttpProxySourceDataAddress(baseUrl, oauth2Provision); - final Asset asset = new Asset(id, description, address); - - api.createAsset(asset); + private static final Logger log = LoggerFactory.getLogger(HttpProxyTransferSteps.class); + + private static final String ID = "id"; + private static final String DESCRIPTION = "description"; + private static final String BASE_URL = "baseUrl"; + private static final String ASSET_ID = "asset id"; + private static final String RECEIVER_HTTP_ENDPOINT = "receiverHttpEndpoint"; + + @Given("'{connector}' has a http proxy assets") + public void hasAssets(Connector connector, DataTable table) throws Exception { + final DataManagementAPI api = connector.getDataManagementAPI(); + + for (var map : table.asMaps()) { + final String id = map.get(ID); + final String description = map.get(DESCRIPTION); + final String baseUrl = map.get(BASE_URL); + + var oauth2Provision = + Arrays.stream(Oauth2DataAddressFields.values()) + .map(it -> it.text) + .anyMatch(map::containsKey) + ? new HttpProxySourceDataAddress.Oauth2Provision( + map.get(Oauth2DataAddressFields.TOKEN_URL.text), + map.get(Oauth2DataAddressFields.CLIENT_ID.text), + map.get(Oauth2DataAddressFields.CLIENT_SECRET.text), + map.get(Oauth2DataAddressFields.SCOPE.text)) + : null; + + final DataAddress address = new HttpProxySourceDataAddress(baseUrl, oauth2Provision); + final Asset asset = new Asset(id, description, address); + + api.createAsset(asset); + } } - } - - @When("'{connector}' initiates HttpProxy transfer from '{connector}'") - public void sokratesInitiateHttpProxyTransferProcessFromPlato( - Connector consumer, Connector provider, DataTable dataTable) throws IOException { - final DataManagementAPI api = consumer.getDataManagementAPI(); - final String receiverUrl = provider.getEnvironment().getIdsUrl() + "/data"; - - final List negotiation = api.getNegotiations(); - final String agreementId = negotiation.get(0).getAgreementId(); - final DataAddress dataAddress = new HttpProxySinkDataAddress(); - - for (var map : dataTable.asMaps()) { - final String assetId = map.get(ASSET_ID); - final String receiverHttpEndpoint = map.get(RECEIVER_HTTP_ENDPOINT); - final Transfer transfer = - api.initiateTransferProcess( - receiverUrl, agreementId, assetId, dataAddress, receiverHttpEndpoint); - - transfer.waitUntilComplete(api); + + @When("'{connector}' initiates HttpProxy transfer from '{connector}'") + public void sokratesInitiateHttpProxyTransferProcessFromPlato( + Connector consumer, Connector provider, DataTable dataTable) throws IOException { + var api = consumer.getDataManagementAPI(); + var receiverUrl = provider.getEnvironment().getIdsUrl() + "/data"; + + var negotiation = api.getNegotiations(); + var agreementId = negotiation.get(0).getAgreementId(); + var dataAddress = new HttpProxySinkDataAddress(); + + for (var map : dataTable.asMaps()) { + final String assetId = map.get(ASSET_ID); + final String receiverHttpEndpoint = map.get(RECEIVER_HTTP_ENDPOINT); + var transfer = api.initiateTransferProcess(receiverUrl, agreementId, assetId, dataAddress, receiverHttpEndpoint); + + transfer.waitUntilComplete(api); + } + } + + @Then("the backend application of '{connector}' has received data") + public void theBackendApplicationOfSocratesHasReceivedData(Connector consumer) { + var api = consumer.getBackendServiceBackendAPI(); + when(api.list(eq("/"))).thenReturn(List.of("item1", "item2")); + await() + .atMost(Duration.ofSeconds(20)) + .pollInterval(Duration.ofSeconds(1)) + .untilAsserted(() -> { + final List transferredData = api.list("/"); + Assertions.assertNotEquals(0, transferredData.size()); + }); } - } - - @Then("the backend application of '{connector}' has received data") - public void theBackendApplicationOfSocratesHasReceivedData(Connector consumer) { - final BackendServiceBackendAPI api = consumer.getBackendServiceBackendAPI(); - await() - .atMost(Duration.ofSeconds(20)) - .pollInterval(Duration.ofSeconds(1)) - .untilAsserted(() ->{ - final List transferredData = api.list("/"); - Assertions.assertNotEquals(0, transferredData.size()); - }); - } - - private enum Oauth2DataAddressFields { - TOKEN_URL("oauth2 token url"), - CLIENT_ID("oauth2 client id"), - CLIENT_SECRET("oauth2 client secret"), - SCOPE("oauth2 scope"); - - private final String text; - - Oauth2DataAddressFields(String text) { - this.text = text; + + private enum Oauth2DataAddressFields { + TOKEN_URL("oauth2 token url"), + CLIENT_ID("oauth2 client id"), + CLIENT_SECRET("oauth2 client secret"), + SCOPE("oauth2 scope"); + + private final String text; + + Oauth2DataAddressFields(String text) { + this.text = text; + } } - } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/NegotiationSteps.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/NegotiationSteps.java index 5872d2dfe..7a713ff1b 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/NegotiationSteps.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/NegotiationSteps.java @@ -23,11 +23,6 @@ import io.cucumber.datatable.DataTable; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import lombok.extern.slf4j.Slf4j; import org.eclipse.tractusx.edc.tests.data.ContractNegotiation; import org.eclipse.tractusx.edc.tests.data.ContractNegotiationState; import org.eclipse.tractusx.edc.tests.data.Negotiation; @@ -35,60 +30,66 @@ import org.eclipse.tractusx.edc.tests.data.Policy; import org.junit.jupiter.api.Assertions; -@Slf4j +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + + public class NegotiationSteps { - private static final String DEFINITION_ID = "definition id"; - private static final String ASSET_ID = "asset id"; - private ContractNegotiation lastInitiatedNegotiation; + private static final String DEFINITION_ID = "definition id"; + private static final String ASSET_ID = "asset id"; - @When("'{connector}' sends '{connector}' an offer without constraints") - public void sendAnOfferWithoutConstraints(Connector sender, Connector receiver, DataTable table) - throws IOException { + private ContractNegotiation lastInitiatedNegotiation; - final DataManagementAPI dataManagementAPI = sender.getDataManagementAPI(); - final String receiverIdsUrl = receiver.getEnvironment().getIdsUrl() + "/data"; + @When("'{connector}' sends '{connector}' an offer without constraints") + public void sendAnOfferWithoutConstraints(Connector sender, Connector receiver, DataTable table) + throws IOException { - for (Map map : table.asMaps()) { - final String definitionId = map.get(DEFINITION_ID); - final String assetId = map.get(ASSET_ID); + DataManagementAPI dataManagementAPI = sender.getDataManagementAPI(); + String receiverIdsUrl = receiver.getEnvironment().getIdsUrl() + "/data"; - final Permission permission = new Permission("USE", null, new ArrayList<>()); - final Policy policy = new Policy("foo", List.of(permission)); + for (Map map : table.asMaps()) { + String definitionId = map.get(DEFINITION_ID); + String assetId = map.get(ASSET_ID); - final Negotiation negotiation = - dataManagementAPI.initiateNegotiation(receiverIdsUrl, definitionId, assetId, policy); + Permission permission = new Permission("USE", new ArrayList<>(), null); + Policy policy = new Policy("foo", List.of(permission)); - // wait for negotiation to complete - negotiation.waitUntilComplete(dataManagementAPI); + Negotiation negotiation = + dataManagementAPI.initiateNegotiation(receiverIdsUrl, definitionId, assetId, policy); - lastInitiatedNegotiation = dataManagementAPI.getNegotiation(negotiation.getId()); + // wait for negotiation to complete + negotiation.waitUntilComplete(dataManagementAPI); + + lastInitiatedNegotiation = dataManagementAPI.getNegotiation(negotiation.getId()); + } } - } - @When("'{connector}' successfully negotiation a contract agreement with '{connector}'") - public void sokratesSuccessfullyNegotiationAContractAgreementPlatoFor( - Connector consumer, Connector provider, DataTable table) throws IOException { - final DataManagementAPI api = consumer.getDataManagementAPI(); + @When("'{connector}' successfully negotiation a contract agreement with '{connector}'") + public void sokratesSuccessfullyNegotiationAContractAgreementPlatoFor( + Connector consumer, Connector provider, DataTable table) throws IOException { + DataManagementAPI api = consumer.getDataManagementAPI(); - final Map map = table.asMap(); - final String definitionId = map.get(DEFINITION_ID); - final String assetId = map.get(ASSET_ID); + Map map = table.asMap(); + String definitionId = map.get(DEFINITION_ID); + String assetId = map.get(ASSET_ID); - // as default always the "allow all" policy is used. So we can assume this here, too. - final Permission permission = new Permission("USE", null, new ArrayList<>()); - final Policy policy = new Policy("policy-id", List.of(permission)); + // as default always the "allow all" policy is used. So we can assume this here, too. + Permission permission = new Permission("USE", new ArrayList<>(), null); + Policy policy = new Policy("policy-id", List.of(permission)); - final String receiverUrl = provider.getEnvironment().getIdsUrl(); - final Negotiation negotiation = - api.initiateNegotiation(receiverUrl, assetId, definitionId, policy); + String receiverUrl = provider.getEnvironment().getIdsUrl(); + Negotiation negotiation = + api.initiateNegotiation(receiverUrl, assetId, definitionId, policy); - negotiation.waitUntilComplete(api); - } + negotiation.waitUntilComplete(api); + } - @Then("the negotiation is declined") - public void assertLastNegotiationDeclined() { - Assertions.assertEquals(ContractNegotiationState.DECLINED, lastInitiatedNegotiation.getState()); - } + @Then("the negotiation is declined") + public void assertLastNegotiationDeclined() { + Assertions.assertEquals(ContractNegotiationState.DECLINED, lastInitiatedNegotiation.getState()); + } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/PolicyStepDefs.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/PolicyStepDefs.java index a7ede22be..d8bac4466 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/PolicyStepDefs.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/PolicyStepDefs.java @@ -20,45 +20,54 @@ package org.eclipse.tractusx.edc.tests; -import static java.util.Arrays.stream; -import static java.util.stream.Collectors.toList; - import io.cucumber.datatable.DataTable; import io.cucumber.java.en.Given; +import org.eclipse.tractusx.edc.tests.data.BusinessPartnerNumberConstraint; +import org.eclipse.tractusx.edc.tests.data.Constraint; +import org.eclipse.tractusx.edc.tests.data.OrConstraint; +import org.eclipse.tractusx.edc.tests.data.PayMeConstraint; +import org.eclipse.tractusx.edc.tests.data.Permission; +import org.eclipse.tractusx.edc.tests.data.Policy; + import java.util.ArrayList; import java.util.List; import java.util.Map; -import org.eclipse.tractusx.edc.tests.data.*; + +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.toList; public class PolicyStepDefs { - @Given("'{connector}' has the following policies") - public void hasPolicies(Connector connector, DataTable table) throws Exception { - var api = connector.getDataManagementAPI(); - var policies = table.asMaps().stream().map(this::parseRow).collect(toList()); + @Given("'{connector}' has the following policies") + public void hasPolicies(Connector connector, DataTable table) throws Exception { + var api = connector.getDataManagementAPI(); + var policies = table.asMaps().stream().map(this::parseRow).collect(toList()); - for (var policy : policies) api.createPolicy(policy); - } + for (var policy : policies) { + api.createPolicy(policy); + } + } - private Policy parseRow(Map row) { - var id = row.get("id"); - var action = row.get("action"); - var constraints = new ArrayList(); + private Policy parseRow(Map row) { + var id = row.get("id"); + var action = row.get("action"); + var constraints = new ArrayList(); - var businessPartnerNumber = row.get("businessPartnerNumber"); - if (businessPartnerNumber != null && !businessPartnerNumber.isBlank()) { - var bpnConstraints = - stream(businessPartnerNumber.split(",")) - .map(BusinessPartnerNumberConstraint::new) - .collect(toList()); - constraints.add(new OrConstraint(bpnConstraints)); - } + var businessPartnerNumber = row.get("businessPartnerNumber"); + if (businessPartnerNumber != null && !businessPartnerNumber.isBlank()) { + var bpnConstraints = + stream(businessPartnerNumber.split(",")) + .map(BusinessPartnerNumberConstraint::new) + .collect(toList()); + constraints.add(new OrConstraint(bpnConstraints)); + } - var payMe = row.get("payMe"); - if (payMe != null && !payMe.isBlank()) - constraints.add(new PayMeConstraint(Double.parseDouble(payMe))); + var payMe = row.get("payMe"); + if (payMe != null && !payMe.isBlank()) { + constraints.add(new PayMeConstraint(Double.parseDouble(payMe))); + } - var permission = new Permission(action, null, constraints); - return new Policy(id, List.of(permission)); - } + var permission = new Permission(action, constraints, null); + return new Policy(id, List.of(permission)); + } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/S3FileTransferStepsDefs.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/S3FileTransferStepsDefs.java index c4bc85a27..05f5f1242 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/S3FileTransferStepsDefs.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/S3FileTransferStepsDefs.java @@ -19,21 +19,10 @@ package org.eclipse.tractusx.edc.tests; -import static org.awaitility.Awaitility.await; -import static org.junit.jupiter.api.Assertions.fail; - import io.cucumber.datatable.DataTable; import io.cucumber.java.AfterAll; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; import org.eclipse.tractusx.edc.tests.data.Asset; import org.eclipse.tractusx.edc.tests.data.DataAddress; import org.eclipse.tractusx.edc.tests.data.Negotiation; @@ -45,135 +34,145 @@ import org.eclipse.tractusx.edc.tests.util.Timeouts; import org.junit.jupiter.api.Assertions; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.fail; + public class S3FileTransferStepsDefs { - @Given("'{connector}' has an empty storage bucket called {string}") - public void hasEmptyStorageBucket(Connector connector, String bucketName) { - S3Client s3 = connector.getS3Client(); + private static final String COMPLETION_MARKER = ".complete"; + private File fileToTransfer; + private String assetId; + private String agreementId; - s3.createBucket(bucketName); + @AfterAll + public static void bucketsCleanup() { + S3Client s3 = new S3Client(Environment.byName("Sokrates")); + s3.deleteAllBuckets(); + } - Assertions.assertTrue(s3.listBuckets().contains(bucketName)); - Assertions.assertEquals(0, s3.listBucketContent(bucketName).size()); - } + @Given("'{connector}' has an empty storage bucket called {string}") + public void hasEmptyStorageBucket(Connector connector, String bucketName) { + S3Client s3 = connector.getS3Client(); - private File fileToTransfer; + s3.createBucket(bucketName); - @Given("'{connector}' has a storage bucket called {string} with the file called {string}") - public void hasAStorageBucketWithTheFile(Connector connector, String bucketName, String fileName) - throws IOException { + Assertions.assertTrue(s3.listBuckets().contains(bucketName)); + Assertions.assertEquals(0, s3.listBucketContent(bucketName).size()); + } - S3Client s3 = connector.getS3Client(); - s3.createBucket(bucketName); - fileToTransfer = s3.uploadFile(bucketName, fileName); + @Given("'{connector}' has a storage bucket called {string} with the file called {string}") + public void hasAStorageBucketWithTheFile(Connector connector, String bucketName, String fileName) + throws IOException { - Set bucketContent = s3.listBucketContent(bucketName); + S3Client s3 = connector.getS3Client(); + s3.createBucket(bucketName); + fileToTransfer = s3.uploadFile(bucketName, fileName); - Assertions.assertEquals(1, bucketContent.size()); - Assertions.assertTrue(bucketContent.contains(fileName)); - } + Set bucketContent = s3.listBucketContent(bucketName); - @Given("'{connector}' has the following S3 assets") - public void hasAssets(Connector connector, DataTable table) { - final DataManagementAPI api = connector.getDataManagementAPI(); + Assertions.assertEquals(1, bucketContent.size()); + Assertions.assertTrue(bucketContent.contains(fileName)); + } - parseDataTable(table) - .forEach( - asset -> { - try { - api.createAsset(asset); - } catch (IOException e) { - fail(e.getMessage()); - } - }); - } + @Given("'{connector}' has the following S3 assets") + public void hasAssets(Connector connector, DataTable table) { + DataManagementAPI api = connector.getDataManagementAPI(); + + parseDataTable(table) + .forEach( + asset -> { + try { + api.createAsset(asset); + } catch (IOException e) { + fail(e.getMessage()); + } + }); + } - private String assetId; - private String agreementId; + @Then("'{connector}' negotiates the contract successfully with '{connector}'") + public void negotiateContract(Connector sender, Connector receiver, DataTable dataTable) + throws IOException { - @Then("'{connector}' negotiates the contract successfully with '{connector}'") - public void negotiateContract(Connector sender, Connector receiver, DataTable dataTable) - throws IOException { + String definitionId = dataTable.asMaps().get(0).get("contract offer id"); + assetId = dataTable.asMaps().get(0).get("asset id"); + String policyId = dataTable.asMaps().get(0).get("policy id"); - String definitionId = dataTable.asMaps().get(0).get("contract offer id"); - assetId = dataTable.asMaps().get(0).get("asset id"); - String policyId = dataTable.asMaps().get(0).get("policy id"); + Policy policy = + new Policy(policyId, List.of(new Permission("USE", new ArrayList<>(), null))); - final Policy policy = - new Policy(policyId, List.of(new Permission("USE", null, new ArrayList<>()))); + DataManagementAPI dataManagementAPI = sender.getDataManagementAPI(); + String receiverIdsUrl = receiver.getEnvironment().getIdsUrl() + "/data"; - final DataManagementAPI dataManagementAPI = sender.getDataManagementAPI(); - final String receiverIdsUrl = receiver.getEnvironment().getIdsUrl() + "/data"; + Negotiation negotiation = + dataManagementAPI.initiateNegotiation(receiverIdsUrl, definitionId, assetId, policy); + negotiation.waitUntilComplete(dataManagementAPI); - final Negotiation negotiation = - dataManagementAPI.initiateNegotiation(receiverIdsUrl, definitionId, assetId, policy); - negotiation.waitUntilComplete(dataManagementAPI); + agreementId = dataManagementAPI.getNegotiation(negotiation.getId()).getAgreementId(); + } - agreementId = dataManagementAPI.getNegotiation(negotiation.getId()).getAgreementId(); - } + @Then("'{connector}' initiate S3 transfer process from '{connector}'") + public void initiateTransferProcess(Connector sender, Connector receiver, DataTable dataTable) + throws IOException { + DataAddress dataAddress = createDataAddress(dataTable.asMaps().get(0)); - @Then("'{connector}' initiate S3 transfer process from '{connector}'") - public void initiateTransferProcess(Connector sender, Connector receiver, DataTable dataTable) - throws IOException { - DataAddress dataAddress = createDataAddress(dataTable.asMaps().get(0)); + DataManagementAPI dataManagementAPI = sender.getDataManagementAPI(); + String receiverIdsUrl = receiver.getEnvironment().getIdsUrl() + "/data"; - final DataManagementAPI dataManagementAPI = sender.getDataManagementAPI(); - final String receiverIdsUrl = receiver.getEnvironment().getIdsUrl() + "/data"; + Transfer transferProcess = + dataManagementAPI.initiateTransferProcess( + receiverIdsUrl, agreementId, assetId, dataAddress); + transferProcess.waitUntilComplete(dataManagementAPI); - final Transfer transferProcess = - dataManagementAPI.initiateTransferProcess( - receiverIdsUrl, agreementId, assetId, dataAddress); - transferProcess.waitUntilComplete(dataManagementAPI); + Assertions.assertNotNull(transferProcess.getId()); + } - Assertions.assertNotNull(transferProcess.getId()); - } + @Then("'{connector}' has a storage bucket called {string} with transferred file called {string}") + public void consumerHasAStorageBucketWithFileTransferred( + Connector connector, String bucketName, String fileName) throws IOException { + S3Client s3 = connector.getS3Client(); + await() + .pollDelay(Duration.ofMillis(500)) + .atMost(Timeouts.FILE_TRANSFER) + .until(() -> isFilePresent(s3, bucketName, fileName + COMPLETION_MARKER)); + + Set bucketContent = s3.listBucketContent(bucketName); + + Assertions.assertEquals(2, bucketContent.size()); + Assertions.assertTrue(bucketContent.contains(fileName)); + Assertions.assertArrayEquals( + Files.readAllBytes(fileToTransfer.toPath()), + Files.readAllBytes(s3.downloadFile(bucketName, fileName).toPath())); + } - private static final String COMPLETION_MARKER = ".complete"; + private boolean isFilePresent(S3Client s3, String bucketName, String fileName) { + return s3.listBucketContent(bucketName).contains(fileName); + } - @Then("'{connector}' has a storage bucket called {string} with transferred file called {string}") - public void consumerHasAStorageBucketWithFileTransferred( - Connector connector, String bucketName, String fileName) throws IOException { - S3Client s3 = connector.getS3Client(); - await() - .pollDelay(Duration.ofMillis(500)) - .atMost(Timeouts.FILE_TRANSFER) - .until(() -> isFilePresent(s3, bucketName, fileName + COMPLETION_MARKER)); + private List parseDataTable(DataTable table) { + List assetsWithDataAddresses = new ArrayList<>(); - Set bucketContent = s3.listBucketContent(bucketName); + for (Map map : table.asMaps()) { + String id = map.get("id"); + String description = map.get("description"); + assetsWithDataAddresses.add(new Asset(id, description, createDataAddress(map))); + } - Assertions.assertEquals(2, bucketContent.size()); - Assertions.assertTrue(bucketContent.contains(fileName)); - Assertions.assertArrayEquals( - Files.readAllBytes(fileToTransfer.toPath()), - Files.readAllBytes(s3.downloadFile(bucketName, fileName).toPath())); - } - - private boolean isFilePresent(S3Client s3, String bucketName, String fileName) { - return s3.listBucketContent(bucketName).contains(fileName); - } - - private List parseDataTable(DataTable table) { - final List assetsWithDataAddresses = new ArrayList<>(); - - for (Map map : table.asMaps()) { - String id = map.get("id"); - String description = map.get("description"); - assetsWithDataAddresses.add(new Asset(id, description, createDataAddress(map))); + return assetsWithDataAddresses; } - return assetsWithDataAddresses; - } - - private DataAddress createDataAddress(Map map) { - final String bucketName = map.get("data_address_s3_bucket_name"); - final String region = map.get("data_address_s3_region"); - final String keyName = map.get("data_address_s3_key_name"); - return new S3DataAddress(bucketName, region, keyName); - } - - @AfterAll - public static void bucketsCleanup() { - S3Client s3 = new S3Client(Environment.byName("Sokrates")); - s3.deleteAllBuckets(); - } + private DataAddress createDataAddress(Map map) { + String bucketName = map.get("data_address_s3_bucket_name"); + String region = map.get("data_address_s3_region"); + String keyName = map.get("data_address_s3_key_name"); + return new S3DataAddress(bucketName, region, keyName); + } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Asset.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Asset.java index acccef8d8..47142d0e9 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Asset.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Asset.java @@ -19,14 +19,28 @@ */ package org.eclipse.tractusx.edc.tests.data; -import lombok.NonNull; -import lombok.Value; +import java.util.Objects; -@Value public class Asset { - @NonNull String Id; + private final String id; + private final String description; + private final DataAddress dataAddress; - @NonNull String description; + public Asset(String id, String description, DataAddress dataAddress) { + this.id = Objects.requireNonNull(id); + this.description = Objects.requireNonNull(description); + this.dataAddress = Objects.requireNonNull(dataAddress); + } - @NonNull DataAddress dataAddress; + public String getId() { + return id; + } + + public String getDescription() { + return description; + } + + public DataAddress getDataAddress() { + return dataAddress; + } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/BusinessPartnerNumberConstraint.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/BusinessPartnerNumberConstraint.java index b9c64d158..a103313ee 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/BusinessPartnerNumberConstraint.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/BusinessPartnerNumberConstraint.java @@ -1,10 +1,35 @@ +/* + * 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 + */ + package org.eclipse.tractusx.edc.tests.data; -import lombok.NonNull; -import lombok.Value; +import java.util.Objects; -@Value public class BusinessPartnerNumberConstraint implements Constraint { - @NonNull String businessPartnerNumber; + private final String businessPartnerNumber; + + public BusinessPartnerNumberConstraint(String businessPartnerNumber) { + this.businessPartnerNumber = Objects.requireNonNull(businessPartnerNumber); + } + + public String getBusinessPartnerNumber() { + return businessPartnerNumber; + } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Constraint.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Constraint.java index c63fbed8a..73bccde7a 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Constraint.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Constraint.java @@ -1,3 +1,23 @@ +/* + * 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 + */ + package org.eclipse.tractusx.edc.tests.data; -public interface Constraint {} +public interface Constraint { +} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractDefinition.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractDefinition.java index a9fca04a1..c90fe1788 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractDefinition.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractDefinition.java @@ -20,17 +20,42 @@ package org.eclipse.tractusx.edc.tests.data; import java.util.List; -import lombok.NonNull; -import lombok.Value; +import java.util.Objects; + -@Value public class ContractDefinition { - @NonNull String id; + private final String id; + + private final String contractPolicyId; + private final String acccessPolicyId; + + private final List assetIds; + private final Long validity; + + public ContractDefinition(String id, String contractPolicyId, String acccessPolicyId, List assetIds, Long validity) { + this.id = Objects.requireNonNull(id); + this.contractPolicyId = Objects.requireNonNull(contractPolicyId); + this.acccessPolicyId = Objects.requireNonNull(acccessPolicyId); + this.assetIds = assetIds; + this.validity = validity; + } + + public String getId() { + return id; + } + + public String getContractPolicyId() { + return contractPolicyId; + } + + public String getAcccessPolicyId() { + return acccessPolicyId; + } + + public List getAssetIds() { + return assetIds; + } - @NonNull String contractPolicyId; - @NonNull String acccessPolicyId; - List assetIds; - Long validity; } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractNegotiation.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractNegotiation.java index 109249744..67f9dafb0 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractNegotiation.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractNegotiation.java @@ -20,12 +20,29 @@ package org.eclipse.tractusx.edc.tests.data; -import lombok.NonNull; -import lombok.Value; +import java.util.Objects; -@Value public class ContractNegotiation { - @NonNull String id; - String agreementId; - @NonNull ContractNegotiationState state; + private final String id; + private final ContractNegotiationState state; + private final String agreementId; + + + public ContractNegotiation(String id, ContractNegotiationState state, String agreementId) { + this.id = Objects.requireNonNull(id); + this.state = Objects.requireNonNull(state); + this.agreementId = agreementId; + } + + public String getId() { + return id; + } + + public ContractNegotiationState getState() { + return state; + } + + public String getAgreementId() { + return agreementId; + } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractOffer.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractOffer.java index 75dfd8d27..7ac87cb9a 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractOffer.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractOffer.java @@ -19,12 +19,28 @@ */ package org.eclipse.tractusx.edc.tests.data; -import lombok.NonNull; -import lombok.Value; +import java.util.Objects; -@Value public class ContractOffer { - @NonNull String id; - Policy policy; - String assetId; + private final String id; + private final Policy policy; + private final String assetId; + + public ContractOffer(String id, Policy policy, String assetId) { + this.id = Objects.requireNonNull(id); + this.policy = policy; + this.assetId = assetId; + } + + public String getId() { + return id; + } + + public Policy getPolicy() { + return policy; + } + + public String getAssetId() { + return assetId; + } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/HttpProxySinkDataAddress.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/HttpProxySinkDataAddress.java index 503701e24..2466438be 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/HttpProxySinkDataAddress.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/HttpProxySinkDataAddress.java @@ -1,3 +1,23 @@ +/* + * 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 + */ + package org.eclipse.tractusx.edc.tests.data; -public class HttpProxySinkDataAddress implements DataAddress {} +public class HttpProxySinkDataAddress implements DataAddress { +} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/HttpProxySourceDataAddress.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/HttpProxySourceDataAddress.java index 4a5946cc9..b9e92a4e5 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/HttpProxySourceDataAddress.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/HttpProxySourceDataAddress.java @@ -1,18 +1,71 @@ +/* + * 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 + */ + package org.eclipse.tractusx.edc.tests.data; -import lombok.NonNull; -import lombok.Value; +import java.util.Objects; + -@Value public class HttpProxySourceDataAddress implements DataAddress { - @NonNull String baseUrl; - Oauth2Provision oauth2Provision; - - @Value - public static class Oauth2Provision { - @NonNull String tokenUrl; - @NonNull String clientId; - @NonNull String clientSecret; - String scope; - } + private final String baseUrl; + private final Oauth2Provision oauth2Provision; + + public HttpProxySourceDataAddress(String baseUrl, Oauth2Provision oauth2Provision) { + this.baseUrl = Objects.requireNonNull(baseUrl); + this.oauth2Provision = oauth2Provision; + } + + public String getBaseUrl() { + return baseUrl; + } + + public Oauth2Provision getOauth2Provision() { + return oauth2Provision; + } + + public static class Oauth2Provision { + private final String tokenUrl; + private final String clientId; + private final String clientSecret; + private final String scope; + + public Oauth2Provision(String tokenUrl, String clientId, String clientSecret, String scope) { + this.tokenUrl = Objects.requireNonNull(tokenUrl); + this.clientId = Objects.requireNonNull(clientId); + this.clientSecret = Objects.requireNonNull(clientSecret); + this.scope = scope; + } + + public String getTokenUrl() { + return tokenUrl; + } + + public String getScope() { + return scope; + } + + public String getClientId() { + return clientId; + } + + public String getClientSecret() { + return clientSecret; + } + } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Negotiation.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Negotiation.java index 40845e4c0..eb650139f 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Negotiation.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Negotiation.java @@ -1,34 +1,62 @@ +/* + * 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 + */ + package org.eclipse.tractusx.edc.tests.data; -import static org.awaitility.Awaitility.await; +import org.eclipse.tractusx.edc.tests.DataManagementAPI; +import org.eclipse.tractusx.edc.tests.util.Timeouts; -import groovyjarjarantlr4.v4.runtime.misc.NotNull; import java.io.IOException; import java.time.Duration; +import java.util.Objects; import java.util.stream.Stream; -import lombok.Value; -import org.eclipse.tractusx.edc.tests.DataManagementAPI; -import org.eclipse.tractusx.edc.tests.util.Timeouts; -@Value +import static org.awaitility.Awaitility.await; + + public class Negotiation { - @NotNull String id; - - public void waitUntilComplete(DataManagementAPI dataManagementAPI) { - await() - .pollDelay(Duration.ofMillis(2000)) - .atMost(Timeouts.CONTRACT_NEGOTIATION) - .until(() -> isComplete(dataManagementAPI)); - } - - public boolean isComplete(DataManagementAPI dataManagementAPI) throws IOException { - var negotiation = dataManagementAPI.getNegotiation(id); - return negotiation != null - && Stream.of( - ContractNegotiationState.ERROR, - ContractNegotiationState.CONFIRMED, - ContractNegotiationState.DECLINED) - .anyMatch((l) -> l.equals(negotiation.getState())); - } + + private final String id; + + public Negotiation(String id) { + this.id = Objects.requireNonNull(id); + } + + public void waitUntilComplete(DataManagementAPI dataManagementAPI) { + await() + .pollDelay(Duration.ofMillis(5000)) + .atMost(Timeouts.CONTRACT_NEGOTIATION) + .until(() -> isComplete(dataManagementAPI)); + } + + public boolean isComplete(DataManagementAPI dataManagementAPI) throws IOException { + var negotiation = dataManagementAPI.getNegotiation(id); + return negotiation != null + && Stream.of( + ContractNegotiationState.ERROR, + ContractNegotiationState.CONFIRMED, + ContractNegotiationState.DECLINED) + .anyMatch((l) -> l.equals(negotiation.getState())); + } + + public String getId() { + return id; + } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/NullDataAddress.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/NullDataAddress.java index 778600b86..0757fa4cd 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/NullDataAddress.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/NullDataAddress.java @@ -1,10 +1,30 @@ +/* + * 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 + */ + package org.eclipse.tractusx.edc.tests.data; public class NullDataAddress implements DataAddress { - private static final NullDataAddress _instance = new NullDataAddress(); + private static final NullDataAddress _instance = new NullDataAddress(); - public static DataAddress INSTANCE = new NullDataAddress(); + public static DataAddress INSTANCE = new NullDataAddress(); - private NullDataAddress() {} + private NullDataAddress() { + } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/OrConstraint.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/OrConstraint.java index 88bf3438d..14d135f18 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/OrConstraint.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/OrConstraint.java @@ -1,11 +1,37 @@ +/* + * 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 + */ + package org.eclipse.tractusx.edc.tests.data; import java.util.List; -import lombok.NonNull; -import lombok.Value; +import java.util.Objects; + -@Value public class OrConstraint implements Constraint { - @NonNull List constraints; + private final List constraints; + + public OrConstraint(List constraints) { + this.constraints = Objects.requireNonNull(constraints); + } + + public List getConstraints() { + return constraints; + } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/PayMeConstraint.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/PayMeConstraint.java index 4376346b2..1412b8d76 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/PayMeConstraint.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/PayMeConstraint.java @@ -20,13 +20,19 @@ package org.eclipse.tractusx.edc.tests.data; -import lombok.Value; - /** * The PayMe constraint should be used when no constraint validation/enforcement in the EDC is * intended. */ -@Value + public class PayMeConstraint implements Constraint { - double amount; + private final double amount; + + public PayMeConstraint(double amount) { + this.amount = amount; + } + + public double getAmount() { + return amount; + } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Permission.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Permission.java index 128e6686f..e90cbfaf0 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Permission.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Permission.java @@ -20,13 +20,30 @@ package org.eclipse.tractusx.edc.tests.data; import java.util.List; -import lombok.NonNull; -import lombok.Value; +import java.util.Objects; + -@Value public class Permission { - @NonNull String action; - String target; + private final String action; + private final List constraints; + private final String target; + + + public Permission(String action, List constraints, String target) { + this.action = Objects.requireNonNull(action); + this.constraints = Objects.requireNonNull(constraints); + this.target = target; + } + + public String getAction() { + return action; + } + + public List getConstraints() { + return constraints; + } - @NonNull List constraints; + public String getTarget() { + return target; + } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Policy.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Policy.java index 27ea65d7a..c58c79206 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Policy.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Policy.java @@ -21,11 +21,23 @@ package org.eclipse.tractusx.edc.tests.data; import java.util.List; -import lombok.NonNull; -import lombok.Value; +import java.util.Objects; + -@Value public class Policy { - String id; - @NonNull List Permission; + private final String id; + private final List Permission; + + public Policy(String id, List permission) { + this.id = id; + Permission = Objects.requireNonNull(permission); + } + + public String getId() { + return id; + } + + public List getPermission() { + return Permission; + } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/S3DataAddress.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/S3DataAddress.java index a59843447..d46b51ea0 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/S3DataAddress.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/S3DataAddress.java @@ -1,12 +1,42 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + package org.eclipse.tractusx.edc.tests.data; -import lombok.NonNull; -import lombok.Value; +import java.util.Objects; -@Value public class S3DataAddress implements DataAddress { - @NonNull String bucketName; - @NonNull String region; - @NonNull String keyName; + private final String bucketName; + private final String region; + private final String keyName; + + public S3DataAddress(String bucketName, String region, String keyName) { + this.bucketName = Objects.requireNonNull(bucketName); + this.region = Objects.requireNonNull(region); + this.keyName = Objects.requireNonNull(keyName); + } + + public String getBucketName() { + return bucketName; + } + + public String getRegion() { + return region; + } + + public String getKeyName() { + return keyName; + } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Transfer.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Transfer.java index ebd722d07..de409252d 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Transfer.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Transfer.java @@ -1,31 +1,60 @@ +/* + * 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 + */ + package org.eclipse.tractusx.edc.tests.data; -import static org.awaitility.Awaitility.await; +import org.eclipse.tractusx.edc.tests.DataManagementAPI; +import org.eclipse.tractusx.edc.tests.util.Timeouts; import java.io.IOException; import java.time.Duration; -import lombok.Value; -import org.eclipse.tractusx.edc.tests.DataManagementAPI; -import org.eclipse.tractusx.edc.tests.util.Timeouts; -@Value +import static org.awaitility.Awaitility.await; + + public class Transfer { - String id; + private final String id; + + public Transfer(String id) { + this.id = id; + } + + public void waitUntilComplete(DataManagementAPI dataManagementAPI) { + await() + .pollDelay(Duration.ofMillis(2000)) + .atMost(Timeouts.FILE_TRANSFER) + .until(() -> isComplete(dataManagementAPI)); + } - public void waitUntilComplete(DataManagementAPI dataManagementAPI) { - await() - .pollDelay(Duration.ofMillis(2000)) - .atMost(Timeouts.FILE_TRANSFER) - .until(() -> isComplete(dataManagementAPI)); - } + public boolean isComplete(DataManagementAPI dataManagementAPI) throws IOException { + var transferProcess = dataManagementAPI.getTransferProcess(id); + if (transferProcess == null) { + return false; + } - public boolean isComplete(DataManagementAPI dataManagementAPI) throws IOException { - var transferProcess = dataManagementAPI.getTransferProcess(id); - if (transferProcess == null) return false; + var state = transferProcess.getState(); - var state = transferProcess.getState(); + return state == TransferProcessState.COMPLETED || state == TransferProcessState.ERROR; + } - return state == TransferProcessState.COMPLETED || state == TransferProcessState.ERROR; - } + public String getId() { + return id; + } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/TransferProcess.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/TransferProcess.java index 28be5157a..1c00e86c3 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/TransferProcess.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/TransferProcess.java @@ -19,11 +19,22 @@ package org.eclipse.tractusx.edc.tests.data; -import lombok.NonNull; -import lombok.Value; +import java.util.Objects; -@Value public class TransferProcess { - @NonNull String id; - @NonNull TransferProcessState state; + private final String id; + private final TransferProcessState state; + + public TransferProcess(String id, TransferProcessState state) { + this.id = Objects.requireNonNull(id); + this.state = Objects.requireNonNull(state); + } + + public String getId() { + return id; + } + + public TransferProcessState getState() { + return state; + } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/util/DatabaseCleaner.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/util/DatabaseCleaner.java index 53aececa9..e03f38e98 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/util/DatabaseCleaner.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/util/DatabaseCleaner.java @@ -24,28 +24,33 @@ import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; -import lombok.RequiredArgsConstructor; -@RequiredArgsConstructor + public class DatabaseCleaner { - private static final String SQL = - "DELETE FROM edc_contract_negotiation;\n" - + "DELETE FROM edc_contract_agreement;\n" - + "DELETE FROM edc_transfer_process;\n" - + "DELETE FROM edc_contract_definitions;\n" - + "DELETE FROM edc_policydefinitions;\n" - + "DELETE FROM edc_asset;\n" - + "DELETE FROM edc_lease;"; - - private final String url; - private final String user; - private final String password; - - public void run() throws SQLException { - try (Connection con = DriverManager.getConnection(url, user, password)) { - Statement st = con.createStatement(); - st.executeUpdate(SQL); + private static final String SQL = + "DELETE FROM edc_contract_negotiation;\n" + + "DELETE FROM edc_contract_agreement;\n" + + "DELETE FROM edc_transfer_process;\n" + + "DELETE FROM edc_contract_definitions;\n" + + "DELETE FROM edc_policydefinitions;\n" + + "DELETE FROM edc_asset;\n" + + "DELETE FROM edc_lease;"; + + private final String url; + private final String user; + private final String password; + + public DatabaseCleaner(String url, String user, String password) { + this.url = url; + this.user = user; + this.password = password; + } + + public void run() throws SQLException { + try (Connection con = DriverManager.getConnection(url, user, password)) { + Statement st = con.createStatement(); + st.executeUpdate(SQL); + } } - } } diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/util/S3Client.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/util/S3Client.java index c2779ce0d..63ab60324 100644 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/util/S3Client.java +++ b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/util/S3Client.java @@ -19,16 +19,9 @@ package org.eclipse.tractusx.edc.tests.util; -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; -import lombok.extern.slf4j.Slf4j; import org.eclipse.tractusx.edc.tests.Environment; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.core.ResponseBytes; @@ -45,80 +38,89 @@ import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.awssdk.services.s3.model.S3Object; -@Slf4j +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + + public class S3Client { + private static final Logger log = LoggerFactory.getLogger(S3Client.class); + private final software.amazon.awssdk.services.s3.S3Client s3; + + public S3Client(Environment environment) { + + s3 = + software.amazon.awssdk.services.s3.S3Client.builder() + .region(Region.US_EAST_1) + .forcePathStyle(true) + .endpointOverride(URI.create(environment.getAwsEndpointOverride())) + .credentialsProvider( + StaticCredentialsProvider.create( + AwsBasicCredentials.create( + environment.getAwsAccessKey(), environment.getAwsSecretAccessKey()))) + .build(); + } + + public void createBucket(String bucketName) { + try { + s3.createBucket(CreateBucketRequest.builder().bucket(bucketName).build()); + } catch (BucketAlreadyOwnedByYouException e) { + log.info("'{}' bucket already owned - skipped bucket creation", bucketName); + } + } + + public File uploadFile(String bucketName, String fileName) throws IOException { + File tempFile = File.createTempFile(fileName, null); + Files.write( + tempFile.toPath(), "Will fail if the file has no content".getBytes(StandardCharsets.UTF_8)); + + s3.putObject( + PutObjectRequest.builder().bucket(bucketName).key(fileName).build(), + RequestBody.fromFile(tempFile)); + + return tempFile; + } + + public List listBuckets() { + return s3.listBuckets().buckets().stream().map(Bucket::name).collect(Collectors.toList()); + } + + public Set listBucketContent(String bucketName) { + return s3 + .listObjects(ListObjectsRequest.builder().bucket(bucketName).build()) + .contents() + .stream() + .map(S3Object::key) + .collect(Collectors.toSet()); + } + + public File downloadFile(String bucketName, String fileName) throws IOException { + ResponseBytes objectAsBytes = + s3.getObjectAsBytes(GetObjectRequest.builder().bucket(bucketName).key(fileName).build()); + + return Files.write(File.createTempFile(fileName, null).toPath(), objectAsBytes.asByteArray()) + .toFile(); + } + + public void deleteAllBuckets() { + List buckets = s3.listBuckets().buckets(); + buckets.forEach(this::clearBucket); + buckets.forEach( + bucket -> s3.deleteBucket(DeleteBucketRequest.builder().bucket(bucket.name()).build())); + } - private final software.amazon.awssdk.services.s3.S3Client s3; - - public S3Client(Environment environment) { - - s3 = - software.amazon.awssdk.services.s3.S3Client.builder() - .region(Region.US_EAST_1) - .forcePathStyle(true) - .endpointOverride(URI.create(environment.getAwsEndpointOverride())) - .credentialsProvider( - StaticCredentialsProvider.create( - AwsBasicCredentials.create( - environment.getAwsAccessKey(), environment.getAwsSecretAccessKey()))) - .build(); - } - - public void createBucket(String bucketName) { - try { - s3.createBucket(CreateBucketRequest.builder().bucket(bucketName).build()); - } catch (BucketAlreadyOwnedByYouException e) { - log.info("'{}' bucket already owned - skipped bucket creation", bucketName); + private void clearBucket(Bucket bucket) { + String bucketName = bucket.name(); + s3.listObjects(ListObjectsRequest.builder().bucket(bucketName).build()) + .contents() + .forEach( + s3Object -> + s3.deleteObject( + DeleteObjectRequest.builder().bucket(bucketName).key(s3Object.key()).build())); } - } - - public File uploadFile(String bucketName, String fileName) throws IOException { - File tempFile = File.createTempFile(fileName, null); - Files.write( - tempFile.toPath(), "Will fail if the file has no content".getBytes(StandardCharsets.UTF_8)); - - s3.putObject( - PutObjectRequest.builder().bucket(bucketName).key(fileName).build(), - RequestBody.fromFile(tempFile)); - - return tempFile; - } - - public List listBuckets() { - return s3.listBuckets().buckets().stream().map(Bucket::name).collect(Collectors.toList()); - } - - public Set listBucketContent(String bucketName) { - return s3 - .listObjects(ListObjectsRequest.builder().bucket(bucketName).build()) - .contents() - .stream() - .map(S3Object::key) - .collect(Collectors.toSet()); - } - - public File downloadFile(String bucketName, String fileName) throws IOException { - ResponseBytes objectAsBytes = - s3.getObjectAsBytes(GetObjectRequest.builder().bucket(bucketName).key(fileName).build()); - - return Files.write(File.createTempFile(fileName, null).toPath(), objectAsBytes.asByteArray()) - .toFile(); - } - - public void deleteAllBuckets() { - List buckets = s3.listBuckets().buckets(); - buckets.forEach(this::clearBucket); - buckets.forEach( - bucket -> s3.deleteBucket(DeleteBucketRequest.builder().bucket(bucket.name()).build())); - } - - private void clearBucket(Bucket bucket) { - String bucketName = bucket.name(); - s3.listObjects(ListObjectsRequest.builder().bucket(bucketName).build()) - .contents() - .forEach( - s3Object -> - s3.deleteObject( - DeleteObjectRequest.builder().bucket(bucketName).key(s3Object.key()).build())); - } } diff --git a/edc-tests/cucumber/src/test/resources/org/eclipse/tractusx/edc/tests/features/HttpProxyDataTransfer.feature b/edc-tests/cucumber/src/test/resources/org/eclipse/tractusx/edc/tests/features/HttpProxyDataTransfer.feature index d318ad745..b04970ec7 100644 --- a/edc-tests/cucumber/src/test/resources/org/eclipse/tractusx/edc/tests/features/HttpProxyDataTransfer.feature +++ b/edc-tests/cucumber/src/test/resources/org/eclipse/tractusx/edc/tests/features/HttpProxyDataTransfer.feature @@ -24,24 +24,6 @@ Feature: HttpProxy Data Transfer Given 'Plato' has an empty database Given 'Sokrates' has an empty database - Scenario: Connector transfers data via HttpProxy - Given 'Plato' has a http proxy assets - | id | description | baseUrl | - | asset-1 | http proxy transfer asset | http://localhost:8081/api/check/liveness | - And 'Plato' has the following policies - | id | action | - | policy-1 | USE | - And 'Plato' has the following contract definitions - | id | access policy | contract policy | asset | - | contract-definition-1 | policy-1 | policy-1 | asset-1 | - When 'Sokrates' negotiates the contract successfully with 'Plato' - | contract offer id | asset id | policy id | - | contract-definition-1 | asset-1 | policy-1 | - And 'Sokrates' initiates HttpProxy transfer from 'Plato' - | asset id | receiverHttpEndpoint | - | asset-1 | http://backend:8080 | - Then the backend application of 'Sokrates' has received data - Scenario: Connector transfers data via HttpProxy, data on provider side requires oauth2 authentication Given 'Plato' has a http proxy assets | id | description | baseUrl | oauth2 token url | oauth2 client id | oauth2 client secret | oauth2 scope | diff --git a/charts/edc-controlplane/.helmignore b/edc-tests/deployment/src/main/resources/helm/omejdn/.helmignore similarity index 82% rename from charts/edc-controlplane/.helmignore rename to edc-tests/deployment/src/main/resources/helm/omejdn/.helmignore index 148b31d6c..0e8a0eb36 100644 --- a/charts/edc-controlplane/.helmignore +++ b/edc-tests/deployment/src/main/resources/helm/omejdn/.helmignore @@ -21,9 +21,3 @@ .idea/ *.tmproj .vscode/ - -README.md.gotmpl - -# Accept only values.yaml -values?*.yaml -values?*.yml diff --git a/edc-tests/deployment/src/main/resources/helm/omejdn/Chart.yaml b/edc-tests/deployment/src/main/resources/helm/omejdn/Chart.yaml new file mode 100644 index 000000000..b12948a5d --- /dev/null +++ b/edc-tests/deployment/src/main/resources/helm/omejdn/Chart.yaml @@ -0,0 +1,43 @@ +# 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 +# + +--- +apiVersion: v2 +name: ids-daps +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +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.0.1 + +# 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.0.1" diff --git a/edc-tests/deployment/src/main/resources/helm/omejdn/README.md b/edc-tests/deployment/src/main/resources/helm/omejdn/README.md new file mode 100644 index 000000000..f85a94889 --- /dev/null +++ b/edc-tests/deployment/src/main/resources/helm/omejdn/README.md @@ -0,0 +1,21 @@ +# Omejdn DAPS + +This chart deployes an [IDS Omejdn DAPS](https://github.com/Fraunhofer-AISEC/omejdn-server). + +Two Eclipse Dataspace Connectors need to be registered at the same DAPS instance, to be able to talk to each other. Each connector is registered in the DAPS by an unique client ID and a correpsonding client certificate. + +New connectors are configured in the omejdn _values.yaml_. + +In each Eclipse Dataspace Connector configure the following properties to use the DAPS. + +```properties + edc.oauth.client.id= + + edc.oauth.provider.jwks.url="http://:4567/.well-known/jwks.json" + edc.oauth.token.url="http://:4567/token" + + edc.oauth.private.key.alias= + edc.oauth.public.key.alias= + + edc.oauth.provider.audience=idsc:IDS_CONNECTORS_ALL +``` diff --git a/charts/edc-dataplane/templates/_helpers.tpl b/edc-tests/deployment/src/main/resources/helm/omejdn/templates/_helpers.tpl similarity index 66% rename from charts/edc-dataplane/templates/_helpers.tpl rename to edc-tests/deployment/src/main/resources/helm/omejdn/templates/_helpers.tpl index 3615298cd..95b115eee 100644 --- a/charts/edc-dataplane/templates/_helpers.tpl +++ b/edc-tests/deployment/src/main/resources/helm/omejdn/templates/_helpers.tpl @@ -1,7 +1,7 @@ {{/* Expand the name of the chart. */}} -{{- define "edc-dataplane.name" -}} +{{- define "omejdn.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- end }} @@ -10,7 +10,7 @@ Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). If release name contains chart name it will be used as a full name. */}} -{{- define "edc-dataplane.fullname" -}} +{{- define "omejdn.fullname" -}} {{- if .Values.fullnameOverride }} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} {{- else }} @@ -26,17 +26,16 @@ If release name contains chart name it will be used as a full name. {{/* Create chart name and version as used by the chart label. */}} -{{- define "edc-dataplane.chart" -}} +{{- define "omejdn.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- end }} {{/* Common labels */}} -{{- define "edc-dataplane.labels" -}} -helm.sh/chart: {{ include "edc-dataplane.chart" . }} -{{ include "edc-dataplane.selectorLabels" . }} -{{ include "edc-dataplane.customLabels" . }} +{{- define "omejdn.labels" -}} +helm.sh/chart: {{ include "omejdn.chart" . }} +{{ include "omejdn.selectorLabels" . }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} @@ -46,26 +45,17 @@ app.kubernetes.io/managed-by: {{ .Release.Service }} {{/* Selector labels */}} -{{- define "edc-dataplane.selectorLabels" -}} -app.kubernetes.io/name: {{ include "edc-dataplane.name" . }} +{{- define "omejdn.selectorLabels" -}} +app.kubernetes.io/name: {{ include "omejdn.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} -{{/* -Custom labels -*/}} -{{- define "edc-dataplane.customLabels" -}} -{{- with .Values.customLabels }} -{{ toYaml . }} -{{- end }} -{{- end }} - {{/* Create the name of the service account to use */}} -{{- define "edc-dataplane.serviceAccountName" -}} +{{- define "omejdn.serviceAccountName" -}} {{- if .Values.serviceAccount.create }} -{{- default (include "edc-dataplane.fullname" .) .Values.serviceAccount.name }} +{{- default (include "omejdn.fullname" .) .Values.serviceAccount.name }} {{- else }} {{- default "default" .Values.serviceAccount.name }} {{- end }} diff --git a/edc-tests/deployment/src/main/resources/helm/omejdn/templates/configmap.yaml b/edc-tests/deployment/src/main/resources/helm/omejdn/templates/configmap.yaml new file mode 100644 index 000000000..97efcc12b --- /dev/null +++ b/edc-tests/deployment/src/main/resources/helm/omejdn/templates/configmap.yaml @@ -0,0 +1,92 @@ +# 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 +# + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "omejdn.fullname" . }} + labels: + {{- include "omejdn.labels" . | nindent 4 }} +data: + scope_mapping.yml: |- + --- + idsc:IDS_CONNECTOR_ATTRIBUTES_ALL: + - referringConnector + + omejdn.yml: |- + --- + host: http://ids-daps:4567/ + path_prefix: '' + bind_to: 0.0.0.0 + allow_origin: "*" + app_env: debug + openid: false + user_backend: + - yaml + user_backend_default: yaml + accept_audience: idsc:IDS_CONNECTORS_ALL + issuer: http://ids-daps:4567/ + environment: development + default_audience: + - idsc:IDS_CONNECTORS_ALL + access_token: + expiration: 3600 + algorithm: RS256 + id_token: + expiration: 3600 + algorithm: RS256 + + plugins.yml: |- + --- + plugins: + token_user_attributes: + + clients.yml: |- + --- + - client_id: data-plane-oauth2 + client_secret: supersecret + name: provision oauth2 + grant_types: + - client_credentials + token_endpoint_auth_method: client_secret_post + scope: openid +{{- range $i, $val := .Values.connectors }} + - client_id: {{ quote $val.id }} + name: {{ quote $val.name }} + token_endpoint_auth_method: private_key_jwt + grant_types: + - client_credentials + scope: + - idsc:IDS_CONNECTOR_ATTRIBUTES_ALL + attributes: + - key: idsc + value: IDS_CONNECTOR_ATTRIBUTES_ALL + - key: securityProfile + value: idsc:BASE_SECURITY_PROFILE + {{- range $key, $value := $val.attributes }} + - key: {{ $key }} + value: {{ $value }} + {{- end }} + redirect_uri: http://localhost:4200 +{{ end -}} + + +{{- range $i, $val := .Values.connectors }} + {{ $val.name }}: {{ quote $val.certificate | toString }} +{{ end -}} diff --git a/edc-tests/deployment/src/main/resources/helm/omejdn/templates/deployment.yaml b/edc-tests/deployment/src/main/resources/helm/omejdn/templates/deployment.yaml new file mode 100644 index 000000000..58bfff105 --- /dev/null +++ b/edc-tests/deployment/src/main/resources/helm/omejdn/templates/deployment.yaml @@ -0,0 +1,168 @@ +# 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 +# + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "omejdn.fullname" . }} + labels: + {{- include "omejdn.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "omejdn.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "omejdn.selectorLabels" . | nindent 8 }} + spec: + {{- if .Values.imagePullSecret.dockerconfigjson }} + imagePullSecrets: + - name: {{ include "omejdn.fullname" . }}-imagepullsecret + {{- else }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} + serviceAccountName: {{ include "omejdn.serviceAccountName" . }} + automountServiceAccountToken: {{ .Values.automountServiceAccountToken }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + initContainers: + - name: init-daps-pvc + image: alpine + command: + - "sh" + - "-c" + args: + - | + cp /opt/config/omejdn.yml /etc/daps/omejdn.yml + cp /opt/config/clients.yml /etc/daps/clients.yml + cp /opt/config/plugins.yml /etc/daps/plugins.yml + cp /opt/config/scope_mapping.yml /etc/daps/scope_mapping.yml + apk add --update openssl + openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout /etc/keys/omejdn/omejdn.key \ + -subj "/C=DE/ST=Berlin/L=Berlin/O=Tractus-X-EDC-Test, Inc./OU=DE" + volumeMounts: + - mountPath: /etc/daps + name: config-dir + - mountPath: /etc/keys/omejdn + name: omejdn-key-dir + - mountPath: /opt/config/omejdn.yml + name: omejdn-config + subPath: omejdn.yml + - mountPath: /opt/config/scope_mapping.yml + name: scope-mapping + subPath: scope_mapping.yml + - mountPath: /opt/config/clients.yml + name: clients-config + subPath: clients.yml + - mountPath: /opt/config/plugins.yml + name: plugins-config + subPath: plugins.yml + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + volumeMounts: + - mountPath: /opt/config/ + name: config-dir + - mountPath: /opt/keys/omejdn/omejdn.key + name: omejdn-key-dir + subPath: omejdn.key + - mountPath: /opt/keys/clients/ + name: client-certificates + ports: + - name: http + containerPort: 4567 + protocol: TCP + livenessProbe: + httpGet: + path: /jwks.json + port: http + readinessProbe: + httpGet: + path: /jwks.json + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + env: + - name: OMEJDN_JWT_AUD_OVERRIDE + value: "idsc:IDS_CONNECTORS_ALL" + - name: OMEJDN_PLUGINS + value: "config/plugins.yml" + volumes: + - name: config-dir + emptyDir: { } + - name: omejdn-key-dir + emptyDir: { } + - name: omejdn-config + configMap: + name: {{ include "omejdn.fullname" . }} + items: + - key: omejdn.yml + path: omejdn.yml + - name: scope-mapping + configMap: + name: {{ include "omejdn.fullname" . }} + items: + - key: scope_mapping.yml + path: scope_mapping.yml + - name: clients-config + configMap: + name: {{ include "omejdn.fullname" . }} + items: + - key: clients.yml + path: clients.yml + - name: plugins-config + configMap: + name: {{ include "omejdn.fullname" . }} + items: + - key: plugins.yml + path: plugins.yml + - name: client-certificates + configMap: + name: {{ include "omejdn.fullname" . }} + items: + {{- range $i, $val := .Values.connectors }} + - key: {{ $val.name }} + path: {{ $val.id }}.cert + {{- end }} diff --git a/charts/edc-dataplane/templates/hpa.yaml b/edc-tests/deployment/src/main/resources/helm/omejdn/templates/hpa.yaml similarity index 75% rename from charts/edc-dataplane/templates/hpa.yaml rename to edc-tests/deployment/src/main/resources/helm/omejdn/templates/hpa.yaml index 037934aeb..cf5eb97d0 100644 --- a/charts/edc-dataplane/templates/hpa.yaml +++ b/edc-tests/deployment/src/main/resources/helm/omejdn/templates/hpa.yaml @@ -1,8 +1,4 @@ -# -# Copyright (c) 2023 ZF Friedrichshafen AG -# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH -# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) -# Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation +# Copyright (c) 2023 Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. @@ -25,15 +21,14 @@ apiVersion: autoscaling/v2beta1 kind: HorizontalPodAutoscaler metadata: - name: {{ include "edc-dataplane.fullname" . }} - namespace: {{ .Release.Namespace | default "default" | quote }} + name: {{ include "omejdn.fullname" . }} labels: - {{- include "edc-dataplane.labels" . | nindent 4 }} + {{- include "omejdn.labels" . | nindent 4 }} spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment - name: {{ include "edc-dataplane.fullname" . }} + name: {{ include "omejdn.fullname" . }} minReplicas: {{ .Values.autoscaling.minReplicas }} maxReplicas: {{ .Values.autoscaling.maxReplicas }} metrics: diff --git a/edc-tests/deployment/src/main/resources/helm/omejdn/templates/imagepullsecret.yaml b/edc-tests/deployment/src/main/resources/helm/omejdn/templates/imagepullsecret.yaml new file mode 100644 index 000000000..44f573e0f --- /dev/null +++ b/edc-tests/deployment/src/main/resources/helm/omejdn/templates/imagepullsecret.yaml @@ -0,0 +1,31 @@ +# 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 + # + +{{- if .Values.imagePullSecret.dockerconfigjson }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "edc-dataplane.fullname" . }}-imagepullsecret + namespace: {{ .Release.Namespace | default "default" | quote }} + labels: + {{- include "edc-dataplane.labels" . | nindent 4 }} +data: + .dockerconfigjson: {{ .Values.imagePullSecret.dockerconfigjson }} +type: kubernetes.io/dockerconfigjson +{{- end }} diff --git a/edc-tests/deployment/src/main/resources/helm/omejdn/templates/service.yaml b/edc-tests/deployment/src/main/resources/helm/omejdn/templates/service.yaml new file mode 100644 index 000000000..947e69742 --- /dev/null +++ b/edc-tests/deployment/src/main/resources/helm/omejdn/templates/service.yaml @@ -0,0 +1,34 @@ +# 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 +# + +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "omejdn.fullname" . }} + labels: + {{- include "omejdn.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "omejdn.selectorLabels" . | nindent 4 }} diff --git a/edc-tests/deployment/src/main/resources/helm/omejdn/templates/serviceaccount.yaml b/edc-tests/deployment/src/main/resources/helm/omejdn/templates/serviceaccount.yaml new file mode 100644 index 000000000..536f31871 --- /dev/null +++ b/edc-tests/deployment/src/main/resources/helm/omejdn/templates/serviceaccount.yaml @@ -0,0 +1,31 @@ +# 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 +# + +{{- if .Values.serviceAccount.create -}} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "omejdn.serviceAccountName" . }} + labels: + {{- include "omejdn.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/edc-tests/deployment/src/main/resources/helm/omejdn/values.yaml b/edc-tests/deployment/src/main/resources/helm/omejdn/values.yaml new file mode 100644 index 000000000..f411b8774 --- /dev/null +++ b/edc-tests/deployment/src/main/resources/helm/omejdn/values.yaml @@ -0,0 +1,109 @@ +# 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 +# + +--- +# Default values for omejdn. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# -- Specifies how many replicas of a deployed pod shall be created during the deployment +# Note: If horizontal pod autoscaling is enabled this setting has no effect +replicaCount: 1 + +image: + # -- Which omjedn container image to use + repository: ghcr.io/fraunhofer-aisec/omejdn-server + # -- [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) to use + pullPolicy: IfNotPresent + # -- Overrides the image tag whose default is the chart appVersion + tag: "1.7.1" + +imagePullSecret: + # -- Image pull secret to create to [obtain the container image from private registries](https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry) + # Note: This value needs to adhere to the [(base64 encoded) .dockerconfigjson format](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/#registry-secret-existing-credentials). + # Furthermore, if 'imagePullSecret.dockerconfigjson' is defined, it takes precedence over 'imagePullSecrets'. + dockerconfigjson: "" + +# -- Overrides the charts name +nameOverride: "" + +# -- Overrides the releases full name +fullnameOverride: "" + +serviceAccount: + # -- Specifies whether a [service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) should be created per release + create: true + # -- [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) to add to the service account + annotations: {} + # -- The name of the service account to use. If not set and create is true, a name is generated using the release's fullname template + name: "" + +# -- Whether to [automount kubernetes API credentials](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#use-the-default-service-account-to-access-the-api-server) into the pod +automountServiceAccountToken: false + +# -- [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) added to deployed [pods](https://kubernetes.io/docs/concepts/workloads/pods/) +podAnnotations: {} + +# 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 +podSecurityContext: {} + +# The [container security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container) defines privilege and access control settings for a Container within a pod +securityContext: {} + +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 + # -- [Service type](https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service) to expose the running application on a set of Pods as a network service. + port: 4567 + +# -- [Resource management](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) applied to the deployed pod +resources: {} + +autoscaling: + # -- Enables [horizontal pod autoscaling](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) + enabled: false + # -- Minimal replicas if resource consumption falls below resource threshholds + minReplicas: 1 + # -- Maximum replicas if resource consumption exceeds resource threshholds + maxReplicas: 100 + # -- targetAverageUtilization of cpu provided to a pod + targetCPUUtilizationPercentage: 80 + # -- targetAverageUtilization of memory provided to a pod + targetMemoryUtilizationPercentage: 80 + +# -- [Node-Selector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector) to constrain the Pod to nodes with specific labels. +nodeSelector: {} + +# -- [Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) are applied to Pods to schedule onto nodes with matching taints. +tolerations: [] + +# -- [Affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity) constrains which nodes the Pod can be scheduled on based on node labels. +affinity: {} + +# List of connector clients. Certificate and Client-ID must be configured in parallel. +#
+# Example Connector: +# - id: grMsEz3EcsS3ENYJufNgUIeg4QsaL49M0gWxSexPdC4pon96Nvju90D8RlvAJB21 +# name: my-connector +# attributes: +# issuerConnector: http://localhost:8080/ +# certificate: |- +# -----BEGIN CERTIFICATE----- +# foo +# -----END CERTIFICATE----- +connectors: [] diff --git a/edc-tests/deployment/src/main/resources/helm/test-infrastructure/.gitignore b/edc-tests/deployment/src/main/resources/helm/test-infrastructure/.gitignore new file mode 100644 index 000000000..8681aba50 --- /dev/null +++ b/edc-tests/deployment/src/main/resources/helm/test-infrastructure/.gitignore @@ -0,0 +1,4 @@ +# ignore downloaded helm depdencies +charts/ + +Chart.lock diff --git a/charts/edc-dataplane/.helmignore b/edc-tests/deployment/src/main/resources/helm/test-infrastructure/.helmignore similarity index 82% rename from charts/edc-dataplane/.helmignore rename to edc-tests/deployment/src/main/resources/helm/test-infrastructure/.helmignore index 148b31d6c..8c60d7821 100644 --- a/charts/edc-dataplane/.helmignore +++ b/edc-tests/deployment/src/main/resources/helm/test-infrastructure/.helmignore @@ -21,9 +21,4 @@ .idea/ *.tmproj .vscode/ - -README.md.gotmpl - -# Accept only values.yaml -values?*.yaml -values?*.yml +docs diff --git a/edc-tests/deployment/src/main/resources/helm/test-infrastructure/Chart.yaml b/edc-tests/deployment/src/main/resources/helm/test-infrastructure/Chart.yaml new file mode 100644 index 000000000..0ac95d502 --- /dev/null +++ b/edc-tests/deployment/src/main/resources/helm/test-infrastructure/Chart.yaml @@ -0,0 +1,63 @@ +# 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 +# + +--- +apiVersion: v2 +name: all-in-one +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +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.1.0 + +# 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: "1.16.0" + +dependencies: + # IDS Dynamic Attribute Provisioning Service (IAM) + - name: ids-daps + version: 0.0.1 + repository: "file://../omejdn" + alias: idsdaps + condition: install.daps + + # HashiCorp Vault + - name: vault + alias: vault + version: 0.20.0 + repository: https://helm.releases.hashicorp.com + + # PostgreSQL + - name: postgresql + alias: postgresql + version: 12.1.6 + repository: https://charts.bitnami.com/bitnami \ No newline at end of file diff --git a/edc-tests/deployment/src/main/resources/helm/test-infrastructure/README.md b/edc-tests/deployment/src/main/resources/helm/test-infrastructure/README.md new file mode 100644 index 000000000..e927d7bc5 --- /dev/null +++ b/edc-tests/deployment/src/main/resources/helm/test-infrastructure/README.md @@ -0,0 +1,54 @@ +# Supporting Infrastructure Deployment + +The Supporting Infrastructure Deployment creates a complete, independent and already configured EDC test environment. +During the automated business tests, these infrastructure components are deployed together with two connectors (Plato & Sokrates). + +This deployment could also be used as + +- reference setup for teams, that want to create their own connector +- standalone infrastructure to try things out + +This deployment should **never** be used + +- in **any** production or near production environments +- in **any** long living internet facing connector setups + +## Omejdn DAPS + +The Dynamic Attribute Provisioning Service (DAPS) is a component of the IDS Ecosystem. +The Fraunhofer Institute has created a DAPS reference implementation, the Omejdn +DAPS ([link](https://github.com/Fraunhofer-AISEC/omejdn-server)). This deplyoment configures and deployes a instance of +this reference implementation. + +Definition of DAPS from the IDS Reference architecture v3.0: + +> The Identity Provider acts as an agent for the International +> Data Spaces Association. It is responsible for issuing technical identities to parties that have been approved to become +> Participants in the International Data Spaces. The Identity +> Provider is instructed to issue identities based on approved +> roles (e.g., App Store or App Provider). Only if equipped with +> such an identity, an entity is allowed to participate in the International Data Spaces + +Also, please note, that the Omejdn DAPS is meant as research sandbox and should not be used in anq +productive environment. + +> **IMPORTANT:** Omejdn is meant to be a research sandbox in which we can (re)implement standard protocols and +> potentially extend and modify functionality under the hood to support research projects. Use at your own +> risk! ([source](https://github.com/Fraunhofer-AISEC/omejdn-server)) + +## HashiCorp Vault + +The Control- and Data Plane persist confidential in the vault and persist and communicate using only the secret +names. Hence, it is not possible to run a connector without an instance of a vault. + +## PostgreSQL + +This database is used to persist the state of the Control Plane. + +## Setup + +Simply execute the following comment in a shell: + +```shell +helm install infra edc-tests/deployment/src/main/resources/helm/test-infrastructure --update-dependencies +``` diff --git a/edc-tests/deployment/src/main/resources/helm/test-infrastructure/values.yaml b/edc-tests/deployment/src/main/resources/helm/test-infrastructure/values.yaml new file mode 100644 index 000000000..30e89ee38 --- /dev/null +++ b/edc-tests/deployment/src/main/resources/helm/test-infrastructure/values.yaml @@ -0,0 +1,553 @@ +# 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 +# + +--- + +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +fullnameOverride: "" +nameOverride: "" +# -- 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) +imagePullSecrets: [] +customLabels: {} +runtime: + controlplane: + image: + # -- Which derivate of the control plane to use. when left empty the deployment will select the correct image automatically + repository: "" + # -- [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) to use + pullPolicy: IfNotPresent + # -- Overrides the image tag whose default is the chart appVersion + tag: "" + initContainers: [] + debug: + enabled: false + port: 1044 + suspendOnStart: false + internationalDataSpaces: + id: TXDC + description: Tractus-X Eclipse IDS Data Space Connector + title: "" + maintainer: "" + curator: "" + catalogId: TXDC-Catalog + livenessProbe: + # -- Whether to enable kubernetes [liveness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) + enabled: true + # -- seconds to wait before performing the first liveness check + initialDelaySeconds: 30 + # -- this fields specifies that kubernetes should perform a liveness check every 10 seconds + periodSeconds: 10 + # -- number of seconds after which the probe times out + timeoutSeconds: 5 + # -- when a probe fails kubernetes will try 6 times before giving up + failureThreshold: 6 + # -- number of consecutive successes for the probe to be considered successful after having failed + successThreshold: 1 + readinessProbe: + # -- Whether to enable kubernetes [readiness-probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) + enabled: true + # -- seconds to wait before performing the first readiness check + initialDelaySeconds: 30 + # -- this fields specifies that kubernetes should perform a readiness check every 10 seconds + periodSeconds: 10 + # -- number of seconds after which the probe times out + timeoutSeconds: 5 + # -- when a probe fails kubernetes will try 6 times before giving up + failureThreshold: 6 + # -- number of consecutive successes for the probe to be considered successful after having failed + successThreshold: 1 + # -- endpoints of the control plane + endpoints: + # -- default api for health checks, should not be added to any ingress + default: + # -- port for incoming api calls + port: 8080 + # -- path for incoming api calls + path: /api + # -- data management api, used by internal users, can be added to an ingress and must not be internet facing + management: + # -- port for incoming api calls + port: 8081 + # -- path for incoming api calls + path: /management + # -- authentication key, must be attached to each 'X-Api-Key' request header + authKey: "" + # -- control api, used for internal control calls. can be added to the internal ingress, but should probably not + control: + # -- port for incoming api calls + port: 8083 + # -- path for incoming api calls + path: /control + # -- ids api, used for inter connector communication and must be internet facing + protocol: + # -- port for incoming api calls + port: 8084 + # -- path for incoming api calls + path: /api/v1/ids + # -- metrics api, used for application metrics, must not be internet facing + metrics: + # -- port for incoming api calls + port: 9090 + # -- path for incoming api calls + path: /metrics + # -- observability api with unsecured access, must not be internet facing + observability: + # -- port for incoming API calls + port: 8085 + # -- observability api, provides /health /readiness and /liveness endpoints + path: /observability + # -- allow or disallow insecure access, i.e. access without authentication + insecure: 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 + annotations: {} + # -- additional labels for the pod + podLabels: {} + # -- additional annotations for the pod + podAnnotations: {} + # -- 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 + podSecurityContext: + seccompProfile: + # -- Restrict a Container's Syscalls with seccomp + type: RuntimeDefault + # -- Runs all processes within a pod with a special uid + runAsUser: 10001 + # -- Processes within a pod will belong to this guid + runAsGroup: 10001 + # -- The owner for volumes and any files created within volumes will belong to this guid + fsGroup: 10001 + # The [container security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container) defines privilege and access control settings for a Container within a pod + securityContext: + capabilities: + # -- Specifies which capabilities to drop to reduce syscall attack surface + drop: + - ALL + # -- Specifies which capabilities to add to issue specialized syscalls + add: [] + # -- Whether the root filesystem is mounted in read-only mode + readOnlyRootFilesystem: true + # -- Controls [Privilege Escalation](https://kubernetes.io/docs/concepts/security/pod-security-policy/#privilege-escalation) enabling setuid binaries changing the effective user ID + allowPrivilegeEscalation: false + # -- Requires the container to run without root privileges + runAsNonRoot: true + # -- The container's process will run with the specified uid + runAsUser: 10001 + # Extra environment variables that will be pass onto deployment pods + env: {} + # ENV_NAME: value + + # "valueFrom" environment variable references that will be added to deployment pods. Name is templated. + # ref: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#envvarsource-v1-core + envValueFrom: {} + # ENV_NAME: + # configMapKeyRef: + # name: configmap-name + # key: value_key + # secretKeyRef: + # name: secret-name + # key: value_key + + # [Kubernetes Secret Resource](https://kubernetes.io/docs/concepts/configuration/secret/) names to load environment variables from + envSecretNames: [] + # - first-secret + # - second-secret + + # [Kubernetes ConfigMap Resource](https://kubernetes.io/docs/concepts/configuration/configmap/) names to load environment variables from + envConfigMapNames: [] + # - first-config-map + # - second-config-map + + ## Ingress declaration to expose the network service. + ingresses: + ## Public / Internet facing Ingress + - enabled: false + # -- The hostname to be used to precisely map incoming traffic onto the underlying network service + hostname: "edc-control.local" + # -- Additional ingress annotations to add + annotations: {} + # -- EDC endpoints exposed by this ingress resource + endpoints: + - ids + # -- Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use + className: "" + # -- TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource + tls: + # -- Enables TLS on the ingress resource + enabled: false + # -- If present overwrites the default secret name + secretName: "" + ## Adds [cert-manager](https://cert-manager.io/docs/) annotations to the ingress resource + certManager: + # -- If preset enables certificate generation via cert-manager namespace scoped issuer + issuer: "" + # -- If preset enables certificate generation via cert-manager cluster-wide issuer + clusterIssuer: "" + ## Private / Intranet facing Ingress + - enabled: false + # -- The hostname to be used to precisely map incoming traffic onto the underlying network service + hostname: "edc-control.intranet" + # -- Additional ingress annotations to add + annotations: {} + # -- EDC endpoints exposed by this ingress resource + endpoints: + - management + - control + # -- Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use + className: "" + # -- TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource + tls: + # -- Enables TLS on the ingress resource + enabled: false + # -- If present overwrites the default secret name + secretName: "" + ## Adds [cert-manager](https://cert-manager.io/docs/) annotations to the ingress resource + certManager: + # -- If preset enables certificate generation via cert-manager namespace scoped issuer + issuer: "" + # -- If preset enables certificate generation via cert-manager cluster-wide issuer + clusterIssuer: "" + # -- declare where to mount [volumes](https://kubernetes.io/docs/concepts/storage/volumes/) into the container + volumeMounts: [] + # -- [volume](https://kubernetes.io/docs/concepts/storage/volumes/) directories + volumes: [] + # -- [resource management](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) for the container + resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + replicaCount: 1 + autoscaling: + # -- Enables [horizontal pod autoscaling](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) + enabled: false + # -- Minimal replicas if resource consumption falls below resource threshholds + minReplicas: 1 + # -- Maximum replicas if resource consumption exceeds resource threshholds + maxReplicas: 100 + # -- targetAverageUtilization of cpu provided to a pod + targetCPUUtilizationPercentage: 80 + # -- targetAverageUtilization of memory provided to a pod + targetMemoryUtilizationPercentage: 80 + # -- configuration of the [Open Telemetry Agent](https://opentelemetry.io/docs/instrumentation/java/automatic/agent-config/) to collect and expose metrics + opentelemetry: |- + otel.javaagent.enabled=false + otel.javaagent.debug=false + # -- configuration of the [Java Util Logging Facade](https://docs.oracle.com/javase/7/docs/technotes/guides/logging/overview.html) + logging: |- + .level=INFO + org.eclipse.edc.level=ALL + handlers=java.util.logging.ConsoleHandler + java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter + java.util.logging.ConsoleHandler.level=ALL + java.util.logging.SimpleFormatter.format=[%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS] [%4$-7s] %5$s%6$s%n + # [node selector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector) to constrain pods to nodes + nodeSelector: {} + # [tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) to configure preferred nodes + tolerations: [] + # [affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity) to configure which nodes the pods can be scheduled on + affinity: {} + url: + # -- Explicitly declared url for reaching the ids api (e.g. if ingresses not used) + ids: "" + dataplane: + image: + # -- Which derivate of the data plane to use. when left empty the deployment will select the correct image automatically + repository: "" + # -- [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) to use + pullPolicy: IfNotPresent + # -- Overrides the image tag whose default is the chart appVersion + tag: "" + initContainers: [] + debug: + enabled: false + port: 1044 + suspendOnStart: false + livenessProbe: + # -- Whether to enable kubernetes [liveness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) + enabled: true + # -- seconds to wait before performing the first liveness check + initialDelaySeconds: 30 + # -- this fields specifies that kubernetes should perform a liveness check every 10 seconds + periodSeconds: 10 + # -- number of seconds after which the probe times out + timeoutSeconds: 5 + # -- when a probe fails kubernetes will try 6 times before giving up + failureThreshold: 6 + # -- number of consecutive successes for the probe to be considered successful after having failed + successThreshold: 1 + readinessProbe: + # -- Whether to enable kubernetes [readiness-probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) + enabled: true + # -- seconds to wait before performing the first readiness check + initialDelaySeconds: 30 + # -- this fields specifies that kubernetes should perform a liveness check every 10 seconds + periodSeconds: 10 + # -- number of seconds after which the probe times out + timeoutSeconds: 5 + # -- when a probe fails kubernetes will try 6 times before giving up + failureThreshold: 6 + # -- number of consecutive successes for the probe to be considered successful after having failed + successThreshold: 1 + 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 + port: 80 + endpoints: + default: + port: 8080 + path: /api + public: + port: 8081 + path: /api/public + control: + port: 8083 + path: /api/dataplane/control + observability: + # -- port for incoming API calls + port: 8085 + # -- observability api, provides /health /readiness and /liveness endpoints + path: /observability + # -- allow or disallow insecure access, i.e. access without authentication + insecure: true + metrics: + port: 9090 + path: /metrics + aws: + endpointOverride: "" + accessKeyId: "" + secretAccessKey: "" + # -- additional labels for the pod + podLabels: {} + # -- additional annotations for the pod + podAnnotations: {} + # -- 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 + podSecurityContext: + seccompProfile: + # -- Restrict a Container's Syscalls with seccomp + type: RuntimeDefault + # -- Runs all processes within a pod with a special uid + runAsUser: 10001 + # -- Processes within a pod will belong to this guid + runAsGroup: 10001 + # -- The owner for volumes and any files created within volumes will belong to this guid + fsGroup: 10001 + # The [container security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container) defines privilege and access control settings for a Container within a pod + securityContext: + capabilities: + # -- Specifies which capabilities to drop to reduce syscall attack surface + drop: + - ALL + # -- Specifies which capabilities to add to issue specialized syscalls + add: [] + # -- Whether the root filesystem is mounted in read-only mode + readOnlyRootFilesystem: true + # -- Controls [Privilege Escalation](https://kubernetes.io/docs/concepts/security/pod-security-policy/#privilege-escalation) enabling setuid binaries changing the effective user ID + allowPrivilegeEscalation: false + # -- Requires the container to run without root privileges + runAsNonRoot: true + # -- The container's process will run with the specified uid + runAsUser: 10001 + # Extra environment variables that will be pass onto deployment pods + env: {} + # ENV_NAME: value + + # "valueFrom" environment variable references that will be added to deployment pods. Name is templated. + # ref: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#envvarsource-v1-core + envValueFrom: {} + # ENV_NAME: + # configMapKeyRef: + # name: configmap-name + # key: value_key + # secretKeyRef: + # name: secret-name + # key: value_key + + # [Kubernetes Secret Resource](https://kubernetes.io/docs/concepts/configuration/secret/) names to load environment variables from + envSecretNames: [] + # - first-secret + # - second-secret + + # [Kubernetes ConfigMap Resource](https://kubernetes.io/docs/concepts/configuration/configmap/) names to load environment variables from + envConfigMapNames: [] + # - first-config-map + # - second-config-map + + ## Ingress declaration to expose the network service. + ingresses: + ## Public / Internet facing Ingress + - enabled: false + # -- The hostname to be used to precisely map incoming traffic onto the underlying network service + hostname: "edc-data.local" + # -- Additional ingress annotations to add + annotations: {} + # -- EDC endpoints exposed by this ingress resource + endpoints: + - public + # -- Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use + className: "" + # -- TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource + tls: + # -- Enables TLS on the ingress resource + enabled: false + # -- If present overwrites the default secret name + secretName: "" + ## Adds [cert-manager](https://cert-manager.io/docs/) annotations to the ingress resource + certManager: + # -- If preset enables certificate generation via cert-manager namespace scoped issuer + issuer: "" + # -- If preset enables certificate generation via cert-manager cluster-wide issuer + clusterIssuer: "" + # -- declare where to mount [volumes](https://kubernetes.io/docs/concepts/storage/volumes/) into the container + volumeMounts: [] + # -- [volume](https://kubernetes.io/docs/concepts/storage/volumes/) directories + volumes: [] + # -- [resource management](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) for the container + resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + replicaCount: 1 + autoscaling: + # -- Enables [horizontal pod autoscaling](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) + enabled: false + # -- Minimal replicas if resource consumption falls below resource threshholds + minReplicas: 1 + # -- Maximum replicas if resource consumption exceeds resource threshholds + maxReplicas: 100 + # -- targetAverageUtilization of cpu provided to a pod + targetCPUUtilizationPercentage: 80 + # -- targetAverageUtilization of memory provided to a pod + targetMemoryUtilizationPercentage: 80 + # -- configuration of the [Open Telemetry Agent](https://opentelemetry.io/docs/instrumentation/java/automatic/agent-config/) to collect and expose metrics + opentelemetry: |- + otel.javaagent.enabled=false + otel.javaagent.debug=false + # -- configuration of the [Java Util Logging Facade](https://docs.oracle.com/javase/7/docs/technotes/guides/logging/overview.html) + logging: |- + .level=INFO + org.eclipse.edc.level=ALL + handlers=java.util.logging.ConsoleHandler + java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter + java.util.logging.ConsoleHandler.level=ALL + java.util.logging.SimpleFormatter.format=[%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS] [%4$-7s] %5$s%6$s%n + # [node selector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector) to constrain pods to nodes + nodeSelector: {} + # [tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) to configure preferred nodes + tolerations: [] + # [affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity) to configure which nodes the pods can be scheduled on + affinity: {} + url: + # -- Explicitly declared url for reaching the public api (e.g. if ingresses not used) + public: "" + postgresql: + enabled: false + jdbcUrl: "" + username: "" + password: "" + vault: + hashicorp: + enabled: true + url: "" + token: "" + timeout: 30 + healthCheck: + enabled: true + standbyOk: true + paths: + secret: /v1/secret + health: /v1/sys/health + secretNames: + transferProxyTokenSignerPrivateKey: transfer-proxy-token-signer-private-key + transferProxyTokenSignerPublicKey: transfer-proxy-token-signer-public-key + transferProxyTokenEncryptionAesKey: transfer-proxy-token-encryption-aes-key + dapsPrivateKey: daps-private-key + dapsPublicKey: daps-public-key + daps: + url: "" + clientId: "" + paths: + jwks: /jwks.json + token: /token + backendService: + httpProxyTokenReceiverUrl: "" + serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + # -- 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) + imagePullSecrets: [] +######## +# DAPS # +######## +idsdaps: + fullnameOverride: "ids-daps" + connectors: + - id: E7:07:2D:74:56:66:31:F0:7B:10:EA:B6:03:06:4C:23:7F:ED:A6:65:keyid:E7:07:2D:74:56:66:31:F0:7B:10:EA:B6:03:06:4C:23:7F:ED:A6:65 + name: sokrates + attributes: + referringConnector: http://sokrates-controlplane/BPNSOKRATES + # Must be the same certificate that is stores in section 'sokrates-vault' + certificate: |- # must be set externally! +############## +# POSTGRESQL # +############## +postgresql: + fullnameOverride: "postgresql" + primary: + persistence: + enabled: false + readReplicas: + persistence: + enabled: false + auth: + database: "edc" + username: "user" + password: "password" +######### +# VAULT # +######### +vault: + fullnameOverride: "vault" + injector: + enabled: false + server: + dev: + enabled: true + devRootToken: "root" + # Must be the same certificate that is configured in section 'ids-daps' + postStart: # must be set externally! diff --git a/edc-tests/deployment/src/main/resources/helm/tractusx-connector-azure-vault-test.yaml b/edc-tests/deployment/src/main/resources/helm/tractusx-connector-azure-vault-test.yaml new file mode 100644 index 000000000..bc247f91e --- /dev/null +++ b/edc-tests/deployment/src/main/resources/helm/tractusx-connector-azure-vault-test.yaml @@ -0,0 +1,100 @@ +# +# 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 +# + +## This file can be used to verify that the chart is working properly. It provides an exemplary configuration +## that is intended to be used with the supporting infrastructure. +## 1. install DAPS: +## helm install infrastructure edc-tests/deployment/src/main/resources/helm/test-infrastructure --wait-for-jobs +## +## 2. set Azure KevVault secrets, either through the Azure Portal or through az cli: +## az keyvault secret set --vault-name --name daps-crt --value +## az keyvault secret set --vault-name --name daps-key --value +## az keyvault secret set --vault-name --name aes-keys --value +## +## 3. install the connector plus its third-party dependencies (Postgres): +## helm install tx-prod charts/tractusx-connector-azure-vault-app -f charts/tractusx-connector-azure-vault-app/example.yaml --dependency-update \ +## --set vault.azure.client= \ +## --set vault.azure.tenant= \ +## --set vault.azure.secret= + +fullnameOverride: tx-prod + +################################ +# EDC ControlPlane + DataPlane # +################################ +controlplane: + service: + type: NodePort + endpoints: + management: + authKey: password + image: + pullPolicy: Never + tag: "latest" + repository: "edc-controlplane-postgresql-azure-vault" + securityContext: + # avoids some errors in the log: cannot write temp files of large multipart requests when R/O + readOnlyRootFilesystem: false + +dataplane: + image: + pullPolicy: Never + tag: "latest" + repository: "edc-dataplane-azure-vault" + + securityContext: + # avoids some errors in the log: cannot write temp files of large multipart requests when R/O + readOnlyRootFilesystem: false + + aws: + endpointOverride: http://minio:9000 + secretAccessKey: qwerty123 + accessKeyId: qwerty123 + +postgresql: + username: user + password: password + jdbcUrl: jdbc:postgresql://postgresql:5432/edc + +vault: + azure: + name: '' + client: '' + tenant: '' + secret: + certificate: + + secretNames: + transferProxyTokenSignerPublicKey: daps-crt + transferProxyTokenSignerPrivateKey: daps-key + transferProxyTokenEncryptionAesKey: aes-keys + dapsPrivateKey: daps-key + dapsPublicKey: daps-crt + + # this must be set through CLI args: --set vault.secrets=$YOUR_VAULT_SECRETS where YOUR_VAULT_SECRETS should + # be a string in the format "key1:secret1;key2:secret2;..." + secrets: + +daps: + url: "http://ids-daps:4567" + clientId: "E7:07:2D:74:56:66:31:F0:7B:10:EA:B6:03:06:4C:23:7F:ED:A6:65:keyid:E7:07:2D:74:56:66:31:F0:7B:10:EA:B6:03:06:4C:23:7F:ED:A6:65" + +backendService: + httpProxyTokenReceiverUrl: "http://backend:8080" + diff --git a/edc-tests/deployment/src/main/resources/helm/tractusx-connector-test.yaml b/edc-tests/deployment/src/main/resources/helm/tractusx-connector-test.yaml new file mode 100644 index 000000000..05ba6cef2 --- /dev/null +++ b/edc-tests/deployment/src/main/resources/helm/tractusx-connector-test.yaml @@ -0,0 +1,81 @@ +# +# 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 +# + +fullnameOverride: tx-prod + +################################ +# EDC ControlPlane + DataPlane # +################################ +controlplane: + service: + type: NodePort + endpoints: + management: + authKey: password + image: + pullPolicy: Never + tag: "latest" + repository: "edc-controlplane-postgresql-hashicorp-vault" + securityContext: + # avoids some errors in the log: cannot write temp files of large multipart requests when R/O + readOnlyRootFilesystem: false + +dataplane: + image: + pullPolicy: Never + tag: "latest" + repository: "edc-dataplane-hashicorp-vault" + + securityContext: + # avoids some errors in the log: cannot write temp files of large multipart requests when R/O + readOnlyRootFilesystem: false + + aws: + endpointOverride: http://minio:9000 + secretAccessKey: qwerty123 + accessKeyId: qwerty123 + +postgresql: + username: user + password: password + jdbcUrl: jdbc:postgresql://postgresql:5432/edc + +vault: + hashicorp: + url: http://vault:8200 + token: root + + secretNames: + transferProxyTokenSignerPublicKey: daps-crt + transferProxyTokenSignerPrivateKey: daps-key + transferProxyTokenEncryptionAesKey: aes-keys + dapsPrivateKey: daps-key + dapsPublicKey: daps-crt + + # this must be set through CLI args: --set vault.secrets=$YOUR_VAULT_SECRETS where YOUR_VAULT_SECRETS should + # be a string in the format "key1:secret1;key2:secret2;..." + secrets: + +daps: + url: "http://ids-daps:4567" + clientId: "E7:07:2D:74:56:66:31:F0:7B:10:EA:B6:03:06:4C:23:7F:ED:A6:65:keyid:E7:07:2D:74:56:66:31:F0:7B:10:EA:B6:03:06:4C:23:7F:ED:A6:65" + +backendService: + httpProxyTokenReceiverUrl: "http://backend:8080" + diff --git a/edc-tests/deployment/src/main/resources/prepare-test.sh b/edc-tests/deployment/src/main/resources/prepare-test.sh new file mode 100755 index 000000000..51306abc8 --- /dev/null +++ b/edc-tests/deployment/src/main/resources/prepare-test.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# +# 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 +# +# SPDX-License-Identifier: Apache-2.0 +# +# Contributors: +# Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation +# +# + +set -euo pipefail + +if [ "$#" -lt 1 ]; then + echo "usage prepare-test.sh PATH_TO_YAML" + echo "" + echo "Please provide the path to the YAML file, which contains the config for the test infrastructure! In most cases + this will be edc-tests/deployment/src/main/resources/helm/test-infrastructure/values.yaml" + exit 42 +fi + +VALUES_FILE=$1 +KEY_FILE=daps.key +CERT_FILE=daps.cert + +# generate a new short-lived certificate and export the private key +openssl req -newkey rsa:2048 -new -nodes -x509 -days 1 -keyout $KEY_FILE -out $CERT_FILE -subj "/CN=test" + +DAPSCRT=$(cat $CERT_FILE) +DAPSKEY=$(cat $KEY_FILE) +AES_KEY=$( echo aes_enckey_test | base64) +echo $AES_KEY > aes.key + +# replace the cert for DAPS +yq -i ".idsdaps.connectors[0].certificate=\"$DAPSCRT\"" "$VALUES_FILE" + +# add a "postStart" command to the vault config, that creates a daps-key, daps-cert and an aes-keys secret +yq -i ".vault.server.postStart |= [\"sh\",\"-c\",\"{\nsleep 5\n\ncat << EOF | /bin/vault kv put secret/daps-crt content=-\n$DAPSCRT\nEOF\n\n +cat << EOF | /bin/vault kv put secret/daps-key content=-\n$DAPSKEY\nEOF\n\n +/bin/vault kv put secret/aes-keys content=$AES_KEY\n\n}\"]" "$VALUES_FILE" \ No newline at end of file diff --git a/edc-tests/e2e-tests/build.gradle.kts b/edc-tests/e2e-tests/build.gradle.kts index 6e2f7af27..d716d89c2 100644 --- a/edc-tests/e2e-tests/build.gradle.kts +++ b/edc-tests/e2e-tests/build.gradle.kts @@ -17,6 +17,7 @@ plugins { } dependencies { + testImplementation("com.squareup.okhttp3:mockwebserver:5.0.0-alpha.11") testImplementation(libs.restAssured) testImplementation(libs.postgres) testImplementation(libs.awaitility) @@ -28,10 +29,13 @@ dependencies { testImplementation(edc.core.api) testImplementation(edc.spi.catalog) testImplementation(edc.api.catalog) - testImplementation(testFixtures(edc.junit)) + testImplementation(edc.api.contractnegotiation) + testImplementation(edc.api.transferprocess) + testImplementation(edc.spi.dataplane.selector) + } // do not publish edcBuild { publish.set(false) -} \ No newline at end of file +} 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 e007a824d..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 @@ -17,59 +17,89 @@ import org.junit.jupiter.api.extension.RegisterExtension; -import java.util.Map; +import java.util.HashMap; -import static java.lang.String.format; -import static org.eclipse.edc.junit.testfixtures.TestUtils.tempDirectory; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.IDS_PATH; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_CONNECTOR_PATH; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_CONNECTOR_PORT; +import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_DATAPLANE_CONTROL_PORT; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_IDS_API; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_IDS_API_PORT; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_MANAGEMENT_PATH; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_MANAGEMENT_PORT; -import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_ASSET_FILE; +import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_PUBLIC_API_PORT; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_CONNECTOR_PATH; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_CONNECTOR_PORT; +import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_DATAPLANE_CONTROL_PORT; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_IDS_API; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_IDS_API_PORT; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_MANAGEMENT_PATH; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_MANAGEMENT_PORT; +import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_PUBLIC_API_PORT; public class MultiRuntimeTest { - public static final String SOKRATES_ASSET_PATH = format("%s/%s.txt", tempDirectory(), SOKRATES_ASSET_FILE); @RegisterExtension protected static Participant sokrates = new Participant( ":edc-tests:runtime", "SOKRATES", - Map.of( - "edc.ids.id", "urn:connector:sokrates", - "web.http.port", String.valueOf(SOKRATES_CONNECTOR_PORT), - "web.http.path", SOKRATES_CONNECTOR_PATH, - "edc.test.asset.path", SOKRATES_ASSET_PATH, - "web.http.management.port", String.valueOf(SOKRATES_MANAGEMENT_PORT), - "web.http.management.path", SOKRATES_MANAGEMENT_PATH, - "web.http.ids.port", String.valueOf(SOKRATES_IDS_API_PORT), - "web.http.ids.path", IDS_PATH, - "edc.api.auth.key", "testkey", - "ids.webhook.address", SOKRATES_IDS_API)); + new HashMap<>() { + { + put("edc.connector.name", "sokrates"); + put("edc.ids.id", "urn:connector:sokrates"); + put("web.http.port", String.valueOf(SOKRATES_CONNECTOR_PORT)); + put("web.http.path", SOKRATES_CONNECTOR_PATH); + put("web.http.management.port", String.valueOf(SOKRATES_MANAGEMENT_PORT)); + put("web.http.management.path", SOKRATES_MANAGEMENT_PATH); + put("web.http.ids.port", String.valueOf(SOKRATES_IDS_API_PORT)); + put("web.http.ids.path", IDS_PATH); + put("edc.api.auth.key", "testkey"); + put("ids.webhook.address", SOKRATES_IDS_API); + put("web.http.public.path", "/api/public"); + put("web.http.public.port", SOKRATES_PUBLIC_API_PORT); + + // embedded dataplane config + put("web.http.control.path", "/api/dataplane/control"); + put("web.http.control.port", SOKRATES_DATAPLANE_CONTROL_PORT); + put("edc.dataplane.token.validation.endpoint", "http://localhost:" + SOKRATES_DATAPLANE_CONTROL_PORT + "/api/dataplane/control/token"); + put("edc.dataplane.selector.httpplane.url", "http://localhost:" + SOKRATES_DATAPLANE_CONTROL_PORT + "/api/dataplane/control"); + put("edc.dataplane.selector.httpplane.sourcetypes", "HttpData"); + 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"); + } + }); + @RegisterExtension protected static Participant plato = new Participant( ":edc-tests:runtime", "PLATO", - Map.of( - "edc.ids.id", "urn:connector:plato", - "web.http.default.port", String.valueOf(PLATO_CONNECTOR_PORT), - "web.http.default.path", PLATO_CONNECTOR_PATH, - "web.http.management.port", String.valueOf(PLATO_MANAGEMENT_PORT), - "web.http.management.path", PLATO_MANAGEMENT_PATH, - "web.http.ids.port", String.valueOf(PLATO_IDS_API_PORT), - "web.http.ids.path", IDS_PATH, - "edc.api.auth.key", "testkey", - "ids.webhook.address", PLATO_IDS_API)); - - + new HashMap<>() { + { + put("edc.connector.name", "plato"); + put("edc.ids.id", "urn:connector:plato"); + put("web.http.default.port", String.valueOf(PLATO_CONNECTOR_PORT)); + put("web.http.default.path", PLATO_CONNECTOR_PATH); + put("web.http.management.port", String.valueOf(PLATO_MANAGEMENT_PORT)); + put("web.http.management.path", PLATO_MANAGEMENT_PATH); + put("web.http.ids.port", String.valueOf(PLATO_IDS_API_PORT)); + put("web.http.ids.path", IDS_PATH); + put("edc.api.auth.key", "testkey"); + put("ids.webhook.address", PLATO_IDS_API); + put("web.http.public.port", PLATO_PUBLIC_API_PORT); + put("web.http.public.path", "/api/public"); + // embedded dataplane config + put("web.http.control.path", "/api/dataplane/control"); + put("web.http.control.port", PLATO_DATAPLANE_CONTROL_PORT); + put("edc.dataplane.token.validation.endpoint", "http://localhost:" + PLATO_DATAPLANE_CONTROL_PORT + "/api/dataplane/control/token"); + put("edc.dataplane.selector.httpplane.url", "http://localhost:" + PLATO_DATAPLANE_CONTROL_PORT + "/api/dataplane/control"); + 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 7f17696ba..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 @@ -15,31 +15,47 @@ package org.eclipse.tractusx.edc.lifecycle; import io.restassured.specification.RequestSpecification; +import org.eclipse.edc.api.model.IdResponseDto; import org.eclipse.edc.api.query.QuerySpecDto; import org.eclipse.edc.catalog.spi.Catalog; import org.eclipse.edc.connector.api.management.catalog.model.CatalogRequestDto; +import org.eclipse.edc.connector.api.management.contractnegotiation.model.ContractNegotiationDto; +import org.eclipse.edc.connector.api.management.contractnegotiation.model.ContractOfferDescription; +import org.eclipse.edc.connector.api.management.contractnegotiation.model.NegotiationInitiateRequestDto; +import org.eclipse.edc.connector.api.management.transferprocess.model.TransferProcessDto; +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; import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.edc.spi.system.injection.InjectionContainer; import org.eclipse.edc.spi.types.TypeManager; +import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.edc.spi.types.domain.HttpDataAddress; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; import org.eclipse.tractusx.edc.token.MockDapsService; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; import java.net.URI; +import java.time.Duration; +import java.time.temporal.ChronoUnit; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; import static io.restassured.RestAssured.given; import static io.restassured.http.ContentType.JSON; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.lessThan; public class Participant extends EdcRuntimeExtension implements BeforeAllCallback, AfterAllCallback { @@ -48,8 +64,9 @@ public class Participant extends EdcRuntimeExtension implements BeforeAllCallbac private final String idsEndpoint; private final TypeManager typeManager = new TypeManager(); private final String idsId; - private DataWiper wiper; private final String bpn; + private final String backend; + private DataWiper wiper; public Participant(String moduleName, String runtimeName, Map properties) { super(moduleName, runtimeName, properties); @@ -58,23 +75,32 @@ public Participant(String moduleName, String runtimeName, Map pr this.apiKey = properties.get("edc.api.auth.key"); this.idsId = properties.get("edc.ids.id"); 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 - public void beforeTestExecution(ExtensionContext extensionContext) throws Exception { + public void beforeTestExecution(ExtensionContext extensionContext) { //do nothing - we only want to start the runtime once wiper.clearPersistence(); } @Override - public void afterTestExecution(ExtensionContext context) throws Exception { + public void afterTestExecution(ExtensionContext context) { } @Override - protected void bootExtensions(ServiceExtensionContext context, List> serviceExtensions) { - super.bootExtensions(context, serviceExtensions); - wiper = new DataWiper(context); + public void beforeAll(ExtensionContext context) throws Exception { + //only run this once + super.beforeTestExecution(context); + } + + @Override + public void afterAll(ExtensionContext context) throws Exception { + super.afterTestExecution(context); } /** @@ -106,6 +132,31 @@ public void createAsset(String id, Map properties) { } + /** + * Creates an asset with the given ID and props using the participant's Data Management API + */ + public void createAsset(String id, Map asserProperties, HttpDataAddress address) { + asserProperties = new HashMap<>(asserProperties); + asserProperties.put("asset:prop:id", id); + asserProperties.put("asset:prop:description", "test description"); + + var asset = Map.of( + "asset", Map.of( + "id", id, + "properties", asserProperties + ), + "dataAddress", address + ); + + baseRequest() + .body(asset) + .when() + .post("/assets") + .then() + .statusCode(200) + .contentType(JSON); + } + /** * Creates a {@link org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition} using the participant's Data Management API */ @@ -168,6 +219,44 @@ public Catalog requestCatalog(Participant other, QuerySpecDto query) { return typeManager.readValue(body, Catalog.class); } + public String negotiateContract(Participant other, String assetId) { + var catalog = requestCatalog(other); + assertThat(catalog.getContractOffers()).withFailMessage("Catalog received from " + other.idsId + " was empty!").isNotEmpty(); + var response = baseRequest() + .when() + .body(NegotiationInitiateRequestDto.Builder.newInstance() + .connectorAddress(other.idsEndpoint + "/data") + .connectorId(getBpn()) + .offer(catalog.getContractOffers().stream().filter(o -> o.getAsset().getId().equals(assetId)) + .findFirst().map(co -> ContractOfferDescription.Builder.newInstance() + .assetId(assetId) + .offerId(co.getId()) + .policy(co.getPolicy()) + .validity(ChronoUnit.SECONDS.between(co.getContractStart(), co.getContractEnd().plus(Duration.ofMillis(500)))) // the plus 1 is required due to https://github.com/eclipse-edc/Connector/issues/2650 + .build()) + .orElseThrow((() -> new RuntimeException("A contract for assetId " + assetId + " could not be negotiated")))) + .build() + ) + .post("/contractnegotiations") + .then(); + + var body = response.extract().body().asString(); + assertThat(response.extract().statusCode()).withFailMessage(body).isBetween(200, 299); + + return typeManager.readValue(body, IdResponseDto.class).getId(); + } + + public ContractNegotiationDto getNegotiation(String negotiationId) { + var response = baseRequest() + .when() + .get("/contractnegotiations/" + negotiationId) + .then(); + + var body = response.extract().body().asString(); + assertThat(response.extract().statusCode()).withFailMessage(body).isBetween(200, 299); + return typeManager.readValue(body, ContractNegotiationDto.class); + } + /** * Returns this participant's IDS ID */ @@ -182,15 +271,71 @@ public String getBpn() { return bpn; } - @Override - public void beforeAll(ExtensionContext context) throws Exception { - //only run this once - super.beforeTestExecution(context); + public String requestTransfer(String contractId, String assetId, Participant other, DataAddress destination, String dataRequestId) { + var response = baseRequest() + .when() + .body(TransferRequestDto.Builder.newInstance() + .assetId(assetId) + .id(dataRequestId) + .connectorAddress(other.idsEndpoint + "/data") + .managedResources(false) + .contractId(contractId) + .connectorId(bpn) + .protocol("ids-multipart") + .dataDestination(destination) + .build()) + .post("/transferprocess") + .then(); + + var body = response.extract().body().asString(); + assertThat(response.extract().statusCode()).withFailMessage(body).isBetween(200, 299); + + return typeManager.readValue(body, IdResponseDto.class).getId(); + } + + public TransferProcessDto getTransferProcess(String transferProcessId) { + var json = baseRequest() + .when() + .get("/transferprocess/" + transferProcessId) + .then() + .statusCode(allOf(greaterThanOrEqualTo(200), lessThan(300))) + .extract().body().asString(); + + return typeManager.readValue(json, TransferProcessDto.class); + + } + + public EndpointDataReference getDataReference(String dataRequestId) { + var dataReference = new AtomicReference(); + + var result = given() + .when() + .get(backend + "/{id}", dataRequestId) + .then() + .statusCode(200) + .extract() + .body() + .as(EndpointDataReference.class); + dataReference.set(result); + + return dataReference.get(); + } + + public String pullData(EndpointDataReference edr, Map queryParams) { + var response = given() + .baseUri(edr.getEndpoint()) + .header(edr.getAuthKey(), edr.getAuthCode()) + .queryParams(queryParams) + .when() + .get(); + assertThat(response.statusCode()).isBetween(200, 300); + return response.body().asString(); } @Override - public void afterAll(ExtensionContext context) throws Exception { - super.afterTestExecution(context); + protected void bootExtensions(ServiceExtensionContext context, List> serviceExtensions) { + super.bootExtensions(context, serviceExtensions); + wiper = new DataWiper(context); } private RequestSpecification baseRequest() { diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/TestRuntimeConfiguration.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/TestRuntimeConfiguration.java index 145ae476f..f865d39d9 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/TestRuntimeConfiguration.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/TestRuntimeConfiguration.java @@ -14,39 +14,30 @@ package org.eclipse.tractusx.edc.lifecycle; -import java.util.concurrent.TimeUnit; - import static org.eclipse.edc.junit.testfixtures.TestUtils.getFreePort; -public class TestRuntimeConfiguration { - - - public static final String IDS_PATH = "/api/v1/ids"; - - - public static final int PLATO_CONNECTOR_PORT = getFreePort(); - public static final int PLATO_MANAGEMENT_PORT = getFreePort(); - public static final String PLATO_CONNECTOR_PATH = "/api"; - public static final String PLATO_MANAGEMENT_PATH = "/api/v1/management"; - public static final String CONSUMER_CONNECTOR_MANAGEMENT_URL = "http://localhost:" + PLATO_MANAGEMENT_PORT + PLATO_MANAGEMENT_PATH; - public static final int PLATO_IDS_API_PORT = getFreePort(); - public static final String PLATO_IDS_API = "http://localhost:" + PLATO_IDS_API_PORT; - - public static final int SOKRATES_CONNECTOR_PORT = getFreePort(); - public static final int SOKRATES_MANAGEMENT_PORT = getFreePort(); - public static final String SOKRATES_CONNECTOR_PATH = "/api"; - public static final String SOKRATES_MANAGEMENT_PATH = "/api/v1/management"; - public static final int SOKRATES_IDS_API_PORT = getFreePort(); - public static final String SOKRATES_IDS_API = "http://localhost:" + SOKRATES_IDS_API_PORT; +class TestRuntimeConfiguration { - public static final String PROVIDER_IDS_API_DATA = "http://localhost:" + SOKRATES_IDS_API_PORT + IDS_PATH + "/data"; - public static final String PROVIDER_ASSET_ID = "test-document"; - public static final long CONTRACT_VALIDITY = TimeUnit.HOURS.toSeconds(1); + static final String IDS_PATH = "/api/v1/ids"; + static final int PLATO_CONNECTOR_PORT = getFreePort(); + static final int PLATO_MANAGEMENT_PORT = getFreePort(); + static final String PLATO_CONNECTOR_PATH = "/api"; + static final String PLATO_MANAGEMENT_PATH = "/api/v1/management"; + static final int PLATO_IDS_API_PORT = getFreePort(); + static final String PLATO_IDS_API = "http://localhost:" + PLATO_IDS_API_PORT; - public static final String SOKRATES_ASSET_FILE = "text-document.txt"; + static final int SOKRATES_CONNECTOR_PORT = getFreePort(); + static final int SOKRATES_MANAGEMENT_PORT = getFreePort(); + static final String SOKRATES_CONNECTOR_PATH = "/api"; + static final String SOKRATES_MANAGEMENT_PATH = "/api/v1/management"; + static final int SOKRATES_IDS_API_PORT = getFreePort(); + static final String SOKRATES_IDS_API = "http://localhost:" + SOKRATES_IDS_API_PORT; - public static final String PROVIDER_CONNECTOR_MANAGEMENT_URL = "http://localhost:" + SOKRATES_MANAGEMENT_PORT + SOKRATES_MANAGEMENT_PATH; + static final String SOKRATES_PUBLIC_API_PORT = String.valueOf(getFreePort()); + static final String PLATO_PUBLIC_API_PORT = String.valueOf(getFreePort()); + static final String PLATO_DATAPLANE_CONTROL_PORT = String.valueOf(getFreePort()); + static final String SOKRATES_DATAPLANE_CONTROL_PORT = String.valueOf(getFreePort()); } diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/policy/PolicyHelperFunctions.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/policy/PolicyHelperFunctions.java index b255aa078..56d92afbe 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/policy/PolicyHelperFunctions.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/policy/PolicyHelperFunctions.java @@ -24,6 +24,7 @@ import org.eclipse.edc.policy.model.OrConstraint; import org.eclipse.edc.policy.model.Permission; import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.policy.model.PolicyType; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -53,4 +54,16 @@ public static PolicyDefinition businessPartnerNumberPolicy(String id, String... .build()) .build()).build()).build(); } + + public static PolicyDefinition noConstraintPolicy(String id) { + return PolicyDefinition.Builder.newInstance() + .id(id) + .policy(Policy.Builder.newInstance() + .permission(Permission.Builder.newInstance() + .action(Action.Builder.newInstance().type("USE").build()) + .build()) + .type(PolicyType.SET) + .build()) + .build(); + } } diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/CatalogTest.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/CatalogTest.java index b7812ae4a..ec8045f5b 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/CatalogTest.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/CatalogTest.java @@ -16,14 +16,8 @@ import org.eclipse.edc.api.query.QuerySpecDto; -import org.eclipse.edc.connector.policy.spi.PolicyDefinition; import org.eclipse.edc.junit.annotations.EndToEndTest; -import org.eclipse.edc.policy.model.Action; -import org.eclipse.edc.policy.model.Permission; -import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.policy.model.PolicyType; import org.eclipse.tractusx.edc.lifecycle.MultiRuntimeTest; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -32,16 +26,11 @@ import static java.util.stream.IntStream.range; import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.tractusx.edc.policy.PolicyHelperFunctions.businessPartnerNumberPolicy; +import static org.eclipse.tractusx.edc.policy.PolicyHelperFunctions.noConstraintPolicy; @EndToEndTest public class CatalogTest extends MultiRuntimeTest { - - @BeforeAll - static void setup() { - - } - @Test void requestCatalog_fulfillsPolicy_shouldReturnOffer() { // arrange @@ -133,16 +122,6 @@ void requestCatalog_of1000Assets_shouldContainAll() { } - private PolicyDefinition noConstraintPolicy(String id) { - return PolicyDefinition.Builder.newInstance() - .id(id) - .policy(Policy.Builder.newInstance() - .permission(Permission.Builder.newInstance() - .action(Action.Builder.newInstance().type("USE").build()) - .build()) - .type(PolicyType.SET) - .build()) - .build(); - } + } 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 new file mode 100644 index 000000000..1f93ae5e7 --- /dev/null +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/HttpConsumerPullWithProxyTest.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.tests; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.eclipse.edc.connector.api.management.transferprocess.model.TransferProcessDto; +import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationStates; +import org.eclipse.edc.connector.transfer.spi.types.TransferProcessStates; +import org.eclipse.edc.junit.annotations.EndToEndTest; +import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.edc.spi.types.domain.HttpDataAddress; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.tractusx.edc.lifecycle.MultiRuntimeTest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.time.Duration; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; + +import static java.time.Duration.ofSeconds; +import static org.assertj.core.api.Assertions.assertThat; +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.businessPartnerNumberPolicy; + +@EndToEndTest +public class HttpConsumerPullWithProxyTest extends MultiRuntimeTest { + private static final Duration ASYNC_TIMEOUT = ofSeconds(45); + private static final Duration ASYNC_POLL_INTERVAL = ofSeconds(1); + private final long ONE_WEEK = 60 * 60 * 24 * 7; + MockWebServer server = new MockWebServer(); + + @Test + void transferData_privateBackend() throws IOException, InterruptedException { + var assetId = "api-asset-1"; + var url = server.url("/mock/api"); + server.start(); + + var authCodeHeaderName = "test-authkey"; + var authCode = "test-authcode"; + plato.createAsset(assetId, Map.of(), HttpDataAddress.Builder.newInstance() + .contentType("application/json") + .baseUrl(url.toString()) + .authKey(authCodeHeaderName) + .authCode(authCode) + .build()); + 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); + + // forward declarations of our actual values + var transferProcessId = new AtomicReference(); + var dataRequestId = UUID.randomUUID().toString(); + var contractAgreementId = new AtomicReference(); + var edr = new AtomicReference(); + + + // wait for the successful contract negotiation + await().pollInterval(ASYNC_POLL_INTERVAL) + .atMost(ASYNC_TIMEOUT) + .untilAsserted(() -> { + var negotiation = sokrates.getNegotiation(negotiationId); + assertThat(negotiation.getState()).isEqualTo(ContractNegotiationStates.CONFIRMED.toString()); + contractAgreementId.set(negotiation.getContractAgreementId()); + assertThat(contractAgreementId).isNotNull(); + transferProcessId.set(sokrates.requestTransfer(contractAgreementId.get(), assetId, plato, DataAddress.Builder.newInstance() + .type(HTTP_PROXY) + .build(), dataRequestId)); + assertThat(transferProcessId).isNotNull(); + }); + + // wait until transfer process completes + await().pollInterval(fibonacci()) + .atMost(ASYNC_TIMEOUT) + .untilAsserted(() -> { + var tp = sokrates.getTransferProcess(transferProcessId.get()); + assertThat(tp).isNotNull() + .extracting(TransferProcessDto::getState).isEqualTo(TransferProcessStates.COMPLETED.toString()); + }); + + // wait until EDC is available on the consumer side + server.enqueue(new MockResponse().setBody("test response").setResponseCode(200)); + await().pollInterval(fibonacci()) + .atMost(ASYNC_TIMEOUT) + .untilAsserted(() -> { + edr.set(sokrates.getDataReference(dataRequestId)); + assertThat(edr).isNotNull(); + }); + + // pull data out of provider's backend service: + // Cons-DP -> Prov-DP -> Prov-backend + assertThat(sokrates.pullData(edr.get(), Map.of())).isEqualTo("test response"); + var rq = server.takeRequest(); + assertThat(rq.getHeader(authCodeHeaderName)).isEqualTo(authCode); + assertThat(rq.getHeader("Edc-Contract-Agreement-Id")).isEqualTo(contractAgreementId.get()); + assertThat(rq.getMethod()).isEqualToIgnoringCase("GET"); + } + + @AfterEach + void teardown() throws IOException { + server.shutdown(); + } +} diff --git a/edc-tests/runtime/build.gradle.kts b/edc-tests/runtime/build.gradle.kts index dc31011c0..0123162fb 100644 --- a/edc-tests/runtime/build.gradle.kts +++ b/edc-tests/runtime/build.gradle.kts @@ -15,18 +15,28 @@ plugins { `java-library` id("application") - id("com.github.johnrengelman.shadow") version "8.0.0" + id("com.github.johnrengelman.shadow") version "8.1.1" } dependencies { - runtimeOnly(project(":edc-controlplane:edc-controlplane-base")) { + // use basic (all in-mem) control plane + implementation(project(":edc-controlplane:edc-controlplane-base")) { exclude("org.eclipse.edc", "oauth2-core") exclude("org.eclipse.edc", "oauth2-daps") exclude(module = "data-encryption") exclude(module = "control-plane-adapter") } + + // use basic (all in-mem) data plane + runtimeOnly(project(":edc-dataplane:edc-dataplane-base")) { + exclude("org.eclipse.edc", "api-observability") + } + + implementation(edc.core.controlplane) + // for the controller + implementation(libs.jakarta.rsApi) } application { @@ -41,4 +51,4 @@ tasks.withType { // do not publish edcBuild { publish.set(false) -} \ No newline at end of file +} diff --git a/edc-tests/runtime/src/main/java/org/eclipse/tractusx/edc/lifecycle/ConsumerEdrHandlerController.java b/edc-tests/runtime/src/main/java/org/eclipse/tractusx/edc/lifecycle/ConsumerEdrHandlerController.java new file mode 100644 index 000000000..5ca08a922 --- /dev/null +++ b/edc-tests/runtime/src/main/java/org/eclipse/tractusx/edc/lifecycle/ConsumerEdrHandlerController.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.lifecycle; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +@Path("/consumer") +public class ConsumerEdrHandlerController { + + private final Monitor monitor; + private Map dataReference; + + public ConsumerEdrHandlerController(Monitor monitor) { + this.monitor = monitor; + dataReference = new HashMap<>(); + } + + @Path("/datareference") + @POST + @Consumes({ MediaType.APPLICATION_JSON }) + public void pushDataReference(EndpointDataReference edr) { + monitor.debug("Received new endpoint data reference with url " + edr.getEndpoint()); + dataReference.put(edr.getId(), edr); + } + + @Path("/datareference/{id}") + @GET + @Produces({ MediaType.APPLICATION_JSON }) + public EndpointDataReference getDataReference(@PathParam("id") String id) { + return Optional.ofNullable(dataReference.get(id)).orElseGet(() -> + { + monitor.warning("No EndpointDataReference found with id " + id); + return null; + }); + } + +} diff --git a/edc-tests/runtime/src/main/java/org/eclipse/tractusx/edc/lifecycle/ConsumerServicesExtension.java b/edc-tests/runtime/src/main/java/org/eclipse/tractusx/edc/lifecycle/ConsumerServicesExtension.java new file mode 100644 index 000000000..f46ef3e4e --- /dev/null +++ b/edc-tests/runtime/src/main/java/org/eclipse/tractusx/edc/lifecycle/ConsumerServicesExtension.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.lifecycle; + +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.web.spi.WebService; + +public class ConsumerServicesExtension implements ServiceExtension { + @Inject + private WebService webService; + + @Override + public void initialize(ServiceExtensionContext context) { + webService.registerResource("default", new ConsumerEdrHandlerController(context.getMonitor())); + } +} diff --git a/edc-tests/runtime/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-tests/runtime/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 000000000..619665085 --- /dev/null +++ b/edc-tests/runtime/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,15 @@ +# +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# +# 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 +# +# SPDX-License-Identifier: Apache-2.0 +# +# Contributors: +# Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation +# +# + +org.eclipse.tractusx.edc.lifecycle.ConsumerServicesExtension diff --git a/gradle.properties b/gradle.properties index 206345621..6f20531b8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ groupId=org.eclipse.tractusx.edc -version=0.3.2 +version=0.3.4 javaVersion=11 # configure the build: diff --git a/lintconf.yaml b/lintconf.yaml index 45c708042..f310a9da8 100644 --- a/lintconf.yaml +++ b/lintconf.yaml @@ -1,3 +1,22 @@ +# +# Copyright (c) 2021, 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 +# + --- rules: braces: diff --git a/pr_etiquette.md b/pr_etiquette.md index ce9ec73f8..26b513a9f 100644 --- a/pr_etiquette.md +++ b/pr_etiquette.md @@ -10,7 +10,7 @@ Submitting pull requests in EDC should be done while adhering to a couple of sim - No surprise PRs please. Before you submit a PR, open a discussion or an issue outlining your planned work and give people time to comment. It may even be advisable to contact committers using the `@mention` feature. Unsolicited PRs may get ignored or rejected. -- Create your working branch in your fork of TractusX-EDC, and create the PR against the upstream `develop` branch +- Create your working branch in your fork of Tractus-X EDC, and create the PR against the upstream `main` branch - Create focused PRs: your work should be focused on one particular feature or bug. Do not create broad-scoped PRs that solve multiple issues as reviewers may reject those PR bombs outright. - Provide a clear description and motivation in the PR description in GitHub. This makes the reviewer's life much @@ -58,6 +58,13 @@ Submitting pull requests in EDC should be done while adhering to a couple of sim - Be civil and objective. No foul language, insulting or otherwise abusive language will be tolerated. The goal is to _encourage_ contributions. -## The technical committers +## The technical committers (as of April 05, 2023) -- TBD +Main committers for the Tractus-X EDC project: + +- @paullatzelsperger +- @florianrusch-zf + +Alternatively, the following Tractus-X committers can also step in: + +- @SebastianBezold diff --git a/settings.gradle.kts b/settings.gradle.kts index e0fc39433..4a269e4e0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,22 @@ +/** + * 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 + */ + rootProject.name = "tractusx-edc" include(":edc-extensions:business-partner-validation") @@ -19,9 +38,9 @@ include(":edc-tests:cucumber") // modules for controlplane artifacts include(":edc-controlplane") include(":edc-controlplane:edc-controlplane-base") -include(":edc-controlplane:edc-controlplane-memory") +include(":edc-controlplane:edc-runtime-memory") include(":edc-controlplane:edc-controlplane-memory-hashicorp-vault") -include(":edc-controlplane:edc-controlplane-postgresql") +include(":edc-controlplane:edc-controlplane-postgresql-azure-vault") include(":edc-controlplane:edc-controlplane-postgresql-hashicorp-vault") // modules for dataplane artifacts @@ -52,13 +71,13 @@ dependencyResolutionManagement { versionCatalogs { create("libs") { from("org.eclipse.edc:edc-versions:0.0.1-20230220-SNAPSHOT") - library("testcontainers-junit", "org.testcontainers","junit-jupiter").version("1.17.6") - library("apache-sshd-core", "org.apache.sshd","sshd-core").version("2.9.2") - library("apache-sshd-sftp", "org.apache.sshd","sshd-sftp").version("2.9.2") + library("testcontainers-junit", "org.testcontainers", "junit-jupiter").version("1.17.6") + library("apache-sshd-core", "org.apache.sshd", "sshd-core").version("2.9.2") + library("apache-sshd-sftp", "org.apache.sshd", "sshd-sftp").version("2.9.2") } // 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") @@ -86,6 +105,9 @@ dependencyResolutionManagement { library("api-management", "org.eclipse.edc", "management-api").versionRef("edc") library("api-catalog", "org.eclipse.edc", "catalog-api").versionRef("edc") library("api-observability", "org.eclipse.edc", "api-observability").versionRef("edc") + library("api-contractnegotiation", "org.eclipse.edc", "contract-negotiation-api").versionRef("edc") + library("api-dataplane", "org.eclipse.edc", "data-plane-api").versionRef("edc") + library("api-transferprocess", "org.eclipse.edc", "transfer-process-api").versionRef("edc") library("ext-http", "org.eclipse.edc", "http").versionRef("edc") library("spi-ids", "org.eclipse.edc", "ids-spi").versionRef("edc") library("ids", "org.eclipse.edc", "ids").versionRef("edc") @@ -140,6 +162,8 @@ dependencyResolutionManagement { "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") diff --git a/styleguide.md b/styleguide.md index e5183465b..cfde3bebd 100644 --- a/styleguide.md +++ b/styleguide.md @@ -1,4 +1,4 @@ -# Eclipse Tractus-X EDC Code Style Guide +# Tractus-X EDC Code Style Guide In order to maintain a coherent code style throughout the project we ask every contributor to adhere to a few simple style guidelines. We assume most developers will use at least something like `vim` and therefore have support for