From 509ba8913fb4ee6382226b1ce5fbe8179812f848 Mon Sep 17 00:00:00 2001 From: Fabian Burth Date: Tue, 28 May 2024 19:37:05 +0200 Subject: [PATCH] Follow Up To Maven Access (#781) ## Description This Pull Request is a follow up to the [Pull Request](https://github.com/open-component-model/ocm/pull/731) adding a Maven Access Method. This Pull Request adds a corresponding input type, a blob access and corresponding helper methods in the elements package (and therefore makes several adjustments). --- .golangci.yaml | 3 + .../controllercmds/common/manifests.go | 3 +- .../commands/controllercmds/install/cmd.go | 2 +- .../commands/controllercmds/uninstall/cmd.go | 7 +- .../uninstall/uninstall_cert_manager.go | 4 +- .../commands/misccmds/credentials/get/cmd.go | 2 +- .../commands/ocmcmds/common/addhdlrs/utils.go | 2 +- .../common/handlers/pluginhdlr/typehandler.go | 2 +- .../common/handlers/vershdlr/typehandler.go | 2 +- .../ocmcmds/common/inputs/options/standard.go | 11 +- .../ocmcmds/common/inputs/types/init.go | 1 + .../ocmcmds/common/inputs/types/maven/cli.go | 32 ++ .../common/inputs/types/maven/input_test.go | 96 ++++ .../ocmcmds/common/inputs/types/maven/spec.go | 99 ++++ .../common/inputs/types/maven}/suite_test.go | 4 +- .../types/maven/testdata/resources1.yaml | 8 + .../ocmcmds/common/inputs/types/maven/type.go | 43 ++ .../common/options/schemaoption/option.go | 2 +- cmds/ocm/commands/ocmcmds/common/resources.go | 4 +- cmds/ocm/commands/ocmcmds/common/utils.go | 2 +- .../commands/ocmcmds/components/add/cmd.go | 2 +- .../ocmcmds/components/download/cmd.go | 2 +- .../ocmcmds/components/transfer/cmd.go | 2 +- cmds/ocm/commands/ocmcmds/ctf/transfer/cmd.go | 2 +- .../commands/ocmcmds/plugins/install/cmd.go | 2 +- .../commands/toicmds/package/bootstrap/cmd.go | 2 +- .../commands/toicmds/package/describe/cmd.go | 2 +- cmds/ocm/commands/verbs/controller/cmd.go | 2 +- cmds/ocm/pkg/utils/command.go | 2 +- cmds/ocm/pkg/utils/handling.go | 1 + .../plugin_accessmethod_compose.md | 7 +- docs/pluginreference/plugin_descriptor.md | 7 +- .../plugin_valueset_compose.md | 7 +- .../ocm_add_resource-configuration.md | 65 ++- docs/reference/ocm_add_resources.md | 65 ++- .../reference/ocm_add_source-configuration.md | 65 ++- docs/reference/ocm_add_sources.md | 65 ++- docs/reference/ocm_logging.md | 3 +- docs/reference/ocm_ocm-accessmethods.md | 20 +- docs/reference/ocm_ocm-uploadhandlers.md | 8 +- .../ocm_transfer_commontransportarchive.md | 8 +- .../ocm_transfer_componentversions.md | 8 +- .../lib/tour/01-getting-started/README.md | 14 +- .../01-basic-componentversion-creation.go | 2 +- .../README.md | 2 +- .../03-working-with-credentials/common.go | 2 +- .../common.go | 2 +- .../06-signing-component-versions/common.go | 2 +- go.mod | 3 +- go.sum | 4 +- pkg/blobaccess/maven/access.go | 38 ++ pkg/blobaccess/maven/access_test.go | 125 +++++ pkg/blobaccess/maven/maven.go | 36 ++ pkg/blobaccess/maven/options.go | 204 ++++++++ pkg/blobaccess/maven/suite_test.go | 13 + pkg/blobaccess/maven/utils.go | 167 +++++++ pkg/blobaccess/standard.go | 17 + pkg/blobaccess/wget/options.go | 3 + pkg/common/accessio/retry_test.go | 1 + pkg/common/accessobj/cachedblob.go | 6 +- pkg/contexts/config/gc_test.go | 2 +- .../{mvn => maven}/identity/identity.go | 28 +- pkg/contexts/credentials/gc_test.go | 2 +- .../repositories/npm/repository_test.go | 2 +- .../repositories/vault/repo_int_test.go | 33 +- .../repositories/vault/repo_test.go | 2 +- .../datacontext/attrs/tmpcache/attr.go | 18 +- .../datacontext/attrs/vfsattr/attr.go | 2 +- pkg/contexts/oci/gc_test.go | 2 +- pkg/contexts/oci/repositories/ctf/ctf_test.go | 2 +- pkg/contexts/ocm/accessmethods/init.go | 2 +- .../accessmethods/{mvn => maven}/README.md | 0 .../ocm/accessmethods/{mvn => maven}/cli.go | 22 +- .../ocm/accessmethods/maven/method.go | 159 +++++++ .../ocm/accessmethods/maven/method_test.go | 133 ++++++ .../mvn => accessmethods/maven}/suite_test.go | 4 +- .../ocm/accessmethods/mvn/coordinates.go | 144 ------ .../ocm/accessmethods/mvn/coordinates_test.go | 55 --- .../ocm/accessmethods/mvn/integration_test.go | 98 ---- pkg/contexts/ocm/accessmethods/mvn/method.go | 444 ------------------ .../ocm/accessmethods/mvn/method_test.go | 87 ---- .../ocm/accessmethods/options/standard.go | 9 +- .../ocm/accessmethods/wget/options.go | 15 - .../handlers/generic/maven/blobhandler.go | 126 +++++ .../generic/maven/blobhandler_test.go | 101 ++++ .../generic/{mvn => maven}/registration.go | 60 ++- .../{mvn => maven}/registration_test.go | 12 +- .../handlers/generic/maven/suite_test.go | 13 + .../handlers/generic/mvn/blobhandler.go | 180 ------- .../handlers/generic/mvn/blobhandler_test.go | 43 -- .../handlers/generic/npm/registration_test.go | 2 +- pkg/contexts/ocm/blobhandler/handlers/init.go | 2 +- pkg/contexts/ocm/cpi/builder.go | 50 ++ pkg/contexts/ocm/cpi/interface.go | 4 - .../artifactaccess/mavenaccess/options.go | 20 + .../artifactaccess/mavenaccess/resource.go | 39 ++ .../artifactaccess/mvnaccess/resource.go | 30 -- .../artifactblob/helmblob/helmblob_test.go | 37 ++ .../artifactblob/helmblob/suite_test.go | 13 + .../helmblob/testdata/testchart1/.helmignore | 23 + .../testdata/testchart1/.idea/somefile | 0 .../helmblob/testdata/testchart1/Chart.yaml | 24 + .../testdata/testchart1/templates/NOTES.txt | 22 + .../testchart1/templates/_helpers.tpl | 62 +++ .../testchart1/templates/deployment.yaml | 61 +++ .../testdata/testchart1/templates/hpa.yaml | 28 ++ .../testchart1/templates/ingress.yaml | 61 +++ .../testchart1/templates/service.yaml | 15 + .../testchart1/templates/serviceaccount.yaml | 12 + .../templates/tests/test-connection.yaml | 15 + .../helmblob/testdata/testchart1/values.yaml | 81 ++++ .../helmblob/testdata/testchart2/.helmignore | 23 + .../testdata/testchart2/.idea/somefile | 0 .../helmblob/testdata/testchart2/Chart.yaml | 24 + .../testdata/testchart2/templates/NOTES.txt | 22 + .../testchart2/templates/_helpers.tpl | 62 +++ .../testchart2/templates/deployment.yaml | 61 +++ .../testdata/testchart2/templates/hpa.yaml | 28 ++ .../testchart2/templates/ingress.yaml | 61 +++ .../testchart2/templates/service.yaml | 15 + .../testchart2/templates/serviceaccount.yaml | 12 + .../templates/tests/test-connection.yaml | 15 + .../helmblob/testdata/testchart2/values.yaml | 81 ++++ .../artifactblob/mavenblob/access_test.go | 61 +++ .../artifactblob/mavenblob/options.go | 96 ++++ .../artifactblob/mavenblob/resource.go | 43 ++ .../artifactblob/mavenblob/suite_test.go | 13 + .../elements/artifactblob/textblob/options.go | 2 +- .../artifactblob/wgetblob/resource.go | 2 +- pkg/contexts/ocm/gc_test.go | 2 +- pkg/contexts/ocm/plugin/cache/updater.go | 2 +- .../repositories/genericocireg/repo_test.go | 3 +- pkg/contexts/ocm/resourcetypes/const.go | 4 +- pkg/contexts/ocm/transfer/autohandler_test.go | 1 + pkg/contexts/ocm/utils/localize/format.go | 2 +- pkg/env/env.go | 96 +++- pkg/exception/exception_test.go | 1 + pkg/iotools/hashReaderWriter.go | 140 +++--- pkg/iotools/hashReaderWriter_test.go | 49 +- pkg/maven/access.go | 407 ++++++++++++++++ pkg/maven/access_test.go | 132 ++++++ pkg/maven/coordinates.go | 218 +++++++++ pkg/maven/coordinates_test.go | 52 ++ pkg/maven/logging.go | 7 + pkg/maven/maventest/testdata.go | 9 + .../fail/test/repository/42/repository-42.pom | 0 .../test/repository/42/repository-42.pom.sha1 | 0 .../sdk-modules-bom-5.7.0-random-content.json | 1 + ...modules-bom-5.7.0-random-content.json.sha1 | 1 + .../sdk-modules-bom-5.7.0-random-content.txt | 1 + ...-modules-bom-5.7.0-random-content.txt.sha1 | 1 + .../5.7.0/sdk-modules-bom-5.7.0-sources.jar | Bin 0 -> 595 bytes .../sdk-modules-bom-5.7.0-sources.jar.sha1 | 1 + .../5.7.0/sdk-modules-bom-5.7.0.jar | Bin 0 -> 595 bytes .../5.7.0/sdk-modules-bom-5.7.0.jar.sha1 | 1 + .../5.7.0/sdk-modules-bom-5.7.0.pom | 0 .../5.7.0/sdk-modules-bom-5.7.0.pom.sha1 | 0 pkg/maven/suite_test.go | 13 + pkg/maven/utils.go | 37 ++ pkg/optionutils/utils.go | 7 + pkg/refmgmt/finalized/finalized_test.go | 2 +- pkg/testutils/tcp.go | 30 ++ pkg/utils/package.go | 44 ++ 163 files changed, 4145 insertions(+), 1458 deletions(-) create mode 100644 cmds/ocm/commands/ocmcmds/common/inputs/types/maven/cli.go create mode 100644 cmds/ocm/commands/ocmcmds/common/inputs/types/maven/input_test.go create mode 100644 cmds/ocm/commands/ocmcmds/common/inputs/types/maven/spec.go rename {pkg/contexts/ocm/accessmethods/mvn => cmds/ocm/commands/ocmcmds/common/inputs/types/maven}/suite_test.go (72%) create mode 100644 cmds/ocm/commands/ocmcmds/common/inputs/types/maven/testdata/resources1.yaml create mode 100644 cmds/ocm/commands/ocmcmds/common/inputs/types/maven/type.go create mode 100644 pkg/blobaccess/maven/access.go create mode 100644 pkg/blobaccess/maven/access_test.go create mode 100644 pkg/blobaccess/maven/maven.go create mode 100644 pkg/blobaccess/maven/options.go create mode 100644 pkg/blobaccess/maven/suite_test.go create mode 100644 pkg/blobaccess/maven/utils.go rename pkg/contexts/credentials/builtin/{mvn => maven}/identity/identity.go (67%) rename pkg/contexts/ocm/accessmethods/{mvn => maven}/README.md (100%) rename pkg/contexts/ocm/accessmethods/{mvn => maven}/cli.go (73%) create mode 100644 pkg/contexts/ocm/accessmethods/maven/method.go create mode 100644 pkg/contexts/ocm/accessmethods/maven/method_test.go rename pkg/contexts/ocm/{blobhandler/handlers/generic/mvn => accessmethods/maven}/suite_test.go (72%) delete mode 100644 pkg/contexts/ocm/accessmethods/mvn/coordinates.go delete mode 100644 pkg/contexts/ocm/accessmethods/mvn/coordinates_test.go delete mode 100644 pkg/contexts/ocm/accessmethods/mvn/integration_test.go delete mode 100644 pkg/contexts/ocm/accessmethods/mvn/method.go delete mode 100644 pkg/contexts/ocm/accessmethods/mvn/method_test.go create mode 100644 pkg/contexts/ocm/blobhandler/handlers/generic/maven/blobhandler.go create mode 100644 pkg/contexts/ocm/blobhandler/handlers/generic/maven/blobhandler_test.go rename pkg/contexts/ocm/blobhandler/handlers/generic/{mvn => maven}/registration.go (51%) rename pkg/contexts/ocm/blobhandler/handlers/generic/{mvn => maven}/registration_test.go (58%) create mode 100644 pkg/contexts/ocm/blobhandler/handlers/generic/maven/suite_test.go delete mode 100644 pkg/contexts/ocm/blobhandler/handlers/generic/mvn/blobhandler.go delete mode 100644 pkg/contexts/ocm/blobhandler/handlers/generic/mvn/blobhandler_test.go create mode 100644 pkg/contexts/ocm/cpi/builder.go create mode 100644 pkg/contexts/ocm/elements/artifactaccess/mavenaccess/options.go create mode 100644 pkg/contexts/ocm/elements/artifactaccess/mavenaccess/resource.go delete mode 100644 pkg/contexts/ocm/elements/artifactaccess/mvnaccess/resource.go create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/helmblob_test.go create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/suite_test.go create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/.helmignore create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/.idea/somefile create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/Chart.yaml create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/NOTES.txt create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/_helpers.tpl create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/deployment.yaml create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/hpa.yaml create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/ingress.yaml create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/service.yaml create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/serviceaccount.yaml create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/tests/test-connection.yaml create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/values.yaml create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/.helmignore create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/.idea/somefile create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/Chart.yaml create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/NOTES.txt create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/_helpers.tpl create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/deployment.yaml create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/hpa.yaml create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/ingress.yaml create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/service.yaml create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/serviceaccount.yaml create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/tests/test-connection.yaml create mode 100644 pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/values.yaml create mode 100644 pkg/contexts/ocm/elements/artifactblob/mavenblob/access_test.go create mode 100644 pkg/contexts/ocm/elements/artifactblob/mavenblob/options.go create mode 100644 pkg/contexts/ocm/elements/artifactblob/mavenblob/resource.go create mode 100644 pkg/contexts/ocm/elements/artifactblob/mavenblob/suite_test.go create mode 100644 pkg/maven/access.go create mode 100644 pkg/maven/access_test.go create mode 100644 pkg/maven/coordinates.go create mode 100644 pkg/maven/coordinates_test.go create mode 100644 pkg/maven/logging.go create mode 100644 pkg/maven/maventest/testdata.go rename pkg/{contexts/ocm/accessmethods/mvn/testdata => maven/maventest/testdata/.m2}/fail/test/repository/42/repository-42.pom (100%) rename pkg/{contexts/ocm/accessmethods/mvn/testdata => maven/maventest/testdata/.m2}/fail/test/repository/42/repository-42.pom.sha1 (100%) create mode 100644 pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-random-content.json create mode 100644 pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-random-content.json.sha1 create mode 100644 pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-random-content.txt create mode 100644 pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-random-content.txt.sha1 create mode 100644 pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-sources.jar create mode 100644 pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-sources.jar.sha1 create mode 100644 pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0.jar create mode 100644 pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0.jar.sha1 rename pkg/{contexts/ocm/accessmethods/mvn => maven/maventest}/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0.pom (100%) rename pkg/{contexts/ocm/accessmethods/mvn => maven/maventest}/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0.pom.sha1 (100%) create mode 100644 pkg/maven/suite_test.go create mode 100644 pkg/maven/utils.go create mode 100644 pkg/optionutils/utils.go create mode 100644 pkg/testutils/tcp.go create mode 100644 pkg/utils/package.go diff --git a/.golangci.yaml b/.golangci.yaml index 8a105b4382..9e4ab89249 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -134,6 +134,9 @@ linters-settings: line-length: 120 gosec: exclude-generated: true + gocritic: + disabled-checks: + - elseif issues: exclude: diff --git a/cmds/ocm/commands/controllercmds/common/manifests.go b/cmds/ocm/commands/controllercmds/common/manifests.go index bf1a5b07d9..c54bc5a7c6 100644 --- a/cmds/ocm/commands/controllercmds/common/manifests.go +++ b/cmds/ocm/commands/controllercmds/common/manifests.go @@ -7,9 +7,10 @@ import ( "path/filepath" "github.com/fluxcd/pkg/ssa" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "github.com/open-component-model/ocm/pkg/contexts/clictx" "github.com/open-component-model/ocm/pkg/out" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) func Install(ctx context.Context, octx clictx.Context, sm *ssa.ResourceManager, releaseURL, baseURL, manifest, filename, version string, dryRun bool) error { diff --git a/cmds/ocm/commands/controllercmds/install/cmd.go b/cmds/ocm/commands/controllercmds/install/cmd.go index 5d5a5b8bea..25427a1ba5 100644 --- a/cmds/ocm/commands/controllercmds/install/cmd.go +++ b/cmds/ocm/commands/controllercmds/install/cmd.go @@ -6,7 +6,6 @@ import ( "time" "github.com/fluxcd/pkg/ssa" - "github.com/open-component-model/ocm/cmds/ocm/commands/controllercmds/common" "github.com/spf13/cobra" "github.com/spf13/pflag" corev1 "k8s.io/api/core/v1" @@ -15,6 +14,7 @@ import ( "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/open-component-model/ocm/cmds/ocm/commands/controllercmds/common" "github.com/open-component-model/ocm/cmds/ocm/commands/controllercmds/names" "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" diff --git a/cmds/ocm/commands/controllercmds/uninstall/cmd.go b/cmds/ocm/commands/controllercmds/uninstall/cmd.go index 3c1e497462..47c06530c4 100644 --- a/cmds/ocm/commands/controllercmds/uninstall/cmd.go +++ b/cmds/ocm/commands/controllercmds/uninstall/cmd.go @@ -6,15 +6,16 @@ import ( "time" "github.com/fluxcd/pkg/ssa" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "k8s.io/cli-runtime/pkg/genericclioptions" + "github.com/open-component-model/ocm/cmds/ocm/commands/controllercmds/common" "github.com/open-component-model/ocm/cmds/ocm/commands/controllercmds/names" "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" "github.com/open-component-model/ocm/pkg/contexts/clictx" "github.com/open-component-model/ocm/pkg/out" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "k8s.io/cli-runtime/pkg/genericclioptions" ) var ( diff --git a/cmds/ocm/commands/controllercmds/uninstall/uninstall_cert_manager.go b/cmds/ocm/commands/controllercmds/uninstall/uninstall_cert_manager.go index 944c881eb0..e0380e264b 100644 --- a/cmds/ocm/commands/controllercmds/uninstall/uninstall_cert_manager.go +++ b/cmds/ocm/commands/controllercmds/uninstall/uninstall_cert_manager.go @@ -2,12 +2,14 @@ package uninstall import ( "context" - _ "embed" "fmt" "os" + _ "embed" + "github.com/fluxcd/pkg/ssa" "github.com/mandelsoft/filepath/pkg/filepath" + "github.com/open-component-model/ocm/cmds/ocm/commands/controllercmds/common" "github.com/open-component-model/ocm/pkg/out" ) diff --git a/cmds/ocm/commands/misccmds/credentials/get/cmd.go b/cmds/ocm/commands/misccmds/credentials/get/cmd.go index 48e5f574b4..a634692cb9 100644 --- a/cmds/ocm/commands/misccmds/credentials/get/cmd.go +++ b/cmds/ocm/commands/misccmds/credentials/get/cmd.go @@ -5,7 +5,6 @@ import ( "strings" "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/listformat" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -15,6 +14,7 @@ import ( "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" "github.com/open-component-model/ocm/pkg/contexts/clictx" "github.com/open-component-model/ocm/pkg/contexts/credentials" + "github.com/open-component-model/ocm/pkg/listformat" "github.com/open-component-model/ocm/pkg/out" ) diff --git a/cmds/ocm/commands/ocmcmds/common/addhdlrs/utils.go b/cmds/ocm/commands/ocmcmds/common/addhdlrs/utils.go index 08ee455eb0..53743e4521 100644 --- a/cmds/ocm/commands/ocmcmds/common/addhdlrs/utils.go +++ b/cmds/ocm/commands/ocmcmds/common/addhdlrs/utils.go @@ -8,7 +8,6 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/errkind" "gopkg.in/yaml.v3" "k8s.io/apimachinery/pkg/util/validation/field" @@ -18,6 +17,7 @@ import ( "github.com/open-component-model/ocm/pkg/common/accessio" "github.com/open-component-model/ocm/pkg/contexts/clictx" "github.com/open-component-model/ocm/pkg/contexts/ocm" + "github.com/open-component-model/ocm/pkg/errkind" "github.com/open-component-model/ocm/pkg/runtime" utils2 "github.com/open-component-model/ocm/pkg/utils" "github.com/open-component-model/ocm/pkg/utils/template" diff --git a/cmds/ocm/commands/ocmcmds/common/handlers/pluginhdlr/typehandler.go b/cmds/ocm/commands/ocmcmds/common/handlers/pluginhdlr/typehandler.go index 4ba9f91550..b24da832ed 100644 --- a/cmds/ocm/commands/ocmcmds/common/handlers/pluginhdlr/typehandler.go +++ b/cmds/ocm/commands/ocmcmds/common/handlers/pluginhdlr/typehandler.go @@ -4,7 +4,7 @@ import ( "strings" "github.com/mandelsoft/goutils/errors" - + "github.com/open-component-model/ocm/cmds/ocm/pkg/output" "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" "github.com/open-component-model/ocm/pkg/contexts/clictx" diff --git a/cmds/ocm/commands/ocmcmds/common/handlers/vershdlr/typehandler.go b/cmds/ocm/commands/ocmcmds/common/handlers/vershdlr/typehandler.go index 875da9e575..83967edd1e 100644 --- a/cmds/ocm/commands/ocmcmds/common/handlers/vershdlr/typehandler.go +++ b/cmds/ocm/commands/ocmcmds/common/handlers/vershdlr/typehandler.go @@ -6,8 +6,8 @@ import ( "sort" "github.com/Masterminds/semver/v3" - "github.com/mandelsoft/goutils/errors" + "github.com/open-component-model/ocm/cmds/ocm/pkg/output" "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" "github.com/open-component-model/ocm/pkg/contexts/clictx" diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/options/standard.go b/cmds/ocm/commands/ocmcmds/common/inputs/options/standard.go index c43f18f318..74a22ddc28 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/options/standard.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/options/standard.go @@ -6,13 +6,20 @@ import ( ) var ( - HintOption = options.HintOption - MediaTypeOption = options.MediatypeOption + HintOption = options.HintOption + MediaTypeOption = options.MediatypeOption + URLOption = options.URLOption HTTPHeaderOption = options.HTTPHeaderOption HTTPVerbOption = options.HTTPVerbOption HTTPBodyOption = options.HTTPBodyOption HTTPRedirectOption = options.HTTPRedirectOption + + RepositoryOption = options.RepositoryOption + GroupOption = options.GroupOption + ArtifactOption = options.ArtifactOption + ClassifierOption = options.ClassifierOption + ExtensionOption = options.ExtensionOption ) // string options diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/init.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/init.go index ccf3c66649..8c42a5781a 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/init.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/init.go @@ -7,6 +7,7 @@ import ( _ "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/dockermulti" _ "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/file" _ "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/helm" + _ "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/maven" _ "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact" _ "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff" _ "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8" diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/cli.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/cli.go new file mode 100644 index 0000000000..88b6fcbc63 --- /dev/null +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/cli.go @@ -0,0 +1,32 @@ +package maven + +import ( + "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" + "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" +) + +func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { + return flagsets.NewConfigOptionTypeSetHandler( + TYPE, AddConfig, + options.URLOption, + options.PathOption, + options.GroupOption, + options.ArtifactOption, + options.VersionOption, + // optional + options.ClassifierOption, + options.ExtensionOption, + ) +} + +func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { + flagsets.AddFieldByOptionP(opts, options.URLOption, config, "repoUrl") + flagsets.AddFieldByOptionP(opts, options.PathOption, config, "path") + flagsets.AddFieldByOptionP(opts, options.GroupOption, config, "groupId") + flagsets.AddFieldByOptionP(opts, options.ArtifactOption, config, "artifactId") + flagsets.AddFieldByOptionP(opts, options.VersionOption, config, "version") + // optional + flagsets.AddFieldByOptionP(opts, options.ClassifierOption, config, "classifier") + flagsets.AddFieldByOptionP(opts, options.ExtensionOption, config, "extension") + return nil +} diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/input_test.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/input_test.go new file mode 100644 index 0000000000..7731e45ac5 --- /dev/null +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/input_test.go @@ -0,0 +1,96 @@ +package maven_test + +import ( + "crypto" + "github.com/open-component-model/ocm/pkg/maven/maventest" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + + "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" + "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" + "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/comparch" + "github.com/open-component-model/ocm/pkg/mime" + "github.com/open-component-model/ocm/pkg/utils/tarutils" +) + +const ( + ARCH = "test.ca" + VERSION = "v1" +) + +var _ = Describe("Test Environment", func() { + var env *TestEnv + + BeforeEach(func() { + env = NewTestEnv(TestData(), maventest.TestData("/maven/testdata")) + + Expect(env.Execute("create", "ca", "-ft", "directory", "test.de/x", VERSION, "--provider", "mandelsoft", "--file", ARCH)).To(Succeed()) + }) + + AfterEach(func() { + env.Cleanup() + }) + + It("add maven from file system described by resources.yaml", func() { + Expect(env.Execute("add", "resources", "--file", ARCH, "/testdata/resources1.yaml")).To(Succeed()) + data, err := env.ReadFile(env.Join(ARCH, comparch.ComponentDescriptorFileName)) + Expect(err).To(Succeed()) + cd, err := compdesc.Decode(data) + Expect(err).To(Succeed()) + Expect(len(cd.Resources)).To(Equal(1)) + access := Must(env.Context.OCMContext().AccessSpecForSpec(cd.Resources[0].Access)).(*localblob.AccessSpec) + Expect(access.MediaType).To(Equal(mime.MIME_TGZ)) + fi := Must(env.FileSystem().Stat(env.Join(ARCH, "blobs", access.LocalReference))) + Expect(fi.Size()).To(Equal(int64(1570))) + li := Must(tarutils.ListArchiveContent(env.Join(ARCH, "blobs", access.LocalReference), env.FileSystem())) + Expect(li).To(ConsistOf( + "sdk-modules-bom-5.7.0-random-content.json", + "sdk-modules-bom-5.7.0-random-content.txt", + "sdk-modules-bom-5.7.0-sources.jar", + "sdk-modules-bom-5.7.0.jar", + "sdk-modules-bom-5.7.0.pom")) + Expect(cd.Resources[0].Digest.HashAlgorithm).To(Equal(crypto.SHA256.String())) + Expect(cd.Resources[0].Digest.Value).To(Equal("16cfb5ced0ea7688dba14aeb0d3aa76ad46e4661bfcc556ffd7287de3b2f7152")) + }) + + It("add maven from file system described by cli options", func() { + meta := ` +name: testdata +type: mavenArtifact +` + Expect(env.Execute("add", "resources", "--file", ARCH, "--resource", meta, "--inputType", "maven", + "--inputPath", "/maven/testdata/.m2/repository", "--groupId", "com.sap.cloud.sdk", "--artifactId", "sdk-modules-bom", + "--inputVersion", "5.7.0", "--classifier", "", "--extension", "pom")).To(Succeed()) + data, err := env.ReadFile(env.Join(ARCH, comparch.ComponentDescriptorFileName)) + Expect(err).To(Succeed()) + cd, err := compdesc.Decode(data) + Expect(err).To(Succeed()) + Expect(len(cd.Resources)).To(Equal(1)) + access := Must(env.Context.OCMContext().AccessSpecForSpec(cd.Resources[0].Access)).(*localblob.AccessSpec) + Expect(access.MediaType).To(Equal(mime.MIME_XML)) + fi := Must(env.FileSystem().Stat(env.Join(ARCH, "blobs", access.LocalReference))) + Expect(fi.Size()).To(Equal(int64(7153))) + }) + + It("add maven file from file system described by cli options", func() { + meta := ` +name: testdata +type: mavenArtifact +` + Expect(env.Execute("add", "resources", "--file", ARCH, "--resource", meta, "--inputType", "maven", + "--inputPath", "/maven/testdata/.m2/repository", "--groupId", "com.sap.cloud.sdk", "--artifactId", "sdk-modules-bom", + "--inputVersion", "5.7.0", "--extension", "pom")).To(Succeed()) + data, err := env.ReadFile(env.Join(ARCH, comparch.ComponentDescriptorFileName)) + Expect(err).To(Succeed()) + cd, err := compdesc.Decode(data) + Expect(err).To(Succeed()) + Expect(len(cd.Resources)).To(Equal(1)) + access := Must(env.Context.OCMContext().AccessSpecForSpec(cd.Resources[0].Access)).(*localblob.AccessSpec) + Expect(access.MediaType).To(Equal(mime.MIME_TGZ)) + fi := Must(env.FileSystem().Stat(env.Join(ARCH, "blobs", access.LocalReference))) + Expect(fi.Size()).To(Equal(int64(1109))) + }) +}) diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/spec.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/spec.go new file mode 100644 index 0000000000..b07c924979 --- /dev/null +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/spec.go @@ -0,0 +1,99 @@ +package maven + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" + "github.com/open-component-model/ocm/pkg/blobaccess" + mavenblob "github.com/open-component-model/ocm/pkg/blobaccess/maven" + "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/vfsattr" + "github.com/open-component-model/ocm/pkg/maven" +) + +type Spec struct { + cpi.PathSpec `json:",inline"` + // RepoUrl defines the url from which the artifact is downloaded. + RepoUrl string `json:"repoUrl,omitempty"` + + maven.Coordinates `json:",inline"` +} + +var _ inputs.InputSpec = (*Spec)(nil) + +func New(repoUrl, groupId, artifactId, version string, classifier, extension *string) *Spec { + return &Spec{ + PathSpec: cpi.NewPathSpec(TYPE, ""), + RepoUrl: repoUrl, + Coordinates: *maven.NewCoordinates(groupId, artifactId, version, + maven.WithOptionalClassifier(classifier), + maven.WithOptionalExtension(extension)), + } +} + +func NewForFilePath(filePath, groupId, artifactId, version string, classifier, extension *string) *Spec { + return &Spec{ + PathSpec: cpi.NewPathSpec(TYPE, filePath), + RepoUrl: "", + Coordinates: *maven.NewCoordinates(groupId, artifactId, version, + maven.WithOptionalClassifier(classifier), + maven.WithOptionalExtension(extension)), + } +} + +func (s *Spec) Validate(fldPath *field.Path, ctx inputs.Context, inputFilePath string) field.ErrorList { + var allErrs field.ErrorList + if s.RepoUrl == "" { + allErrs = s.PathSpec.Validate(fldPath, ctx, inputFilePath) + } else { + if s.Path != "" { + pathField := fldPath.Child("path") + allErrs = append(allErrs, field.Forbidden(pathField, "only path or repoUrl can be specified, not both")) + } + } + if s.ArtifactId == "" { + pathField := fldPath.Child("artifactId") + allErrs = append(allErrs, field.Invalid(pathField, s.ArtifactId, "no artifact id")) + } + if s.GroupId == "" { + pathField := fldPath.Child("groupId") + allErrs = append(allErrs, field.Invalid(pathField, s.GroupId, "no group id")) + } + if s.Version == "" { + pathField := fldPath.Child("version") + allErrs = append(allErrs, field.Invalid(pathField, s.GroupId, "no group id")) + } + + return allErrs +} + +func (s *Spec) GetBlob(ctx inputs.Context, info inputs.InputResourceInfo) (blobaccess.BlobAccess, string, error) { + var repo *maven.Repository + var err error + + fs := ctx.FileSystem() + if s.Path != "" { + inputInfo, inputPath, err := inputs.FileInfo(ctx, s.Path, info.InputFilePath) + if err != nil { + return nil, "", err + } + if !inputInfo.IsDir() { + return nil, "", fmt.Errorf("maven file repository must be a directory") + } + repo = maven.NewFileRepository(inputPath, fs) + } else { + repo, err = maven.NewUrlRepository(s.RepoUrl, fs) + if err != nil { + return nil, "", err + } + } + access, err := mavenblob.BlobAccessForMavenCoords(repo, &s.Coordinates, + mavenblob.WithCredentialContext(ctx), + mavenblob.WithLoggingContext(ctx), + mavenblob.WithCachingFileSystem(vfsattr.Get(ctx)), + ) + + return access, "", err +} diff --git a/pkg/contexts/ocm/accessmethods/mvn/suite_test.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/suite_test.go similarity index 72% rename from pkg/contexts/ocm/accessmethods/mvn/suite_test.go rename to cmds/ocm/commands/ocmcmds/common/inputs/types/maven/suite_test.go index c62cadc7b0..c3662b31b0 100644 --- a/pkg/contexts/ocm/accessmethods/mvn/suite_test.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/suite_test.go @@ -1,4 +1,4 @@ -package mvn_test +package maven_test import ( "testing" @@ -9,5 +9,5 @@ import ( func TestConfig(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "Maven (mvn) Test Suite") + RunSpecs(t, "Input Type maven") } diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/testdata/resources1.yaml b/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/testdata/resources1.yaml new file mode 100644 index 0000000000..161a29b424 --- /dev/null +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/testdata/resources1.yaml @@ -0,0 +1,8 @@ +name: myblob +type: mavenArtifact +input: + type: maven + path: ../maven/testdata/.m2/repository + groupId: com.sap.cloud.sdk + artifactId: sdk-modules-bom + version: 5.7.0 \ No newline at end of file diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/type.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/type.go new file mode 100644 index 0000000000..7c80db8e33 --- /dev/null +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/type.go @@ -0,0 +1,43 @@ +package maven + +import ( + "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" +) + +const TYPE = "maven" + +func init() { + inputs.DefaultInputTypeScheme.Register(inputs.NewInputType(TYPE, &Spec{}, usage, ConfigHandler())) +} + +const usage = ` +The repoUrl is the url pointing either to the http endpoint of a maven +repository (e.g. https://repo.maven.apache.org/maven2/) or to a file system based +maven repository (e.g. file://local/directory). + +This blob type specification supports the following fields: +- **repoUrl** *string* + + This REQUIRED property describes the url from which the resource is to be + accessed. + +- **groupId** *string* + + This REQUIRED property describes the groupId of a maven artifact. + +- **artifactId** *string* + + This REQUIRED property describes artifactId of a maven artifact. + +- **version** *string* + + This REQUIRED property describes the version of a maven artifact. + +- **classifier** *string* + + This OPTIONAL property describes the classifier of a maven artifact. + +- **extension** *string* + + This OPTIONAL property describes the extension of a maven artifact. +` diff --git a/cmds/ocm/commands/ocmcmds/common/options/schemaoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/schemaoption/option.go index 46df9fc6ab..5e463f0058 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/schemaoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/schemaoption/option.go @@ -2,12 +2,12 @@ package schemaoption import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/errkind" "github.com/spf13/pflag" "github.com/open-component-model/ocm/cmds/ocm/pkg/options" "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + "github.com/open-component-model/ocm/pkg/errkind" "github.com/open-component-model/ocm/pkg/listformat" utils2 "github.com/open-component-model/ocm/pkg/utils" ) diff --git a/cmds/ocm/commands/ocmcmds/common/resources.go b/cmds/ocm/commands/ocmcmds/common/resources.go index d64c52e427..db8b5697b6 100644 --- a/cmds/ocm/commands/ocmcmds/common/resources.go +++ b/cmds/ocm/commands/ocmcmds/common/resources.go @@ -4,11 +4,10 @@ import ( "encoding/json" "fmt" - "github.com/mandelsoft/goutils/sliceutils" _ "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types" - utils2 "github.com/open-component-model/ocm/pkg/utils" "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/sliceutils" "github.com/mandelsoft/vfs/pkg/vfs" "github.com/spf13/pflag" "golang.org/x/text/cases" @@ -33,6 +32,7 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/comparch" "github.com/open-component-model/ocm/pkg/logging" "github.com/open-component-model/ocm/pkg/mime" + utils2 "github.com/open-component-model/ocm/pkg/utils" ) const ComponentVersionTag = "" diff --git a/cmds/ocm/commands/ocmcmds/common/utils.go b/cmds/ocm/commands/ocmcmds/common/utils.go index 27df8f52f2..5985d94681 100644 --- a/cmds/ocm/commands/ocmcmds/common/utils.go +++ b/cmds/ocm/commands/ocmcmds/common/utils.go @@ -5,7 +5,7 @@ import ( "strings" "github.com/mandelsoft/goutils/errors" - + "github.com/open-component-model/ocm/cmds/ocm/pkg/options" "github.com/open-component-model/ocm/pkg/contexts/clictx" "github.com/open-component-model/ocm/pkg/contexts/ocm" diff --git a/cmds/ocm/commands/ocmcmds/components/add/cmd.go b/cmds/ocm/commands/ocmcmds/components/add/cmd.go index cce645da04..648babe18a 100644 --- a/cmds/ocm/commands/ocmcmds/components/add/cmd.go +++ b/cmds/ocm/commands/ocmcmds/components/add/cmd.go @@ -3,12 +3,12 @@ package add import ( "fmt" + "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/goutils/general" "github.com/mandelsoft/vfs/pkg/vfs" "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/mandelsoft/goutils/errors" "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/formatoption" "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" diff --git a/cmds/ocm/commands/ocmcmds/components/download/cmd.go b/cmds/ocm/commands/ocmcmds/components/download/cmd.go index 76759a6bb2..1735291dad 100644 --- a/cmds/ocm/commands/ocmcmds/components/download/cmd.go +++ b/cmds/ocm/commands/ocmcmds/components/download/cmd.go @@ -4,9 +4,9 @@ import ( "path" "strings" + "github.com/mandelsoft/goutils/errors" "github.com/spf13/cobra" - "github.com/mandelsoft/goutils/errors" "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/destoption" "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/formatoption" ocmcommon "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" diff --git a/cmds/ocm/commands/ocmcmds/components/transfer/cmd.go b/cmds/ocm/commands/ocmcmds/components/transfer/cmd.go index 81c577ae13..f91a39b22d 100644 --- a/cmds/ocm/commands/ocmcmds/components/transfer/cmd.go +++ b/cmds/ocm/commands/ocmcmds/components/transfer/cmd.go @@ -4,12 +4,12 @@ import ( "encoding/json" "fmt" + "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/goutils/maputils" "github.com/mandelsoft/vfs/pkg/vfs" "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/mandelsoft/goutils/errors" "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/closureoption" "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/formatoption" ocmcommon "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" diff --git a/cmds/ocm/commands/ocmcmds/ctf/transfer/cmd.go b/cmds/ocm/commands/ocmcmds/ctf/transfer/cmd.go index beb4daa7e7..d57aefdaeb 100644 --- a/cmds/ocm/commands/ocmcmds/ctf/transfer/cmd.go +++ b/cmds/ocm/commands/ocmcmds/ctf/transfer/cmd.go @@ -1,9 +1,9 @@ package transfer import ( + "github.com/mandelsoft/goutils/errors" "github.com/spf13/cobra" - "github.com/mandelsoft/goutils/errors" "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/closureoption" "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/formatoption" ocmcommon "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" diff --git a/cmds/ocm/commands/ocmcmds/plugins/install/cmd.go b/cmds/ocm/commands/ocmcmds/plugins/install/cmd.go index d5b4182cdb..2462191b93 100644 --- a/cmds/ocm/commands/ocmcmds/plugins/install/cmd.go +++ b/cmds/ocm/commands/ocmcmds/plugins/install/cmd.go @@ -3,10 +3,10 @@ package install import ( "fmt" + "github.com/mandelsoft/goutils/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/mandelsoft/goutils/errors" ocmcommon "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/handlers/pluginhdlr" "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" diff --git a/cmds/ocm/commands/toicmds/package/bootstrap/cmd.go b/cmds/ocm/commands/toicmds/package/bootstrap/cmd.go index 96554e0287..8555d8bdde 100644 --- a/cmds/ocm/commands/toicmds/package/bootstrap/cmd.go +++ b/cmds/ocm/commands/toicmds/package/bootstrap/cmd.go @@ -3,11 +3,11 @@ package bootstrap import ( "fmt" + "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/vfs/pkg/vfs" "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/mandelsoft/goutils/errors" ocmcommon "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr" "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" diff --git a/cmds/ocm/commands/toicmds/package/describe/cmd.go b/cmds/ocm/commands/toicmds/package/describe/cmd.go index 994570c0c7..cccf87782c 100644 --- a/cmds/ocm/commands/toicmds/package/describe/cmd.go +++ b/cmds/ocm/commands/toicmds/package/describe/cmd.go @@ -4,9 +4,9 @@ import ( "fmt" "strings" + "github.com/mandelsoft/goutils/errors" "github.com/spf13/cobra" - "github.com/mandelsoft/goutils/errors" ocmcommon "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr" "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" diff --git a/cmds/ocm/commands/verbs/controller/cmd.go b/cmds/ocm/commands/verbs/controller/cmd.go index 3ad8733b2c..a79e2741f7 100644 --- a/cmds/ocm/commands/verbs/controller/cmd.go +++ b/cmds/ocm/commands/verbs/controller/cmd.go @@ -1,11 +1,11 @@ package controller import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/controllercmds/uninstall" "github.com/spf13/cobra" "github.com/open-component-model/ocm/cmds/ocm/commands/controllercmds/install" "github.com/open-component-model/ocm/cmds/ocm/commands/controllercmds/names" + "github.com/open-component-model/ocm/cmds/ocm/commands/controllercmds/uninstall" "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" "github.com/open-component-model/ocm/pkg/contexts/clictx" ) diff --git a/cmds/ocm/pkg/utils/command.go b/cmds/ocm/pkg/utils/command.go index aec015928b..212cd13c7d 100644 --- a/cmds/ocm/pkg/utils/command.go +++ b/cmds/ocm/pkg/utils/command.go @@ -4,10 +4,10 @@ import ( "reflect" "strings" + "github.com/mandelsoft/goutils/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/mandelsoft/goutils/errors" "github.com/open-component-model/ocm/cmds/ocm/pkg/options" "github.com/open-component-model/ocm/pkg/cobrautils" "github.com/open-component-model/ocm/pkg/contexts/clictx" diff --git a/cmds/ocm/pkg/utils/handling.go b/cmds/ocm/pkg/utils/handling.go index d0592950c1..ba2d2193a1 100644 --- a/cmds/ocm/pkg/utils/handling.go +++ b/cmds/ocm/pkg/utils/handling.go @@ -6,6 +6,7 @@ import ( "reflect" "github.com/mandelsoft/goutils/errors" + "github.com/open-component-model/ocm/cmds/ocm/pkg/output" ) diff --git a/docs/pluginreference/plugin_accessmethod_compose.md b/docs/pluginreference/plugin_accessmethod_compose.md index d9c0455651..e451f8f7c7 100644 --- a/docs/pluginreference/plugin_accessmethod_compose.md +++ b/docs/pluginreference/plugin_accessmethod_compose.md @@ -35,20 +35,21 @@ by the plugin name. The following predefined option types can be used: - - accessClassifier: [*string*] mvn classifier - - accessExtension: [*string*] mvn extension name - - accessGroup: [*string*] GroupID or namespace - accessHostname: [*string*] hostname used for access - accessPackage: [*string*] package or object name - accessRegistry: [*string*] registry base URL - accessRepository: [*string*] repository URL - accessVersion: [*string*] version for access specification + - artifactId: [*string*] maven artifact id - body: [*string*] body of a http request - bucket: [*string*] bucket name + - classifier: [*string*] maven classifier - comment: [*string*] comment field value - commit: [*string*] git commit id - digest: [*string*] blob digest + - extension: [*string*] maven extension name - globalAccess: [*map[string]YAML*] access specification for global access + - groupId: [*string*] maven group id - header: [*string:string,string*] http headers - hint: [*string*] (repository) hint for local artifacts - mediaType: [*string*] media type for artifact blob representation diff --git a/docs/pluginreference/plugin_descriptor.md b/docs/pluginreference/plugin_descriptor.md index c3ecbdd8a4..e2c05f2bae 100644 --- a/docs/pluginreference/plugin_descriptor.md +++ b/docs/pluginreference/plugin_descriptor.md @@ -120,20 +120,21 @@ It uses the following fields: The following predefined option types can be used: - - accessClassifier: [*string*] mvn classifier - - accessExtension: [*string*] mvn extension name - - accessGroup: [*string*] GroupID or namespace - accessHostname: [*string*] hostname used for access - accessPackage: [*string*] package or object name - accessRegistry: [*string*] registry base URL - accessRepository: [*string*] repository URL - accessVersion: [*string*] version for access specification + - artifactId: [*string*] maven artifact id - body: [*string*] body of a http request - bucket: [*string*] bucket name + - classifier: [*string*] maven classifier - comment: [*string*] comment field value - commit: [*string*] git commit id - digest: [*string*] blob digest + - extension: [*string*] maven extension name - globalAccess: [*map[string]YAML*] access specification for global access + - groupId: [*string*] maven group id - header: [*string:string,string*] http headers - hint: [*string*] (repository) hint for local artifacts - mediaType: [*string*] media type for artifact blob representation diff --git a/docs/pluginreference/plugin_valueset_compose.md b/docs/pluginreference/plugin_valueset_compose.md index 09bf007037..f7513c805b 100644 --- a/docs/pluginreference/plugin_valueset_compose.md +++ b/docs/pluginreference/plugin_valueset_compose.md @@ -35,20 +35,21 @@ by the plugin name. The following predefined option types can be used: - - accessClassifier: [*string*] mvn classifier - - accessExtension: [*string*] mvn extension name - - accessGroup: [*string*] GroupID or namespace - accessHostname: [*string*] hostname used for access - accessPackage: [*string*] package or object name - accessRegistry: [*string*] registry base URL - accessRepository: [*string*] repository URL - accessVersion: [*string*] version for access specification + - artifactId: [*string*] maven artifact id - body: [*string*] body of a http request - bucket: [*string*] bucket name + - classifier: [*string*] maven classifier - comment: [*string*] comment field value - commit: [*string*] git commit id - digest: [*string*] blob digest + - extension: [*string*] maven extension name - globalAccess: [*map[string]YAML*] access specification for global access + - groupId: [*string*] maven group id - header: [*string:string,string*] http headers - hint: [*string*] (repository) hint for local artifacts - mediaType: [*string*] media type for artifact blob representation diff --git a/docs/reference/ocm_add_resource-configuration.md b/docs/reference/ocm_add_resource-configuration.md index 679bcc2f27..533a494438 100644 --- a/docs/reference/ocm_add_resource-configuration.md +++ b/docs/reference/ocm_add_resource-configuration.md @@ -24,20 +24,21 @@ resource-configuration, resourceconfig, rsccfg, rcfg ``` --access YAML blob access specification (YAML) - --accessClassifier string mvn classifier - --accessExtension string mvn extension name - --accessGroup string GroupID or namespace --accessHostname string hostname used for access --accessPackage string package or object name --accessRegistry string registry base URL --accessRepository string repository URL --accessType string type of blob access specification --accessVersion string version for access specification + --artifactId string maven artifact id --body string body of a http request --bucket string bucket name + --classifier string maven classifier --commit string git commit id --digest string blob digest + --extension string maven extension name --globalAccess YAML access specification for global access + --groupId string maven group id --header :,,... http headers (default {}) --hint string (repository) hint for local artifacts --mediaType string media type for artifact blob representation @@ -53,7 +54,11 @@ resource-configuration, resourceconfig, rsccfg, rcfg #### Input Specification Options ``` + --artifactId string maven artifact id --body string body of a http request + --classifier string maven classifier + --extension string maven extension name + --groupId string maven group id --header :,,... http headers (default {}) --hint string (repository) hint for local artifacts --input YAML blob input specification (YAML) @@ -368,6 +373,40 @@ with the field type in the input field: Options used to configure fields: --hint, --inputCompress, --inputHelmRepository, --inputPath, --inputVersion, --mediaType +- Input type maven + + The repoUrl is the url pointing either to the http endpoint of a maven + repository (e.g. https://repo.maven.apache.org/maven2/) or to a file system based + maven repository (e.g. file://local/directory). + + This blob type specification supports the following fields: + - **repoUrl** *string* + + This REQUIRED property describes the url from which the resource is to be + accessed. + + - **groupId** *string* + + This REQUIRED property describes the groupId of a maven artifact. + + - **artifactId** *string* + + This REQUIRED property describes artifactId of a maven artifact. + + - **version** *string* + + This REQUIRED property describes the version of a maven artifact. + + - **classifier** *string* + + This OPTIONAL property describes the classifier of a maven artifact. + + - **extension** *string* + + This OPTIONAL property describes the extension of a maven artifact. + + Options used to configure fields: --artifactId, --classifier, --extension, --groupId, --inputPath, --inputVersion, --url + - Input type ociArtifact The path must denote an OCI image reference. @@ -683,40 +722,40 @@ shown below. Options used to configure fields: --globalAccess, --hint, --mediaType, --reference -- Access type mvn +- Access type maven - This method implements the access of a Maven (mvn) artifact in a Maven repository. + This method implements the access of a Maven artifact in a Maven repository. The following versions are supported: - Version v1 The type specific specification fields are: - - **repository** *string* + - **repoUrl** *string* - Base URL of the Maven (mvn) repository + URL of the Maven repository - **groupId** *string* - The groupId of the Maven (mvn) artifact + The groupId of the Maven artifact - **artifactId** *string* - The artifactId of the Maven (mvn) artifact + The artifactId of the Maven artifact - **version** *string* - The version name of the Maven (mvn) artifact + The version name of the Maven artifact - **classifier** *string* - The optional classifier of the Maven (mvn) artifact + The optional classifier of the Maven artifact - **extension** *string* - The optional extension of the Maven (mvn) artifact + The optional extension of the Maven artifact - Options used to configure fields: --accessClassifier, --accessExtension, --accessGroup, --accessPackage, --accessRepository, --accessVersion + Options used to configure fields: --accessRepository, --accessVersion, --artifactId, --classifier, --extension, --groupId - Access type none diff --git a/docs/reference/ocm_add_resources.md b/docs/reference/ocm_add_resources.md index 9e8878f8e8..944f87bf64 100644 --- a/docs/reference/ocm_add_resources.md +++ b/docs/reference/ocm_add_resources.md @@ -30,20 +30,21 @@ resources, resource, res, r ``` --access YAML blob access specification (YAML) - --accessClassifier string mvn classifier - --accessExtension string mvn extension name - --accessGroup string GroupID or namespace --accessHostname string hostname used for access --accessPackage string package or object name --accessRegistry string registry base URL --accessRepository string repository URL --accessType string type of blob access specification --accessVersion string version for access specification + --artifactId string maven artifact id --body string body of a http request --bucket string bucket name + --classifier string maven classifier --commit string git commit id --digest string blob digest + --extension string maven extension name --globalAccess YAML access specification for global access + --groupId string maven group id --header :,,... http headers (default {}) --hint string (repository) hint for local artifacts --mediaType string media type for artifact blob representation @@ -59,7 +60,11 @@ resources, resource, res, r #### Input Specification Options ``` + --artifactId string maven artifact id --body string body of a http request + --classifier string maven classifier + --extension string maven extension name + --groupId string maven group id --header :,,... http headers (default {}) --hint string (repository) hint for local artifacts --input YAML blob input specification (YAML) @@ -378,6 +383,40 @@ with the field type in the input field: Options used to configure fields: --hint, --inputCompress, --inputHelmRepository, --inputPath, --inputVersion, --mediaType +- Input type maven + + The repoUrl is the url pointing either to the http endpoint of a maven + repository (e.g. https://repo.maven.apache.org/maven2/) or to a file system based + maven repository (e.g. file://local/directory). + + This blob type specification supports the following fields: + - **repoUrl** *string* + + This REQUIRED property describes the url from which the resource is to be + accessed. + + - **groupId** *string* + + This REQUIRED property describes the groupId of a maven artifact. + + - **artifactId** *string* + + This REQUIRED property describes artifactId of a maven artifact. + + - **version** *string* + + This REQUIRED property describes the version of a maven artifact. + + - **classifier** *string* + + This OPTIONAL property describes the classifier of a maven artifact. + + - **extension** *string* + + This OPTIONAL property describes the extension of a maven artifact. + + Options used to configure fields: --artifactId, --classifier, --extension, --groupId, --inputPath, --inputVersion, --url + - Input type ociArtifact The path must denote an OCI image reference. @@ -693,40 +732,40 @@ shown below. Options used to configure fields: --globalAccess, --hint, --mediaType, --reference -- Access type mvn +- Access type maven - This method implements the access of a Maven (mvn) artifact in a Maven repository. + This method implements the access of a Maven artifact in a Maven repository. The following versions are supported: - Version v1 The type specific specification fields are: - - **repository** *string* + - **repoUrl** *string* - Base URL of the Maven (mvn) repository + URL of the Maven repository - **groupId** *string* - The groupId of the Maven (mvn) artifact + The groupId of the Maven artifact - **artifactId** *string* - The artifactId of the Maven (mvn) artifact + The artifactId of the Maven artifact - **version** *string* - The version name of the Maven (mvn) artifact + The version name of the Maven artifact - **classifier** *string* - The optional classifier of the Maven (mvn) artifact + The optional classifier of the Maven artifact - **extension** *string* - The optional extension of the Maven (mvn) artifact + The optional extension of the Maven artifact - Options used to configure fields: --accessClassifier, --accessExtension, --accessGroup, --accessPackage, --accessRepository, --accessVersion + Options used to configure fields: --accessRepository, --accessVersion, --artifactId, --classifier, --extension, --groupId - Access type none diff --git a/docs/reference/ocm_add_source-configuration.md b/docs/reference/ocm_add_source-configuration.md index f7d2b4fc32..7ebe3f1241 100644 --- a/docs/reference/ocm_add_source-configuration.md +++ b/docs/reference/ocm_add_source-configuration.md @@ -24,20 +24,21 @@ source-configuration, sourceconfig, srccfg, scfg ``` --access YAML blob access specification (YAML) - --accessClassifier string mvn classifier - --accessExtension string mvn extension name - --accessGroup string GroupID or namespace --accessHostname string hostname used for access --accessPackage string package or object name --accessRegistry string registry base URL --accessRepository string repository URL --accessType string type of blob access specification --accessVersion string version for access specification + --artifactId string maven artifact id --body string body of a http request --bucket string bucket name + --classifier string maven classifier --commit string git commit id --digest string blob digest + --extension string maven extension name --globalAccess YAML access specification for global access + --groupId string maven group id --header :,,... http headers (default {}) --hint string (repository) hint for local artifacts --mediaType string media type for artifact blob representation @@ -53,7 +54,11 @@ source-configuration, sourceconfig, srccfg, scfg #### Input Specification Options ``` + --artifactId string maven artifact id --body string body of a http request + --classifier string maven classifier + --extension string maven extension name + --groupId string maven group id --header :,,... http headers (default {}) --hint string (repository) hint for local artifacts --input YAML blob input specification (YAML) @@ -368,6 +373,40 @@ with the field type in the input field: Options used to configure fields: --hint, --inputCompress, --inputHelmRepository, --inputPath, --inputVersion, --mediaType +- Input type maven + + The repoUrl is the url pointing either to the http endpoint of a maven + repository (e.g. https://repo.maven.apache.org/maven2/) or to a file system based + maven repository (e.g. file://local/directory). + + This blob type specification supports the following fields: + - **repoUrl** *string* + + This REQUIRED property describes the url from which the resource is to be + accessed. + + - **groupId** *string* + + This REQUIRED property describes the groupId of a maven artifact. + + - **artifactId** *string* + + This REQUIRED property describes artifactId of a maven artifact. + + - **version** *string* + + This REQUIRED property describes the version of a maven artifact. + + - **classifier** *string* + + This OPTIONAL property describes the classifier of a maven artifact. + + - **extension** *string* + + This OPTIONAL property describes the extension of a maven artifact. + + Options used to configure fields: --artifactId, --classifier, --extension, --groupId, --inputPath, --inputVersion, --url + - Input type ociArtifact The path must denote an OCI image reference. @@ -683,40 +722,40 @@ shown below. Options used to configure fields: --globalAccess, --hint, --mediaType, --reference -- Access type mvn +- Access type maven - This method implements the access of a Maven (mvn) artifact in a Maven repository. + This method implements the access of a Maven artifact in a Maven repository. The following versions are supported: - Version v1 The type specific specification fields are: - - **repository** *string* + - **repoUrl** *string* - Base URL of the Maven (mvn) repository + URL of the Maven repository - **groupId** *string* - The groupId of the Maven (mvn) artifact + The groupId of the Maven artifact - **artifactId** *string* - The artifactId of the Maven (mvn) artifact + The artifactId of the Maven artifact - **version** *string* - The version name of the Maven (mvn) artifact + The version name of the Maven artifact - **classifier** *string* - The optional classifier of the Maven (mvn) artifact + The optional classifier of the Maven artifact - **extension** *string* - The optional extension of the Maven (mvn) artifact + The optional extension of the Maven artifact - Options used to configure fields: --accessClassifier, --accessExtension, --accessGroup, --accessPackage, --accessRepository, --accessVersion + Options used to configure fields: --accessRepository, --accessVersion, --artifactId, --classifier, --extension, --groupId - Access type none diff --git a/docs/reference/ocm_add_sources.md b/docs/reference/ocm_add_sources.md index 746d02c5ee..d2120df821 100644 --- a/docs/reference/ocm_add_sources.md +++ b/docs/reference/ocm_add_sources.md @@ -29,20 +29,21 @@ sources, source, src, s ``` --access YAML blob access specification (YAML) - --accessClassifier string mvn classifier - --accessExtension string mvn extension name - --accessGroup string GroupID or namespace --accessHostname string hostname used for access --accessPackage string package or object name --accessRegistry string registry base URL --accessRepository string repository URL --accessType string type of blob access specification --accessVersion string version for access specification + --artifactId string maven artifact id --body string body of a http request --bucket string bucket name + --classifier string maven classifier --commit string git commit id --digest string blob digest + --extension string maven extension name --globalAccess YAML access specification for global access + --groupId string maven group id --header :,,... http headers (default {}) --hint string (repository) hint for local artifacts --mediaType string media type for artifact blob representation @@ -58,7 +59,11 @@ sources, source, src, s #### Input Specification Options ``` + --artifactId string maven artifact id --body string body of a http request + --classifier string maven classifier + --extension string maven extension name + --groupId string maven group id --header :,,... http headers (default {}) --hint string (repository) hint for local artifacts --input YAML blob input specification (YAML) @@ -376,6 +381,40 @@ with the field type in the input field: Options used to configure fields: --hint, --inputCompress, --inputHelmRepository, --inputPath, --inputVersion, --mediaType +- Input type maven + + The repoUrl is the url pointing either to the http endpoint of a maven + repository (e.g. https://repo.maven.apache.org/maven2/) or to a file system based + maven repository (e.g. file://local/directory). + + This blob type specification supports the following fields: + - **repoUrl** *string* + + This REQUIRED property describes the url from which the resource is to be + accessed. + + - **groupId** *string* + + This REQUIRED property describes the groupId of a maven artifact. + + - **artifactId** *string* + + This REQUIRED property describes artifactId of a maven artifact. + + - **version** *string* + + This REQUIRED property describes the version of a maven artifact. + + - **classifier** *string* + + This OPTIONAL property describes the classifier of a maven artifact. + + - **extension** *string* + + This OPTIONAL property describes the extension of a maven artifact. + + Options used to configure fields: --artifactId, --classifier, --extension, --groupId, --inputPath, --inputVersion, --url + - Input type ociArtifact The path must denote an OCI image reference. @@ -691,40 +730,40 @@ shown below. Options used to configure fields: --globalAccess, --hint, --mediaType, --reference -- Access type mvn +- Access type maven - This method implements the access of a Maven (mvn) artifact in a Maven repository. + This method implements the access of a Maven artifact in a Maven repository. The following versions are supported: - Version v1 The type specific specification fields are: - - **repository** *string* + - **repoUrl** *string* - Base URL of the Maven (mvn) repository + URL of the Maven repository - **groupId** *string* - The groupId of the Maven (mvn) artifact + The groupId of the Maven artifact - **artifactId** *string* - The artifactId of the Maven (mvn) artifact + The artifactId of the Maven artifact - **version** *string* - The version name of the Maven (mvn) artifact + The version name of the Maven artifact - **classifier** *string* - The optional classifier of the Maven (mvn) artifact + The optional classifier of the Maven artifact - **extension** *string* - The optional extension of the Maven (mvn) artifact + The optional extension of the Maven artifact - Options used to configure fields: --accessClassifier, --accessExtension, --accessGroup, --accessPackage, --accessRepository, --accessVersion + Options used to configure fields: --accessRepository, --accessVersion, --artifactId, --classifier, --extension, --groupId - Access type none diff --git a/docs/reference/ocm_logging.md b/docs/reference/ocm_logging.md index f72d0b3fcb..1f6b89b152 100644 --- a/docs/reference/ocm_logging.md +++ b/docs/reference/ocm_logging.md @@ -24,10 +24,11 @@ The following *realms* are used by the command line tool: - ocm/compdesc: component descriptor handling - ocm/config: configuration management - ocm/context: context lifecycle + - ocm/credentials: Credentials - ocm/credentials/dockerconfig: docker config handling as credential repository - ocm/credentials/vault: HashiCorp Vault Access - ocm/downloader: Downloaders - - ocm/mvn: Maven repository + - ocm/maven: Maven repository - ocm/npm: NPM registry - ocm/oci/mapping: OCM to OCI Registry Mapping - ocm/oci/ocireg: OCI repository handling diff --git a/docs/reference/ocm_ocm-accessmethods.md b/docs/reference/ocm_ocm-accessmethods.md index 1c02801075..8ae09db829 100644 --- a/docs/reference/ocm_ocm-accessmethods.md +++ b/docs/reference/ocm_ocm-accessmethods.md @@ -183,40 +183,40 @@ shown below. Options used to configure fields: --globalAccess, --hint, --mediaType, --reference -- Access type mvn +- Access type maven - This method implements the access of a Maven (mvn) artifact in a Maven repository. + This method implements the access of a Maven artifact in a Maven repository. The following versions are supported: - Version v1 The type specific specification fields are: - - **repository** *string* + - **repoUrl** *string* - Base URL of the Maven (mvn) repository + URL of the Maven repository - **groupId** *string* - The groupId of the Maven (mvn) artifact + The groupId of the Maven artifact - **artifactId** *string* - The artifactId of the Maven (mvn) artifact + The artifactId of the Maven artifact - **version** *string* - The version name of the Maven (mvn) artifact + The version name of the Maven artifact - **classifier** *string* - The optional classifier of the Maven (mvn) artifact + The optional classifier of the Maven artifact - **extension** *string* - The optional extension of the Maven (mvn) artifact + The optional extension of the Maven artifact - Options used to configure fields: --accessClassifier, --accessExtension, --accessGroup, --accessPackage, --accessRepository, --accessVersion + Options used to configure fields: --accessRepository, --accessVersion, --artifactId, --classifier, --extension, --groupId - Access type none diff --git a/docs/reference/ocm_ocm-uploadhandlers.md b/docs/reference/ocm_ocm-uploadhandlers.md index d3b3219957..5c6ac57448 100644 --- a/docs/reference/ocm_ocm-uploadhandlers.md +++ b/docs/reference/ocm_ocm-uploadhandlers.md @@ -73,14 +73,14 @@ The following handler names are possible: sub namespace of the form <plugin name>/<handler> - - ocm/mvnArtifact: uploading mvn artifacts + - ocm/mavenArtifact: uploading maven artifacts - The ocm/mvnArtifact uploader is able to upload mvn artifacts (whole GAV only!) - as artifact archive according to the mvn artifact spec. + The ocm/mavenArtifact uploader is able to upload maven artifacts (whole GAV only!) + as artifact archive according to the maven artifact spec. If registered the default mime type is: application/x-tgz It accepts a plain string for the URL or a config with the following field: - 'url': the URL of the mvn repository. + 'url': the URL of the maven repository. diff --git a/docs/reference/ocm_transfer_commontransportarchive.md b/docs/reference/ocm_transfer_commontransportarchive.md index c3f6c2d20a..eaa4b1f1fb 100644 --- a/docs/reference/ocm_transfer_commontransportarchive.md +++ b/docs/reference/ocm_transfer_commontransportarchive.md @@ -147,14 +147,14 @@ The uploader name may be a path expression with the following possibilities: sub namespace of the form <plugin name>/<handler> - - ocm/mvnArtifact: uploading mvn artifacts + - ocm/mavenArtifact: uploading maven artifacts - The ocm/mvnArtifact uploader is able to upload mvn artifacts (whole GAV only!) - as artifact archive according to the mvn artifact spec. + The ocm/mavenArtifact uploader is able to upload maven artifacts (whole GAV only!) + as artifact archive according to the maven artifact spec. If registered the default mime type is: application/x-tgz It accepts a plain string for the URL or a config with the following field: - 'url': the URL of the mvn repository. + 'url': the URL of the maven repository. diff --git a/docs/reference/ocm_transfer_componentversions.md b/docs/reference/ocm_transfer_componentversions.md index f251617d44..b58c17b15f 100644 --- a/docs/reference/ocm_transfer_componentversions.md +++ b/docs/reference/ocm_transfer_componentversions.md @@ -204,14 +204,14 @@ The uploader name may be a path expression with the following possibilities: sub namespace of the form <plugin name>/<handler> - - ocm/mvnArtifact: uploading mvn artifacts + - ocm/mavenArtifact: uploading maven artifacts - The ocm/mvnArtifact uploader is able to upload mvn artifacts (whole GAV only!) - as artifact archive according to the mvn artifact spec. + The ocm/mavenArtifact uploader is able to upload maven artifacts (whole GAV only!) + as artifact archive according to the maven artifact spec. If registered the default mime type is: application/x-tgz It accepts a plain string for the URL or a config with the following field: - 'url': the URL of the mvn repository. + 'url': the URL of the maven repository. diff --git a/examples/lib/tour/01-getting-started/README.md b/examples/lib/tour/01-getting-started/README.md index 76423db018..5f807a67f3 100644 --- a/examples/lib/tour/01-getting-started/README.md +++ b/examples/lib/tour/01-getting-started/README.md @@ -164,32 +164,32 @@ differ, because the code always describes the latest version): ``` resources of the latest version: - version: 0.9.0 + version: 0.10.0 provider: ocm.software 1: name: ocmcli extra identity: "architecture"="amd64","os"="linux" resource type: executable - access: Local blob sha256:1de1c90f23d0a3dbb8d8646f09380f1da257f9d10796b42dc4ef85e8df93a135[] + access: Local blob sha256:e2d8f578083e9317bd199b3f374b7ea60e7f28cf989e8d39ae0ea54ac4fa8847[] 2: name: ocmcli extra identity: "architecture"="arm64","os"="linux" resource type: executable - access: Local blob sha256:ca049bb09399020ce0822fd18c0a534ae0d02c3e0180f05dd4faccf61176a267[] + access: Local blob sha256:2acef3da732a6674fb047f3d60f0dabcbb60ffeb8dd362a169df97c4dc4489a8[] 3: name: ocmcli extra identity: "architecture"="arm64","os"="darwin" resource type: executable - access: Local blob sha256:1e32b3f1a08c72e3187b247f8931ea9d0554240fd452a4df129d6036c62b0476[] + access: Local blob sha256:b05fbc5e8aaa3622e2ecc39ead7f066030fd183c625b0dc202dbac8131f06d1d[] 4: name: ocmcli extra identity: "architecture"="amd64","os"="darwin" resource type: executable - access: Local blob sha256:04708d2f9845dd6d52f2b8f94e930f3a74a1a098b7ee401e001307d4b4fcc703[] + access: Local blob sha256:aec88249f7e5a395eaa18ac6017831b275c7de90d3c10f0cd9e572027ad6c6e9[] 5: name: ocmcli extra identity: "architecture"="amd64","os"="windows" resource type: executable - access: Local blob sha256:e8cf5dfd1ab02ab982e6f1a425d426fc1f7dc83e6385d26d0477525a4a66c629[] + access: Local blob sha256:cdbac49bd004aa2a8b89fafb9e845f45bfcfaab2df402296f55b403b9b1035a2[] 6: name: ocmcli-image extra identity: resource type: ociImage - access: OCI artifact ghcr.io/open-component-model/ocm/ocm.software/ocmcli/ocmcli-image:0.9.0 + access: OCI artifact ghcr.io/open-component-model/ocm/ocm.software/ocmcli/ocmcli-image:0.10.0 ``` Resources have some metadata, like their identity and a resource type. diff --git a/examples/lib/tour/02-composing-a-component-version/01-basic-componentversion-creation.go b/examples/lib/tour/02-composing-a-component-version/01-basic-componentversion-creation.go index 7284bcc274..da13cdc0bb 100644 --- a/examples/lib/tour/02-composing-a-component-version/01-basic-componentversion-creation.go +++ b/examples/lib/tour/02-composing-a-component-version/01-basic-componentversion-creation.go @@ -173,7 +173,7 @@ data: some very important data required to understand this component // The above case could also be written as follows: // --- begin setup by access --- res := textblob.ResourceAccess(cv.GetContext(), meta, yamldata, - textblob.WithimeType(mime.MIME_YAML)) + textblob.WithMimeType(mime.MIME_YAML)) err = cv.SetResourceAccess(res) if err != nil { return errors.Wrapf(err, "cannot add yaml document") diff --git a/examples/lib/tour/02-composing-a-component-version/README.md b/examples/lib/tour/02-composing-a-component-version/README.md index 74953c7385..effd262404 100644 --- a/examples/lib/tour/02-composing-a-component-version/README.md +++ b/examples/lib/tour/02-composing-a-component-version/README.md @@ -212,7 +212,7 @@ The above case could also be written as follows: ```go res := textblob.ResourceAccess(cv.GetContext(), meta, yamldata, - textblob.WithimeType(mime.MIME_YAML)) + textblob.WithMimeType(mime.MIME_YAML)) err = cv.SetResourceAccess(res) if err != nil { return errors.Wrapf(err, "cannot add yaml document") diff --git a/examples/lib/tour/03-working-with-credentials/common.go b/examples/lib/tour/03-working-with-credentials/common.go index 27aa3149db..86151feab0 100644 --- a/examples/lib/tour/03-working-with-credentials/common.go +++ b/examples/lib/tour/03-working-with-credentials/common.go @@ -136,7 +136,7 @@ data: some very important data required to understand this component // component version. // The above case could be written as follows, also: res := textblob.ResourceAccess(cv.GetContext(), meta, yamldata, - textblob.WithimeType(mime.MIME_YAML)) + textblob.WithMimeType(mime.MIME_YAML)) err = cv.SetResourceAccess(res) if err != nil { return errors.Wrapf(err, "cannot add yaml document") diff --git a/examples/lib/tour/05-transporting-component-versions/common.go b/examples/lib/tour/05-transporting-component-versions/common.go index e170b62eec..81fdab6c30 100644 --- a/examples/lib/tour/05-transporting-component-versions/common.go +++ b/examples/lib/tour/05-transporting-component-versions/common.go @@ -135,7 +135,7 @@ data: some very important data required to understand this component // component version. // The above case could be written as follows, also: res := textblob.ResourceAccess(cv.GetContext(), meta, yamldata, - textblob.WithimeType(mime.MIME_YAML)) + textblob.WithMimeType(mime.MIME_YAML)) err = cv.SetResourceAccess(res) if err != nil { return errors.Wrapf(err, "cannot add yaml document") diff --git a/examples/lib/tour/06-signing-component-versions/common.go b/examples/lib/tour/06-signing-component-versions/common.go index 094ae6c094..f0d013f572 100644 --- a/examples/lib/tour/06-signing-component-versions/common.go +++ b/examples/lib/tour/06-signing-component-versions/common.go @@ -141,7 +141,7 @@ data: some very important data required to understand this component // component version. // The above case could be written as follows, also: res := textblob.ResourceAccess(cv.GetContext(), meta, yamldata, - textblob.WithimeType(mime.MIME_YAML)) + textblob.WithMimeType(mime.MIME_YAML)) err = cv.SetResourceAccess(res) if err != nil { return errors.Wrapf(err, "cannot add yaml document") diff --git a/go.mod b/go.mod index df7787db42..84a8c9eeed 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.21 github.com/aws/aws-sdk-go-v2/service/ecr v1.28.3 github.com/aws/aws-sdk-go-v2/service/s3 v1.54.3 + github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 github.com/containerd/containerd v1.7.17 github.com/containerd/log v0.1.0 github.com/containers/image/v5 v5.31.0 @@ -38,7 +39,7 @@ require ( github.com/klauspost/compress v1.17.8 github.com/klauspost/pgzip v1.2.6 github.com/mandelsoft/filepath v0.0.0-20240223090642-3e2777258aa3 - github.com/mandelsoft/goutils v0.0.0-20240523093855-5385ed52b460 + github.com/mandelsoft/goutils v0.0.0-20240527090454-525d51156f92 github.com/mandelsoft/logging v0.0.0-20240201091719-67180059d6bf github.com/mandelsoft/spiff v1.7.0-beta-5 github.com/mandelsoft/vfs v0.4.3 diff --git a/go.sum b/go.sum index 8708b4b5d7..f635f11af8 100644 --- a/go.sum +++ b/go.sum @@ -681,8 +681,8 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mandelsoft/filepath v0.0.0-20240223090642-3e2777258aa3 h1:oo9nIgnyiBgYPbcZslRT4y29siuL5EoNJ/t1tr0xEVQ= github.com/mandelsoft/filepath v0.0.0-20240223090642-3e2777258aa3/go.mod h1:LxhqC7khDoRENwooP6f/vWvia9ivj6TqLYrR39zqkN0= -github.com/mandelsoft/goutils v0.0.0-20240523093855-5385ed52b460 h1:q44fRqo/PC3eTexd8Q3pO2BTHTABVXsW6DBXRUhC97E= -github.com/mandelsoft/goutils v0.0.0-20240523093855-5385ed52b460/go.mod h1:EbNqk9JceSMq7MJuALB/vlOpoD4MAGE0TenM9TR+C0o= +github.com/mandelsoft/goutils v0.0.0-20240527090454-525d51156f92 h1:JKHpPtPpkCA7AVRv5trXmqAcncjbfkjiv4x7wGSJnEc= +github.com/mandelsoft/goutils v0.0.0-20240527090454-525d51156f92/go.mod h1:EbNqk9JceSMq7MJuALB/vlOpoD4MAGE0TenM9TR+C0o= github.com/mandelsoft/logging v0.0.0-20240201091719-67180059d6bf h1:WEmgzeArDbp6Aw34jmziMIE5ygo2zpl/atXRq3D7lSw= github.com/mandelsoft/logging v0.0.0-20240201091719-67180059d6bf/go.mod h1:uO460C1lIB3IOOgrbXhAlz3AKsOv4T2K6ALBn3PwuSg= github.com/mandelsoft/spiff v1.7.0-beta-5 h1:3kC10nTviDQhL8diSxp7i4IC2iSiDg6KPbH1CAq7Lfw= diff --git a/pkg/blobaccess/maven/access.go b/pkg/blobaccess/maven/access.go new file mode 100644 index 0000000000..b9b963f5d9 --- /dev/null +++ b/pkg/blobaccess/maven/access.go @@ -0,0 +1,38 @@ +package maven + +import ( + "github.com/mandelsoft/goutils/optionutils" + + "github.com/open-component-model/ocm/pkg/blobaccess" + "github.com/open-component-model/ocm/pkg/blobaccess/bpi" + "github.com/open-component-model/ocm/pkg/maven" +) + +func DataAccessForMaven(repo *maven.Repository, groupId, artifactId, version string, opts ...Option) (blobaccess.DataAccess, error) { + return BlobAccessForMaven(repo, groupId, artifactId, version, opts...) +} + +func BlobAccessForMaven(repo *maven.Repository, groupId, artifactId, version string, opts ...Option) (blobaccess.BlobAccess, error) { + eff := optionutils.EvalOptions(opts...) + s := &spec{ + coords: maven.NewCoordinates(groupId, artifactId, version, maven.WithOptionalClassifier(eff.Classifier), maven.WithOptionalExtension(eff.Extension)), + repo: repo, + options: eff, + } + return s.getBlobAccess() +} + +func BlobAccessForMavenCoords(repo *maven.Repository, coords *maven.Coordinates, opts ...Option) (blobaccess.BlobAccess, error) { + return BlobAccessForMaven(repo, coords.GroupId, coords.ArtifactId, coords.Version, optionutils.WithDefaults(opts, WithOptionalClassifier(coords.Classifier), WithOptionalExtension(coords.Extension))...) +} + +func BlobAccessProviderForMaven(repo *maven.Repository, groupId, artifactId, version string, opts ...Option) bpi.BlobAccessProvider { + return bpi.BlobAccessProviderFunction(func() (bpi.BlobAccess, error) { + b, err := BlobAccessForMaven(repo, groupId, artifactId, version, opts...) + return b, err + }) +} + +func BlobAccessProviderForMavenCoords(repo *maven.Repository, coords *maven.Coordinates, opts ...Option) bpi.BlobAccessProvider { + return BlobAccessProviderForMaven(repo, coords.GroupId, coords.ArtifactId, coords.Version, optionutils.WithDefaults(opts, WithOptionalClassifier(coords.Classifier), WithOptionalExtension(coords.Extension))...) +} diff --git a/pkg/blobaccess/maven/access_test.go b/pkg/blobaccess/maven/access_test.go new file mode 100644 index 0000000000..e2e10c96df --- /dev/null +++ b/pkg/blobaccess/maven/access_test.go @@ -0,0 +1,125 @@ +package maven_test + +import ( + "github.com/open-component-model/ocm/pkg/maven/maventest" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/open-component-model/ocm/pkg/env/builder" + . "github.com/open-component-model/ocm/pkg/testutils" + + me "github.com/open-component-model/ocm/pkg/blobaccess/maven" + "github.com/open-component-model/ocm/pkg/maven" + "github.com/open-component-model/ocm/pkg/utils/tarutils" +) + +const ( + MAVEN_PATH = "/testdata/.m2/repository" + FAIL_PATH = "/testdata/.m2/fail" + MAVEN_CENTRAL_ADDRESS = "repo.maven.apache.org:443" + MAVEN_CENTRAL = "https://repo.maven.apache.org/maven2/" + MAVEN_GROUP_ID = "maven" + MAVEN_ARTIFACT_ID = "maven" + MAVEN_VERSION = "1.1" +) + +var _ = Describe("blobaccess for maven", func() { + + Context("maven filesystem repository", func() { + var env *Builder + var repo *maven.Repository + + BeforeEach(func() { + env = NewBuilder(maventest.TestData()) + repo = maven.NewFileRepository(MAVEN_PATH, env.FileSystem()) + }) + + AfterEach(func() { + MustBeSuccessful(env.Cleanup()) + }) + + It("blobaccess for gav", func() { + coords := maven.NewCoordinates("com.sap.cloud.sdk", "sdk-modules-bom", "5.7.0") + + b := Must(me.BlobAccessForMaven(repo, coords.GroupId, coords.ArtifactId, coords.Version, me.WithCachingFileSystem(env.FileSystem()))) + defer Close(b, "blobaccess") + files := Must(tarutils.ListArchiveContentFromReader(Must(b.Reader()))) + Expect(files).To(ConsistOf("sdk-modules-bom-5.7.0.pom", "sdk-modules-bom-5.7.0.jar", "sdk-modules-bom-5.7.0-random-content.txt", + "sdk-modules-bom-5.7.0-random-content.json", "sdk-modules-bom-5.7.0-sources.jar")) + }) + + It("blobaccess for files with the same classifier", func() { + coords := maven.NewCoordinates("com.sap.cloud.sdk", "sdk-modules-bom", "5.7.0", + maven.WithClassifier("random-content")) + + b := Must(me.BlobAccessForMavenCoords(repo, coords, me.WithCachingFileSystem(env.FileSystem()))) + defer Close(b, "blobaccess") + files := Must(tarutils.ListArchiveContentFromReader(Must(b.Reader()))) + Expect(files).To(ConsistOf("sdk-modules-bom-5.7.0-random-content.txt", + "sdk-modules-bom-5.7.0-random-content.json")) + }) + + It("blobaccess for files with empty classifier", func() { + coords := maven.NewCoordinates("com.sap.cloud.sdk", "sdk-modules-bom", "5.7.0", + maven.WithClassifier("")) + + b := Must(me.BlobAccessForMavenCoords(repo, coords, me.WithCachingFileSystem(env.FileSystem()))) + defer Close(b, "blobaccess") + files := Must(tarutils.ListArchiveContentFromReader(Must(b.Reader()))) + Expect(files).To(ConsistOf("sdk-modules-bom-5.7.0.pom", + "sdk-modules-bom-5.7.0.jar")) + }) + + It("blobaccess for files with extension", func() { + coords := maven.NewCoordinates("com.sap.cloud.sdk", "sdk-modules-bom", "5.7.0", + maven.WithExtension("jar")) + + b := Must(me.BlobAccessForMavenCoords(repo, coords, me.WithCachingFileSystem(env.FileSystem()))) + defer Close(b, "blobaccess") + files := Must(tarutils.ListArchiveContentFromReader(Must(b.Reader()))) + Expect(files).To(ConsistOf("sdk-modules-bom-5.7.0-sources.jar", + "sdk-modules-bom-5.7.0.jar")) + }) + + It("blobaccess for files with extension", func() { + coords := maven.NewCoordinates("com.sap.cloud.sdk", "sdk-modules-bom", "5.7.0", + maven.WithExtension("txt")) + + b := Must(me.BlobAccessForMavenCoords(repo, coords, me.WithCachingFileSystem(env.FileSystem()))) + defer Close(b, "blobaccess") + files := Must(tarutils.ListArchiveContentFromReader(Must(b.Reader()))) + Expect(files).To(ConsistOf("sdk-modules-bom-5.7.0-random-content.txt")) + }) + + It("blobaccess for a single file with classifier and extension", func() { + coords := maven.NewCoordinates("com.sap.cloud.sdk", "sdk-modules-bom", "5.7.0", + maven.WithClassifier("random-content"), maven.WithExtension("json")) + + b := Must(me.BlobAccessForMavenCoords(repo, coords, me.WithCachingFileSystem(env.FileSystem()))) + defer Close(b, "blobaccess") + Expect(string(Must(b.Get()))).To(Equal(`{"some": "test content"}`)) + }) + }) + + Context("maven http repository", func() { + if PingTCPServer(MAVEN_CENTRAL_ADDRESS, time.Second) == nil { + var coords *maven.Coordinates + BeforeEach(func() { + coords = maven.NewCoordinates(MAVEN_GROUP_ID, MAVEN_ARTIFACT_ID, MAVEN_VERSION) + }) + It("blobaccess for gav", func() { + repo := Must(maven.NewUrlRepository(MAVEN_CENTRAL)) + b := Must(me.BlobAccessForMavenCoords(repo, coords)) + defer Close(b, "blobaccess") + files := Must(tarutils.ListArchiveContentFromReader(Must(b.Reader()))) + Expect(files).To(ConsistOf( + "maven-1.1-RC1.javadoc.javadoc.jar", + "maven-1.1-sources.jar", + "maven-1.1.jar", + "maven-1.1.pom", + )) + }) + } + }) +}) diff --git a/pkg/blobaccess/maven/maven.go b/pkg/blobaccess/maven/maven.go new file mode 100644 index 0000000000..5d334937bb --- /dev/null +++ b/pkg/blobaccess/maven/maven.go @@ -0,0 +1,36 @@ +package maven + +import ( + "github.com/mandelsoft/goutils/optionutils" + "github.com/mandelsoft/vfs/pkg/vfs" + + "github.com/open-component-model/ocm/pkg/maven" +) + +type ( + BlobMeta = maven.FileMeta + Repository = maven.Repository + Coordinates = maven.Coordinates +) + +func NewFileRepository(path string, fss ...vfs.FileSystem) *Repository { + return maven.NewFileRepository(path, fss...) +} + +func NewUrlRepository(repoUrl string, fss ...vfs.FileSystem) (*Repository, error) { + return maven.NewUrlRepository(repoUrl, fss...) +} + +type optionwrapper struct { + options *Options +} + +func (o *optionwrapper) ApplyTo(opts *Coordinates) { + maven.WithOptionalExtension(o.options.Extension).ApplyTo(opts) + maven.WithOptionalClassifier(o.options.Classifier).ApplyTo(opts) +} + +func NewCoordinates(groupId, artifactId, version string, opts ...Option) *Coordinates { + eff := optionutils.EvalOptions(opts...) + return maven.NewCoordinates(groupId, artifactId, version, &optionwrapper{eff}) +} diff --git a/pkg/blobaccess/maven/options.go b/pkg/blobaccess/maven/options.go new file mode 100644 index 0000000000..8ceae8c2ea --- /dev/null +++ b/pkg/blobaccess/maven/options.go @@ -0,0 +1,204 @@ +package maven + +import ( + "github.com/mandelsoft/goutils/optionutils" + "github.com/mandelsoft/logging" + "github.com/mandelsoft/vfs/pkg/vfs" + + "github.com/open-component-model/ocm/pkg/contexts/credentials" + "github.com/open-component-model/ocm/pkg/contexts/datacontext" + "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/tmpcache" + ocmlog "github.com/open-component-model/ocm/pkg/logging" + "github.com/open-component-model/ocm/pkg/maven" +) + +type Option = optionutils.Option[*Options] + +type Options struct { + CredentialContext credentials.Context + LoggingContext logging.Context + CachingContext datacontext.Context + CachingFileSystem vfs.FileSystem + CachingPath string + // Credentials allows to pass credentials and certificates for the http communication + Credentials credentials.Credentials + // Classifier defines the classifier of the maven file coordinates + Classifier *string + // Extension defines the extension of the maven file coordinates + Extension *string +} + +func (o *Options) Logger(keyValuePairs ...interface{}) logging.Logger { + return ocmlog.LogContext(o.LoggingContext, o.CredentialContext).Logger(maven.REALM).WithValues(keyValuePairs...) +} + +func (o *Options) Cache() *tmpcache.Attribute { + if o.CachingPath != "" { + return tmpcache.New(o.CachingPath, o.CachingFileSystem) + } + if o.CachingContext == nil { + return tmpcache.Get(o.CredentialContext) + } + return tmpcache.Get(o.CachingContext) +} + +func (o *Options) GetCredentials(repo *maven.Repository, groupId string) (maven.Credentials, error) { + if repo.IsFileSystem() { + return nil, nil + } + + switch { + case o.Credentials != nil: + return MapCredentials(o.Credentials), nil + case o.CredentialContext != nil: + return GetCredentials(o.CredentialContext, repo, groupId) + default: + return nil, nil + } +} + +func (o *Options) ApplyTo(opts *Options) { + if opts == nil { + return + } + if o.CredentialContext != nil { + opts.CredentialContext = o.CredentialContext + } + if o.LoggingContext != nil { + opts.LoggingContext = o.LoggingContext + } + if o.CachingFileSystem != nil { + opts.CachingFileSystem = o.CachingFileSystem + } + if o.Credentials != nil { + opts.Credentials = o.Credentials + } + if o.Classifier != nil { + opts.Classifier = o.Classifier + } + if o.Extension != nil { + opts.Extension = o.Extension + } +} + +//////////////////////////////////////////////////////////////////////////////// + +type context struct { + credentials.Context +} + +func (o context) ApplyTo(opts *Options) { + opts.CredentialContext = o +} + +func WithCredentialContext(ctx credentials.ContextProvider) Option { + return context{ctx.CredentialsContext()} +} + +//////////////////////////////////////////////////////////////////////////////// + +type loggingContext struct { + logging.Context +} + +func (o loggingContext) ApplyTo(opts *Options) { + opts.LoggingContext = o +} + +func WithLoggingContext(ctx logging.ContextProvider) Option { + return loggingContext{ctx.LoggingContext()} +} + +//////////////////////////////////////////////////////////////////////////////// + +type cachingContext struct { + datacontext.Context +} + +func (o cachingContext) ApplyTo(opts *Options) { + opts.CachingContext = o +} + +func WithCachingContext(ctx datacontext.Context) Option { + return cachingContext{ctx} +} + +//////////////////////////////////////////////////////////////////////////////// + +type cachingFileSystem struct { + fs vfs.FileSystem +} + +func (o *cachingFileSystem) ApplyTo(opts *Options) { + opts.CachingFileSystem = o.fs +} + +func WithCachingFileSystem(fs vfs.FileSystem) Option { + return &cachingFileSystem{fs: fs} +} + +//////////////////////////////////////////////////////////////////////////////// + +type cachingPath string + +func (o cachingPath) ApplyTo(opts *Options) { + opts.CachingPath = string(o) +} + +func WithCachingPath(p string) Option { + return cachingPath(p) +} + +/////////////////////////////////////////////////////////////////////////////// + +type creds struct { + credentials.Credentials +} + +func (o creds) ApplyTo(opts *Options) { + opts.Credentials = o.Credentials +} + +func WithCredentials(c credentials.Credentials) Option { + return creds{c} +} + +//////////////////////////////////////////////////////////////////////////////// + +type classifier string + +func (o classifier) ApplyTo(opts *Options) { + opts.Classifier = optionutils.PointerTo(string(o)) +} + +func WithClassifier(c string) Option { + return classifier(c) +} + +func WithOptionalClassifier(c *string) Option { + if c != nil { + return WithClassifier(*c) + } + return nil +} + +//////////////////////////////////////////////////////////////////////////////// + +type extension string + +func (o extension) ApplyTo(opts *Options) { + opts.Extension = optionutils.PointerTo(string(o)) +} + +func WithExtension(e string) Option { + return extension(e) +} + +func WithOptionalExtension(e *string) Option { + if e != nil { + return WithExtension(*e) + } + return nil +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/pkg/blobaccess/maven/suite_test.go b/pkg/blobaccess/maven/suite_test.go new file mode 100644 index 0000000000..b69187676e --- /dev/null +++ b/pkg/blobaccess/maven/suite_test.go @@ -0,0 +1,13 @@ +package maven_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Maven Blob Access Test Suite") +} diff --git a/pkg/blobaccess/maven/utils.go b/pkg/blobaccess/maven/utils.go new file mode 100644 index 0000000000..b1f4d774ba --- /dev/null +++ b/pkg/blobaccess/maven/utils.go @@ -0,0 +1,167 @@ +package maven + +import ( + "io" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/finalizer" + "github.com/mandelsoft/goutils/optionutils" + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/mandelsoft/vfs/pkg/vfs" + "github.com/opencontainers/go-digest" + + "github.com/open-component-model/ocm/pkg/blobaccess" + "github.com/open-component-model/ocm/pkg/blobaccess/bpi" + "github.com/open-component-model/ocm/pkg/common/accessio" + "github.com/open-component-model/ocm/pkg/common/accessobj" + "github.com/open-component-model/ocm/pkg/contexts/credentials" + "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/maven/identity" + "github.com/open-component-model/ocm/pkg/iotools" + "github.com/open-component-model/ocm/pkg/maven" + "github.com/open-component-model/ocm/pkg/mime" + "github.com/open-component-model/ocm/pkg/utils" + "github.com/open-component-model/ocm/pkg/utils/tarutils" +) + +type coords = *maven.Coordinates + +type spec struct { + coords + repo *maven.Repository + options *Options +} + +func (s *spec) getBlobAccess() (_ bpi.BlobAccess, rerr error) { + var finalize finalizer.Finalizer + defer finalize.FinalizeWithErrorPropagation(&rerr) + + log := s.options.Logger("RepoUrl", s.repo.String()) + creds, err := s.options.GetCredentials(s.repo, s.GroupId) + if err != nil { + return nil, err + } + fileMap, err := s.repo.GavFiles(s.coords, creds) + if err != nil { + return nil, err + } + + fileMap = s.coords.FilterFileMap(fileMap) + + switch l := len(fileMap); { + case l <= 0: + return nil, errors.New("no maven artifact files found") + case l == 1 && optionutils.AsValue(s.Extension) != "" && s.Classifier != nil: + for file, hash := range fileMap { + metadata, err := s.repo.GetFileMeta(s.coords, file, hash, creds) + if err != nil { + return nil, err + } + return blobAccessForRepositoryAccess(metadata, creds, s.options) + } + // default: continue below with: create tmpfs where all files can be downloaded to and packed together as tar.gz + } + + tmpfs, err := osfs.NewTempFileSystem() + if err != nil { + return nil, err + } + finalize.With(func() error { + return vfs.Cleanup(tmpfs) + }) + + for file, hash := range fileMap { + loop := finalize.Nested() + metadata, err := s.repo.GetFileMeta(s.coords, file, hash, creds) + if err != nil { + return nil, err + } + + // download the artifact into the temporary file system + out, err := tmpfs.Create(file) + if err != nil { + return nil, err + } + loop.Close(out) + + reader, err := metadata.Location.GetReader(creds) + if err != nil { + return nil, err + } + loop.Close(reader) + if hash > 0 { + dreader := iotools.NewDigestReaderWithHash(hash, reader) + _, err = io.Copy(out, dreader) + if err != nil { + return nil, err + } + sum := dreader.Digest().Encoded() + if metadata.Hash != sum { + return nil, errors.Newf("%s digest mismatch: expected %s, found %s", metadata.HashType, metadata.Hash, sum) + } + } else { + _, err = io.Copy(out, reader) + return nil, err + } + err = loop.Finalize() + if err != nil { + return nil, err + } + } + + // pack all downloaded files into a tar.gz file + fs := utils.FileSystem(s.options.CachingFileSystem) + tgz, err := vfs.TempFile(fs, "", "maven-"+s.coords.FileNamePrefix()+"-*.tar.gz") + if err != nil { + return nil, err + } + + dw := iotools.NewDigestWriterWith(digest.SHA256, tgz) + finalize.Close(dw) + + err = tarutils.TgzFs(tmpfs, dw) + if err != nil { + return nil, err + } + log.Debug("created", "file", tgz.Name()) + return blobaccess.ForTemporaryFilePathWithMeta(mime.MIME_TGZ, dw.Digest(), dw.Size(), tgz.Name(), fs), nil +} + +func blobAccessForRepositoryAccess(meta *BlobMeta, creds maven.Credentials, opts *Options) (bpi.BlobAccess, error) { + reader := func() (io.ReadCloser, error) { + return meta.Location.GetReader(creds) + } + if meta.Hash != "" { + getreader := reader + reader = func() (io.ReadCloser, error) { + readCloser, err := getreader() + if err != nil { + return nil, err + } + return iotools.VerifyingReaderWithHash(readCloser, meta.HashType, meta.Hash), nil + } + } + acc := blobaccess.DataAccessForReaderFunction(reader, meta.Location.String()) + return accessobj.CachedBlobAccessForWriterWithCache(opts.Cache(), meta.MimeType, accessio.NewDataAccessWriter(acc)), nil +} + +func MapCredentials(creds credentials.Credentials) maven.Credentials { + if creds == nil || (!creds.ExistsProperty(identity.ATTR_USERNAME) && !creds.ExistsProperty(identity.ATTR_PASSWORD)) { + return nil + } + return &maven.BasicAuthCredentials{ + Username: creds.GetProperty(identity.ATTR_USERNAME), + Password: creds.GetProperty(identity.ATTR_PASSWORD), + } +} + +func GetCredentials(ctx credentials.ContextProvider, repo *Repository, groupId string) (maven.Credentials, error) { + consumerid, err := identity.GetConsumerId(repo.String(), groupId) + if err != nil { + return nil, err + } + creds, err := credentials.CredentialsForConsumer(ctx, consumerid, identity.IdentityMatcher) + if err != nil { + return nil, err + } + return MapCredentials(creds), nil +} diff --git a/pkg/blobaccess/standard.go b/pkg/blobaccess/standard.go index ea8379cdf6..7555ac3114 100644 --- a/pkg/blobaccess/standard.go +++ b/pkg/blobaccess/standard.go @@ -321,6 +321,15 @@ func ForTemporaryFile(mime string, temp vfs.File, fss ...vfs.FileSystem) BlobAcc }) } +func ForTemporaryFileWithMeta(mime string, digest digest.Digest, size int64, temp vfs.File, fss ...vfs.FileSystem) BlobAccess { + return bpi.NewBlobAccessForBase(bpi.BaseAccessForDataAccessAndMeta(mime, &temporaryFileBlob{ + _blobAccess: ForFile(mime, temp.Name(), fss...), + filesystem: utils.FileSystem(fss...), + path: temp.Name(), + file: temp, + }, digest, size)) +} + func ForTemporaryFilePath(mime string, temp string, fss ...vfs.FileSystem) BlobAccess { return bpi.NewBlobAccessForBase(&temporaryFileBlob{ _blobAccess: ForFile(mime, temp, fss...), @@ -329,6 +338,14 @@ func ForTemporaryFilePath(mime string, temp string, fss ...vfs.FileSystem) BlobA }) } +func ForTemporaryFilePathWithMeta(mime string, digest digest.Digest, size int64, temp string, fss ...vfs.FileSystem) BlobAccess { + return bpi.NewBlobAccessForBase(bpi.BaseAccessForDataAccessAndMeta(mime, &temporaryFileBlob{ + _blobAccess: ForFile(mime, temp, fss...), + filesystem: utils.FileSystem(fss...), + path: temp, + }, digest, size)) +} + //////////////////////////////////////////////////////////////////////////////// type mimeBlob struct { diff --git a/pkg/blobaccess/wget/options.go b/pkg/blobaccess/wget/options.go index a14fb5614f..a808e4545c 100644 --- a/pkg/blobaccess/wget/options.go +++ b/pkg/blobaccess/wget/options.go @@ -64,6 +64,9 @@ func (o *Options) ApplyTo(opts *Options) { if o.LoggingContext != nil { opts.LoggingContext = o.LoggingContext } + if o.Credentials != nil { + opts.Credentials = o.Credentials + } if o.Header != nil { opts.Header = o.Header } diff --git a/pkg/common/accessio/retry_test.go b/pkg/common/accessio/retry_test.go index 73d764ff19..e67df7cf49 100644 --- a/pkg/common/accessio/retry_test.go +++ b/pkg/common/accessio/retry_test.go @@ -9,6 +9,7 @@ import ( . "github.com/open-component-model/ocm/pkg/testutils" "github.com/mandelsoft/goutils/errors" + "github.com/open-component-model/ocm/pkg/common/accessio" ) diff --git a/pkg/common/accessobj/cachedblob.go b/pkg/common/accessobj/cachedblob.go index 5ca6cfbab8..4e08c175ef 100644 --- a/pkg/common/accessobj/cachedblob.go +++ b/pkg/common/accessobj/cachedblob.go @@ -29,10 +29,14 @@ type CachedBlobAccess struct { var _ bpi.BlobAccessBase = (*CachedBlobAccess)(nil) func CachedBlobAccessForWriter(ctx datacontext.Context, mime string, src accessio.DataWriter) blobaccess.BlobAccess { + return CachedBlobAccessForWriterWithCache(tmpcache.Get(ctx), mime, src) +} + +func CachedBlobAccessForWriterWithCache(cache *tmpcache.Attribute, mime string, src accessio.DataWriter) blobaccess.BlobAccess { return bpi.NewBlobAccessForBase(&CachedBlobAccess{ source: src, mime: mime, - cache: tmpcache.Get(ctx), + cache: cache, }) } diff --git a/pkg/contexts/config/gc_test.go b/pkg/contexts/config/gc_test.go index 79e38c2974..996cb23388 100644 --- a/pkg/contexts/config/gc_test.go +++ b/pkg/contexts/config/gc_test.go @@ -6,9 +6,9 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/runtimefinalizer" me "github.com/open-component-model/ocm/pkg/contexts/config" + "github.com/open-component-model/ocm/pkg/runtimefinalizer" ) var _ = Describe("area test", func() { diff --git a/pkg/contexts/credentials/builtin/mvn/identity/identity.go b/pkg/contexts/credentials/builtin/maven/identity/identity.go similarity index 67% rename from pkg/contexts/credentials/builtin/mvn/identity/identity.go rename to pkg/contexts/credentials/builtin/maven/identity/identity.go index 0a5bf6fc74..491e38b453 100644 --- a/pkg/contexts/credentials/builtin/mvn/identity/identity.go +++ b/pkg/contexts/credentials/builtin/maven/identity/identity.go @@ -1,30 +1,26 @@ package identity import ( - "errors" - "net/http" - . "net/url" "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" "github.com/open-component-model/ocm/pkg/contexts/credentials/identity/hostpath" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" "github.com/open-component-model/ocm/pkg/listformat" "github.com/open-component-model/ocm/pkg/logging" ) const ( - // CONSUMER_TYPE is the mvn repository type. + // CONSUMER_TYPE is the maven repository type. CONSUMER_TYPE = "MavenRepository" - // ATTR_USERNAME is the username attribute. Required for login at any mvn registry. + // ATTR_USERNAME is the username attribute. Required for login at any maven registry. ATTR_USERNAME = cpi.ATTR_USERNAME - // ATTR_PASSWORD is the password attribute. Required for login at any mvn registry. + // ATTR_PASSWORD is the password attribute. Required for login at any maven registry. ATTR_PASSWORD = cpi.ATTR_PASSWORD ) // REALM the logging realm / prefix. -var REALM = logging.DefineSubRealm("Maven repository", "mvn") +var REALM = logging.DefineSubRealm("Maven repository", "maven") func init() { attrs := listformat.FormatListElements("", listformat.StringElementDescriptionList{ @@ -64,19 +60,3 @@ func GetCredentials(ctx cpi.ContextProvider, repoUrl, groupId string) (cpi.Crede } return cpi.CredentialsForConsumer(ctx.CredentialsContext(), id) } - -func BasicAuth(req *http.Request, ctx accspeccpi.Context, repoUrl, groupId string) (err error) { - credentials, err := GetCredentials(ctx, repoUrl, groupId) - if err != nil { - return err - } - if credentials == nil { - logging.DynamicLogger(REALM).Debug("No credentials found. BasicAuth not required?", "url", repoUrl, "groupId", groupId) - return nil - } - if !credentials.ExistsProperty(ATTR_USERNAME) || !credentials.ExistsProperty(ATTR_PASSWORD) { - return errors.New("missing username or password in credentials") - } - req.SetBasicAuth(credentials.GetProperty(ATTR_USERNAME), credentials.GetProperty(ATTR_PASSWORD)) - return -} diff --git a/pkg/contexts/credentials/gc_test.go b/pkg/contexts/credentials/gc_test.go index 648e9a9e9c..c96f94c418 100644 --- a/pkg/contexts/credentials/gc_test.go +++ b/pkg/contexts/credentials/gc_test.go @@ -6,9 +6,9 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/runtimefinalizer" me "github.com/open-component-model/ocm/pkg/contexts/credentials" + "github.com/open-component-model/ocm/pkg/runtimefinalizer" ) var _ = Describe("area test", func() { diff --git a/pkg/contexts/credentials/repositories/npm/repository_test.go b/pkg/contexts/credentials/repositories/npm/repository_test.go index e7fd29cfa6..82694bcc48 100644 --- a/pkg/contexts/credentials/repositories/npm/repository_test.go +++ b/pkg/contexts/credentials/repositories/npm/repository_test.go @@ -6,7 +6,6 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/runtimefinalizer" . "github.com/open-component-model/ocm/pkg/testutils" "github.com/open-component-model/ocm/pkg/common" @@ -14,6 +13,7 @@ import ( npmCredentials "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/npm/identity" "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" local "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/npm" + "github.com/open-component-model/ocm/pkg/runtimefinalizer" ) var _ = Describe("NPM config - .npmrc", func() { diff --git a/pkg/contexts/credentials/repositories/vault/repo_int_test.go b/pkg/contexts/credentials/repositories/vault/repo_int_test.go index 13456f8f36..3109660a3e 100644 --- a/pkg/contexts/credentials/repositories/vault/repo_int_test.go +++ b/pkg/contexts/credentials/repositories/vault/repo_int_test.go @@ -5,17 +5,16 @@ package vault_test import ( "context" "fmt" - "net" "os" "os/exec" "time" - "github.com/hashicorp/vault-client-go" - "github.com/hashicorp/vault-client-go/schema" - "github.com/mandelsoft/goutils/errors" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + . "github.com/open-component-model/ocm/pkg/testutils" + + "github.com/hashicorp/vault-client-go" + "github.com/hashicorp/vault-client-go/schema" "github.com/open-component-model/ocm/pkg/common" "github.com/open-component-model/ocm/pkg/contexts/credentials" @@ -23,7 +22,6 @@ import ( me "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/vault" "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/vault/identity" "github.com/open-component-model/ocm/pkg/runtime" - . "github.com/open-component-model/ocm/pkg/testutils" ) type vaultMode string @@ -461,32 +459,11 @@ func StartVaultServer(mode vaultMode, rootToken, address string) (*exec.Cmd, *va err = cmd.Start() if err == nil { - err = WaitForTCPServer(address, time.Minute) + err = PingTCPServer(address, time.Minute) } return cmd, vaultClient, cancelFunc, err } -func WaitForTCPServer(address string, dur time.Duration) error { - var conn net.Conn - var d net.Dialer - - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - - end := time.Now().Add(dur) - err := errors.New("timed out waiting for server to start") - for time.Now().Before(end) { - conn, err = d.DialContext(ctx, "tcp", address) - if err != nil { - time.Sleep(time.Second) - continue - } - conn.Close() - break - } - return err -} - func SetUpVaultAccess(ctx context.Context, credctx credentials.Context, client *vault.Client, policy string) { _ = Must(client.System.AuthEnableMethod(ctx, "approle", schema.AuthEnableMethodRequest{Type: "approle"})) _ = Must(client.System.PoliciesWriteAclPolicy(ctx, VAULT_POLICY_NAME, schema.PoliciesWriteAclPolicyRequest{Policy: policy})) diff --git a/pkg/contexts/credentials/repositories/vault/repo_test.go b/pkg/contexts/credentials/repositories/vault/repo_test.go index 5eb7bdc748..72e3ee2d5c 100644 --- a/pkg/contexts/credentials/repositories/vault/repo_test.go +++ b/pkg/contexts/credentials/repositories/vault/repo_test.go @@ -7,12 +7,12 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + . "github.com/open-component-model/ocm/pkg/testutils" "github.com/open-component-model/ocm/pkg/common" "github.com/open-component-model/ocm/pkg/contexts/credentials" me "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/vault" "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/vault/identity" - . "github.com/open-component-model/ocm/pkg/testutils" ) const ( diff --git a/pkg/contexts/datacontext/attrs/tmpcache/attr.go b/pkg/contexts/datacontext/attrs/tmpcache/attr.go index b71abfe230..bbe00a818f 100644 --- a/pkg/contexts/datacontext/attrs/tmpcache/attr.go +++ b/pkg/contexts/datacontext/attrs/tmpcache/attr.go @@ -62,6 +62,13 @@ type Attribute struct { Filesystem vfs.FileSystem } +func New(path string, fss ...vfs.FileSystem) *Attribute { + return &Attribute{ + Path: path, + Filesystem: utils.FileSystem(fss...), + } +} + func (a *Attribute) CreateTempFile(pat string) (vfs.File, error) { err := a.Filesystem.MkdirAll(a.Path, 0o777) if err != nil { @@ -73,8 +80,15 @@ func (a *Attribute) CreateTempFile(pat string) (vfs.File, error) { //////////////////////////////////////////////////////////////////////////////// func Get(ctx datacontext.Context) *Attribute { - v := ctx.GetAttributes().GetAttribute(ATTR_KEY) - fs := utils.FileSystem(vfsattr.Get(ctx)) + var v interface{} + var fs vfs.FileSystem + + if ctx != nil { + v = ctx.GetAttributes().GetAttribute(ATTR_KEY) + fs = utils.FileSystem(vfsattr.Get(ctx)) + } + fs = utils.FileSystem(fs) + if v != nil { a := v.(*Attribute) if a.Filesystem == nil { diff --git a/pkg/contexts/datacontext/attrs/vfsattr/attr.go b/pkg/contexts/datacontext/attrs/vfsattr/attr.go index c0b8858d23..5ba3ef9645 100644 --- a/pkg/contexts/datacontext/attrs/vfsattr/attr.go +++ b/pkg/contexts/datacontext/attrs/vfsattr/attr.go @@ -35,7 +35,7 @@ Virtual filesystem to use for command line context. func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { if _, ok := v.(vfs.FileSystem); !ok { - return nil, fmt.Errorf("vfs.FileSystem required") + return nil, fmt.Errorf("vfs.CachingFileSystem required") } return nil, nil } diff --git a/pkg/contexts/oci/gc_test.go b/pkg/contexts/oci/gc_test.go index 404dbad31e..bc48501b92 100644 --- a/pkg/contexts/oci/gc_test.go +++ b/pkg/contexts/oci/gc_test.go @@ -6,9 +6,9 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/runtimefinalizer" me "github.com/open-component-model/ocm/pkg/contexts/oci" + "github.com/open-component-model/ocm/pkg/runtimefinalizer" ) var _ = Describe("area test", func() { diff --git a/pkg/contexts/oci/repositories/ctf/ctf_test.go b/pkg/contexts/oci/repositories/ctf/ctf_test.go index 34aa84669f..5ca0cd470c 100644 --- a/pkg/contexts/oci/repositories/ctf/ctf_test.go +++ b/pkg/contexts/oci/repositories/ctf/ctf_test.go @@ -10,13 +10,13 @@ import ( . "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf/testhelper" . "github.com/open-component-model/ocm/pkg/testutils" + "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/goutils/finalizer" "github.com/mandelsoft/logging" "github.com/mandelsoft/vfs/pkg/osfs" "github.com/mandelsoft/vfs/pkg/vfs" "github.com/opencontainers/go-digest" - "github.com/mandelsoft/goutils/errors" "github.com/open-component-model/ocm/pkg/blobaccess" "github.com/open-component-model/ocm/pkg/common/accessio" "github.com/open-component-model/ocm/pkg/common/accessobj" diff --git a/pkg/contexts/ocm/accessmethods/init.go b/pkg/contexts/ocm/accessmethods/init.go index f0a70ce7dd..55ffe41be8 100644 --- a/pkg/contexts/ocm/accessmethods/init.go +++ b/pkg/contexts/ocm/accessmethods/init.go @@ -6,7 +6,7 @@ import ( _ "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" _ "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localfsblob" _ "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localociblob" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/mvn" + _ "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/maven" _ "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/none" _ "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/npm" _ "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" diff --git a/pkg/contexts/ocm/accessmethods/mvn/README.md b/pkg/contexts/ocm/accessmethods/maven/README.md similarity index 100% rename from pkg/contexts/ocm/accessmethods/mvn/README.md rename to pkg/contexts/ocm/accessmethods/maven/README.md diff --git a/pkg/contexts/ocm/accessmethods/mvn/cli.go b/pkg/contexts/ocm/accessmethods/maven/cli.go similarity index 73% rename from pkg/contexts/ocm/accessmethods/mvn/cli.go rename to pkg/contexts/ocm/accessmethods/maven/cli.go index dd81479a6b..db82b8a8b9 100644 --- a/pkg/contexts/ocm/accessmethods/mvn/cli.go +++ b/pkg/contexts/ocm/accessmethods/maven/cli.go @@ -1,4 +1,4 @@ -package mvn +package maven import ( "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" @@ -10,7 +10,7 @@ func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { Type, AddConfig, options.RepositoryOption, options.GroupOption, - options.PackageOption, + options.ArtifactOption, options.VersionOption, // optional options.ClassifierOption, @@ -19,7 +19,7 @@ func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { } func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { - flagsets.AddFieldByOptionP(opts, options.RepositoryOption, config, "repository") + flagsets.AddFieldByOptionP(opts, options.RepositoryOption, config, "repoUrl") flagsets.AddFieldByOptionP(opts, options.GroupOption, config, "groupId") flagsets.AddFieldByOptionP(opts, options.PackageOption, config, "artifactId") flagsets.AddFieldByOptionP(opts, options.VersionOption, config, "version") @@ -30,33 +30,33 @@ func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { } var usage = ` -This method implements the access of a Maven (mvn) artifact in a Maven repository. +This method implements the access of a Maven artifact in a Maven repository. ` var formatV1 = ` The type specific specification fields are: -- **repository** *string* +- **repoUrl** *string* - Base URL of the Maven (mvn) repository + URL of the Maven repository - **groupId** *string* - The groupId of the Maven (mvn) artifact + The groupId of the Maven artifact - **artifactId** *string* - The artifactId of the Maven (mvn) artifact + The artifactId of the Maven artifact - **version** *string* - The version name of the Maven (mvn) artifact + The version name of the Maven artifact - **classifier** *string* - The optional classifier of the Maven (mvn) artifact + The optional classifier of the Maven artifact - **extension** *string* - The optional extension of the Maven (mvn) artifact + The optional extension of the Maven artifact ` diff --git a/pkg/contexts/ocm/accessmethods/maven/method.go b/pkg/contexts/ocm/accessmethods/maven/method.go new file mode 100644 index 0000000000..d377c884fd --- /dev/null +++ b/pkg/contexts/ocm/accessmethods/maven/method.go @@ -0,0 +1,159 @@ +package maven + +import ( + "fmt" + + "github.com/mandelsoft/goutils/optionutils" + + "github.com/open-component-model/ocm/pkg/blobaccess" + mavenblob "github.com/open-component-model/ocm/pkg/blobaccess/maven" + "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/maven/identity" + "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/vfsattr" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" + "github.com/open-component-model/ocm/pkg/maven" + "github.com/open-component-model/ocm/pkg/runtime" +) + +// Type is the access type of Maven repository. +const ( + Type = "maven" + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +func init() { + accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](Type, accspeccpi.WithDescription(usage))) + accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](TypeV1, accspeccpi.WithFormatSpec(formatV1), accspeccpi.WithConfigHandler(ConfigHandler()))) +} + +// AccessSpec describes the access for a Maven artifact. +type AccessSpec struct { + runtime.ObjectVersionedType `json:",inline"` + + // RepoUrl is the base URL of the Maven repository. + RepoUrl string `json:"repoUrl"` + + maven.Coordinates `json:",inline"` +} + +// Option defines the interface function "ApplyTo()". +type Option = maven.CoordinateOption + +type WithClassifier = maven.WithClassifier + +func WithOptionalClassifier(c *string) Option { + return maven.WithOptionalClassifier(c) +} + +type WithExtension = maven.WithExtension + +func WithOptionalExtension(e *string) Option { + return maven.WithOptionalExtension(e) +} + +/////////////////////////////////////////////////////////////////////////////// + +var _ accspeccpi.AccessSpec = (*AccessSpec)(nil) + +// New creates a new Maven repository access spec version v1. +func New(repository, groupId, artifactId, version string, opts ...Option) *AccessSpec { + accessSpec := &AccessSpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + RepoUrl: repository, + Coordinates: *maven.NewCoordinates(groupId, artifactId, version, opts...), + } + return accessSpec +} + +// NewForCoordinates creates a new Maven repository access spec version v1. +func NewForCoordinates(repository string, coords *maven.Coordinates, opts ...Option) *AccessSpec { + optionutils.ApplyOptions(coords, opts...) + accessSpec := &AccessSpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + RepoUrl: repository, + Coordinates: *coords, + } + return accessSpec +} + +func (a *AccessSpec) Describe(_ accspeccpi.Context) string { + return fmt.Sprintf("Maven package '%s' in repository '%s' path '%s'", a.Coordinates.String(), a.RepoUrl, a.Coordinates.FilePath()) +} + +func (_ *AccessSpec) IsLocal(accspeccpi.Context) bool { + return false +} + +func (a *AccessSpec) GlobalAccessSpec(_ accspeccpi.Context) accspeccpi.AccessSpec { + return a +} + +// GetReferenceHint returns the reference hint for the Maven (mvn) artifact. +func (a *AccessSpec) GetReferenceHint(_ accspeccpi.ComponentVersionAccess) string { + return a.String() +} + +func (_ *AccessSpec) GetType() string { + return Type +} + +func (a *AccessSpec) AccessMethod(cv accspeccpi.ComponentVersionAccess) (accspeccpi.AccessMethod, error) { + octx := cv.GetContext() + + repo, err := maven.NewUrlRepository(a.RepoUrl, vfsattr.Get(cv.GetContext())) + if err != nil { + return nil, err + } + + factory := func() (blobaccess.BlobAccess, error) { + return mavenblob.BlobAccessForMavenCoords(repo, &a.Coordinates, + mavenblob.WithCredentialContext(octx), + mavenblob.WithLoggingContext(octx), + mavenblob.WithCachingFileSystem(vfsattr.Get(octx))) + } + return accspeccpi.AccessMethodForImplementation(accspeccpi.NewDefaultMethodImpl(cv, a, "", a.MimeType(), factory), nil) +} + +func (a *AccessSpec) GetInexpensiveContentVersionIdentity(cv accspeccpi.ComponentVersionAccess) string { + creds, err := identity.GetCredentials(cv.GetContext(), a.RepoUrl, a.GroupId) + if err != nil { + return "" + } + mvncreds := mavenblob.MapCredentials(creds) + fs := vfsattr.Get(cv.GetContext()) + repo, err := maven.NewUrlRepository(a.RepoUrl, fs) + if err != nil { + return "" + } + files, err := repo.GavFiles(&a.Coordinates, mvncreds) + if err != nil { + return "" + } + files = a.Coordinates.FilterFileMap(files) + if len(files) != 1 { + return "" + } + if optionutils.AsValue(a.Extension) == "" { + return "" + } + for _, h := range files { + id, _ := a.Location(repo).GetHash(mvncreds, h) + return id + } + return "" +} + +func (a *AccessSpec) BaseUrl() string { + return a.RepoUrl + "/" + a.GavPath() +} + +func (a *AccessSpec) ArtifactUrl() string { + repo, err := maven.NewUrlRepository(a.RepoUrl) + if err != nil { + return "" + } + return a.Location(repo).String() +} + +func (a *AccessSpec) GetCoordinates() *maven.Coordinates { + return a.Coordinates.Copy() +} diff --git a/pkg/contexts/ocm/accessmethods/maven/method_test.go b/pkg/contexts/ocm/accessmethods/maven/method_test.go new file mode 100644 index 0000000000..20310c92d0 --- /dev/null +++ b/pkg/contexts/ocm/accessmethods/maven/method_test.go @@ -0,0 +1,133 @@ +package maven_test + +import ( + "crypto" + "github.com/open-component-model/ocm/pkg/maven/maventest" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/open-component-model/ocm/pkg/env/builder" + . "github.com/open-component-model/ocm/pkg/testutils" + + "github.com/open-component-model/ocm/pkg/contexts/ocm" + me "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/maven" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" + "github.com/open-component-model/ocm/pkg/iotools" + "github.com/open-component-model/ocm/pkg/mime" + "github.com/open-component-model/ocm/pkg/utils/tarutils" +) + +const ( + MAVEN_PATH = "/testdata/.m2/repository" + FAILPATH = "/testdata/.m2/fail" + MAVEN_CENTRAL = "https://repo.maven.apache.org/maven2/" + MAVEN_CENTRAL_ADDRESS = "repo.maven.apache.org:443" + MAVEN_GROUP_ID = "maven" + MAVEN_ARTIFACT_ID = "maven" + MAVEN_VERSION = "1.1" +) + +var _ = Describe("local accessmethods.maven.AccessSpec tests", func() { + var env *Builder + var cv ocm.ComponentVersionAccess + + BeforeEach(func() { + env = NewBuilder(maventest.TestData()) + cv = &cpi.DummyComponentVersionAccess{env.OCMContext()} + }) + + AfterEach(func() { + env.Cleanup() + }) + + It("accesses local artifact", func() { + acc := me.New("file://"+MAVEN_PATH, "com.sap.cloud.sdk", "sdk-modules-bom", "5.7.0") + m := Must(acc.AccessMethod(cv)) + defer Close(m) + Expect(m.MimeType()).To(Equal(mime.MIME_TGZ)) + r := Must(m.Reader()) + defer Close(r) + dr := iotools.NewDigestReaderWithHash(crypto.SHA1, r) + for { + var buf [8096]byte + _, err := dr.Read(buf[:]) + if err != nil { + break + } + } + Expect(dr.Size()).To(Equal(int64(1570))) + Expect(dr.Digest().String()).To(Equal("SHA-1:359d02795bcc737e81c7f2f0ac32f49351d41867")) + }) + + It("accesses local artifact with empty classifier and with extension", func() { + acc := me.New("file://"+MAVEN_PATH, "com.sap.cloud.sdk", "sdk-modules-bom", "5.7.0", me.WithClassifier(""), me.WithExtension("pom")) + m := Must(acc.AccessMethod(cv)) + defer Close(m) + Expect(m.MimeType()).To(Equal(mime.MIME_XML)) + r := Must(m.Reader()) + defer Close(r) + + dr := iotools.NewDigestReaderWithHash(crypto.SHA1, r) + for { + var buf [8096]byte + _, err := dr.Read(buf[:]) + if err != nil { + break + } + } + + Expect(dr.Size()).To(Equal(int64(7153))) + Expect(dr.Digest().String()).To(Equal("SHA-1:34ccdeb9c008f8aaef90873fc636b09d3ae5c709")) + }) + + It("accesses local artifact with extension", func() { + acc := me.New("file://"+MAVEN_PATH, "com.sap.cloud.sdk", "sdk-modules-bom", "5.7.0", me.WithExtension("pom")) + m := Must(acc.AccessMethod(cv)) + defer Close(m) + Expect(m.MimeType()).To(Equal(mime.MIME_TGZ)) + r := Must(m.Reader()) + defer Close(r) + dr := iotools.NewDigestReaderWithHash(crypto.SHA1, r) + list := Must(tarutils.ListArchiveContentFromReader(dr)) + Expect(list).To(ConsistOf("sdk-modules-bom-5.7.0.pom")) + + Expect(dr.Size()).To(Equal(int64(1109))) + Expect(dr.Digest().String()).To(Equal("SHA-1:4ee125ffe4f7690588833f1217a13cc741e4df5f")) + }) + + It("Describe", func() { + acc := me.New("file://"+FAILPATH, "test", "repository", "42", me.WithExtension("pom")) + Expect(acc.Describe(nil)).To(Equal("Maven package 'test:repository:42::pom' in repository 'file:///testdata/.m2/fail' path 'test/repository/42/repository-42.pom'")) + }) + + It("detects digests mismatch", func() { + acc := me.New("file://"+FAILPATH, "test", "repository", "42", me.WithExtension("pom")) + m := Must(acc.AccessMethod(cv)) + defer Close(m) + _, err := m.Reader() + Expect(err).To(MatchError(ContainSubstring("SHA-1 digest mismatch: expected 44a77645201d1a8fc5213ace787c220eabbd0967, found b3242b8c31f8ce14f729b8fd132ac77bc4bc5bf7"))) + }) + + Context("me http repository", func() { + if PingTCPServer(MAVEN_CENTRAL_ADDRESS, time.Second) == nil { + It("blobaccess for gav", func() { + acc := me.New(MAVEN_CENTRAL, MAVEN_GROUP_ID, MAVEN_ARTIFACT_ID, MAVEN_VERSION) + m := Must(acc.AccessMethod(cv)) + defer Close(m) + files := Must(tarutils.ListArchiveContentFromReader(Must(m.Reader()))) + Expect(files).To(ConsistOf( + "maven-1.1-RC1.javadoc.javadoc.jar", + "maven-1.1-sources.jar", + "maven-1.1.jar", + "maven-1.1.pom", + )) + }) + + It("inexpensive id", func() { + acc := me.New(MAVEN_CENTRAL, MAVEN_GROUP_ID, MAVEN_ARTIFACT_ID, MAVEN_VERSION, me.WithClassifier(""), me.WithExtension("pom")) + Expect(acc.GetInexpensiveContentVersionIdentity(cv)).To(Equal("4fb753e1a4ab7acebc39557f1aa292052775b0d8")) + }) + } + }) +}) diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/mvn/suite_test.go b/pkg/contexts/ocm/accessmethods/maven/suite_test.go similarity index 72% rename from pkg/contexts/ocm/blobhandler/handlers/generic/mvn/suite_test.go rename to pkg/contexts/ocm/accessmethods/maven/suite_test.go index c62cadc7b0..d43b2dbd6b 100644 --- a/pkg/contexts/ocm/blobhandler/handlers/generic/mvn/suite_test.go +++ b/pkg/contexts/ocm/accessmethods/maven/suite_test.go @@ -1,4 +1,4 @@ -package mvn_test +package maven_test import ( "testing" @@ -9,5 +9,5 @@ import ( func TestConfig(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "Maven (mvn) Test Suite") + RunSpecs(t, "Maven Test Suite") } diff --git a/pkg/contexts/ocm/accessmethods/mvn/coordinates.go b/pkg/contexts/ocm/accessmethods/mvn/coordinates.go deleted file mode 100644 index d39e09c2e5..0000000000 --- a/pkg/contexts/ocm/accessmethods/mvn/coordinates.go +++ /dev/null @@ -1,144 +0,0 @@ -package mvn - -import ( - "fmt" - "mime" - "path" - "path/filepath" - "strings" - - ocmmime "github.com/open-component-model/ocm/pkg/mime" -) - -// Coordinates holds the typical Maven coordinates groupId, artifactId, version. Optional also classifier and extension. -// https://maven.apache.org/ref/3.9.6/maven-core/artifact-handlers.html -type Coordinates struct { - // GroupId of the Maven (mvn) artifact. - GroupId string `json:"groupId"` - // ArtifactId of the Maven (mvn) artifact. - ArtifactId string `json:"artifactId"` - // Version of the Maven (mvn) artifact. - Version string `json:"version"` - // Classifier of the Maven (mvn) artifact. - Classifier string `json:"classifier"` - // Extension of the Maven (mvn) artifact. - Extension string `json:"extension"` -} - -// GAV returns the GAV coordinates of the Maven Coordinates. -func (c *Coordinates) GAV() string { - return c.GroupId + ":" + c.ArtifactId + ":" + c.Version -} - -// String returns the Coordinates as a string (GroupId:ArtifactId:Version:Classifier:Extension). -func (c *Coordinates) String() string { - return c.GroupId + ":" + c.ArtifactId + ":" + c.Version + ":" + c.Classifier + ":" + c.Extension -} - -// GavPath returns the Maven repository path. -func (c *Coordinates) GavPath() string { - return c.GroupPath() + "/" + c.ArtifactId + "/" + c.Version -} - -// FilePath returns the Maven Coordinates's GAV-name with classifier and extension. -// Which is equal to the URL-path of the artifact in the repository. -// Default extension is jar. -func (c *Coordinates) FilePath() string { - path := c.GavPath() + "/" + c.FileNamePrefix() - if c.Classifier != "" { - path += "-" + c.Classifier - } - if c.Extension != "" { - path += "." + c.Extension - } else { - path += ".jar" - } - return path -} - -func (c *Coordinates) Url(baseUrl string) string { - return baseUrl + "/" + c.FilePath() -} - -// GroupPath returns GroupId with `/` instead of `.`. -func (c *Coordinates) GroupPath() string { - return strings.ReplaceAll(c.GroupId, ".", "/") -} - -func (c *Coordinates) FileNamePrefix() string { - return c.ArtifactId + "-" + c.Version -} - -// Purl returns the Package URL of the Maven Coordinates. -func (c *Coordinates) Purl() string { - return "pkg:maven/" + c.GroupId + "/" + c.ArtifactId + "@" + c.Version -} - -// SetClassifierExtensionBy extracts the classifier and extension from the filename (without any path prefix). -func (c *Coordinates) SetClassifierExtensionBy(filename string) error { - s := strings.TrimPrefix(path.Base(filename), c.FileNamePrefix()) - if strings.HasPrefix(s, "-") { - s = strings.TrimPrefix(s, "-") - i := strings.Index(s, ".") - if i < 0 { - return fmt.Errorf("no extension after classifier found in filename: %s", filename) - } - c.Classifier = s[:i] - s = strings.TrimPrefix(s, c.Classifier) - } else { - c.Classifier = "" - } - c.Extension = strings.TrimPrefix(s, ".") - return nil -} - -// MimeType returns the MIME type of the Maven Coordinates based on the file extension. -// Default is application/x-tgz. -func (c *Coordinates) MimeType() string { - m := mime.TypeByExtension("." + c.Extension) - if m != "" { - return m - } - return ocmmime.MIME_TGZ -} - -// Copy creates a new Coordinates with the same values. -func (c *Coordinates) Copy() *Coordinates { - return &Coordinates{ - GroupId: c.GroupId, - ArtifactId: c.ArtifactId, - Version: c.Version, - Classifier: c.Classifier, - Extension: c.Extension, - } -} - -// Parse creates an Coordinates from it's serialized form (see Coordinates.String). -func Parse(serializedArtifact string) (*Coordinates, error) { - parts := strings.Split(serializedArtifact, ":") - if len(parts) < 3 { - return nil, fmt.Errorf("invalid artifact string: %s", serializedArtifact) - } - artifact := &Coordinates{ - GroupId: parts[0], - ArtifactId: parts[1], - Version: parts[2], - } - if len(parts) >= 4 { - artifact.Classifier = parts[3] - } - if len(parts) >= 5 { - artifact.Extension = parts[4] - } - return artifact, nil -} - -// IsResource returns true if the filename is not a checksum or signature file. -func IsResource(fileName string) bool { - switch filepath.Ext(fileName) { - case ".asc", ".md5", ".sha1", ".sha256", ".sha512": - return false - default: - return true - } -} diff --git a/pkg/contexts/ocm/accessmethods/mvn/coordinates_test.go b/pkg/contexts/ocm/accessmethods/mvn/coordinates_test.go deleted file mode 100644 index 63df1268ae..0000000000 --- a/pkg/contexts/ocm/accessmethods/mvn/coordinates_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package mvn - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("Maven Test Environment", func() { - - It("GAV, GroupPath, FilePath", func() { - artifact := &Coordinates{ - GroupId: "ocm.software", - ArtifactId: "hello-ocm", - Version: "0.0.1", - Extension: "jar", - } - Expect(artifact.GAV()).To(Equal("ocm.software:hello-ocm:0.0.1")) - Expect(artifact.GroupPath()).To(Equal("ocm/software")) - Expect(artifact.FilePath()).To(Equal("ocm/software/hello-ocm/0.0.1/hello-ocm-0.0.1.jar")) - }) - - It("SetClassifierExtensionBy", func() { - artifact := &Coordinates{ - GroupId: "ocm.software", - ArtifactId: "hello-ocm", - Version: "0.0.1", - } - artifact.SetClassifierExtensionBy("hello-ocm-0.0.1.pom") - Expect(artifact.Classifier).To(Equal("")) - Expect(artifact.Extension).To(Equal("pom")) - - artifact.SetClassifierExtensionBy("hello-ocm-0.0.1-tests.jar") - Expect(artifact.Classifier).To(Equal("tests")) - Expect(artifact.Extension).To(Equal("jar")) - - artifact.ArtifactId = "apache-maven" - artifact.Version = "3.9.6" - artifact.SetClassifierExtensionBy("apache-maven-3.9.6-bin.tar.gz") - Expect(artifact.Classifier).To(Equal("bin")) - Expect(artifact.Extension).To(Equal("tar.gz")) - }) - - It("parse GAV", func() { - gav := "org.apache.commons:commons-compress:1.26.1:cyclonedx:xml" - artifact, err := Parse(gav) - Expect(err).To(BeNil()) - Expect(artifact.String()).To(Equal(gav)) - Expect(artifact.GroupId).To(Equal("org.apache.commons")) - Expect(artifact.ArtifactId).To(Equal("commons-compress")) - Expect(artifact.Version).To(Equal("1.26.1")) - Expect(artifact.Classifier).To(Equal("cyclonedx")) - Expect(artifact.Extension).To(Equal("xml")) - Expect(artifact.FilePath()).To(Equal("org/apache/commons/commons-compress/1.26.1/commons-compress-1.26.1-cyclonedx.xml")) - }) -}) diff --git a/pkg/contexts/ocm/accessmethods/mvn/integration_test.go b/pkg/contexts/ocm/accessmethods/mvn/integration_test.go deleted file mode 100644 index b2eca2b1d0..0000000000 --- a/pkg/contexts/ocm/accessmethods/mvn/integration_test.go +++ /dev/null @@ -1,98 +0,0 @@ -//go:build integration - -package mvn_test - -import ( - "crypto" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/mvn" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - . "github.com/open-component-model/ocm/pkg/env" - . "github.com/open-component-model/ocm/pkg/env/builder" - "github.com/open-component-model/ocm/pkg/mime" - . "github.com/open-component-model/ocm/pkg/testutils" - "github.com/open-component-model/ocm/pkg/utils/tarutils" -) - -var _ = Describe("online accessmethods.mvn.AccessSpec integration tests", func() { - var env *Builder - var cv ocm.ComponentVersionAccess - - BeforeEach(func() { - env = NewBuilder(TestData()) - cv = &cpi.DummyComponentVersionAccess{env.OCMContext()} - }) - - AfterEach(func() { - env.Cleanup() - }) - - // https://repo1.maven.org/maven2/com/sap/cloud/sdk/sdk-modules-bom/5.7.0 - It("one single pom only", func() { - acc := mvn.New("https://repo1.maven.org/maven2", "com.sap.cloud.sdk", "sdk-modules-bom", "5.7.0") - files, err := acc.GavFiles(cv.GetContext()) - Expect(err).ToNot(HaveOccurred()) - Expect(files).To(HaveLen(1)) - Expect(files["sdk-modules-bom-5.7.0.pom"]).To(Equal(crypto.SHA1)) - }) - It("GetPackageMeta - com.sap.cloud.sdk", func() { - acc := mvn.New("https://repo1.maven.org/maven2", "com.sap.cloud.sdk", "sdk-modules-bom", "5.7.0") - meta, err := acc.GetPackageMeta(ocm.DefaultContext()) - Expect(err).ToNot(HaveOccurred()) - Expect(meta.Bin).To(HavePrefix("file://")) - Expect(meta.Bin).To(ContainSubstring("mvn-sdk-modules-bom-5.7.0-")) - Expect(meta.Bin).To(HaveSuffix(".tar.gz")) - Expect(meta.Hash).To(Equal("345fe2e640663c3cd6ac87b7afb92e1c934f665f75ddcb9555bc33e1813ef00b")) - Expect(meta.HashType).To(Equal(crypto.SHA256)) - }) - - // https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.9.6 - It("apache-maven, with bin + tar.gz etc.", func() { - acc := mvn.New("https://repo1.maven.org/maven2", "org.apache.maven", "apache-maven", "3.9.6") - Expect(acc).ToNot(BeNil()) - Expect(acc.BaseUrl()).To(Equal("https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.9.6")) - files, err := acc.GavFiles(cv.GetContext()) - Expect(err).ToNot(HaveOccurred()) - Expect(files).To(HaveLen(8)) - Expect(files["apache-maven-3.9.6-src.zip"]).To(Equal(crypto.SHA512)) - Expect(files["apache-maven-3.9.6.pom"]).To(Equal(crypto.SHA1)) - }) - - // https://repo1.maven.org/maven2/com/sap/cloud/environment/servicebinding/java-sap-vcap-services/0.10.4 - It("accesses local artifact", func() { - acc := mvn.New("https://repo1.maven.org/maven2", "com.sap.cloud.environment.servicebinding", "java-sap-vcap-services", "0.10.4") - meta, err := acc.GetPackageMeta(ocm.DefaultContext()) - Expect(err).ToNot(HaveOccurred()) - Expect(meta.Bin).To(HavePrefix("file://")) - m := Must(acc.AccessMethod(cv)) - defer m.Close() - Expect(m.MimeType()).To(Equal(mime.MIME_TGZ)) - /* manually also tested with repos: - - https://repo1.maven.org/maven2/org/apache/commons/commons-compress/1.26.1/ // cyclonedx - - https://repo1.maven.org/maven2/cn/afternode/commons/commons/1.6/ // gradle module! - */ - }) - - // https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.9.6 - It("apache-maven, 'bin' zip + tar.gz only!", func() { - acc := mvn.New("https://repo1.maven.org/maven2", "org.apache.maven", "apache-maven", "3.9.6", mvn.WithClassifier("bin")) - Expect(acc).ToNot(BeNil()) - Expect(acc.BaseUrl()).To(Equal("https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.9.6")) - files, err := acc.GavFiles(cv.GetContext()) - Expect(err).ToNot(HaveOccurred()) - Expect(files).To(HaveLen(8)) // the repo contains 8 files... - m := Must(acc.AccessMethod(cv)) - defer m.Close() - Expect(err).ToNot(HaveOccurred()) - Expect(m.MimeType()).To(Equal(mime.MIME_TGZ)) - r := Must(m.Reader()) - defer r.Close() - list, err := tarutils.ListArchiveContentFromReader(r) - Expect(err).ToNot(HaveOccurred()) - Expect(list).To(HaveLen(2)) // ...but with the classifier set, we're interested only in two! - }) -}) diff --git a/pkg/contexts/ocm/accessmethods/mvn/method.go b/pkg/contexts/ocm/accessmethods/mvn/method.go deleted file mode 100644 index d430b774c2..0000000000 --- a/pkg/contexts/ocm/accessmethods/mvn/method.go +++ /dev/null @@ -1,444 +0,0 @@ -package mvn - -import ( - "bytes" - "context" - "crypto" - "fmt" - "io" - "net/http" - "path" - "sort" - "strings" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/optionutils" - "github.com/mandelsoft/vfs/pkg/osfs" - "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/opencontainers/go-digest" - "golang.org/x/exp/slices" - "golang.org/x/net/html" - - "github.com/open-component-model/ocm/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/common/accessobj" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/mvn/identity" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/vfsattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/iotools" - "github.com/open-component-model/ocm/pkg/logging" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/utils/tarutils" -) - -// Type is the access type of Maven (mvn) repository. -const ( - Type = "mvn" - TypeV1 = Type + runtime.VersionSeparator + "v1" -) - -func init() { - accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](Type, accspeccpi.WithDescription(usage))) - accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](TypeV1, accspeccpi.WithFormatSpec(formatV1), accspeccpi.WithConfigHandler(ConfigHandler()))) -} - -// AccessSpec describes the access for a Maven (mvn) artifact. -type AccessSpec struct { - runtime.ObjectVersionedType `json:",inline"` - - // Repository is the base URL of the Maven (mvn) repository. - Repository string `json:"repository"` - - Coordinates `json:",inline"` -} - -// Option defines the interface function "ApplyTo()". -type Option = optionutils.Option[*AccessSpec] - -var _ accspeccpi.AccessSpec = (*AccessSpec)(nil) - -var log = logging.DynamicLogger(identity.REALM) - -// New creates a new Maven (mvn) repository access spec version v1. -func New(repository, groupId, artifactId, version string, options ...Option) *AccessSpec { - accessSpec := &AccessSpec{ - ObjectVersionedType: runtime.NewVersionedTypedObject(Type), - Repository: repository, - Coordinates: Coordinates{ - GroupId: groupId, - ArtifactId: artifactId, - Version: version, - Classifier: "", - Extension: "", - }, - } - optionutils.ApplyOptions(accessSpec, options...) - return accessSpec -} - -// classifier Option for Maven (mvn) Coordinates. -type classifier string - -func (c classifier) ApplyTo(a *AccessSpec) { - a.Classifier = string(c) -} - -// WithClassifier sets the classifier of the Maven (mvn) artifact. -func WithClassifier(c string) Option { - return classifier(c) -} - -// extension Option for Maven (mvn) Coordinates. -type extension string - -func (e extension) ApplyTo(a *AccessSpec) { - a.Extension = string(e) -} - -// WithExtension sets the extension of the Maven (mvn) artifact. -func WithExtension(e string) Option { - return extension(e) -} - -func (a *AccessSpec) Describe(_ accspeccpi.Context) string { - return fmt.Sprintf("Maven (mvn) package '%s' in repository '%s' path '%s'", a.Coordinates.String(), a.Repository, a.Coordinates.FilePath()) -} - -func (_ *AccessSpec) IsLocal(accspeccpi.Context) bool { - return false -} - -func (a *AccessSpec) GlobalAccessSpec(_ accspeccpi.Context) accspeccpi.AccessSpec { - return a -} - -// GetReferenceHint returns the reference hint for the Maven (mvn) artifact. -func (a *AccessSpec) GetReferenceHint(_ accspeccpi.ComponentVersionAccess) string { - return a.String() -} - -func (_ *AccessSpec) GetType() string { - return Type -} - -func (a *AccessSpec) AccessMethod(c accspeccpi.ComponentVersionAccess) (accspeccpi.AccessMethod, error) { - return accspeccpi.AccessMethodForImplementation(newMethod(c, a)) -} - -func (a *AccessSpec) GetInexpensiveContentVersionIdentity(access accspeccpi.ComponentVersionAccess) string { - meta, _ := a.GetPackageMeta(access.GetContext()) - if meta != nil { - return meta.Hash - } - return "" -} - -func (a *AccessSpec) BaseUrl() string { - return a.Repository + "/" + a.GavPath() -} - -func (a *AccessSpec) ArtifactUrl() string { - return a.Url(a.Repository) -} - -func (a *AccessSpec) NewArtifact() *Coordinates { - return a.Coordinates.Copy() -} - -type meta struct { - MimeType string `json:"packaging"` - HashType crypto.Hash `json:"hashType"` - Hash string `json:"hash"` - Bin string `json:"bin"` -} - -func update(a *AccessSpec, file string, hash crypto.Hash, metadata *meta, ctx accspeccpi.Context, fs vfs.FileSystem) error { - artifact := a.NewArtifact() - err := artifact.SetClassifierExtensionBy(file) - if err != nil { - return err - } - metadata.Bin = artifact.Url(a.Repository) - log := log.WithValues("file", metadata.Bin) - log.Debug("processing") - metadata.MimeType = artifact.MimeType() - if hash > 0 { - metadata.HashType = hash - metadata.Hash, err = getStringData(ctx, metadata.Bin+hashUrlExt(hash), fs) - if err != nil { - return errors.Wrapf(err, "cannot read %s digest of: %s", hash, metadata.Bin) - } - } else { - log.Warn("no digest available") - } - return nil -} - -func (a *AccessSpec) GetPackageMeta(ctx accspeccpi.Context) (*meta, error) { - fs := vfsattr.Get(ctx) - - log := log.WithValues("BaseUrl", a.BaseUrl()) - fileMap, err := a.GavFiles(ctx, fs) - if err != nil { - return nil, err - } - - if a.Classifier != "" { - fileMap = filterByClassifier(fileMap, a.Classifier) - } - - switch l := len(fileMap); { - case l <= 0: - return nil, errors.New("no maven artifact files found") - case l == 1 && (a.Extension != "" || a.Classifier != ""): - metadata := meta{} - for file, hash := range fileMap { - update(a, file, hash, &metadata, ctx, fs) - } - return &metadata, nil - // default: continue below with: create tempFs where all files can be downloaded to and packed together as tar.gz - } - - if (a.Extension == "") != (a.Classifier == "") { // XOR - log.Warn("Either classifier or extension have been specified, which results in an incomplete GAV!") - } - tempFs, err := osfs.NewTempFileSystem() - if err != nil { - return nil, err - } - defer vfs.Cleanup(tempFs) - - metadata := meta{} - for file, hash := range fileMap { - update(a, file, hash, &metadata, ctx, fs) - - // download the artifact into the temporary file system - e := func() error { - out, err := tempFs.Create(file) - if err != nil { - return err - } - defer out.Close() - reader, err := getReader(ctx, metadata.Bin, fs) - if err != nil { - return err - } - defer reader.Close() - if hash > 0 { - dreader := iotools.NewDigestReaderWithHash(hash, reader) - _, err = io.Copy(out, dreader) - if err != nil { - return err - } - sum := dreader.Digest().Encoded() - if metadata.Hash != sum { - return errors.Newf("%s digest mismatch: expected %s, found %s", metadata.HashType, metadata.Hash, sum) - } - } else { - _, err = io.Copy(out, reader) - return err - } - return err - }() - if e != nil { - return nil, e - } - } - - // pack all downloaded files into a tar.gz file - tgz, err := vfs.TempFile(fs, "", Type+"-"+a.NewArtifact().FileNamePrefix()+"-*.tar.gz") - if err != nil { - return nil, err - } - defer tgz.Close() - - dw := iotools.NewDigestWriterWith(digest.SHA256, tgz) - defer dw.Close() - err = tarutils.TgzFs(tempFs, dw) - if err != nil { - return nil, err - } - - metadata.Bin = "file://" + tgz.Name() - metadata.MimeType = mime.MIME_TGZ - metadata.Hash = dw.Digest().Encoded() - metadata.HashType = crypto.SHA256 - log.Debug("created", "file", metadata.Bin) - - return &metadata, nil -} - -func filterByClassifier(fileMap map[string]crypto.Hash, classifier string) map[string]crypto.Hash { - for file := range fileMap { - if !strings.Contains(file, "-"+classifier+".") { - delete(fileMap, file) - } - } - return fileMap -} - -func (a *AccessSpec) GavFiles(ctx accspeccpi.Context, fs ...vfs.FileSystem) (map[string]crypto.Hash, error) { - if strings.HasPrefix(a.Repository, "file://") { - dir := path.Join(a.Repository[7:], a.GavPath()) - return gavFilesFromDisk(utils.FileSystem(fs...), dir) - } - return a.gavOnlineFiles(ctx) -} - -func gavFilesFromDisk(fs vfs.FileSystem, dir string) (map[string]crypto.Hash, error) { - files, err := tarutils.ListSortedFilesInDir(fs, dir, true) - if err != nil { - return nil, err - } - return filesAndHashes(files), nil -} - -// gavOnlineFiles returns the files of the Maven (mvn) artifact in the repository and their available digests. -func (a *AccessSpec) gavOnlineFiles(ctx accspeccpi.Context) (map[string]crypto.Hash, error) { - log := log.WithValues("BaseUrl", a.BaseUrl()) - log.Debug("gavOnlineFiles") - - reader, err := getReader(ctx, a.BaseUrl(), nil) - if err != nil { - return nil, err - } - defer reader.Close() - - // Which files are listed in the repository? - log.Debug("parse-html") - htmlDoc, err := html.Parse(reader) - if err != nil { - return nil, err - } - var fileList []string - var process func(*html.Node) - prefix := a.FileNamePrefix() - process = func(node *html.Node) { - // check if the node is an element node and the tag is "" - if node.Type == html.ElementNode && node.Data == "a" { - for _, attribute := range node.Attr { - if attribute.Key == "href" { - // check if the href starts with artifactId-version - if strings.HasPrefix(attribute.Val, prefix) { - fileList = append(fileList, attribute.Val) - } - } - } - } - for nextChild := node.FirstChild; nextChild != nil; nextChild = nextChild.NextSibling { - process(nextChild) // recursive call! - } - } - process(htmlDoc) - - return filesAndHashes(fileList), nil -} - -func filesAndHashes(fileList []string) map[string]crypto.Hash { - // Sort the list of files, to ensure always the same results for e.g. identical tar.gz files. - sort.Strings(fileList) - - // Which hash files are available? - result := make(map[string]crypto.Hash, len(fileList)/2) - for _, file := range fileList { - if IsResource(file) { - result[file] = bestAvailableHash(fileList, file) - log.Debug("found", "file", file) - } - } - return result -} - -// bestAvailableHash returns the best available hash for the given file. -// It first checks for SHA-512, then SHA-256, SHA-1, and finally MD5. If nothing is found, it returns 0. -func bestAvailableHash(list []string, filename string) crypto.Hash { - hashes := [5]crypto.Hash{crypto.SHA512, crypto.SHA256, crypto.SHA1, crypto.MD5} - for _, hash := range hashes { - if slices.Contains(list, filename+hashUrlExt(hash)) { - return hash - } - } - return 0 -} - -//////////////////////////////////////////////////////////////////////////////// - -// getStringData reads all data from the given URL and returns it as a string. -func getStringData(ctx accspeccpi.Context, url string, fs vfs.FileSystem) (string, error) { - r, err := getReader(ctx, url, fs) - if err != nil { - return "", err - } - defer r.Close() - b, err := io.ReadAll(r) - if err != nil { - return "", err - } - return string(b), nil -} - -// hashUrlExt returns the 'maven' hash extension for the given hash. -// Maven usually uses sha1, sha256, sha512, md5 instead of SHA-1, SHA-256, SHA-512, MD5. -func hashUrlExt(h crypto.Hash) string { - return "." + strings.ReplaceAll(strings.ToLower(h.String()), "-", "") -} - -func newMethod(c accspeccpi.ComponentVersionAccess, a *AccessSpec) (accspeccpi.AccessMethodImpl, error) { - factory := func() (blobaccess.BlobAccess, error) { - meta, err := a.GetPackageMeta(c.GetContext()) - if err != nil { - return nil, err - } - - reader := func() (io.ReadCloser, error) { - return getReader(c.GetContext(), meta.Bin, vfsattr.Get(c.GetContext())) - } - if meta.Hash != "" { - getreader := reader - reader = func() (io.ReadCloser, error) { - readCloser, err := getreader() - if err != nil { - return nil, err - } - return iotools.VerifyingReaderWithHash(readCloser, meta.HashType, meta.Hash), nil - } - } - acc := blobaccess.DataAccessForReaderFunction(reader, meta.Bin) - return accessobj.CachedBlobAccessForWriter(c.GetContext(), a.MimeType(), accessio.NewDataAccessWriter(acc)), nil - } - // FIXME add Digest! - return accspeccpi.NewDefaultMethodImpl(c, a, "", a.MimeType(), factory), nil -} - -func getReader(ctx accspeccpi.Context, url string, fs vfs.FileSystem) (io.ReadCloser, error) { - if strings.HasPrefix(url, "file://") { - path := url[7:] - return fs.OpenFile(path, vfs.O_RDONLY, 0o600) - } - - req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil) - if err != nil { - return nil, err - } - err = identity.BasicAuth(req, ctx, url, "") - if err != nil { - return nil, err - } - httpClient := &http.Client{} - resp, err := httpClient.Do(req) - if err != nil { - return nil, err - } - if resp.StatusCode != http.StatusOK { - defer resp.Body.Close() - buf := &bytes.Buffer{} - _, err = io.Copy(buf, io.LimitReader(resp.Body, 2000)) - if err == nil { - log.Error("http", "code", resp.Status, "url", url, "body", buf.String()) - } - return nil, errors.Newf("http %s error - %s", resp.Status, url) - } - return resp.Body, nil -} diff --git a/pkg/contexts/ocm/accessmethods/mvn/method_test.go b/pkg/contexts/ocm/accessmethods/mvn/method_test.go deleted file mode 100644 index 7b4ca85a80..0000000000 --- a/pkg/contexts/ocm/accessmethods/mvn/method_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package mvn_test - -import ( - "crypto" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/mvn" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - . "github.com/open-component-model/ocm/pkg/env" - . "github.com/open-component-model/ocm/pkg/env/builder" - "github.com/open-component-model/ocm/pkg/iotools" - "github.com/open-component-model/ocm/pkg/mime" - . "github.com/open-component-model/ocm/pkg/testutils" -) - -const ( - mvnPATH = "/testdata/.m2/repository" - FAILPATH = "/testdata/fail" -) - -var _ = Describe("local accessmethods.mvn.AccessSpec tests", func() { - var env *Builder - var cv ocm.ComponentVersionAccess - - BeforeEach(func() { - env = NewBuilder(TestData()) - cv = &cpi.DummyComponentVersionAccess{env.OCMContext()} - }) - - AfterEach(func() { - env.Cleanup() - }) - - It("accesses local artifact", func() { - acc := mvn.New("file://"+mvnPATH, "com.sap.cloud.sdk", "sdk-modules-bom", "5.7.0") - m := Must(acc.AccessMethod(cv)) - defer m.Close() - Expect(m.MimeType()).To(Equal(mime.MIME_TGZ)) - r := Must(m.Reader()) - defer r.Close() - dr := iotools.NewDigestReaderWithHash(crypto.SHA1, r) - for { - var buf [8096]byte - _, err := dr.Read(buf[:]) - if err != nil { - break - } - } - Expect(dr.Size()).To(Equal(int64(1109))) - Expect(dr.Digest().String()).To(Equal("SHA-1:4ee125ffe4f7690588833f1217a13cc741e4df5f")) - }) - - It("accesses local artifact with extension", func() { - acc := mvn.New("file://"+mvnPATH, "com.sap.cloud.sdk", "sdk-modules-bom", "5.7.0", mvn.WithExtension("pom")) - m := Must(acc.AccessMethod(cv)) - defer m.Close() - Expect(m.MimeType()).To(Equal(mime.MIME_XML)) - r := Must(m.Reader()) - defer r.Close() - dr := iotools.NewDigestReaderWithHash(crypto.SHA1, r) - for { - var buf [8096]byte - _, err := dr.Read(buf[:]) - if err != nil { - break - } - } - Expect(dr.Size()).To(Equal(int64(7153))) - Expect(dr.Digest().String()).To(Equal("SHA-1:34ccdeb9c008f8aaef90873fc636b09d3ae5c709")) - }) - - It("Describe", func() { - acc := mvn.New("file://"+FAILPATH, "test", "repository", "42", mvn.WithExtension("pom")) - Expect(acc.Describe(nil)).To(Equal("Maven (mvn) package 'test:repository:42::pom' in repository 'file:///testdata/fail' path 'test/repository/42/repository-42.pom'")) - }) - - It("detects digests mismatch", func() { - acc := mvn.New("file://"+FAILPATH, "test", "repository", "42", mvn.WithExtension("pom")) - m := Must(acc.AccessMethod(cv)) - defer m.Close() - _, err := m.Reader() - Expect(err).To(MatchError(ContainSubstring("SHA-1 digest mismatch: expected 44a77645201d1a8fc5213ace787c220eabbd0967, found b3242b8c31f8ce14f729b8fd132ac77bc4bc5bf7"))) - }) -}) diff --git a/pkg/contexts/ocm/accessmethods/options/standard.go b/pkg/contexts/ocm/accessmethods/options/standard.go index 7e8680f12b..cc666b3ecc 100644 --- a/pkg/contexts/ocm/accessmethods/options/standard.go +++ b/pkg/contexts/ocm/accessmethods/options/standard.go @@ -18,8 +18,11 @@ var ReferenceOption = RegisterOption(NewStringOptionType("reference", "reference // PackageOption. var PackageOption = RegisterOption(NewStringOptionType("accessPackage", "package or object name")) +// ArtifactOption. +var ArtifactOption = RegisterOption(NewStringOptionType("artifactId", "maven artifact id")) + // GroupOption. -var GroupOption = RegisterOption(NewStringOptionType("accessGroup", "GroupID or namespace")) +var GroupOption = RegisterOption(NewStringOptionType("groupId", "maven group id")) // RepositoryOption. var RepositoryOption = RegisterOption(NewStringOptionType("accessRepository", "repository URL")) @@ -60,7 +63,7 @@ var HTTPRedirectOption = RegisterOption(NewBoolOptionType("noredirect", "http re var CommentOption = RegisterOption(NewStringOptionType("comment", "comment field value")) // ClassifierOption the optional classifier of a maven resource. -var ClassifierOption = RegisterOption(NewStringOptionType("accessClassifier", "mvn classifier")) +var ClassifierOption = RegisterOption(NewStringOptionType("classifier", "maven classifier")) // ExtensionOption the optional extension of a maven resource. -var ExtensionOption = RegisterOption(NewStringOptionType("accessExtension", "mvn extension name")) +var ExtensionOption = RegisterOption(NewStringOptionType("extension", "maven extension name")) diff --git a/pkg/contexts/ocm/accessmethods/wget/options.go b/pkg/contexts/ocm/accessmethods/wget/options.go index 4693d6c697..fcf667661f 100644 --- a/pkg/contexts/ocm/accessmethods/wget/options.go +++ b/pkg/contexts/ocm/accessmethods/wget/options.go @@ -4,10 +4,7 @@ import ( "io" "net/http" - "github.com/mandelsoft/logging" - "github.com/open-component-model/ocm/pkg/blobaccess/wget" - "github.com/open-component-model/ocm/pkg/contexts/credentials" ) type ( @@ -15,22 +12,10 @@ type ( Option = wget.Option ) -func WithCredentialContext(ctx credentials.ContextProvider) Option { - return wget.WithCredentialContext(ctx) -} - -func WithLoggingContext(ctx logging.ContextProvider) Option { - return wget.WithLoggingContext(ctx) -} - func WithMimeType(mime string) Option { return wget.WithMimeType(mime) } -func WithCredentials(c credentials.Credentials) Option { - return wget.WithCredentials(c) -} - func WithHeader(h http.Header) Option { return wget.WithHeader(h) } diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/maven/blobhandler.go b/pkg/contexts/ocm/blobhandler/handlers/generic/maven/blobhandler.go new file mode 100644 index 0000000000..76dd8689bc --- /dev/null +++ b/pkg/contexts/ocm/blobhandler/handlers/generic/maven/blobhandler.go @@ -0,0 +1,126 @@ +package maven + +import ( + "crypto" + + "github.com/mandelsoft/goutils/finalizer" + "github.com/mandelsoft/vfs/pkg/vfs" + + mavenblob "github.com/open-component-model/ocm/pkg/blobaccess/maven" + "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/maven/identity" + access "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/maven" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" + "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" + "github.com/open-component-model/ocm/pkg/iotools" + "github.com/open-component-model/ocm/pkg/logging" + "github.com/open-component-model/ocm/pkg/maven" + "github.com/open-component-model/ocm/pkg/mime" + "github.com/open-component-model/ocm/pkg/utils/tarutils" +) + +const BlobHandlerName = "ocm/" + resourcetypes.MAVEN_ARTIFACT + +type artifactHandler struct { + spec *Config +} + +func NewArtifactHandler(repospec *Config) cpi.BlobHandler { + return &artifactHandler{repospec} +} + +var log = logging.DynamicLogger(identity.REALM) + +func (b *artifactHandler) StoreBlob(blob cpi.BlobAccess, resourceType string, hint string, _ cpi.AccessSpec, ctx cpi.StorageContext) (_ cpi.AccessSpec, rerr error) { + var finalize finalizer.Finalizer + defer finalize.FinalizeWithErrorPropagation(&rerr) + + // check conditions + if b.spec == nil { + return nil, nil + } + mimeType := blob.MimeType() + if resourcetypes.MAVEN_ARTIFACT != resourceType { + log.Debug("not a MVN artifact", "resourceType", resourceType) + return nil, nil + } + if mime.MIME_TGZ != mimeType { + log.Debug("not a tarball, can't be a complete maven GAV", "mimeType", mimeType) + return nil, nil + } + + repo, err := b.spec.GetRepository(ctx.GetContext()) + if err != nil { + return nil, err + } + + // setup logger + log := log.WithValues("repository", repo.String()) + // identify artifact + coords, err := maven.Parse(hint) + if err != nil { + return nil, err + } + if coords.Classifier != nil || coords.Extension != nil { + return nil, nil + } + log = log.WithValues("groupId", coords.GroupId, "artifactId", coords.ArtifactId, "version", coords.Version) + log.Debug("identified") + + blobReader, err := blob.Reader() + if err != nil { + return nil, err + } + finalize.Close(blobReader) + tempFs, err := tarutils.ExtractTgzToTempFs(blobReader) + if err != nil { + return nil, err + } + finalize.With(func() error { return vfs.Cleanup(tempFs) }) + files, err := tarutils.ListSortedFilesInDir(tempFs, "", false) + if err != nil { + return nil, err + } + for _, file := range files { + loop := finalize.Nested() + log.Debug("uploading", "file", file) + err := coords.SetClassifierExtensionBy(file) + if err != nil { + return nil, err + } + readHash, err := tempFs.Open(file) + if err != nil { + return nil, err + } + loop.Close(readHash) + // MD5 + SHA1 are still the most used ones in the maven context + hr := iotools.NewHashReader(readHash, crypto.SHA256, crypto.SHA1, crypto.MD5) + _, err = hr.CalcHashes() + if err != nil { + return nil, err + } + reader, err := tempFs.Open(file) + if err != nil { + return nil, err + } + loop.Close(reader) + creds, err := mavenblob.GetCredentials(ctx.GetContext(), repo, coords.GroupId) + if err != nil { + return nil, err + } + err = repo.Upload(coords, reader, creds, hr.Hashes()) + if err != nil { + return nil, err + } + err = loop.Finalize() + if err != nil { + return nil, err + } + } + + log.Debug("done", "artifact", coords) + url, err := repo.Url() + if err != nil { + return nil, err + } + return access.New(url, coords.GroupId, coords.ArtifactId, coords.Version), nil +} diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/maven/blobhandler_test.go b/pkg/contexts/ocm/blobhandler/handlers/generic/maven/blobhandler_test.go new file mode 100644 index 0000000000..76532ed6bf --- /dev/null +++ b/pkg/contexts/ocm/blobhandler/handlers/generic/maven/blobhandler_test.go @@ -0,0 +1,101 @@ +package maven_test + +import ( + "encoding/json" + "github.com/open-component-model/ocm/pkg/maven/maventest" + "os" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/open-component-model/ocm/pkg/env/builder" + . "github.com/open-component-model/ocm/pkg/testutils" + + "github.com/mandelsoft/goutils/sliceutils" + "github.com/mandelsoft/vfs/pkg/vfs" + + mavenblob "github.com/open-component-model/ocm/pkg/blobaccess/maven" + me "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/generic/maven" + "github.com/open-component-model/ocm/pkg/contexts/ocm/elements" + "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/composition" + "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" + "github.com/open-component-model/ocm/pkg/maven" +) + +const MAVEN_PATH = "/testdata/.m2/repository" + +var _ = Describe("blobhandler generic maven tests", func() { + var env *Builder + var repo *maven.Repository + + BeforeEach(func() { + env = NewBuilder(maventest.TestData()) + repo = maven.NewFileRepository(MAVEN_PATH, env.FileSystem()) + }) + + It("Unmarshal upload response Body", func() { + resp := `{ "repo" : "ocm-mvn-test", + "path" : "/open-component-model/hello-ocm/0.0.2/hello-ocm-0.0.2.jar", + "created" : "2024-04-11T15:09:28.920Z", + "createdBy" : "john.doe", + "downloadUri" : "https://ocm.sofware/repository/ocm-mvn-test/open-component-model/hello-ocm/0.0.2/hello-ocm-0.0.2.jar", + "mimeType" : "application/java-archive", + "size" : "1792", + "checksums" : { + "sha1" : "99d9acac1ff93ac3d52229edec910091af1bc40a", + "md5" : "6cb7520b65d820b3b35773a8daa8368e", + "sha256" : "b19dcd275f72a0cbdead1e5abacb0ef25a0cb55ff36252ef44b1178eeedf9c30" }, + "originalChecksums" : { + "sha256" : "b19dcd275f72a0cbdead1e5abacb0ef25a0cb55ff36252ef44b1178eeedf9c30" }, + "uri" : "https://ocm.sofware/repository/ocm-mvn-test/open-component-model/hello-ocm/0.0.2/hello-ocm-0.0.2.jar" }` + var body maven.Body + err := json.Unmarshal([]byte(resp), &body) + Expect(err).To(BeNil()) + Expect(body.Repo).To(Equal("ocm-mvn-test")) + Expect(body.DownloadUri).To(Equal("https://ocm.sofware/repository/ocm-mvn-test/open-component-model/hello-ocm/0.0.2/hello-ocm-0.0.2.jar")) + Expect(body.Uri).To(Equal("https://ocm.sofware/repository/ocm-mvn-test/open-component-model/hello-ocm/0.0.2/hello-ocm-0.0.2.jar")) + Expect(body.MimeType).To(Equal("application/java-archive")) + Expect(body.Size).To(Equal("1792")) + Expect(body.Checksums["md5"]).To(Equal("6cb7520b65d820b3b35773a8daa8368e")) + Expect(body.Checksums["sha1"]).To(Equal("99d9acac1ff93ac3d52229edec910091af1bc40a")) + Expect(body.Checksums["sha256"]).To(Equal("b19dcd275f72a0cbdead1e5abacb0ef25a0cb55ff36252ef44b1178eeedf9c30")) + Expect(body.Checksums["sha512"]).To(Equal("")) + }) + + It("Upload artifact to file system", func() { + env.OCMContext().BlobHandlers().Register(me.NewArtifactHandler(me.NewFileConfig("target", env.FileSystem()))) + coords := maven.NewCoordinates("com.sap.cloud.sdk", "sdk-modules-bom", "5.7.0") + bacc := Must(mavenblob.BlobAccessForMavenCoords(repo, coords, mavenblob.WithCachingFileSystem(env.FileSystem()))) + defer Close(bacc) + ocmrepo := composition.NewRepository(env) + defer Close(ocmrepo) + cv := composition.NewComponentVersion(env, "acme.org/test", "1.0.0") + MustBeSuccessful(cv.SetResourceBlob(Must(elements.ResourceMeta("test", resourcetypes.MAVEN_ARTIFACT)), bacc, coords.GAV(), nil)) + MustBeSuccessful(ocmrepo.AddComponentVersion(cv)) + l := sliceutils.Transform(Must(vfs.ReadDir(env.FileSystem(), "target/com/sap/cloud/sdk/sdk-modules-bom/5.7.0")), + func(info os.FileInfo) string { + return info.Name() + }) + Expect(l).To(ConsistOf( + "sdk-modules-bom-5.7.0-random-content.json", + "sdk-modules-bom-5.7.0-random-content.json.md5", + "sdk-modules-bom-5.7.0-random-content.json.sha1", + "sdk-modules-bom-5.7.0-random-content.json.sha256", + "sdk-modules-bom-5.7.0-random-content.txt", + "sdk-modules-bom-5.7.0-random-content.txt.md5", + "sdk-modules-bom-5.7.0-random-content.txt.sha1", + "sdk-modules-bom-5.7.0-random-content.txt.sha256", + "sdk-modules-bom-5.7.0-sources.jar", + "sdk-modules-bom-5.7.0-sources.jar.md5", + "sdk-modules-bom-5.7.0-sources.jar.sha1", + "sdk-modules-bom-5.7.0-sources.jar.sha256", + "sdk-modules-bom-5.7.0.jar", + "sdk-modules-bom-5.7.0.jar.md5", + "sdk-modules-bom-5.7.0.jar.sha1", + "sdk-modules-bom-5.7.0.jar.sha256", + "sdk-modules-bom-5.7.0.pom", + "sdk-modules-bom-5.7.0.pom.md5", + "sdk-modules-bom-5.7.0.pom.sha1", + "sdk-modules-bom-5.7.0.pom.sha256")) + }) + +}) diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/mvn/registration.go b/pkg/contexts/ocm/blobhandler/handlers/generic/maven/registration.go similarity index 51% rename from pkg/contexts/ocm/blobhandler/handlers/generic/mvn/registration.go rename to pkg/contexts/ocm/blobhandler/handlers/generic/maven/registration.go index be1436b455..f6024503c2 100644 --- a/pkg/contexts/ocm/blobhandler/handlers/generic/mvn/registration.go +++ b/pkg/contexts/ocm/blobhandler/handlers/generic/maven/registration.go @@ -1,23 +1,61 @@ -package mvn +package maven import ( "encoding/json" "fmt" "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/general" + "github.com/mandelsoft/vfs/pkg/vfs" + "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/vfsattr" "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" + "github.com/open-component-model/ocm/pkg/maven" "github.com/open-component-model/ocm/pkg/mime" "github.com/open-component-model/ocm/pkg/registrations" + "github.com/open-component-model/ocm/pkg/utils" ) +func init() { + cpi.RegisterBlobHandlerRegistrationHandler(BlobHandlerName, &RegistrationHandler{}) +} + type Config struct { - Url string `json:"url"` + Url string `json:"url"` + Path string `json:"path"` + FileSystem vfs.FileSystem `json:"-"` +} + +func NewFileConfig(path string, fss ...vfs.FileSystem) *Config { + return &Config{ + Path: path, + FileSystem: utils.FileSystem(fss...), + } +} + +func NewUrlConfig(url string, fss ...vfs.FileSystem) *Config { + return &Config{ + Url: url, + FileSystem: utils.FileSystem(fss...), + } } type rawConfig Config +func (c *Config) GetRepository(ctx cpi.ContextProvider) (*maven.Repository, error) { + if c.Url != "" && c.Path != "" { + return nil, fmt.Errorf("cannot specify both url and path") + } + if c.Url != "" { + return maven.NewUrlRepository(c.Url, general.OptionalDefaulted(vfsattr.Get(ctx.OCMContext()), c.FileSystem)) + } + if c.Path != "" { + return maven.NewFileRepository(c.Path, general.OptionalDefaulted(vfsattr.Get(ctx.OCMContext()), c.FileSystem)), nil + } + return nil, fmt.Errorf("must specify either url or path") +} + func (c *Config) UnmarshalJSON(data []byte) error { err := json.Unmarshal(data, &c.Url) if err == nil { @@ -33,20 +71,16 @@ func (c *Config) UnmarshalJSON(data []byte) error { return nil } -func init() { - cpi.RegisterBlobHandlerRegistrationHandler(BlobHandlerName, &RegistrationHandler{}) -} - type RegistrationHandler struct{} var _ cpi.BlobHandlerRegistrationHandler = (*RegistrationHandler)(nil) func (r *RegistrationHandler) RegisterByName(handler string, ctx cpi.Context, config cpi.BlobHandlerConfig, olist ...cpi.BlobHandlerOption) (bool, error) { if handler != "" { - return true, fmt.Errorf("invalid %s handler %q", resourcetypes.MVN_ARTIFACT, handler) + return true, fmt.Errorf("invalid %s handler %q", resourcetypes.MAVEN_ARTIFACT, handler) } if config == nil { - return true, fmt.Errorf("mvn target specification required") + return true, fmt.Errorf("maven target specification required") } cfg, err := registrations.DecodeConfig[Config](config) if err != nil { @@ -54,7 +88,7 @@ func (r *RegistrationHandler) RegisterByName(handler string, ctx cpi.Context, co } ctx.BlobHandlers().Register(NewArtifactHandler(cfg), - cpi.ForArtifactType(resourcetypes.MVN_ARTIFACT), + cpi.ForArtifactType(resourcetypes.MAVEN_ARTIFACT), cpi.ForMimeType(mime.MIME_TGZ), cpi.NewBlobHandlerOptions(olist...), ) @@ -63,13 +97,13 @@ func (r *RegistrationHandler) RegisterByName(handler string, ctx cpi.Context, co } func (r *RegistrationHandler) GetHandlers(_ cpi.Context) registrations.HandlerInfos { - return registrations.NewLeafHandlerInfo("uploading mvn artifacts", ` -The `+BlobHandlerName+` uploader is able to upload mvn artifacts (whole GAV only!) -as artifact archive according to the mvn artifact spec. + return registrations.NewLeafHandlerInfo("uploading maven artifacts", ` +The `+BlobHandlerName+` uploader is able to upload maven artifacts (whole GAV only!) +as artifact archive according to the maven artifact spec. If registered the default mime type is: `+mime.MIME_TGZ+` It accepts a plain string for the URL or a config with the following field: -'url': the URL of the mvn repository. +'url': the URL of the maven repository. `, ) } diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/mvn/registration_test.go b/pkg/contexts/ocm/blobhandler/handlers/generic/maven/registration_test.go similarity index 58% rename from pkg/contexts/ocm/blobhandler/handlers/generic/mvn/registration_test.go rename to pkg/contexts/ocm/blobhandler/handlers/generic/maven/registration_test.go index c64b3e13bb..e64514fbc6 100644 --- a/pkg/contexts/ocm/blobhandler/handlers/generic/mvn/registration_test.go +++ b/pkg/contexts/ocm/blobhandler/handlers/generic/maven/registration_test.go @@ -1,23 +1,23 @@ -package mvn_test +package maven_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/open-component-model/ocm/pkg/testutils" - "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/generic/mvn" + "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/generic/maven" "github.com/open-component-model/ocm/pkg/registrations" ) var _ = Describe("Config deserialization Test Environment", func() { It("deserializes string", func() { - cfg := Must(registrations.DecodeConfig[mvn.Config]("test")) - Expect(cfg).To(Equal(&mvn.Config{Url: "test"})) + cfg := Must(registrations.DecodeConfig[maven.Config]("test")) + Expect(cfg).To(Equal(&maven.Config{Url: "test"})) }) It("deserializes struct", func() { - cfg := Must(registrations.DecodeConfig[mvn.Config](`{"Url":"test"}`)) - Expect(cfg).To(Equal(&mvn.Config{Url: "test"})) + cfg := Must(registrations.DecodeConfig[maven.Config](`{"url":"test"}`)) + Expect(cfg).To(Equal(&maven.Config{Url: "test"})) }) }) diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/maven/suite_test.go b/pkg/contexts/ocm/blobhandler/handlers/generic/maven/suite_test.go new file mode 100644 index 0000000000..d43b2dbd6b --- /dev/null +++ b/pkg/contexts/ocm/blobhandler/handlers/generic/maven/suite_test.go @@ -0,0 +1,13 @@ +package maven_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Maven Test Suite") +} diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/mvn/blobhandler.go b/pkg/contexts/ocm/blobhandler/handlers/generic/mvn/blobhandler.go deleted file mode 100644 index 0a11fcc96f..0000000000 --- a/pkg/contexts/ocm/blobhandler/handlers/generic/mvn/blobhandler.go +++ /dev/null @@ -1,180 +0,0 @@ -package mvn - -import ( - "context" - "crypto" - "encoding/json" - "fmt" - "io" - "net/http" - "strings" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/mvn/identity" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/mvn" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/iotools" - "github.com/open-component-model/ocm/pkg/logging" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/utils/tarutils" -) - -const BlobHandlerName = "ocm/" + resourcetypes.MVN_ARTIFACT - -type artifactHandler struct { - spec *Config -} - -func NewArtifactHandler(repospec *Config) cpi.BlobHandler { - return &artifactHandler{repospec} -} - -var log = logging.DynamicLogger(identity.REALM) - -func (b *artifactHandler) StoreBlob(blob cpi.BlobAccess, resourceType string, hint string, _ cpi.AccessSpec, ctx cpi.StorageContext) (cpi.AccessSpec, error) { - // check conditions - if b.spec == nil { - return nil, nil - } - mimeType := blob.MimeType() - if resourcetypes.MVN_ARTIFACT != resourceType { - log.Debug("not a MVN artifact", "resourceType", resourceType) - return nil, nil - } - if mime.MIME_TGZ != mimeType { - log.Debug("not a tarball, can't be a complete mvn GAV", "mimeType", mimeType) - return nil, nil - } - if b.spec.Url == "" { - return nil, errors.New("MVN repository url not provided") - } - - // setup logger - log := log.WithValues("repository", b.spec.Url) - // identify artifact - artifact, err := mvn.Parse(hint) - if err != nil { - return nil, err - } - log = log.WithValues("groupId", artifact.GroupId, "artifactId", artifact.ArtifactId, "version", artifact.Version) - log.Debug("identified") - - blobReader, err := blob.Reader() - if err != nil { - return nil, err - } - defer blobReader.Close() - tempFs, err := tarutils.ExtractTgzToTempFs(blobReader) - if err != nil { - return nil, err - } - defer vfs.Cleanup(tempFs) - files, err := tarutils.ListSortedFilesInDir(tempFs, "", false) - if err != nil { - return nil, err - } - for _, file := range files { - e := func() (err error) { - log.Debug("uploading", "file", file) - err = artifact.SetClassifierExtensionBy(file) - if err != nil { - return - } - readHash, err := tempFs.Open(file) - if err != nil { - return - } - defer readHash.Close() - // MD5 + SHA1 are still the most used ones in the mvn context - hr := iotools.NewHashReader(readHash, crypto.SHA256, crypto.SHA1, crypto.MD5) - _, err = hr.CalcHashes() - if err != nil { - return - } - reader, err := tempFs.Open(file) - if err != nil { - return - } - defer reader.Close() - err = deploy(artifact, b.spec.Url, reader, ctx.GetContext(), hr) - return - }() - if e != nil { - return nil, e - } - } - - log.Debug("done", "artifact", artifact) - return mvn.New(b.spec.Url, artifact.GroupId, artifact.ArtifactId, artifact.Version, mvn.WithClassifier(artifact.Classifier), mvn.WithExtension(artifact.Extension)), nil -} - -// deploy an artifact to the specified destination. See https://jfrog.com/help/r/jfrog-rest-apis/deploy-artifact -func deploy(artifact *mvn.Coordinates, url string, reader io.ReadCloser, ctx accspeccpi.Context, hashes *iotools.HashReader) (err error) { - req, err := http.NewRequestWithContext(context.Background(), http.MethodPut, artifact.Url(url), reader) - if err != nil { - return - } - err = identity.BasicAuth(req, ctx, url, artifact.GroupPath()) - if err != nil { - return - } - // give the remote server a chance to decide based upon the checksum policy - for k, v := range hashes.HttpHeader() { - req.Header.Set(k, v) - } - - // Execute the request - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return - } - defer resp.Body.Close() - - // Check the response - if resp.StatusCode != http.StatusCreated { - all, e := io.ReadAll(resp.Body) - if e != nil { - return e - } - return fmt.Errorf("http (%d) - failed to upload artifact: %s", resp.StatusCode, string(all)) - } - log.Debug("uploaded", "artifact", artifact, "extension", artifact.Extension, "classifier", artifact.Classifier) - - // Validate the response - especially the hash values with the ones we've tried to send - respBody, err := io.ReadAll(resp.Body) - if err != nil { - return - } - var artifactBody Body - err = json.Unmarshal(respBody, &artifactBody) - if err != nil { - return - } - - // let's check only SHA256 for now - digest := hashes.GetString(crypto.SHA256) - remoteDigest := artifactBody.Checksums[strings.ReplaceAll(strings.ToLower(crypto.SHA256.String()), "-", "")] - if remoteDigest == "" { - log.Warn("no checksum found for algorithm, we can't guarantee that the artifact has been uploaded correctly", "algorithm", crypto.SHA256) - } else if remoteDigest != digest { - return errors.New("failed to upload artifact: checksums do not match") - } - log.Debug("digests are ok", "remoteDigest", remoteDigest, "digest", digest) - return -} - -// Body is the response struct of a deployment from the MVN repository (JFrog Artifactory). -type Body struct { - Repo string `json:"repo"` - Path string `json:"path"` - DownloadUri string `json:"downloadUri"` - Uri string `json:"uri"` - MimeType string `json:"mimeType"` - Size string `json:"size"` - Checksums map[string]string `json:"checksums"` -} diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/mvn/blobhandler_test.go b/pkg/contexts/ocm/blobhandler/handlers/generic/mvn/blobhandler_test.go deleted file mode 100644 index a632ace444..0000000000 --- a/pkg/contexts/ocm/blobhandler/handlers/generic/mvn/blobhandler_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package mvn_test - -import ( - "encoding/json" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/generic/mvn" -) - -var _ = Describe("blobhandler generic mvn tests", func() { - - It("Unmarshal deploy response Body", func() { - resp := `{ "repo" : "ocm-mvn-test", - "path" : "/open-component-model/hello-ocm/0.0.2/hello-ocm-0.0.2.jar", - "created" : "2024-04-11T15:09:28.920Z", - "createdBy" : "john.doe", - "downloadUri" : "https://ocm.sofware/repository/ocm-mvn-test/open-component-model/hello-ocm/0.0.2/hello-ocm-0.0.2.jar", - "mimeType" : "application/java-archive", - "size" : "1792", - "checksums" : { - "sha1" : "99d9acac1ff93ac3d52229edec910091af1bc40a", - "md5" : "6cb7520b65d820b3b35773a8daa8368e", - "sha256" : "b19dcd275f72a0cbdead1e5abacb0ef25a0cb55ff36252ef44b1178eeedf9c30" }, - "originalChecksums" : { - "sha256" : "b19dcd275f72a0cbdead1e5abacb0ef25a0cb55ff36252ef44b1178eeedf9c30" }, - "uri" : "https://ocm.sofware/repository/ocm-mvn-test/open-component-model/hello-ocm/0.0.2/hello-ocm-0.0.2.jar" }` - var body mvn.Body - err := json.Unmarshal([]byte(resp), &body) - Expect(err).To(BeNil()) - Expect(body.Repo).To(Equal("ocm-mvn-test")) - Expect(body.DownloadUri).To(Equal("https://ocm.sofware/repository/ocm-mvn-test/open-component-model/hello-ocm/0.0.2/hello-ocm-0.0.2.jar")) - Expect(body.Uri).To(Equal("https://ocm.sofware/repository/ocm-mvn-test/open-component-model/hello-ocm/0.0.2/hello-ocm-0.0.2.jar")) - Expect(body.MimeType).To(Equal("application/java-archive")) - Expect(body.Size).To(Equal("1792")) - Expect(body.Checksums["md5"]).To(Equal("6cb7520b65d820b3b35773a8daa8368e")) - Expect(body.Checksums["sha1"]).To(Equal("99d9acac1ff93ac3d52229edec910091af1bc40a")) - Expect(body.Checksums["sha256"]).To(Equal("b19dcd275f72a0cbdead1e5abacb0ef25a0cb55ff36252ef44b1178eeedf9c30")) - Expect(body.Checksums["sha512"]).To(Equal("")) - }) - -}) diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/npm/registration_test.go b/pkg/contexts/ocm/blobhandler/handlers/generic/npm/registration_test.go index 89aae1d6e9..e254dd1ede 100644 --- a/pkg/contexts/ocm/blobhandler/handlers/generic/npm/registration_test.go +++ b/pkg/contexts/ocm/blobhandler/handlers/generic/npm/registration_test.go @@ -17,7 +17,7 @@ var _ = Describe("Config deserialization Test Environment", func() { }) It("deserializes struct", func() { - cfg := Must(registrations.DecodeConfig[npm.Config](`{"Url":"test"}`)) + cfg := Must(registrations.DecodeConfig[npm.Config](`{"url":"test"}`)) Expect(cfg).To(Equal(&npm.Config{Url: "test"})) }) }) diff --git a/pkg/contexts/ocm/blobhandler/handlers/init.go b/pkg/contexts/ocm/blobhandler/handlers/init.go index b318fe2c9e..3fd4df2811 100644 --- a/pkg/contexts/ocm/blobhandler/handlers/init.go +++ b/pkg/contexts/ocm/blobhandler/handlers/init.go @@ -1,7 +1,7 @@ package handlers import ( - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/generic/mvn" + _ "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/generic/maven" _ "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/generic/npm" _ "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/generic/ocirepo" _ "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/oci/ocirepo" diff --git a/pkg/contexts/ocm/cpi/builder.go b/pkg/contexts/ocm/cpi/builder.go new file mode 100644 index 0000000000..ea045d4983 --- /dev/null +++ b/pkg/contexts/ocm/cpi/builder.go @@ -0,0 +1,50 @@ +package cpi + +import ( + "context" + + "github.com/open-component-model/ocm/pkg/contexts/credentials" + "github.com/open-component-model/ocm/pkg/contexts/datacontext" + "github.com/open-component-model/ocm/pkg/contexts/oci" + "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" +) + +func WithContext(ctx context.Context) internal.Builder { + return internal.Builder{}.WithContext(ctx) +} + +func WithCredentials(ctx credentials.Context) internal.Builder { + return internal.Builder{}.WithCredentials(ctx) +} + +func WithOCIRepositories(ctx oci.Context) internal.Builder { + return internal.Builder{}.WithOCIRepositories(ctx) +} + +func WithRepositoyTypeScheme(scheme RepositoryTypeScheme) internal.Builder { + return internal.Builder{}.WithRepositoyTypeScheme(scheme) +} + +func WithRepositoryDelegation(reg RepositoryDelegationRegistry) internal.Builder { + return internal.Builder{}.WithRepositoryDelegation(reg) +} + +func WithAccessypeScheme(scheme AccessTypeScheme) internal.Builder { + return internal.Builder{}.WithAccessTypeScheme(scheme) +} + +func WithRepositorySpecHandlers(reg RepositorySpecHandlers) internal.Builder { + return internal.Builder{}.WithRepositorySpecHandlers(reg) +} + +func WithBlobHandlers(reg BlobHandlerRegistry) internal.Builder { + return internal.Builder{}.WithBlobHandlers(reg) +} + +func WithBlobDigesters(reg BlobDigesterRegistry) internal.Builder { + return internal.Builder{}.WithBlobDigesters(reg) +} + +func New(mode ...datacontext.BuilderMode) Context { + return internal.Builder{}.New(mode...) +} diff --git a/pkg/contexts/ocm/cpi/interface.go b/pkg/contexts/ocm/cpi/interface.go index 1e62109d1b..8b605836ea 100644 --- a/pkg/contexts/ocm/cpi/interface.go +++ b/pkg/contexts/ocm/cpi/interface.go @@ -110,10 +110,6 @@ func DefaultBlobHandlerProvider(ctx Context) BlobHandlerProvider { return internal.DefaultBlobHandlerProvider(ctx) } -func New() Context { - return internal.Builder{}.New() -} - func NewResourceMeta(name string, typ string, relation metav1.ResourceRelation) *ResourceMeta { return compdesc.NewResourceMeta(name, typ, relation) } diff --git a/pkg/contexts/ocm/elements/artifactaccess/mavenaccess/options.go b/pkg/contexts/ocm/elements/artifactaccess/mavenaccess/options.go new file mode 100644 index 0000000000..8a965b6958 --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactaccess/mavenaccess/options.go @@ -0,0 +1,20 @@ +package mavenaccess + +import "github.com/open-component-model/ocm/pkg/maven" + +type ( + Options = maven.Coordinates + Option = maven.CoordinateOption +) + +type WithClassifier = maven.WithClassifier + +func WithOptionalClassifier(c *string) Option { + return maven.WithOptionalClassifier(c) +} + +type WithExtension = maven.WithExtension + +func WithOptionalExtension(e *string) Option { + return maven.WithOptionalExtension(e) +} diff --git a/pkg/contexts/ocm/elements/artifactaccess/mavenaccess/resource.go b/pkg/contexts/ocm/elements/artifactaccess/mavenaccess/resource.go new file mode 100644 index 0000000000..d2a1d2b21e --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactaccess/mavenaccess/resource.go @@ -0,0 +1,39 @@ +package mavenaccess + +import ( + "github.com/open-component-model/ocm/pkg/contexts/ocm" + access "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/maven" + "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" + "github.com/open-component-model/ocm/pkg/contexts/ocm/elements/artifactaccess/genericaccess" + "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" + "github.com/open-component-model/ocm/pkg/maven" +) + +const TYPE = resourcetypes.MAVEN_ARTIFACT + +func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, repoUrl, groupId, artifactId, version string, opts ...Option) cpi.ArtifactAccess[M] { + if meta.GetType() == "" { + meta.SetType(TYPE) + } + + spec := access.New(repoUrl, groupId, artifactId, version, opts...) + // is global access, must work, otherwise there is an error in the lib. + return genericaccess.MustAccess(ctx, meta, spec) +} + +func ResourceAccess(ctx ocm.Context, meta *cpi.ResourceMeta, repoUrl, groupId, artifactId, version string, opts ...Option) cpi.ResourceAccess { + return Access(ctx, meta, repoUrl, groupId, artifactId, version, opts...) +} + +func ResourceAccessForMavenCoords(ctx ocm.Context, meta *cpi.ResourceMeta, repoUrl string, coords *maven.Coordinates) cpi.ResourceAccess { + return Access(ctx, meta, repoUrl, coords.GroupId, coords.ArtifactId, coords.Version, WithOptionalClassifier(coords.Classifier), WithOptionalExtension(coords.Extension)) +} + +func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, repoUrl, groupId, artifactId, version string, opts ...Option) cpi.SourceAccess { + return Access(ctx, meta, repoUrl, groupId, artifactId, version, opts...) +} + +func SourceAccessForMavenCoords(ctx ocm.Context, meta *cpi.SourceMeta, repoUrl string, coords *maven.Coordinates) cpi.SourceAccess { + return Access(ctx, meta, repoUrl, coords.GroupId, coords.ArtifactId, coords.Version, WithOptionalClassifier(coords.Classifier), WithOptionalExtension(coords.Extension)) +} diff --git a/pkg/contexts/ocm/elements/artifactaccess/mvnaccess/resource.go b/pkg/contexts/ocm/elements/artifactaccess/mvnaccess/resource.go deleted file mode 100644 index 6c90b396a9..0000000000 --- a/pkg/contexts/ocm/elements/artifactaccess/mvnaccess/resource.go +++ /dev/null @@ -1,30 +0,0 @@ -package mvnaccess - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm" - access "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/mvn" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/elements/artifactaccess/genericaccess" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" -) - -const TYPE = resourcetypes.MVN_ARTIFACT - -func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, repository, groupId, artifactId, version string) cpi.ArtifactAccess[M] { - if meta.GetType() == "" { - meta.SetType(TYPE) - } - - spec := access.New(repository, groupId, artifactId, version) - // is global access, must work, otherwise there is an error in the lib. - return genericaccess.MustAccess(ctx, meta, spec) -} - -func ResourceAccess(ctx ocm.Context, meta *cpi.ResourceMeta, repository, groupId, artifactId, version string) cpi.ResourceAccess { - return Access(ctx, meta, repository, groupId, artifactId, version) -} - -func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, repository, groupId, artifactId, version string) cpi.SourceAccess { - return Access(ctx, meta, repository, groupId, artifactId, version) -} diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/helmblob_test.go b/pkg/contexts/ocm/elements/artifactblob/helmblob/helmblob_test.go new file mode 100644 index 0000000000..1889da3ee4 --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/helmblob/helmblob_test.go @@ -0,0 +1,37 @@ +package helmblob_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/open-component-model/ocm/pkg/env/builder" + . "github.com/open-component-model/ocm/pkg/testutils" + + "github.com/open-component-model/ocm/pkg/common/accessobj" + metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" + me "github.com/open-component-model/ocm/pkg/contexts/ocm/elements/artifactblob/helmblob" + ctfocm "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" + "github.com/open-component-model/ocm/pkg/env" +) + +var _ = Describe("", func() { + var e *Builder + + BeforeEach(func() { + e = NewBuilder(env.TestData()) + + }) + + AfterEach(func() { + MustBeSuccessful(e.Cleanup()) + }) + + It("", func() { + ctf := Must(ctfocm.Open(e, accessobj.ACC_CREATE, "/repo", 0o700, e, ctfocm.FormatDirectory)) + defer Close(ctf) + cv := Must(ctf.NewComponentVersion("ocm.software/test-component", "1.0.0")) + defer Close(cv) + MustBeSuccessful(cv.SetResourceAccess(me.ResourceAccess(e.OCMContext(), cpi.NewResourceMeta("helm1", "blob", metav1.LocalRelation), "/testdata/testchart1", me.WithFileSystem(e.FileSystem())))) + MustBeSuccessful(cv.SetResourceAccess(me.ResourceAccess(e.OCMContext(), cpi.NewResourceMeta("helm2", "blob", metav1.LocalRelation), "/testdata/testchart2", me.WithFileSystem(e.FileSystem())))) + MustBeSuccessful(ctf.AddComponentVersion(cv, true)) + }) +}) diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/suite_test.go b/pkg/contexts/ocm/elements/artifactblob/helmblob/suite_test.go new file mode 100644 index 0000000000..451b4150bc --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/helmblob/suite_test.go @@ -0,0 +1,13 @@ +package helmblob_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Helm Blob Test Suite") +} diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/.helmignore b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/.helmignore new file mode 100644 index 0000000000..0e8a0eb36f --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/.idea/somefile b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/.idea/somefile new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/Chart.yaml b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/Chart.yaml new file mode 100644 index 0000000000..01328a81ce --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: testchart1 +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" diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/NOTES.txt b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/NOTES.txt new file mode 100644 index 0000000000..45e51670a8 --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "testchart.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.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 "testchart.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "testchart.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "testchart.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 }} diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/_helpers.tpl b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/_helpers.tpl new file mode 100644 index 0000000000..4b0db05bf5 --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "testchart.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 "testchart.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 "testchart.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "testchart.labels" -}} +helm.sh/chart: {{ include "testchart.chart" . }} +{{ include "testchart.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "testchart.selectorLabels" -}} +app.kubernetes.io/name: {{ include "testchart.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "testchart.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "testchart.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/deployment.yaml b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/deployment.yaml new file mode 100644 index 0000000000..69b8e08feb --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/deployment.yaml @@ -0,0 +1,61 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "testchart.fullname" . }} + labels: + {{- include "testchart.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "testchart.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "testchart.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "testchart.serviceAccountName" . }} + 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: http + containerPort: 8080 + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + readinessProbe: + httpGet: + path: / + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- 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/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/hpa.yaml b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/hpa.yaml new file mode 100644 index 0000000000..51734471d4 --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/hpa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "testchart.fullname" . }} + labels: + {{- include "testchart.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "testchart.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/ingress.yaml b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/ingress.yaml new file mode 100644 index 0000000000..9732d2a24a --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "testchart.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "testchart.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/service.yaml b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/service.yaml new file mode 100644 index 0000000000..86baf14821 --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "testchart.fullname" . }} + labels: + {{- include "testchart.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "testchart.selectorLabels" . | nindent 4 }} diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/serviceaccount.yaml b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/serviceaccount.yaml new file mode 100644 index 0000000000..f728deb2a6 --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "testchart.serviceAccountName" . }} + labels: + {{- include "testchart.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/tests/test-connection.yaml b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/tests/test-connection.yaml new file mode 100644 index 0000000000..a391ef1c46 --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "testchart.fullname" . }}-test-connection" + labels: + {{- include "testchart.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "testchart.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/values.yaml b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/values.yaml new file mode 100644 index 0000000000..b598adba92 --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/values.yaml @@ -0,0 +1,81 @@ +# Default values for testchart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: gcr.io/google_containers/echoserver + pullPolicy: IfNotPresent + tag: "1.0" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +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: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 80 + +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +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: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/.helmignore b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/.helmignore new file mode 100644 index 0000000000..0e8a0eb36f --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/.idea/somefile b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/.idea/somefile new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/Chart.yaml b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/Chart.yaml new file mode 100644 index 0000000000..8f4b7146ce --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: testchart2 +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" diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/NOTES.txt b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/NOTES.txt new file mode 100644 index 0000000000..45e51670a8 --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "testchart.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.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 "testchart.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "testchart.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "testchart.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 }} diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/_helpers.tpl b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/_helpers.tpl new file mode 100644 index 0000000000..4b0db05bf5 --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "testchart.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 "testchart.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 "testchart.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "testchart.labels" -}} +helm.sh/chart: {{ include "testchart.chart" . }} +{{ include "testchart.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "testchart.selectorLabels" -}} +app.kubernetes.io/name: {{ include "testchart.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "testchart.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "testchart.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/deployment.yaml b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/deployment.yaml new file mode 100644 index 0000000000..69b8e08feb --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/deployment.yaml @@ -0,0 +1,61 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "testchart.fullname" . }} + labels: + {{- include "testchart.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "testchart.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "testchart.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "testchart.serviceAccountName" . }} + 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: http + containerPort: 8080 + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + readinessProbe: + httpGet: + path: / + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- 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/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/hpa.yaml b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/hpa.yaml new file mode 100644 index 0000000000..51734471d4 --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/hpa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "testchart.fullname" . }} + labels: + {{- include "testchart.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "testchart.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/ingress.yaml b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/ingress.yaml new file mode 100644 index 0000000000..9732d2a24a --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "testchart.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "testchart.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/service.yaml b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/service.yaml new file mode 100644 index 0000000000..86baf14821 --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "testchart.fullname" . }} + labels: + {{- include "testchart.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "testchart.selectorLabels" . | nindent 4 }} diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/serviceaccount.yaml b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/serviceaccount.yaml new file mode 100644 index 0000000000..f728deb2a6 --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "testchart.serviceAccountName" . }} + labels: + {{- include "testchart.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/tests/test-connection.yaml b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/tests/test-connection.yaml new file mode 100644 index 0000000000..a391ef1c46 --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "testchart.fullname" . }}-test-connection" + labels: + {{- include "testchart.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "testchart.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/values.yaml b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/values.yaml new file mode 100644 index 0000000000..b598adba92 --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/values.yaml @@ -0,0 +1,81 @@ +# Default values for testchart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: gcr.io/google_containers/echoserver + pullPolicy: IfNotPresent + tag: "1.0" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +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: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 80 + +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +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: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/pkg/contexts/ocm/elements/artifactblob/mavenblob/access_test.go b/pkg/contexts/ocm/elements/artifactblob/mavenblob/access_test.go new file mode 100644 index 0000000000..8bf2b4fdd9 --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/mavenblob/access_test.go @@ -0,0 +1,61 @@ +package mavenblob_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/open-component-model/ocm/pkg/env/builder" + "github.com/open-component-model/ocm/pkg/maven/maventest" + . "github.com/open-component-model/ocm/pkg/testutils" + + "github.com/open-component-model/ocm/pkg/contexts/ocm/elements" + me "github.com/open-component-model/ocm/pkg/contexts/ocm/elements/artifactblob/mavenblob" + "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/composition" + "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" + "github.com/open-component-model/ocm/pkg/maven" +) + +const ( + MAVEN_PATH = "/testdata/.m2/repository" + FAIL_PATH = "/testdata/.m2/fail" + MAVEN_CENTRAL_ADDRESS = "repo.maven.apache.org:443" + MAVEN_CENTRAL = "https://repo.maven.apache.org/maven2/" + MAVEN_GROUP_ID = "maven" + MAVEN_ARTIFACT_ID = "maven" + MAVEN_VERSION = "1.1" +) + +var _ = Describe("blobaccess for maven", func() { + + Context("maven filesystem repository", func() { + var env *Builder + var repo *maven.Repository + + BeforeEach(func() { + env = NewBuilder(maventest.TestData()) + repo = maven.NewFileRepository(MAVEN_PATH, env.FileSystem()) + }) + + AfterEach(func() { + MustBeSuccessful(env.Cleanup()) + }) + + It("blobaccess for a single file with classifier and extension", func() { + cv := composition.NewComponentVersion(env.OCMContext(), "acme.org/test", "1.0.0") + defer Close(cv) + + coords := maven.NewCoordinates("com.sap.cloud.sdk", "sdk-modules-bom", "5.7.0", + maven.WithClassifier("random-content"), maven.WithExtension("json")) + + a := me.ResourceAccessForMavenCoords(env.OCMContext(), Must(elements.ResourceMeta("mavenblob", resourcetypes.OCM_JSON, elements.WithLocalRelation())), repo, coords, me.WithCachingFileSystem(env.FileSystem())) + b := Must(a.BlobAccess()) + defer Close(b) + Expect(string(Must(b.Get()))).To(Equal(`{"some": "test content"}`)) + + MustBeSuccessful(cv.SetResourceAccess(a)) + r := Must(cv.GetResourceByIndex(0)) + m := Must(r.AccessMethod()) + defer Close(m) + Expect(string(Must(m.Get()))).To(Equal(`{"some": "test content"}`)) + }) + }) +}) diff --git a/pkg/contexts/ocm/elements/artifactblob/mavenblob/options.go b/pkg/contexts/ocm/elements/artifactblob/mavenblob/options.go new file mode 100644 index 0000000000..6bc448da74 --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/mavenblob/options.go @@ -0,0 +1,96 @@ +package mavenblob + +import ( + "github.com/mandelsoft/goutils/optionutils" + "github.com/mandelsoft/logging" + "github.com/mandelsoft/vfs/pkg/vfs" + + base "github.com/open-component-model/ocm/pkg/blobaccess/maven" + "github.com/open-component-model/ocm/pkg/contexts/credentials" + "github.com/open-component-model/ocm/pkg/contexts/datacontext" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" + "github.com/open-component-model/ocm/pkg/contexts/ocm/elements/artifactblob/api" +) + +type Option = optionutils.Option[*Options] + +type Options struct { + api.Options + Blob base.Options +} + +var ( + _ api.GeneralOptionsProvider = (*Options)(nil) + _ Option = (*Options)(nil) +) + +func (o *Options) ApplyTo(opts *Options) { + o.Options.ApplyTo(&opts.Options) + o.Blob.ApplyTo(&opts.Blob) +} + +func (o *Options) Apply(opts ...Option) { + optionutils.ApplyOptions(o, opts...) +} + +//////////////////////////////////////////////////////////////////////////////// +// General Options + +func WithHint(h string) Option { + return api.WrapHint[Options](h) +} + +func WithGlobalAccess(a cpi.AccessSpec) Option { + return api.WrapGlobalAccess[Options](a) +} + +//////////////////////////////////////////////////////////////////////////////// +// Local Options + +func mapBaseOption(opts *Options) *base.Options { + return &opts.Blob +} + +func wrapBase(o base.Option) Option { + return optionutils.OptionWrapperFunc[*base.Options, *Options](o, mapBaseOption) +} + +func WithCredentialContext(credctx credentials.ContextProvider) Option { + return wrapBase(base.WithCredentialContext(credctx)) +} + +func WithLoggingContext(logctx logging.ContextProvider) Option { + return wrapBase(base.WithLoggingContext(logctx)) +} + +func WithCachingContext(cachectx datacontext.Context) Option { + return wrapBase(base.WithCachingContext(cachectx)) +} + +func WithCachingFileSystem(fs vfs.FileSystem) Option { + return wrapBase(base.WithCachingFileSystem(fs)) +} + +func WithCachingPath(p string) Option { + return wrapBase(base.WithCachingPath(p)) +} + +func WithCredentials(c credentials.Credentials) Option { + return wrapBase(base.WithCredentials(c)) +} + +func WithClassifier(c string) Option { + return wrapBase(base.WithClassifier(c)) +} + +func WithOptionalClassifier(c *string) Option { + return wrapBase(base.WithOptionalClassifier(c)) +} + +func WithExtension(e string) Option { + return wrapBase(base.WithExtension(e)) +} + +func WithOptionalExtension(e *string) Option { + return wrapBase(base.WithOptionalExtension(e)) +} diff --git a/pkg/contexts/ocm/elements/artifactblob/mavenblob/resource.go b/pkg/contexts/ocm/elements/artifactblob/mavenblob/resource.go new file mode 100644 index 0000000000..c977c55ad0 --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/mavenblob/resource.go @@ -0,0 +1,43 @@ +package mavenblob + +import ( + "github.com/mandelsoft/goutils/generics" + "github.com/mandelsoft/goutils/optionutils" + + "github.com/open-component-model/ocm/pkg/blobaccess/maven" + "github.com/open-component-model/ocm/pkg/contexts/ocm" + "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" + "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" +) + +const TYPE = resourcetypes.MAVEN_ARTIFACT + +func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, repo *maven.Repository, groupId, artifactId, version string, opts ...Option) cpi.ArtifactAccess[M] { + eff := optionutils.EvalOptions(optionutils.WithDefaults(opts, WithCredentialContext(ctx))...) + + if meta.GetType() == "" { + meta.SetType(TYPE) + } + + blobprov := maven.BlobAccessProviderForMaven(repo, groupId, artifactId, version, &eff.Blob) + accprov := cpi.NewAccessProviderForBlobAccessProvider(ctx, blobprov, eff.Hint, eff.Global) + // strange type cast is required by Go compiler, meta has the correct type. + return cpi.NewArtifactAccessForProvider(generics.Cast[*M](meta), accprov) +} + +func ResourceAccess(ctx ocm.Context, meta *ocm.ResourceMeta, repo *maven.Repository, groupId, artifactId, version string, opts ...Option) cpi.ResourceAccess { + return Access(ctx, meta, repo, groupId, artifactId, version, opts...) +} + +func ResourceAccessForMavenCoords(ctx ocm.Context, meta *ocm.ResourceMeta, repo *maven.Repository, coords *maven.Coordinates, opts ...Option) cpi.ResourceAccess { + return Access(ctx, meta, repo, coords.GroupId, coords.ArtifactId, coords.Version, optionutils.WithDefaults(opts, WithOptionalClassifier(coords.Classifier), WithOptionalExtension(coords.Extension))...) +} + +func SourceAccess(ctx ocm.Context, meta *ocm.SourceMeta, repo *maven.Repository, groupId, artifactId, version string, opts ...Option) cpi.SourceAccess { + return Access(ctx, meta, repo, groupId, artifactId, version, opts...) +} + +func SourceAccessForMavenCoords(ctx ocm.Context, meta *ocm.SourceMeta, repo *maven.Repository, coords *maven.Coordinates, opts ...Option) cpi.SourceAccess { + return Access(ctx, meta, repo, coords.GroupId, coords.ArtifactId, coords.Version, optionutils.WithDefaults(opts, WithOptionalClassifier(coords.Classifier), WithOptionalExtension(coords.Extension))...) +} diff --git a/pkg/contexts/ocm/elements/artifactblob/mavenblob/suite_test.go b/pkg/contexts/ocm/elements/artifactblob/mavenblob/suite_test.go new file mode 100644 index 0000000000..a083b2234c --- /dev/null +++ b/pkg/contexts/ocm/elements/artifactblob/mavenblob/suite_test.go @@ -0,0 +1,13 @@ +package mavenblob_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Maven Blob Access Test Suite") +} diff --git a/pkg/contexts/ocm/elements/artifactblob/textblob/options.go b/pkg/contexts/ocm/elements/artifactblob/textblob/options.go index 46e99a0035..ab1d01c966 100644 --- a/pkg/contexts/ocm/elements/artifactblob/textblob/options.go +++ b/pkg/contexts/ocm/elements/artifactblob/textblob/options.go @@ -30,7 +30,7 @@ func WithGlobalAccess(a cpi.AccessSpec) Option { //////////////////////////////////////////////////////////////////////////////// // Local Options -func WithimeType(mime string) Option { +func WithMimeType(mime string) Option { return datablob.WithMimeType(mime) } diff --git a/pkg/contexts/ocm/elements/artifactblob/wgetblob/resource.go b/pkg/contexts/ocm/elements/artifactblob/wgetblob/resource.go index 3045c59bef..ef9273b23a 100644 --- a/pkg/contexts/ocm/elements/artifactblob/wgetblob/resource.go +++ b/pkg/contexts/ocm/elements/artifactblob/wgetblob/resource.go @@ -14,7 +14,7 @@ import ( const TYPE = resourcetypes.BLOB func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, url string, opts ...Option) cpi.ArtifactAccess[M] { - eff := optionutils.EvalOptions(append([]Option{WithCredentialContext(ctx), WithLoggingContext(ctx)}, opts...)...) + eff := optionutils.EvalOptions(optionutils.WithDefaults(opts, WithCredentialContext(ctx))...) if meta.GetType() == "" { meta.SetType(TYPE) diff --git a/pkg/contexts/ocm/gc_test.go b/pkg/contexts/ocm/gc_test.go index 2896ae9406..a4ad5f81a7 100644 --- a/pkg/contexts/ocm/gc_test.go +++ b/pkg/contexts/ocm/gc_test.go @@ -6,9 +6,9 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/runtimefinalizer" me "github.com/open-component-model/ocm/pkg/contexts/ocm" + "github.com/open-component-model/ocm/pkg/runtimefinalizer" ) var _ = Describe("area test", func() { diff --git a/pkg/contexts/ocm/plugin/cache/updater.go b/pkg/contexts/ocm/plugin/cache/updater.go index 0f28269d02..cde2925a3a 100644 --- a/pkg/contexts/ocm/plugin/cache/updater.go +++ b/pkg/contexts/ocm/plugin/cache/updater.go @@ -194,7 +194,7 @@ func (o *PluginUpdater) download(session ocm.Session, cv ocm.ComponentVersionAcc break } wrong = r - } else { //nolint: gocritic // yes + } else { if name != "" { wrong = r } diff --git a/pkg/contexts/ocm/repositories/genericocireg/repo_test.go b/pkg/contexts/ocm/repositories/genericocireg/repo_test.go index 29347bb13c..206e0d8464 100644 --- a/pkg/contexts/ocm/repositories/genericocireg/repo_test.go +++ b/pkg/contexts/ocm/repositories/genericocireg/repo_test.go @@ -9,12 +9,11 @@ import ( . "github.com/onsi/gomega" . "github.com/open-component-model/ocm/pkg/testutils" + "github.com/mandelsoft/goutils/finalizer" "github.com/mandelsoft/vfs/pkg/osfs" "github.com/mandelsoft/vfs/pkg/vfs" "github.com/opencontainers/go-digest" - "github.com/mandelsoft/goutils/finalizer" - "github.com/open-component-model/ocm/pkg/blobaccess" "github.com/open-component-model/ocm/pkg/common/accessio" "github.com/open-component-model/ocm/pkg/common/accessobj" diff --git a/pkg/contexts/ocm/resourcetypes/const.go b/pkg/contexts/ocm/resourcetypes/const.go index 6c0dc78650..40ee9453e8 100644 --- a/pkg/contexts/ocm/resourcetypes/const.go +++ b/pkg/contexts/ocm/resourcetypes/const.go @@ -16,8 +16,8 @@ const ( HELM_CHART = "helmChart" // NPM_PACKAGE describes a Node.js (npm) package. NPM_PACKAGE = "npmPackage" - // MVN_ARTIFACT describes a Maven artifact (jar). - MVN_ARTIFACT = "mvnArtifact" + // MAVEN_ARTIFACT describes a Maven artifact (jar). + MAVEN_ARTIFACT = "mavenArtifact" // BLUEPRINT describes a Gardener Landscaper blueprint which is an artifact used in its installations describing // how to deploy a software component. BLUEPRINT = "landscaper.gardener.cloud/blueprint" diff --git a/pkg/contexts/ocm/transfer/autohandler_test.go b/pkg/contexts/ocm/transfer/autohandler_test.go index 2370e26828..92bdcea68c 100644 --- a/pkg/contexts/ocm/transfer/autohandler_test.go +++ b/pkg/contexts/ocm/transfer/autohandler_test.go @@ -6,6 +6,7 @@ import ( . "github.com/open-component-model/ocm/pkg/testutils" "github.com/mandelsoft/goutils/errors" + "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer" "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler" "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/spiff" diff --git a/pkg/contexts/ocm/utils/localize/format.go b/pkg/contexts/ocm/utils/localize/format.go index 5f2a487d13..da1018ad3e 100644 --- a/pkg/contexts/ocm/utils/localize/format.go +++ b/pkg/contexts/ocm/utils/localize/format.go @@ -48,7 +48,7 @@ func (m *ImageMapping) Evaluate(idx int, cv ocm.ComponentVersionAccess, resolver name := "image mapping" if m.Name != "" { name = fmt.Sprintf("%s %q", name, m.Name) - } else { //nolint: gocritic // yes + } else { if idx >= 0 { name = fmt.Sprintf("%s %d", name, idx+1) } diff --git a/pkg/env/env.go b/pkg/env/env.go index ffbabb5de9..2f4da4b7e3 100644 --- a/pkg/env/env.go +++ b/pkg/env/env.go @@ -3,11 +3,14 @@ package env import ( "bytes" "fmt" + "runtime" "runtime/debug" "strings" "github.com/DataDog/gostackparse" + "github.com/mandelsoft/filepath/pkg/filepath" "github.com/mandelsoft/goutils/exception" + "github.com/mandelsoft/goutils/general" "github.com/mandelsoft/vfs/pkg/composefs" "github.com/mandelsoft/vfs/pkg/layerfs" "github.com/mandelsoft/vfs/pkg/memoryfs" @@ -21,7 +24,7 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/credentials" "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/vfsattr" "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/ocm" + ocm "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" "github.com/open-component-model/ocm/pkg/utils" ) @@ -199,7 +202,7 @@ type tdOpt struct { modifiable bool } -func TestData(paths ...string) tdOpt { +func testData(modifiable bool, paths ...string) tdOpt { path := "/testdata" source := "testdata" @@ -214,30 +217,85 @@ func TestData(paths ...string) tdOpt { panic("invalid number of arguments") } return tdOpt{ - path: path, - source: source, + path: path, + source: source, + modifiable: modifiable, } } +func TestData(paths ...string) tdOpt { + return testData(false, paths...) +} + func ModifiableTestData(paths ...string) tdOpt { - path := "/testdata" - source := "testdata" + return testData(true, paths...) +} + +func projectTestData(modifiable bool, source string, dest ...string) Option { + path := "." + for count := 0; count < 20; count++ { + if ok, err := vfs.FileExists(osfs.OsFs, filepath.Join(path, "go.mod")); err != nil || ok { + if err != nil { + panic(err) + } + path = filepath.Join(path, source) + break + } + if count == 19 { + panic("could not find go.mod (within 20 steps)") + } - switch len(paths) { - case 0: - case 1: - source = paths[0] - case 2: - source = paths[0] - path = paths[1] - default: - panic("invalid number of arguments") + path = filepath.Join(path, "..") } - return tdOpt{ - path: path, - source: source, - modifiable: true, + + return testData(modifiable, path, general.OptionalDefaulted("/testdata", dest...)) +} + +func ProjectTestData(source string, dest ...string) Option { + return projectTestData(false, source, dest...) +} + +func ModifiableProjectTestData(source string, dest ...string) Option { + return projectTestData(true, source, dest...) +} + +func projectTestDataForCaller(modifiable bool, dest ...string) Option { + pc, _, _, ok := runtime.Caller(2) + if !ok { + panic("unable to find caller") + } + + // Get the function details from the program counter + caller := runtime.FuncForPC(pc) + if caller == nil { + panic("unable to find caller") } + + fullFuncName := caller.Name() + + // Split the name to extract the package path + // Assuming the format: "package/path.functionName" + lastSlashIndex := strings.LastIndex(fullFuncName, "/") + if lastSlashIndex == -1 { + panic("unable to find package name") + } + + funcIndex := strings.Index(fullFuncName[lastSlashIndex:], ".") + packagePath := fullFuncName[:lastSlashIndex+funcIndex] + path, ok := strings.CutPrefix(packagePath, "github.com/open-component-model/ocm/") + if !ok { + panic("unable to find package name") + } + + return projectTestData(modifiable, filepath.Join(path, "testdata"), dest...) +} + +func ProjectTestDataForCaller(dest ...string) Option { + return projectTestDataForCaller(false, dest...) +} + +func ModifiableProjectTestDataForCaller(dest ...string) Option { + return projectTestDataForCaller(true, dest...) } func (o tdOpt) OptionHandler() OptionHandler { diff --git a/pkg/exception/exception_test.go b/pkg/exception/exception_test.go index 33e95ccda5..b6bdf5e9d0 100644 --- a/pkg/exception/exception_test.go +++ b/pkg/exception/exception_test.go @@ -7,6 +7,7 @@ import ( . "github.com/onsi/gomega" "github.com/mandelsoft/goutils/errors" + "github.com/open-component-model/ocm/pkg/exception" ) diff --git a/pkg/iotools/hashReaderWriter.go b/pkg/iotools/hashReaderWriter.go index b74863f7e7..f36d74b58d 100644 --- a/pkg/iotools/hashReaderWriter.go +++ b/pkg/iotools/hashReaderWriter.go @@ -5,42 +5,79 @@ import ( "fmt" "hash" "io" + "net/http" "strings" "github.com/mandelsoft/goutils/errors" ) +type Hashes map[crypto.Hash]hash.Hash + +func NewHashes(algorithms ...crypto.Hash) Hashes { + hashMap := make(Hashes, len(algorithms)) + for _, algorithm := range algorithms { + hashMap[algorithm] = algorithm.New() + } + return hashMap +} + +func (h Hashes) Write(c int, buf []byte) { + if c > 0 { + for _, hash := range h { + hash.Write(buf[:c]) + } + } +} + +func (h Hashes) AsHttpHeader() http.Header { + headers := make(http.Header, len(h)) + for algorithm := range h { + headers.Set(headerName(algorithm), h.GetString(algorithm)) + } + return headers +} + +func (h Hashes) GetBytes(algorithm crypto.Hash) []byte { + hash := h[algorithm] + if hash != nil { + return hash.Sum(nil) + } + return nil +} + +func (h Hashes) GetString(algorithm crypto.Hash) string { + return fmt.Sprintf("%x", h.GetBytes(algorithm)) +} + +func headerName(algorithm crypto.Hash) string { + a := strings.ReplaceAll(algorithm.String(), "-", "") + return "X-Checksum-" + a[:1] + strings.ToLower(a[1:]) +} + +//////////////////////////////////////////////////////////////////////////////// + type HashReader struct { reader io.Reader - hashMap map[crypto.Hash]hash.Hash + hashMap Hashes } func NewHashReader(delegate io.Reader, algorithms ...crypto.Hash) *HashReader { newInstance := HashReader{ reader: delegate, - hashMap: initMap(algorithms), + hashMap: NewHashes(algorithms...), } return &newInstance } func (h *HashReader) Read(buf []byte) (int, error) { c, err := h.reader.Read(buf) - return write(h, c, buf, err) -} - -func (h *HashReader) GetString(algorithm crypto.Hash) string { - return getString(h, algorithm) -} - -func (h *HashReader) GetBytes(algorithm crypto.Hash) []byte { - return getBytes(h, algorithm) -} - -func (h *HashReader) HttpHeader() map[string]string { - return httpHeader(h) + if err == nil { + h.hashMap.Write(c, buf) + } + return c, err } -func (h *HashReader) hashes() map[crypto.Hash]hash.Hash { +func (h *HashReader) Hashes() Hashes { return h.hashMap } @@ -69,84 +106,25 @@ func (h *HashReader) CalcHashes() (int64, error) { type HashWriter struct { writer io.Writer - hashMap map[crypto.Hash]hash.Hash + hashMap Hashes } func NewHashWriter(w io.Writer, algorithms ...crypto.Hash) *HashWriter { newInstance := HashWriter{ writer: w, - hashMap: initMap(algorithms), + hashMap: NewHashes(algorithms...), } return &newInstance } func (h *HashWriter) Write(buf []byte) (int, error) { c, err := h.writer.Write(buf) - return write(h, c, buf, err) -} - -func (h *HashWriter) GetString(algorithm crypto.Hash) string { - return getString(h, algorithm) -} - -func (h *HashWriter) GetBytes(algorithm crypto.Hash) []byte { - return getBytes(h, algorithm) -} - -func (h *HashWriter) HttpHeader() map[string]string { - return httpHeader(h) -} - -func (h *HashWriter) hashes() map[crypto.Hash]hash.Hash { - return h.hashMap -} - -//////////////////////////////////////////////////////////////////////////////// - -type hashes interface { - hashes() map[crypto.Hash]hash.Hash -} - -func getString(h hashes, algorithm crypto.Hash) string { - return fmt.Sprintf("%x", getBytes(h, algorithm)) -} - -func getBytes(h hashes, algorithm crypto.Hash) []byte { - hash := h.hashes()[algorithm] - if hash != nil { - return hash.Sum(nil) - } - return nil -} - -func httpHeader(h hashes) map[string]string { - headers := make(map[string]string, len(h.hashes())) - for algorithm := range h.hashes() { - headers[headerName(algorithm)] = getString(h, algorithm) - } - return headers -} - -func initMap(algorithms []crypto.Hash) map[crypto.Hash]hash.Hash { - hashMap := make(map[crypto.Hash]hash.Hash, len(algorithms)) - for _, algorithm := range algorithms { - hashMap[algorithm] = algorithm.New() - } - return hashMap -} - -func write(h hashes, c int, buf []byte, err error) (int, error) { - if err == nil && c > 0 { - for _, hash := range h.hashes() { - hash.Write(buf[:c]) - } + if err == nil { + h.hashMap.Write(c, buf) } return c, err } -//////////////////////////////////////////////////////////////////////////////// - -func headerName(hash crypto.Hash) string { - a := strings.ReplaceAll(hash.String(), "-", "") - return "X-Checksum-" + a[:1] + strings.ToLower(a[1:]) +func (h *HashWriter) Hashes() Hashes { + return h.hashMap } diff --git a/pkg/iotools/hashReaderWriter_test.go b/pkg/iotools/hashReaderWriter_test.go index bda2a6fc3d..457fa15342 100644 --- a/pkg/iotools/hashReaderWriter_test.go +++ b/pkg/iotools/hashReaderWriter_test.go @@ -26,26 +26,29 @@ var _ = Describe("Hash Reader Writer tests", func() { It("test HashWriter", func() { s := "Hello Hash!" var b bytes.Buffer - hr := iotools.NewHashWriter(io.Writer(&b)) - hr.Write([]byte(s)) + hw := iotools.NewHashWriter(io.Writer(&b)) + hw.Write([]byte(s)) + hashes := hw.Hashes() Expect(b.String()).To(Equal(s)) - Expect(hr.GetBytes(0)).To(BeNil()) + Expect(hashes.GetBytes(0)).To(BeNil()) b.Reset() w := io.Writer(&b) - hr = iotools.NewHashWriter(w, crypto.SHA1) - hr.Write([]byte(s)) + hw = iotools.NewHashWriter(w, crypto.SHA1) + hw.Write([]byte(s)) + hashes = hw.Hashes() Expect(b.String()).To(Equal(s)) - Expect(hr.GetBytes(0)).To(BeNil()) - Expect(hr.GetString(crypto.SHA1)).To(Equal("5c075ed604db0adc524edd3516e8f0258ca6e58d")) + Expect(hashes.GetBytes(0)).To(BeNil()) + Expect(hashes.GetString(crypto.SHA1)).To(Equal("5c075ed604db0adc524edd3516e8f0258ca6e58d")) b.Reset() - hr = iotools.NewHashWriter(io.Writer(&b), crypto.SHA1, crypto.MD5) - hr.Write([]byte(s)) + hw = iotools.NewHashWriter(io.Writer(&b), crypto.SHA1, crypto.MD5) + hw.Write([]byte(s)) + hashes = hw.Hashes() Expect(b.String()).To(Equal(s)) - Expect(hr.GetBytes(0)).To(BeNil()) - Expect(hr.GetString(crypto.MD5)).To(Equal("c10e8df2e378a1584359b0e546cf0149")) - Expect(hr.GetString(crypto.SHA1)).To(Equal("5c075ed604db0adc524edd3516e8f0258ca6e58d")) + Expect(hashes.GetBytes(0)).To(BeNil()) + Expect(hashes.GetString(crypto.MD5)).To(Equal("c10e8df2e378a1584359b0e546cf0149")) + Expect(hashes.GetString(crypto.SHA1)).To(Equal("5c075ed604db0adc524edd3516e8f0258ca6e58d")) }) It("test HashReader", func() { @@ -53,27 +56,29 @@ var _ = Describe("Hash Reader Writer tests", func() { hr := iotools.NewHashReader(strings.NewReader(s)) buf := make([]byte, len(s)) hr.Read(buf) - Expect(hr.GetBytes(0)).To(BeNil()) + hashes := hr.Hashes() + Expect(hashes.GetBytes(0)).To(BeNil()) Expect(string(buf)).To(Equal(s)) hr = iotools.NewHashReader(strings.NewReader(s), crypto.SHA1) hr.Read(buf) - Expect(hr.GetBytes(0)).To(BeNil()) - Expect(hr.GetString(crypto.SHA1)).To(Equal("5c075ed604db0adc524edd3516e8f0258ca6e58d")) + hashes = hr.Hashes() + Expect(hashes.GetBytes(0)).To(BeNil()) + Expect(hashes.GetString(crypto.SHA1)).To(Equal("5c075ed604db0adc524edd3516e8f0258ca6e58d")) hr = iotools.NewHashReader(strings.NewReader(s), crypto.SHA1) cnt, err := hr.CalcHashes() + hashes = hr.Hashes() Expect(err).To(BeNil()) Expect(cnt).To(Equal(int64(len(s)))) - Expect(hr.GetBytes(0)).To(BeNil()) - Expect(hr.GetString(crypto.SHA1)).To(Equal("5c075ed604db0adc524edd3516e8f0258ca6e58d")) + Expect(hashes.GetBytes(0)).To(BeNil()) + Expect(hashes.GetString(crypto.SHA1)).To(Equal("5c075ed604db0adc524edd3516e8f0258ca6e58d")) hr = iotools.NewHashReader(strings.NewReader(s), crypto.SHA1, crypto.MD5) hr.Read(buf) - Expect(hr.GetBytes(crypto.SHA256)).To(BeNil()) - Expect(hr.GetString(crypto.MD5)).To(Equal("c10e8df2e378a1584359b0e546cf0149")) - Expect(hr.GetString(crypto.MD5)).To(Equal("c10e8df2e378a1584359b0e546cf0149")) - Expect(hr.GetString(crypto.SHA1)).To(Equal("5c075ed604db0adc524edd3516e8f0258ca6e58d")) - Expect(hr.GetString(crypto.SHA1)).To(Equal("5c075ed604db0adc524edd3516e8f0258ca6e58d")) + hashes = hr.Hashes() + Expect(hashes.GetBytes(crypto.SHA256)).To(BeNil()) + Expect(hashes.GetString(crypto.MD5)).To(Equal("c10e8df2e378a1584359b0e546cf0149")) + Expect(hashes.GetString(crypto.SHA1)).To(Equal("5c075ed604db0adc524edd3516e8f0258ca6e58d")) }) }) diff --git a/pkg/maven/access.go b/pkg/maven/access.go new file mode 100644 index 0000000000..3fc972b83e --- /dev/null +++ b/pkg/maven/access.go @@ -0,0 +1,407 @@ +package maven + +import ( + "bytes" + "context" + "crypto" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "github.com/cloudflare/cfssl/log" + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/finalizer" + "github.com/mandelsoft/goutils/general" + "github.com/mandelsoft/vfs/pkg/vfs" + "golang.org/x/exp/maps" + "golang.org/x/net/html" + + "github.com/open-component-model/ocm/pkg/iotools" + "github.com/open-component-model/ocm/pkg/utils" + "github.com/open-component-model/ocm/pkg/utils/tarutils" +) + +type FileMeta struct { + MimeType string + HashType crypto.Hash + Hash string + Location *Location +} + +type Repository struct { + Location +} + +func NewFileRepository(path string, fss ...vfs.FileSystem) *Repository { + return &Repository{Location{ + path: path, + fs: utils.FileSystem(fss...), + }} +} + +func NewUrlRepository(repoUrl string, fss ...vfs.FileSystem) (*Repository, error) { + u, err := url.Parse(repoUrl) + if err != nil { + return nil, err + } + if u.Scheme == "file" { + if u.Host != "" && u.Host != "localhost" { + return nil, errors.Newf("named host not supported for url file scheme: %q", repoUrl) + } + return NewFileRepository(u.Path, fss...), nil + } + return &Repository{Location{ + url: repoUrl, + }}, nil +} + +func (r *Repository) Url() (string, error) { + if r.url != "" { + return r.url, nil + } + p, err := vfs.Canonical(r.fs, r.path, false) + if err != nil { + return "", err + } + return "file://localhost" + p, nil +} + +// Body is the response struct of a deployment from the MVN repository (JFrog Artifactory). +type Body struct { + Repo string `json:"repo"` + Path string `json:"path"` + DownloadUri string `json:"downloadUri"` + Uri string `json:"uri"` + MimeType string `json:"mimeType"` + Size string `json:"size"` + Checksums map[string]string `json:"checksums"` +} + +func (r *Repository) Download(coords *Coordinates, creds Credentials, enforceVerification ...bool) (io.ReadCloser, error) { + files, err := r.GavFiles(coords, creds) + if err != nil { + return nil, err + } + algorithm, ok := files[coords.FileName()] + if !ok { + return nil, errors.ErrNotFound("file", coords.FileName(), coords.GAV()) + } + + var digest string + loc := coords.Location(r) + if algorithm != 0 { + digestFile := loc.AddExtension(HashExt(algorithm)) + reader, err := digestFile.GetReader(creds) + if err != nil { + return nil, err + } + digestData, err := io.ReadAll(reader) + if err != nil { + return nil, err + } + digest = string(digestData) + } else { + if general.Optional(enforceVerification...) { + return nil, fmt.Errorf("unable to verify, no digest available in target repository") + } + } + + reader, err := loc.GetReader(creds) + if err != nil { + return nil, err + } + if algorithm != 0 { + reader = iotools.VerifyingReaderWithHash(reader, algorithm, digest) + } + return reader, nil +} + +func (r *Repository) Upload(coords *Coordinates, reader io.ReadCloser, creds Credentials, hashes iotools.Hashes) (rerr error) { + finalize := finalizer.Finalizer{} + defer finalize.FinalizeWithErrorPropagation(&rerr) + + loc := coords.Location(r) + if r.IsFileSystem() { + err := loc.fs.MkdirAll(vfs.Dir(loc.fs, loc.path), 0o755) + if err != nil { + return err + } + f, err := loc.fs.OpenFile(loc.path, vfs.O_WRONLY|vfs.O_CREATE|vfs.O_TRUNC, 0o644) + if err != nil { + return err + } + finalize.Close(f) + + _, err = io.Copy(f, reader) + if err != nil { + return err + } + + for algorithm := range hashes { + digest := hashes.GetString(algorithm) + p := loc.path + "." + HashExt(algorithm) + err = vfs.WriteFile(loc.fs, p, []byte(digest), 0o644) + if err != nil { + return err + } + } + return nil + } + + req, err := http.NewRequestWithContext(context.Background(), http.MethodPut, loc.String(), reader) + if err != nil { + return err + } + if creds != nil { + err = creds.SetForRequest(req) + if err != nil { + return err + } + } + // give the remote server a chance to decide based upon the checksum policy + for k, v := range hashes.AsHttpHeader() { + req.Header[k] = v + } + + // Execute the request + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return err + } + finalize.Close(resp.Body) + + // Check the response + if resp.StatusCode != http.StatusCreated { + all, e := io.ReadAll(resp.Body) + if e != nil { + return e + } + return fmt.Errorf("http (%d) - failed to upload coords: %s", resp.StatusCode, string(all)) + } + Log.Debug("uploaded", "coords", coords, "extension", coords.Extension, "classifier", coords.Classifier) + + // Validate the response - especially the hash values with the ones we've tried to send + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + var artifactBody Body + err = json.Unmarshal(respBody, &artifactBody) + if err != nil { + return err + } + + algorithm := bestAvailableHash(maps.Keys(hashes)) + digest := hashes.GetString(algorithm) + remoteDigest := artifactBody.Checksums[strings.ReplaceAll(strings.ToLower(algorithm.String()), "-", "")] + if remoteDigest == "" { + Log.Warn("no checksum found for algorithm, we can't guarantee that the coords has been uploaded correctly", "algorithm", algorithm.String()) + } else if remoteDigest != digest { + return errors.New("failed to upload coords: checksums do not match") + } + Log.Debug("digests are ok", "remoteDigest", remoteDigest, "digest", digest) + return err +} + +func (r *Repository) GetFileMeta(c *Coordinates, file string, hash crypto.Hash, creds Credentials) (*FileMeta, error) { + coords := c.Copy() + err := coords.SetClassifierExtensionBy(file) + if err != nil { + return nil, err + } + metadata := &FileMeta{ + Location: coords.Location(r), + MimeType: coords.MimeType(), + } + log := Log.WithValues("file", metadata.Location.String()) + log.Debug("processing") + if hash > 0 { + metadata.HashType = hash + metadata.Hash, err = metadata.Location.GetHash(creds, hash) + if err != nil { + return nil, errors.Wrapf(err, "cannot read %s digest of: %s", hash, metadata.Location) + } + } else { + log.Warn("no digest available") + } + return metadata, nil +} + +func (r *Repository) GavFiles(coords *Coordinates, creds Credentials) (map[string]crypto.Hash, error) { + if r.path != "" { + return gavFilesFromDisk(r.fs, coords.GavLocation(r).path) + } + return gavOnlineFiles(r, coords, creds) +} + +func gavFilesFromDisk(fs vfs.FileSystem, dir string) (map[string]crypto.Hash, error) { + files, err := tarutils.ListSortedFilesInDir(fs, dir, true) + if err != nil { + return nil, err + } + return filesAndHashes(files), nil +} + +// gavOnlineFiles returns the files of the Maven artifact in the repository and their available digests. +func gavOnlineFiles(repo *Repository, coords *Coordinates, creds Credentials) (map[string]crypto.Hash, error) { + log := Log.WithValues("RepoUrl", repo.String(), "GAV", coords.GavPath()) + log.Debug("gavOnlineFiles") + + reader, err := coords.GavLocation(repo).GetReader(creds) + if err != nil { + return nil, err + } + defer reader.Close() + + // Which files are listed in the repository? + log.Debug("parse-html") + htmlDoc, err := html.Parse(reader) + if err != nil { + return nil, err + } + var fileList []string + var process func(*html.Node) + prefix := coords.FileNamePrefix() + process = func(node *html.Node) { + // check if the node is an element node and the tag is "" + if node.Type == html.ElementNode && node.Data == "a" { + for _, attribute := range node.Attr { + if attribute.Key == "href" { + // check if the href starts with artifactId-version + if strings.HasPrefix(attribute.Val, prefix) { + fileList = append(fileList, attribute.Val) + } + } + } + } + for nextChild := node.FirstChild; nextChild != nil; nextChild = nextChild.NextSibling { + process(nextChild) // recursive call! + } + } + process(htmlDoc) + + return filesAndHashes(fileList), nil +} + +func filesAndHashes(fileList []string) map[string]crypto.Hash { + // Which hash files are available? + result := make(map[string]crypto.Hash, len(fileList)/2) + for _, file := range fileList { + if IsResource(file) { + result[file] = bestAvailableHashForFile(fileList, file) + log.Debug("found", "file", file) + } + } + return result +} + +type Location struct { + url string + path string + fs vfs.FileSystem +} + +func (l *Location) MarshalJSON() ([]byte, error) { + return json.Marshal(l.String()) +} + +func (l *Location) IsFileSystem() bool { + return l.path != "" +} + +func (l *Location) AddPath(path string) *Location { + result := *l + var p *string + if result.url != "" { + p = &result.url + } else { + p = &result.path + } + + if !strings.HasSuffix(*p, "/") { + *p += "/" + } + *p += path + return &result +} + +func (l *Location) AddExtension(ext string) *Location { + result := *l + var p *string + if result.url != "" { + p = &result.url + } else { + p = &result.path + } + + *p += "." + ext + return &result +} + +func (l *Location) String() string { + return general.Conditional(l.path != "", l.path, l.url) +} + +func (l *Location) GetHash(creds Credentials, hash crypto.Hash) (string, error) { + // getStringData reads all data from the given URL and returns it as a string. + r, err := l.AddExtension(HashExt(hash)).GetReader(creds) + if err != nil { + return "", err + } + defer r.Close() + b, err := io.ReadAll(r) + if err != nil { + return "", err + } + return string(b), nil +} + +func (l *Location) GetReader(creds Credentials) (io.ReadCloser, error) { + if l.path != "" { + return l.fs.OpenFile(l.path, vfs.O_RDONLY, 0o600) + } + + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, l.url, nil) + if err != nil { + return nil, err + } + if creds != nil { + err = creds.SetForRequest(req) + if err != nil { + return nil, err + } + } + httpClient := &http.Client{} + resp, err := httpClient.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusOK { + defer resp.Body.Close() + buf := &bytes.Buffer{} + _, err = io.Copy(buf, io.LimitReader(resp.Body, 2000)) + if err == nil { + Log.Error("http", "code", resp.Status, "repo", l.url, "body", buf.String()) + } + return nil, errors.Newf("http %s error - %s", resp.Status, l.url) + } + return resp.Body, nil +} + +type Credentials interface { + SetForRequest(req *http.Request) error +} + +type BasicAuthCredentials struct { + Username string + Password string +} + +func (b *BasicAuthCredentials) SetForRequest(req *http.Request) error { + req.SetBasicAuth(b.Username, b.Password) + return nil +} diff --git a/pkg/maven/access_test.go b/pkg/maven/access_test.go new file mode 100644 index 0000000000..4696286597 --- /dev/null +++ b/pkg/maven/access_test.go @@ -0,0 +1,132 @@ +package maven_test + +import ( + "crypto" + "io" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/open-component-model/ocm/pkg/env/builder" + . "github.com/open-component-model/ocm/pkg/testutils" + + "github.com/mandelsoft/goutils/optionutils" + + me "github.com/open-component-model/ocm/pkg/maven" + "github.com/open-component-model/ocm/pkg/maven/maventest" +) + +const ( + MAVEN_PATH = "/testdata/.m2/repository" + FAIL_PATH = "/testdata/.m2/fail" +) + +var _ = Describe("local accessmethods.me.AccessSpec tests", func() { + var env *Builder + var repo *me.Repository + + BeforeEach(func() { + env = NewBuilder(maventest.TestData()) + repo = me.NewFileRepository(MAVEN_PATH, env.FileSystem()) + }) + + AfterEach(func() { + env.Cleanup() + }) + + It("accesses local artifact file", func() { + coords := me.NewCoordinates("com.sap.cloud.sdk", "sdk-modules-bom", "5.7.0") + files := Must(repo.GavFiles(coords, nil)) + Expect(files).To(YAMLEqual(` +sdk-modules-bom-5.7.0-random-content.json: 3 +sdk-modules-bom-5.7.0-random-content.txt: 3 +sdk-modules-bom-5.7.0-sources.jar: 3 +sdk-modules-bom-5.7.0.jar: 3 +sdk-modules-bom-5.7.0.pom: 3 +`)) + }) + + It("accesses local artifact file with extension", func() { + coords := me.NewCoordinates("com.sap.cloud.sdk", "sdk-modules-bom", "5.7.0", me.WithClassifier(""), me.WithExtension("pom")) + hash := Must(coords.Location(repo).GetHash(nil, crypto.SHA1)) + Expect(hash).To(Equal("34ccdeb9c008f8aaef90873fc636b09d3ae5c709")) + }) + + It("access dedicated file", func() { + coords := me.NewCoordinates("com.sap.cloud.sdk", "sdk-modules-bom", "5.7.0", me.WithClassifier(""), me.WithExtension("pom")) + meta := Must(repo.GetFileMeta(coords, "sdk-modules-bom-5.7.0.pom", crypto.SHA1, nil)) + Expect(meta).To(YAMLEqual(` + Hash: 34ccdeb9c008f8aaef90873fc636b09d3ae5c709 + HashType: 3 + MimeType: application/xml + Location: /testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0.pom +`)) + }) + + Context("filtering", func() { + var ( + files map[string]crypto.Hash + coords *me.Coordinates + ) + BeforeEach(func() { + coords = me.NewCoordinates("com.sap.cloud.sdk", "sdk-modules-bom", "5.7.0") + files = Must(repo.GavFiles(coords, nil)) + }) + + It("filters nothing", func() { + Expect(coords.FilterFileMap(files)).To(Equal(files)) + }) + It("filter by empty classifier", func() { + coords.Classifier = optionutils.PointerTo("") + Expect(coords.FilterFileMap(files)).To(YAMLEqual(` +sdk-modules-bom-5.7.0.jar: 3 +sdk-modules-bom-5.7.0.pom: 3 +`)) + }) + It("filter by non-empty classifier", func() { + coords.Classifier = optionutils.PointerTo("random-content") + Expect(coords.FilterFileMap(files)).To(YAMLEqual(` +sdk-modules-bom-5.7.0-random-content.json: 3 +sdk-modules-bom-5.7.0-random-content.txt: 3 +`)) + }) + It("filter by extension", func() { + coords.Extension = optionutils.PointerTo("jar") + Expect(coords.FilterFileMap(files)).To(YAMLEqual(` +sdk-modules-bom-5.7.0-sources.jar: 3 +sdk-modules-bom-5.7.0.jar: 3 +`)) + }) + + It("filter by empty classifier and extension", func() { + coords.Classifier = optionutils.PointerTo("") + coords.Extension = optionutils.PointerTo("jar") + Expect(coords.FilterFileMap(files)).To(YAMLEqual(` +sdk-modules-bom-5.7.0.jar: 3 +`)) + }) + + It("filter by non-empty classifier and extension", func() { + coords.Classifier = optionutils.PointerTo("sources") + coords.Extension = optionutils.PointerTo("jar") + Expect(coords.FilterFileMap(files)).To(YAMLEqual(` +sdk-modules-bom-5.7.0-sources.jar: 3 +`)) + }) + + It("download dedicated file", func() { + coords := me.NewCoordinates("com.sap.cloud.sdk", "sdk-modules-bom", "5.7.0", me.WithClassifier(""), me.WithExtension("pom")) + reader := Must(repo.Download(coords, nil, true)) + data := Must(io.ReadAll(reader)) + Expect(len(data)).To(Equal(7153)) + MustBeSuccessful(reader.Close()) + }) + + It("download dedicated file with filed digest verification", func() { + coords := me.NewCoordinates("test", "repository", "42", me.WithClassifier(""), me.WithExtension("pom")) + repo := me.NewFileRepository(FAIL_PATH, env) + reader := Must(repo.Download(coords, nil, true)) + _ = Must(io.ReadAll(reader)) + Expect(reader.Close()).To(MatchError("SHA-1 digest mismatch: expected 44a77645201d1a8fc5213ace787c220eabbd0967, found b3242b8c31f8ce14f729b8fd132ac77bc4bc5bf7")) + }) + }) +}) diff --git a/pkg/maven/coordinates.go b/pkg/maven/coordinates.go new file mode 100644 index 0000000000..fa1767ba4e --- /dev/null +++ b/pkg/maven/coordinates.go @@ -0,0 +1,218 @@ +package maven + +import ( + "crypto" + "fmt" + "mime" + "path" + "path/filepath" + "strings" + + . "github.com/mandelsoft/goutils/regexutils" + + "github.com/mandelsoft/goutils/generics" + "github.com/mandelsoft/goutils/optionutils" + + ocmmime "github.com/open-component-model/ocm/pkg/mime" +) + +type CoordinateOption = optionutils.Option[*Coordinates] + +type WithClassifier string + +func WithOptionalClassifier(c *string) CoordinateOption { + if c != nil { + return WithClassifier(*c) + } + return nil +} + +func (o WithClassifier) ApplyTo(c *Coordinates) { + c.Classifier = optionutils.PointerTo(string(o)) +} + +type WithExtension string + +func WithOptionalExtension(e *string) CoordinateOption { + if e != nil { + return WithExtension(*e) + } + return nil +} + +func (o WithExtension) ApplyTo(c *Coordinates) { + c.Extension = optionutils.PointerTo(string(o)) +} + +// Coordinates holds the typical Maven coordinates groupId, artifactId, version. Optional also classifier and extension. +// https://maven.apache.org/ref/3.9.6/maven-core/artifact-handlers.html +type Coordinates struct { + // GroupId of the Maven artifact. + GroupId string `json:"groupId"` + // ArtifactId of the Maven artifact. + ArtifactId string `json:"artifactId"` + // Version of the Maven artifact. + Version string `json:"version"` + // Classifier of the Maven artifact. + Classifier *string `json:"classifier,omitempty"` + // Extension of the Maven artifact. + Extension *string `json:"extension,omitempty"` +} + +func NewCoordinates(groupId, artifactId, version string, opts ...CoordinateOption) *Coordinates { + c := &Coordinates{ + GroupId: groupId, + ArtifactId: artifactId, + Version: version, + } + optionutils.ApplyOptions(c, opts...) + return c +} + +// GAV returns the GAV coordinates of the Maven Coordinates. +func (c *Coordinates) GAV() string { + return c.GroupId + ":" + c.ArtifactId + ":" + c.Version +} + +// String returns the Coordinates as a string (GroupId:ArtifactId:Version:WithClassifier:WithExtension). +func (c *Coordinates) String() string { + return c.GroupId + ":" + c.ArtifactId + ":" + c.Version + ":" + optionutils.AsValue(c.Classifier) + ":" + optionutils.AsValue(c.Extension) +} + +// GavPath returns the Maven repository path. +func (c *Coordinates) GavPath() string { + return c.GroupPath() + "/" + c.ArtifactId + "/" + c.Version +} + +func (c *Coordinates) GavLocation(repo *Repository) *Location { + return repo.AddPath(c.GavPath()) +} + +func (c *Coordinates) FileName() string { + file := c.FileNamePrefix() + if optionutils.AsValue(c.Classifier) != "" { + file += "-" + *c.Classifier + } + if optionutils.AsValue(c.Extension) != "" { + file += "." + *c.Extension + } else { + file += ".jar" + } + return file +} + +// FilePath returns the Maven Coordinates's GAV-name with classifier and extension. +// Which is equal to the URL-path of the artifact in the repository. +// Default extension is jar. +func (c *Coordinates) FilePath() string { + return c.GavPath() + "/" + c.FileName() +} + +func (c *Coordinates) Location(repo *Repository) *Location { + return repo.AddPath(c.FilePath()) +} + +// GroupPath returns GroupId with `/` instead of `.`. +func (c *Coordinates) GroupPath() string { + return strings.ReplaceAll(c.GroupId, ".", "/") +} + +func (c *Coordinates) FileNamePrefix() string { + return c.ArtifactId + "-" + c.Version +} + +// Purl returns the Package URL of the Maven Coordinates. +func (c *Coordinates) Purl() string { + return "pkg:maven/" + c.GroupId + "/" + c.ArtifactId + "@" + c.Version +} + +// SetClassifierExtensionBy extracts the classifier and extension from the filename (without any path prefix). +func (c *Coordinates) SetClassifierExtensionBy(filename string) error { + s := strings.TrimPrefix(path.Base(filename), c.FileNamePrefix()) + if strings.HasPrefix(s, "-") { + s = strings.TrimPrefix(s, "-") + i := strings.Index(s, ".") + if i < 0 { + return fmt.Errorf("no extension after classifier found in filename: %s", filename) + } + c.Classifier = optionutils.PointerTo(s[:i]) + s = strings.TrimPrefix(s, optionutils.AsValue(c.Classifier)) + } else { + c.Classifier = optionutils.PointerTo("") + } + c.Extension = optionutils.PointerTo(strings.TrimPrefix(s, ".")) + return nil +} + +// MimeType returns the MIME type of the Maven Coordinates based on the file extension. +// Default is application/x-tgz. +func (c *Coordinates) MimeType() string { + if c.Extension != nil && c.Classifier != nil { + m := mime.TypeByExtension("." + optionutils.AsValue(c.Extension)) + if m != "" { + return m + } + return ocmmime.MIME_OCTET + } + return ocmmime.MIME_TGZ +} + +// Copy creates a new Coordinates with the same values. +func (c *Coordinates) Copy() *Coordinates { + return generics.Pointer(*c) +} + +func (c *Coordinates) FilterFileMap(fileMap map[string]crypto.Hash) map[string]crypto.Hash { + if c.Classifier == nil && c.Extension == nil { + return fileMap + } + exp := Literal(c.ArtifactId + "-" + c.Version) + if optionutils.AsValue(c.Classifier) != "" { + exp = Sequence(exp, Literal("-"+*c.Classifier)) + } + if optionutils.AsValue(c.Extension) != "" { + if c.Classifier == nil { + exp = Sequence(exp, Optional(Literal("-"), Match(".+"))) + } + exp = Sequence(exp, Literal("."+*c.Extension)) + } else { + exp = Sequence(exp, Literal("."), Match(".*")) + } + exp = Anchored(exp) + for file := range fileMap { + if !exp.MatchString(file) { + delete(fileMap, file) + } + } + return fileMap +} + +// Parse creates a Coordinates from it's serialized form (see Coordinates.String). +func Parse(serializedArtifact string) (*Coordinates, error) { + parts := strings.Split(serializedArtifact, ":") + if len(parts) < 3 { + return nil, fmt.Errorf("invalid coordination string: %s", serializedArtifact) + } + coords := &Coordinates{ + GroupId: parts[0], + ArtifactId: parts[1], + Version: parts[2], + } + if len(parts) >= 4 { + coords.Classifier = optionutils.PointerTo(parts[3]) + } + if len(parts) >= 5 { + coords.Extension = optionutils.PointerTo(parts[4]) + } + return coords, nil +} + +// IsResource returns true if the filename is not a checksum or signature file. +func IsResource(fileName string) bool { + switch filepath.Ext(fileName) { + case ".asc", ".md5", ".sha1", ".sha256", ".sha512": + return false + default: + return true + } +} diff --git a/pkg/maven/coordinates_test.go b/pkg/maven/coordinates_test.go new file mode 100644 index 0000000000..9a7aa6300a --- /dev/null +++ b/pkg/maven/coordinates_test.go @@ -0,0 +1,52 @@ +package maven_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/open-component-model/ocm/pkg/testutils" + + "github.com/mandelsoft/goutils/optionutils" + + me "github.com/open-component-model/ocm/pkg/maven" +) + +var _ = Describe("Maven Test Environment", func() { + + It("GAV, GroupPath, FilePath", func() { + coords := me.NewCoordinates("ocm.software", "hello-ocm", "0.0.1", me.WithExtension("jar")) + Expect(coords.GAV()).To(Equal("ocm.software:hello-ocm:0.0.1")) + Expect(coords.GroupPath()).To(Equal("ocm/software")) + Expect(coords.FilePath()).To(Equal("ocm/software/hello-ocm/0.0.1/hello-ocm-0.0.1.jar")) + }) + + It("SetClassifierExtensionBy", func() { + coords := me.NewCoordinates("ocm.software", "hello-ocm", "0.0.1") + MustBeSuccessful(coords.SetClassifierExtensionBy("hello-ocm-0.0.1.pom")) + Expect(coords.Classifier).ToNot(BeNil()) + Expect(optionutils.AsValue(coords.Classifier)).To(Equal("")) + Expect(optionutils.AsValue(coords.Extension)).To(Equal("pom")) + + MustBeSuccessful(coords.SetClassifierExtensionBy("hello-ocm-0.0.1-tests.jar")) + Expect(optionutils.AsValue(coords.Classifier)).To(Equal("tests")) + Expect(optionutils.AsValue(coords.Extension)).To(Equal("jar")) + + coords.ArtifactId = "apache-me" + coords.Version = "3.9.6" + MustBeSuccessful(coords.SetClassifierExtensionBy("apache-me-3.9.6-bin.tar.gz")) + Expect(optionutils.AsValue(coords.Classifier)).To(Equal("bin")) + Expect(optionutils.AsValue(coords.Extension)).To(Equal("tar.gz")) + }) + + It("parse GAV", func() { + gav := "org.apache.commons:commons-compress:1.26.1:cyclonedx:xml" + coords, err := me.Parse(gav) + Expect(err).To(BeNil()) + Expect(coords.String()).To(Equal(gav)) + Expect(coords.GroupId).To(Equal("org.apache.commons")) + Expect(coords.ArtifactId).To(Equal("commons-compress")) + Expect(coords.Version).To(Equal("1.26.1")) + Expect(optionutils.AsValue(coords.Classifier)).To(Equal("cyclonedx")) + Expect(optionutils.AsValue(coords.Extension)).To(Equal("xml")) + Expect(coords.FilePath()).To(Equal("org/apache/commons/commons-compress/1.26.1/commons-compress-1.26.1-cyclonedx.xml")) + }) +}) diff --git a/pkg/maven/logging.go b/pkg/maven/logging.go new file mode 100644 index 0000000000..17bfd5df54 --- /dev/null +++ b/pkg/maven/logging.go @@ -0,0 +1,7 @@ +package maven + +import "github.com/open-component-model/ocm/pkg/logging" + +var REALM = logging.DefineSubRealm("Maven repository", "maven") + +var Log = logging.DynamicLogger(REALM) diff --git a/pkg/maven/maventest/testdata.go b/pkg/maven/maventest/testdata.go new file mode 100644 index 0000000000..64f0c103e8 --- /dev/null +++ b/pkg/maven/maventest/testdata.go @@ -0,0 +1,9 @@ +package maventest + +import ( + "github.com/open-component-model/ocm/pkg/env" +) + +func TestData(dest ...string) env.Option { + return env.ProjectTestDataForCaller(dest...) +} diff --git a/pkg/contexts/ocm/accessmethods/mvn/testdata/fail/test/repository/42/repository-42.pom b/pkg/maven/maventest/testdata/.m2/fail/test/repository/42/repository-42.pom similarity index 100% rename from pkg/contexts/ocm/accessmethods/mvn/testdata/fail/test/repository/42/repository-42.pom rename to pkg/maven/maventest/testdata/.m2/fail/test/repository/42/repository-42.pom diff --git a/pkg/contexts/ocm/accessmethods/mvn/testdata/fail/test/repository/42/repository-42.pom.sha1 b/pkg/maven/maventest/testdata/.m2/fail/test/repository/42/repository-42.pom.sha1 similarity index 100% rename from pkg/contexts/ocm/accessmethods/mvn/testdata/fail/test/repository/42/repository-42.pom.sha1 rename to pkg/maven/maventest/testdata/.m2/fail/test/repository/42/repository-42.pom.sha1 diff --git a/pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-random-content.json b/pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-random-content.json new file mode 100644 index 0000000000..2f1fc35fdc --- /dev/null +++ b/pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-random-content.json @@ -0,0 +1 @@ +{"some": "test content"} \ No newline at end of file diff --git a/pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-random-content.json.sha1 b/pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-random-content.json.sha1 new file mode 100644 index 0000000000..d6836aeaa0 --- /dev/null +++ b/pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-random-content.json.sha1 @@ -0,0 +1 @@ +f0763ff4add043560aa3827cea06bf9335b87f73 \ No newline at end of file diff --git a/pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-random-content.txt b/pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-random-content.txt new file mode 100644 index 0000000000..b4af84697f --- /dev/null +++ b/pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-random-content.txt @@ -0,0 +1 @@ +some test content \ No newline at end of file diff --git a/pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-random-content.txt.sha1 b/pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-random-content.txt.sha1 new file mode 100644 index 0000000000..55eceeb992 --- /dev/null +++ b/pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-random-content.txt.sha1 @@ -0,0 +1 @@ +dbabd43828eccd27e3a109b58454e4ff43c8673e \ No newline at end of file diff --git a/pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-sources.jar b/pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-sources.jar new file mode 100644 index 0000000000000000000000000000000000000000..8564f0addae8de5525417df8294d13c0c2a3a7c0 GIT binary patch literal 595 zcmWIWW@h1HVBlb2I6ZY+1OpP_WME~0p0C?y-!rFuymj?1@_OrPojY@WbCAIm;|EWR^t^m^Jbf>guG$i0z|{3Z zQZu9C$@67uqD!Y`e=7P|#0a#Cor7naNwg)65ZVVl++yk z{N!Byg6wqtq@4Vu#N_1E;$r>W#In>p{gTw;l9a@fME%s^D&0A%HfJ3D=4gl*pE23j>4+%YaO}g$OGf$YN$76k=px&<0`# F1^|3_lo~0p0C?y-!rFuymj?1@_OrPojY@WbCAIm;|EWR^t^m^Jbf>guG$i0z|{3Z zQZu9C$@67uqD!Y`e=7P|#0a#Cor7naNwg)65ZVVl++yk z{N!Byg6wqtq@4Vu#N_1E;$r>W#In>p{gTw;l9a@fME%s^D&0A%HfJ3D=4gl*pE23j>4+%YaO}g$OGf$YN$76k=px&<0`# F1^|3_lo